[oden][oden-js] Rework modules

Damn this is a lot
This commit is contained in:
John Doty 2023-06-24 08:45:39 -07:00
parent aa90cea4a3
commit db8a5f8eed
12 changed files with 280 additions and 105 deletions

View file

@ -1,24 +1,12 @@
use crate::{
callback::new_fn, conversion::RustFunction, Atom, ClassID, Error, Result, Runtime, Value,
ValueRef, ValueResult,
callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Result,
Runtime, Value, ValueRef, ValueResult,
};
use bitflags::bitflags;
use oden_js_sys as sys;
use std::ffi::CString;
use std::ops::{Deref, DerefMut};
/// Different ways to evaluate JavaScript. See the various `eval` methods on
/// `ContextRef`.
pub enum EvalType {
/// Global code, i.e., just plain old JavaScript running in the most
/// boring, traditional context.
Global,
/// Module code, i.e., code running under the context of an `import`, or
/// referenced in HTML as `type=module`.
Module,
}
bitflags! {
pub struct EvalFlags: u32 {
const NONE = 0;
@ -31,8 +19,8 @@ bitflags! {
const STRIP = sys::JS_EVAL_FLAG_STRIP;
/// Compile but do not run. The result is a value with a value type
/// of `ValueType::Module` or `ValueType::FunctionBytecode`, and
/// which can be executed with `value.eval_function`.
/// of `ValueType::FunctionBytecode`, and which can be executed with
/// `value.eval_function`.
const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY;
/// Don't include the stack frames before this eval in the Error()
@ -51,6 +39,10 @@ impl ContextRef {
ContextRef { ctx }
}
pub fn get_runtime(&self) -> Runtime {
Runtime::from_raw(unsafe { sys::JS_GetRuntime(self.ctx) })
}
pub fn is_registered_class(&self, id: &ClassID) -> bool {
let id = id.get();
let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) };
@ -106,30 +98,42 @@ impl ContextRef {
unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) }
}
/// Evaluate the specified JavaScript code as a module.
pub fn load_module(&self, input: &str, filename: &str) -> Result<Value> {
let val = self.eval(input, filename, EvalType::Module, EvalFlags::COMPILE_ONLY)?;
assert!(val.is_module());
val.eval_function(self)?;
Ok(val)
/// Evaluate the specified JavaScript code.
pub fn eval(&self, input: &str, filename: &str, flags: EvalFlags) -> ValueResult {
self.eval_internal(input, filename, sys::JS_EVAL_TYPE_GLOBAL, flags)
}
/// Evaluate the specified JavaScript code.
pub fn eval(
/// Evaluate the specified JavaScript code as a module.
pub fn eval_module(&self, input: &str, filename: &str) -> Result<Module> {
let val = self.eval_internal(
input,
filename,
sys::JS_EVAL_TYPE_MODULE,
EvalFlags::COMPILE_ONLY,
)?;
assert!(val.is_module());
unsafe {
// NOTE: This might be stupid but we're trying to
// intentionally leak the value here; since the module
// itself has a lifetime unconstrained by traditional value
// semantics. (This is a weird edge in QuickJS.)
sys::JS_DupValue(self.ctx, val.val);
Ok(Module::from_raw(
sys::JS_VALUE_GET_PTR(val.val) as *mut sys::JSModuleDef,
self.get_runtime(),
))
}
}
fn eval_internal(
&self,
input: &str,
filename: &str,
eval_type: EvalType,
eval_type: u32,
flags: EvalFlags,
) -> ValueResult {
let c_input = CString::new(input)?;
let c_filename = CString::new(filename)?;
let eval_type = match eval_type {
EvalType::Global => sys::JS_EVAL_TYPE_GLOBAL,
EvalType::Module => sys::JS_EVAL_TYPE_MODULE,
};
let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap();
unsafe {
@ -143,6 +147,18 @@ impl ContextRef {
}
}
/// Import a module by name.
pub fn import_module(&self, name: &str, base: &str) -> Result<Module> {
let name = CString::new(name)?;
let base = CString::new(base)?;
let module = unsafe { sys::JS_RunModule(self.ctx, name.as_ptr(), base.as_ptr()) };
if module.is_null() {
return Err(self.exception_error());
}
Ok(Module::from_raw(module, self.get_runtime()))
}
/// Construct a new string atom.
pub fn new_atom(&self, value: &str) -> Result<Atom> {
let c_value = CString::new(value)?;
@ -377,9 +393,7 @@ mod tests {
let rt = Runtime::new();
let ctx = Context::new(rt);
let val = ctx
.eval("1+1", "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
let val = ctx.eval("1+1", "script", EvalFlags::NONE).unwrap();
assert_eq!(String::from("2"), val.to_string(&ctx).unwrap());
}
@ -390,7 +404,7 @@ mod tests {
let ctx = Context::new(rt);
let compiled = ctx
.eval("1 + 1", "script", EvalType::Global, EvalFlags::COMPILE_ONLY)
.eval("1 + 1", "script", EvalFlags::COMPILE_ONLY)
.expect("Unable to compile code");
let result = compiled
.eval_function(&ctx)
@ -410,9 +424,7 @@ mod tests {
let val = ctx.new_i32(12).unwrap();
go.set_property(&ctx, "foo", &val).unwrap();
let result = ctx
.eval("foo + 3", "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
let result = ctx.eval("foo + 3", "script", EvalFlags::NONE).unwrap();
assert_eq!(String::from("15"), result.to_string(&ctx).unwrap());
}
@ -454,9 +466,7 @@ mod tests {
let val = vr.as_ref().unwrap();
go.set_property(&ctx, "val", val).unwrap();
let result = ctx
.eval(expr, "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
let result = ctx.eval(expr, "script", EvalFlags::NONE).unwrap();
assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap());
}
}
@ -479,12 +489,7 @@ mod tests {
}
let result = ctx
.eval(
"foo().toUpperCase()",
"script",
EvalType::Global,
EvalFlags::NONE,
)
.eval("foo().toUpperCase()", "script", EvalFlags::NONE)
.unwrap();
assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap());
}
@ -493,17 +498,16 @@ mod tests {
fn modules_with_exports() {
let ctx = Context::new(Runtime::new());
let module = ctx
.load_module("const foo = 123; export { foo };", "main.js")
.eval_module("const foo = 123; export { foo };", "main.js")
.expect("Could not load!");
assert_eq!(module.value_type(), ValueType::Module);
let foo = module
.get_module_export(&ctx, "foo")
.get_export(&ctx, "foo")
.expect("Could not get export");
assert_eq!(String::from("123"), foo.to_string(&ctx).unwrap());
let bar = module
.get_module_export(&ctx, "bar")
.get_export(&ctx, "bar")
.expect("Could not get export");
assert!(bar.is_undefined());
}