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
use std::borrow::Cow;
use std::error;
use std::ffi::c_void;
use std::fmt;

use spinoso_exception::Fatal;

use crate::core::{ClassRegistry, Eval, TryConvertMut};
use crate::error::{Error, RubyException};
use crate::extn;
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 file system], and loads the [`extn`]
/// extensions to Ruby Core and Stdlib.
///
/// [in memory virtual file system]: 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.
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) };

    // All operations using the interpreter must occur behind a function
    // boundary so we can catch all errors and ensure we call `interp.close()`.
    //
    // Allowing the `?` operator to be used in the containing
    // `interpreter_with_config` function would result in a memory leak of the
    // interpreter and its heap.
    let mut interp = unsafe { ffi::from_user_data(raw).map_err(|_| InterpreterAllocError::new())? };

    match init(&mut interp, config) {
        Ok(()) => Ok(interp),
        Err(err) => {
            // Cleanup and deallocate on error.
            interp.close();
            Err(err)
        }
    }
}

fn init(interp: &mut Artichoke, config: ReleaseMetadata<'_>) -> Result<(), Error> {
    if let Some(ref mut state) = interp.state {
        let mrb = unsafe { interp.mrb.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
    extn::init(interp, config)?;

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

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

    // mruby lazily initializes some core objects like `top_self` and generates
    // a lot of garbage on start-up. Eagerly initialize the interpreter to
    // provide predictable initialization behavior.
    interp.create_arena_savepoint()?.interp().eval(b"")?;

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

#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
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.try_convert_mut(self.message()).ok()?;
        let value = interp.new_instance::<Fatal>(&[message]).ok().flatten()?;
        Some(value.inner())
    }
}

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

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