artichoke_backend/sys/
mod.rs

1#![warn(missing_docs)]
2
3//! Rust bindings for mruby, customized for Artichoke.
4//!
5//! Bindings are based on the
6//! [vendored mruby sources](https://github.com/artichoke/mruby) and generated
7//! with bindgen.
8
9use std::ffi::CStr;
10use std::fmt::{self, Write};
11
12use crate::types::{self, Ruby};
13
14mod args;
15#[allow(
16    missing_debug_implementations,
17    missing_docs,
18    non_camel_case_types,
19    non_upper_case_globals,
20    non_snake_case,
21    trivial_casts,
22    trivial_numeric_casts,
23    unsafe_op_in_unsafe_fn,
24    unused_qualifications,
25    clippy::all,
26    clippy::pedantic,
27    clippy::restriction,
28    reason = "generated code"
29)]
30#[expect(
31    unnecessary_transmutes,
32    reason = "bindgen bitfields codegen under Rust 1.88+ - https://github.com/rust-lang/rust-bindgen/issues/3241"
33)]
34mod ffi {
35    include!(concat!(env!("OUT_DIR"), "/ffi.rs"));
36}
37pub(crate) mod protect;
38
39pub use self::args::*;
40pub use self::ffi::*;
41
42/// Check whether the given value is the singleton `nil`.
43#[must_use]
44pub fn mrb_sys_value_is_nil(value: mrb_value) -> bool {
45    // SAFETY: `mrb_sys_value_is_nil` only requires a valid `mrb_value` and type
46    // tag, of which `value` is both.
47    unsafe { ffi::mrb_sys_value_is_nil(value) }
48}
49
50/// Check whether the given value is the singleton `false`.
51#[must_use]
52pub fn mrb_sys_value_is_false(value: mrb_value) -> bool {
53    // SAFETY: `mrb_sys_value_is_false` only requires a valid `mrb_value` and type
54    // tag, of which `value` is both.
55    unsafe { ffi::mrb_sys_value_is_false(value) }
56}
57
58/// Check whether the given value is the singleton `true`.
59#[must_use]
60pub fn mrb_sys_value_is_true(value: mrb_value) -> bool {
61    // SAFETY: `mrb_sys_value_is_true` only requires a valid `mrb_value` and type
62    // tag, of which `value` is both.
63    unsafe { ffi::mrb_sys_value_is_true(value) }
64}
65
66/// Return a `nil` `mrb_value`.
67///
68/// The resulting value has `TT_FALSE` type tag and is an immediate value.
69#[must_use]
70pub fn mrb_sys_nil_value() -> mrb_value {
71    // SAFETY: `mrb_sys_nil_value` returns an immediate value and has no safety
72    // obligations to uphold.
73    unsafe { ffi::mrb_sys_nil_value() }
74}
75
76impl Default for mrb_value {
77    fn default() -> Self {
78        mrb_sys_nil_value()
79    }
80}
81
82impl fmt::Debug for mrb_value {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match types::ruby_from_mrb_value(*self) {
85            Ruby::Nil => f.write_str("nil"),
86            Ruby::Bool if mrb_sys_value_is_true(*self) => f.write_str("true"),
87            Ruby::Bool => f.write_str("false"),
88            Ruby::Fixnum => {
89                // SAFETY: value type tag is checked to be a fixnum.
90                let fixnum = unsafe { mrb_sys_fixnum_to_cint(*self) };
91                write!(f, "{fixnum}")
92            }
93            Ruby::Float => {
94                // SAFETY: value type tag is checked to be a float.
95                let float = unsafe { mrb_sys_float_to_cdouble(*self) };
96                write!(f, "{float}")
97            }
98            type_tag => write!(f, "<{type_tag}>"),
99        }
100    }
101}
102
103/// Version metadata `String` for embedded mruby.
104#[must_use]
105pub fn mrb_sys_mruby_version(verbose: bool) -> String {
106    if !verbose {
107        return String::from(env!("CARGO_PKG_VERSION"));
108    }
109
110    let engine = CStr::from_bytes_with_nul(MRUBY_RUBY_ENGINE).unwrap_or(c"unknown");
111    let engine = engine.to_str().unwrap_or("unknown");
112    let version = CStr::from_bytes_with_nul(MRUBY_RUBY_VERSION).unwrap_or(c"0.0.0");
113    let version = version.to_str().unwrap_or("0.0.0");
114
115    let mut out = String::new();
116    out.push_str(engine);
117    out.push(' ');
118    out.push_str(version);
119    out.push_str(" [");
120    out.push_str(env!("CARGO_PKG_VERSION"));
121    out.push(']');
122    out
123}
124
125/// Debug representation for [`mrb_state`].
126///
127/// Returns Ruby engine, interpreter version, engine version, and [`mrb_state`]
128/// address. For example:
129///
130/// ```text
131/// mruby 2.0 (v2.0.1 rev c078758) interpreter at 0x7f85b8800000
132/// ```
133///
134/// This function is infallible and guaranteed not to panic.
135#[must_use]
136pub fn mrb_sys_state_debug(mrb: *mut mrb_state) -> String {
137    let engine = CStr::from_bytes_with_nul(MRUBY_RUBY_ENGINE).unwrap_or(c"unknown");
138    let engine = engine.to_str().unwrap_or("unknown");
139    let version = CStr::from_bytes_with_nul(MRUBY_RUBY_VERSION).unwrap_or(c"0.0.0");
140    let version = version.to_str().unwrap_or("0.0.0");
141
142    let mut debug = String::new();
143    // Explicitly suppressed error since we are only generating debug info and
144    // cannot panic.
145    //
146    // In practice, this call to `write!` will never panic because the `Display`
147    // impls of `str` and `i64` are not buggy and writing to a `String`
148    // `fmt::Write` will never panic on its own.
149    let _ = write!(
150        &mut debug,
151        "{engine} {version} (v{MRUBY_RELEASE_MAJOR}.{MRUBY_RELEASE_MINOR}.{MRUBY_RELEASE_TEENY}) interpreter at {mrb:p}"
152    );
153    debug
154}
155
156#[cfg(test)]
157mod tests {
158    use crate::test::prelude::*;
159
160    #[test]
161    fn interpreter_debug() {
162        // Since the introduction of Rust symbol table, `mrb_open` cannot be
163        // called without an Artichoke `State`.
164        let mut interp = interpreter();
165        // SAFETY: interpreter is initialized.
166        unsafe {
167            let mrb = interp.mrb.as_mut();
168            let debug = sys::mrb_sys_state_debug(mrb);
169            assert_eq!(debug, format!("mruby 3.2 (v3.2.0) interpreter at {:p}", &*mrb));
170        };
171    }
172}