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