1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use std::borrow::Cow;
use std::error;
use std::ffi::c_void;
use std::fmt;

use crate::class_registry::ClassRegistry;
use crate::core::{ConvertMut, Eval};
use crate::error::{Error, RubyException};
use crate::extn;
use crate::extn::core::exception::Fatal;
use crate::ffi;
use crate::gc::{MrbGarbageCollection, State as GcState};
use crate::release_metadata::ReleaseMetadata;
use crate::state::State;
use crate::sys;
use crate::Artichoke;

/// Create and initialize an [`Artichoke`] interpreter.
///
/// This function creates a new [`State`], embeds it in the [`sys::mrb_state`],
/// initializes an [in memory virtual filesystem], and loads the [`extn`]
/// extensions to Ruby Core and Stdlib.
///
/// [in memory virtual filesystem]: crate::load_path
pub fn interpreter() -> Result<Artichoke, Error> {
    let release_meta = ReleaseMetadata::new();
    interpreter_with_config(release_meta)
}

/// Create and initialize an [`Artichoke`] interpreter with build metadata.
///
/// This function takes a customizable configuration for embedding metadata
/// about how Artichoke was built. Otherwise, it behaves identically to the
/// [`interpreter`] function.
#[allow(clippy::module_name_repetitions)]
pub fn interpreter_with_config(config: ReleaseMetadata<'_>) -> Result<Artichoke, Error> {
    let state = State::new()?;
    let state = Box::new(state);
    let alloc_ud = Box::into_raw(state).cast::<c_void>();
    let raw = unsafe { sys::mrb_open_allocf(Some(sys::mrb_default_allocf), alloc_ud) };
    debug!("Try initializing mrb interpreter");

    let mut interp = unsafe { ffi::from_user_data(raw).map_err(|_| InterpreterAllocError::new())? };

    if let Some(ref mut state) = interp.state {
        if let Some(mrb) = unsafe { raw.as_mut() } {
            state.try_init_parser(mrb)
        }
    }

    // mruby garbage collection relies on a fully initialized Array, which we
    // won't have until after `extn::core` is initialized. Disable GC before
    // init and clean up afterward.
    let prior_gc_state = interp.disable_gc();

    // Initialize Artichoke Core and Standard Library runtime
    debug!("Begin initializing Artichoke Core and Standard Library");
    extn::init(&mut interp, config)?;
    debug!("Succeeded initializing Artichoke Core and Standard Library");

    // Load mrbgems
    let mut arena = interp.create_arena_savepoint()?;

    unsafe {
        arena.interp().with_ffi_boundary(|mrb| sys::mrb_init_mrbgems(mrb))?;
    }
    arena.restore();

    debug!(
        "Allocated mrb interpreter: {}",
        sys::mrb_sys_state_debug(unsafe { interp.mrb.as_mut() })
    );

    // mruby lazily initializes some core objects like top_self and generates a
    // lot of garbage on startup. Eagerly initialize the interpreter to provide
    // predictable initialization behavior.
    interp.create_arena_savepoint()?.interp().eval(&[])?;

    if let GcState::Enabled = prior_gc_state {
        interp.enable_gc();
        interp.full_gc();
    }

    Ok(interp)
}

#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[allow(clippy::module_name_repetitions)]
pub struct InterpreterAllocError {
    _private: (),
}

impl InterpreterAllocError {
    /// Constructs a new, default `InterpreterAllocError`.
    #[must_use]
    pub const fn new() -> Self {
        Self { _private: () }
    }
}

impl fmt::Display for InterpreterAllocError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("Failed to allocate Artichoke interpreter")
    }
}

impl error::Error for InterpreterAllocError {}

impl RubyException for InterpreterAllocError {
    fn message(&self) -> Cow<'_, [u8]> {
        Cow::Borrowed(b"Failed to allocate Artichoke Ruby interpreter")
    }

    fn name(&self) -> Cow<'_, str> {
        "fatal".into()
    }

    fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
        let _ = interp;
        None
    }

    fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
        let message = interp.convert_mut(self.message());
        let value = interp.new_instance::<Fatal>(&[message]).ok().flatten()?;
        Some(value.inner())
    }
}

impl From<InterpreterAllocError> for Error {
    fn from(exception: InterpreterAllocError) -> Self {
        Self::from(Box::<dyn RubyException>::from(exception))
    }
}

impl From<Box<InterpreterAllocError>> for Error {
    fn from(exception: Box<InterpreterAllocError>) -> Self {
        Self::from(Box::<dyn RubyException>::from(exception))
    }
}

impl From<InterpreterAllocError> for Box<dyn RubyException> {
    fn from(exception: InterpreterAllocError) -> Box<dyn RubyException> {
        Box::new(exception)
    }
}

impl From<Box<InterpreterAllocError>> for Box<dyn RubyException> {
    fn from(exception: Box<InterpreterAllocError>) -> Box<dyn RubyException> {
        exception
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn open_close() {
        let interp = super::interpreter().unwrap();
        interp.close();
    }
}