This repository has been archived by the owner on Jan 3, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
kernel.rs
123 lines (118 loc) 路 4.14 KB
/
kernel.rs
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
use log::trace;
use mruby_vfs::FileSystem;
use std::ffi::CString;
use std::mem;
use std::path::PathBuf;
use std::rc::Rc;
use crate::convert::TryFromMrb;
use crate::def::{ClassLike, Define};
use crate::eval::{EvalContext, MrbEval};
use crate::extn::core::error::LoadError;
use crate::interpreter::{Mrb, MrbApi, RUBY_LOAD_PATH};
use crate::state::VfsMetadata;
use crate::sys;
use crate::value::Value;
use crate::MrbError;
pub fn patch(interp: &Mrb) -> Result<(), MrbError> {
let kernel = interp.borrow_mut().def_module::<Kernel>("Kernel", None);
kernel
.borrow_mut()
.add_self_method("require", require, sys::mrb_args_rest());
kernel.borrow().define(interp).map_err(|_| MrbError::New)?;
trace!("Patched Kernel#require onto interpreter");
Ok(())
}
pub struct Kernel;
extern "C" fn require(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
let interp = unsafe { interpreter_or_raise!(mrb) };
// Extract required filename from arguments
let name = unsafe {
let name = mem::uninitialized::<sys::mrb_value>();
let argspec = CString::new(sys::specifiers::OBJECT).expect("argspec");
sys::mrb_get_args(mrb, argspec.as_ptr(), &name);
Value::new(&interp, name)
};
let name = unsafe {
unwrap_or_raise!(
interp,
String::try_from_mrb(&interp, name),
interp.nil().inner()
)
};
// track whether any iterations of the loop successfully required a file
let mut success = false;
let mut path = PathBuf::from(&name);
if path.is_relative() {
path = PathBuf::from(RUBY_LOAD_PATH);
}
let files = vec![path.join(&name), path.join(format!("{}.rb", name))];
for path in files {
let is_file = {
let api = interp.borrow();
api.vfs.is_file(&path)
};
if !is_file {
// If no paths are files in the VFS, then the require does
// nothing.
continue;
}
let metadata = {
let api = interp.borrow();
api.vfs.metadata(&path).unwrap_or_else(VfsMetadata::new)
};
// If a file is already required, short circuit
if metadata.is_already_required() {
return interp.bool(false).inner();
}
let context = if let Some(filename) = &path.to_str() {
EvalContext::new(filename)
} else {
EvalContext::new("(require)")
};
// Require Rust MrbFile first because an MrbFile may define classes and
// module with `MrbLoadSources` and Ruby files can require arbitrary
// other files, including some child sources that may depend on these
// module definitions. This behavior is enforced with a test in crate
// mruby-gems. See mruby-gems/src/lib.rs.
if let Some(require) = metadata.require {
// dynamic, Rust-backed `MrbFile` require
interp.push_context(context.clone());
unsafe { unwrap_or_raise!(interp, require(Rc::clone(&interp)), interp.nil().inner()) };
interp.pop_context();
}
let contents = {
let api = interp.borrow();
api.vfs.read_file(&path)
};
if let Ok(contents) = contents {
unsafe {
unwrap_value_or_raise!(interp, interp.eval_with_context(contents, context));
}
} else {
// this branch should be unreachable because the `Mrb` interpreter
// is not `Send` so it can only be owned and accessed by one thread.
return LoadError::raise(&interp, &name);
}
let metadata = metadata.mark_required();
unsafe {
let api = interp.borrow();
unwrap_or_raise!(
interp,
api.vfs.set_metadata(&path, metadata),
interp.nil().inner()
);
}
success = true;
trace!(
r#"Successful require of "{}" at {:?} on {:?}"#,
name,
path,
interp.borrow()
);
}
if success {
interp.bool(success).inner()
} else {
LoadError::raise(&interp, &name)
}
}