[oden][oden-js] Rework modules
Damn this is a lot
This commit is contained in:
parent
aa90cea4a3
commit
db8a5f8eed
12 changed files with 280 additions and 105 deletions
|
|
@ -1,24 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
callback::new_fn, conversion::RustFunction, Atom, ClassID, Error, Result, Runtime, Value,
|
callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Result,
|
||||||
ValueRef, ValueResult,
|
Runtime, Value, ValueRef, ValueResult,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use oden_js_sys as sys;
|
use oden_js_sys as sys;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::ops::{Deref, DerefMut};
|
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! {
|
bitflags! {
|
||||||
pub struct EvalFlags: u32 {
|
pub struct EvalFlags: u32 {
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
|
|
@ -31,8 +19,8 @@ bitflags! {
|
||||||
const STRIP = sys::JS_EVAL_FLAG_STRIP;
|
const STRIP = sys::JS_EVAL_FLAG_STRIP;
|
||||||
|
|
||||||
/// Compile but do not run. The result is a value with a value type
|
/// Compile but do not run. The result is a value with a value type
|
||||||
/// of `ValueType::Module` or `ValueType::FunctionBytecode`, and
|
/// of `ValueType::FunctionBytecode`, and which can be executed with
|
||||||
/// which can be executed with `value.eval_function`.
|
/// `value.eval_function`.
|
||||||
const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY;
|
const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY;
|
||||||
|
|
||||||
/// Don't include the stack frames before this eval in the Error()
|
/// Don't include the stack frames before this eval in the Error()
|
||||||
|
|
@ -51,6 +39,10 @@ impl ContextRef {
|
||||||
ContextRef { ctx }
|
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 {
|
pub fn is_registered_class(&self, id: &ClassID) -> bool {
|
||||||
let id = id.get();
|
let id = id.get();
|
||||||
let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) };
|
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 }) }
|
unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the specified JavaScript code as a module.
|
/// Evaluate the specified JavaScript code.
|
||||||
pub fn load_module(&self, input: &str, filename: &str) -> Result<Value> {
|
pub fn eval(&self, input: &str, filename: &str, flags: EvalFlags) -> ValueResult {
|
||||||
let val = self.eval(input, filename, EvalType::Module, EvalFlags::COMPILE_ONLY)?;
|
self.eval_internal(input, filename, sys::JS_EVAL_TYPE_GLOBAL, flags)
|
||||||
assert!(val.is_module());
|
|
||||||
val.eval_function(self)?;
|
|
||||||
|
|
||||||
Ok(val)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the specified JavaScript code.
|
/// Evaluate the specified JavaScript code as a module.
|
||||||
pub fn eval(
|
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,
|
&self,
|
||||||
input: &str,
|
input: &str,
|
||||||
filename: &str,
|
filename: &str,
|
||||||
eval_type: EvalType,
|
eval_type: u32,
|
||||||
flags: EvalFlags,
|
flags: EvalFlags,
|
||||||
) -> ValueResult {
|
) -> ValueResult {
|
||||||
let c_input = CString::new(input)?;
|
let c_input = CString::new(input)?;
|
||||||
let c_filename = CString::new(filename)?;
|
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();
|
let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap();
|
||||||
|
|
||||||
unsafe {
|
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.
|
/// Construct a new string atom.
|
||||||
pub fn new_atom(&self, value: &str) -> Result<Atom> {
|
pub fn new_atom(&self, value: &str) -> Result<Atom> {
|
||||||
let c_value = CString::new(value)?;
|
let c_value = CString::new(value)?;
|
||||||
|
|
@ -377,9 +393,7 @@ mod tests {
|
||||||
let rt = Runtime::new();
|
let rt = Runtime::new();
|
||||||
let ctx = Context::new(rt);
|
let ctx = Context::new(rt);
|
||||||
|
|
||||||
let val = ctx
|
let val = ctx.eval("1+1", "script", EvalFlags::NONE).unwrap();
|
||||||
.eval("1+1", "script", EvalType::Global, EvalFlags::NONE)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(String::from("2"), val.to_string(&ctx).unwrap());
|
assert_eq!(String::from("2"), val.to_string(&ctx).unwrap());
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +404,7 @@ mod tests {
|
||||||
let ctx = Context::new(rt);
|
let ctx = Context::new(rt);
|
||||||
|
|
||||||
let compiled = ctx
|
let compiled = ctx
|
||||||
.eval("1 + 1", "script", EvalType::Global, EvalFlags::COMPILE_ONLY)
|
.eval("1 + 1", "script", EvalFlags::COMPILE_ONLY)
|
||||||
.expect("Unable to compile code");
|
.expect("Unable to compile code");
|
||||||
let result = compiled
|
let result = compiled
|
||||||
.eval_function(&ctx)
|
.eval_function(&ctx)
|
||||||
|
|
@ -410,9 +424,7 @@ mod tests {
|
||||||
let val = ctx.new_i32(12).unwrap();
|
let val = ctx.new_i32(12).unwrap();
|
||||||
go.set_property(&ctx, "foo", &val).unwrap();
|
go.set_property(&ctx, "foo", &val).unwrap();
|
||||||
|
|
||||||
let result = ctx
|
let result = ctx.eval("foo + 3", "script", EvalFlags::NONE).unwrap();
|
||||||
.eval("foo + 3", "script", EvalType::Global, EvalFlags::NONE)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(String::from("15"), result.to_string(&ctx).unwrap());
|
assert_eq!(String::from("15"), result.to_string(&ctx).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,9 +466,7 @@ mod tests {
|
||||||
let val = vr.as_ref().unwrap();
|
let val = vr.as_ref().unwrap();
|
||||||
go.set_property(&ctx, "val", val).unwrap();
|
go.set_property(&ctx, "val", val).unwrap();
|
||||||
|
|
||||||
let result = ctx
|
let result = ctx.eval(expr, "script", EvalFlags::NONE).unwrap();
|
||||||
.eval(expr, "script", EvalType::Global, EvalFlags::NONE)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap());
|
assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -479,12 +489,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ctx
|
let result = ctx
|
||||||
.eval(
|
.eval("foo().toUpperCase()", "script", EvalFlags::NONE)
|
||||||
"foo().toUpperCase()",
|
|
||||||
"script",
|
|
||||||
EvalType::Global,
|
|
||||||
EvalFlags::NONE,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap());
|
assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap());
|
||||||
}
|
}
|
||||||
|
|
@ -493,17 +498,16 @@ mod tests {
|
||||||
fn modules_with_exports() {
|
fn modules_with_exports() {
|
||||||
let ctx = Context::new(Runtime::new());
|
let ctx = Context::new(Runtime::new());
|
||||||
let module = ctx
|
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!");
|
.expect("Could not load!");
|
||||||
assert_eq!(module.value_type(), ValueType::Module);
|
|
||||||
|
|
||||||
let foo = module
|
let foo = module
|
||||||
.get_module_export(&ctx, "foo")
|
.get_export(&ctx, "foo")
|
||||||
.expect("Could not get export");
|
.expect("Could not get export");
|
||||||
assert_eq!(String::from("123"), foo.to_string(&ctx).unwrap());
|
assert_eq!(String::from("123"), foo.to_string(&ctx).unwrap());
|
||||||
|
|
||||||
let bar = module
|
let bar = module
|
||||||
.get_module_export(&ctx, "bar")
|
.get_export(&ctx, "bar")
|
||||||
.expect("Could not get export");
|
.expect("Could not get export");
|
||||||
assert!(bar.is_undefined());
|
assert!(bar.is_undefined());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ impl TryIntoValue for Error {
|
||||||
Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
|
Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
|
||||||
Error::Exception(v, d) => Err(Error::Exception(v.dup(ctx), d)),
|
Error::Exception(v, d) => Err(Error::Exception(v.dup(ctx), d)),
|
||||||
Error::OutOfMemory => Err(Error::OutOfMemory),
|
Error::OutOfMemory => Err(Error::OutOfMemory),
|
||||||
|
Error::IOError(e) => Err(Error::IOError(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ mod value;
|
||||||
|
|
||||||
pub use atom::{Atom, AtomRef};
|
pub use atom::{Atom, AtomRef};
|
||||||
pub use class::{Class, ClassID};
|
pub use class::{Class, ClassID};
|
||||||
pub use context::{Context, ContextRef, EvalFlags, EvalType};
|
pub use context::{Context, ContextRef, EvalFlags};
|
||||||
pub use conversion::*;
|
pub use conversion::*;
|
||||||
pub use runtime::Runtime;
|
pub use runtime::Runtime;
|
||||||
pub use value::{Value, ValueRef, ValueType};
|
pub use value::{Value, ValueRef, ValueType};
|
||||||
|
|
@ -43,6 +43,8 @@ pub enum Error {
|
||||||
Exception(Value, String),
|
Exception(Value, String),
|
||||||
#[error("out of memory")]
|
#[error("out of memory")]
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
|
#[error("an io error occurred: {0}")]
|
||||||
|
IOError(std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NulError> for Error {
|
impl From<NulError> for Error {
|
||||||
|
|
@ -51,10 +53,26 @@ impl From<NulError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
Error::IOError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
pub type ValueResult = core::result::Result<Value, Error>;
|
pub type ValueResult = core::result::Result<Value, Error>;
|
||||||
|
|
||||||
pub fn throw_string(context: &ContextRef, message: String) -> sys::JSValue {
|
pub(crate) fn throw_error(context: &ContextRef, error: Error) -> sys::JSValue {
|
||||||
|
match error {
|
||||||
|
Error::Exception(v, _) => unsafe {
|
||||||
|
sys::JS_DupValue(context.ctx, v.val);
|
||||||
|
sys::JS_Throw(context.ctx, v.val)
|
||||||
|
},
|
||||||
|
other => throw_string(context, other.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn throw_string(context: &ContextRef, message: String) -> sys::JSValue {
|
||||||
let ctx = context.ctx;
|
let ctx = context.ctx;
|
||||||
match context.new_string(&message) {
|
match context.new_string(&message) {
|
||||||
Ok(e) => unsafe {
|
Ok(e) => unsafe {
|
||||||
|
|
|
||||||
53
oden-js/src/module/loader.rs
Normal file
53
oden-js/src/module/loader.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use super::ModuleRef;
|
||||||
|
use crate::{throw_error, ContextRef, Error, Result};
|
||||||
|
use oden_js_sys as sys;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub enum ModuleSource<'a> {
|
||||||
|
Native(&'a ModuleRef),
|
||||||
|
JavaScript(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ModuleLoader {
|
||||||
|
fn load(&mut self, context: &ContextRef, name: &str) -> Result<ModuleSource>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultModuleLoader {}
|
||||||
|
|
||||||
|
impl DefaultModuleLoader {
|
||||||
|
pub fn new() -> DefaultModuleLoader {
|
||||||
|
DefaultModuleLoader {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleLoader for DefaultModuleLoader {
|
||||||
|
fn load(&mut self, _context: &ContextRef, name: &str) -> Result<ModuleSource> {
|
||||||
|
// Attempt to open the file.
|
||||||
|
let path = Path::new(name);
|
||||||
|
match std::fs::read_to_string(path) {
|
||||||
|
Ok(str) => Ok(ModuleSource::JavaScript(str)),
|
||||||
|
Err(e) => Err(Error::IOError(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_module(
|
||||||
|
context: &ContextRef,
|
||||||
|
name: &str,
|
||||||
|
loader: &mut Box<dyn ModuleLoader>,
|
||||||
|
) -> *mut sys::JSModuleDef {
|
||||||
|
match loader.load(context, name) {
|
||||||
|
Ok(ModuleSource::Native(native)) => native.module,
|
||||||
|
Ok(ModuleSource::JavaScript(js)) => match context.eval_module(&js, name) {
|
||||||
|
Ok(v) => v.module,
|
||||||
|
Err(e) => {
|
||||||
|
throw_error(context, e);
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
throw_error(context, e);
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
oden-js/src/module/mod.rs
Normal file
76
oden-js/src/module/mod.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use crate::{ContextRef, Result, Runtime, Value};
|
||||||
|
use oden_js_sys as sys;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
pub mod loader;
|
||||||
|
pub mod native;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ModuleRef {
|
||||||
|
module: *mut sys::JSModuleDef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleRef {
|
||||||
|
pub(crate) fn from_raw(module: *mut sys::JSModuleDef) -> ModuleRef {
|
||||||
|
ModuleRef { module }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_self(&self, context: &ContextRef) -> Result<()> {
|
||||||
|
let result = unsafe {
|
||||||
|
let v = sys::JSValue {
|
||||||
|
u: sys::JSValueUnion {
|
||||||
|
ptr: self.module as *mut _,
|
||||||
|
},
|
||||||
|
tag: sys::JS_TAG_MODULE as i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
sys::JS_DupValue(context.ctx, v);
|
||||||
|
sys::JS_EvalFunction(context.ctx, v)
|
||||||
|
};
|
||||||
|
context.check_exception(result)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_export(&self, context: &ContextRef, export: &str) -> Result<Value> {
|
||||||
|
self.eval_self(context)?;
|
||||||
|
let c_value = CString::new(export)?;
|
||||||
|
unsafe {
|
||||||
|
context.check_exception(sys::JS_GetModuleExport(
|
||||||
|
context.ctx,
|
||||||
|
self.module,
|
||||||
|
c_value.as_ptr(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Module {
|
||||||
|
m: ModuleRef,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
runtime: Runtime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Module {
|
||||||
|
pub(crate) fn from_raw(module: *mut sys::JSModuleDef, runtime: Runtime) -> Module {
|
||||||
|
Module {
|
||||||
|
m: ModuleRef::from_raw(module),
|
||||||
|
runtime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Module {
|
||||||
|
type Target = ModuleRef;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Module {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::Module;
|
||||||
use crate::{
|
use crate::{
|
||||||
throw_string, Class, ClassID, ContextRef, Error, Result, TryIntoValue, Value, ValueRef,
|
throw_string, Class, ClassID, ContextRef, Error, Result, TryIntoValue, Value, ValueRef,
|
||||||
};
|
};
|
||||||
|
|
@ -186,7 +187,7 @@ pub fn define_native_module<T: NativeModule>(
|
||||||
ctx: &ContextRef,
|
ctx: &ContextRef,
|
||||||
name: &str,
|
name: &str,
|
||||||
module: T,
|
module: T,
|
||||||
) -> Result<()> {
|
) -> Result<Module> {
|
||||||
let c_name = CString::new(name)?;
|
let c_name = CString::new(name)?;
|
||||||
let m = unsafe { sys::JS_NewCModule(ctx.ctx, c_name.as_ptr(), Some(init_func::<T>)) };
|
let m = unsafe { sys::JS_NewCModule(ctx.ctx, c_name.as_ptr(), Some(init_func::<T>)) };
|
||||||
if m.is_null() {
|
if m.is_null() {
|
||||||
|
|
@ -202,7 +203,7 @@ pub fn define_native_module<T: NativeModule>(
|
||||||
let mut import_meta = ctx.check_exception(unsafe { sys::JS_GetImportMeta(ctx.ctx, m) })?;
|
let mut import_meta = ctx.check_exception(unsafe { sys::JS_GetImportMeta(ctx.ctx, m) })?;
|
||||||
import_meta.set_property(ctx, "native_module", &native_value)?;
|
import_meta.set_property(ctx, "native_module", &native_value)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(Module::from_raw(m, ctx.get_runtime()))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GenericNativeModule {
|
struct GenericNativeModule {
|
||||||
|
|
@ -256,7 +257,7 @@ impl<'ctx> NativeModuleBuilder<'ctx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish constructing the native module.
|
/// Finish constructing the native module.
|
||||||
pub fn build(&self, name: &str) -> Result<()> {
|
pub fn build(&self, name: &str) -> Result<Module> {
|
||||||
let context = self.exports.context;
|
let context = self.exports.context;
|
||||||
let mut exports = Vec::new();
|
let mut exports = Vec::new();
|
||||||
for export in self.exports.exports.iter() {
|
for export in self.exports.exports.iter() {
|
||||||
|
|
@ -298,7 +299,7 @@ mod tests {
|
||||||
.expect("Module load should succeed");
|
.expect("Module load should succeed");
|
||||||
|
|
||||||
let js_module = context
|
let js_module = context
|
||||||
.load_module(
|
.eval_module(
|
||||||
r#"
|
r#"
|
||||||
import { foo } from "the_test";
|
import { foo } from "the_test";
|
||||||
export const my_foo = foo;
|
export const my_foo = foo;
|
||||||
|
|
@ -308,7 +309,7 @@ export const my_foo = foo;
|
||||||
.expect("Evaluation of the test script should succeed");
|
.expect("Evaluation of the test script should succeed");
|
||||||
|
|
||||||
let my_foo = js_module
|
let my_foo = js_module
|
||||||
.get_module_export(&context, "my_foo")
|
.get_export(&context, "my_foo")
|
||||||
.expect("Retrieving JS export should succeed");
|
.expect("Retrieving JS export should succeed");
|
||||||
|
|
||||||
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("23"));
|
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("23"));
|
||||||
|
|
@ -328,7 +329,7 @@ export const my_foo = foo;
|
||||||
.expect("Module build should succeed");
|
.expect("Module build should succeed");
|
||||||
|
|
||||||
let js_module = context
|
let js_module = context
|
||||||
.load_module(
|
.eval_module(
|
||||||
r#"
|
r#"
|
||||||
import { foo, bar } from "the_test";
|
import { foo, bar } from "the_test";
|
||||||
export const my_foo = foo + bar;
|
export const my_foo = foo + bar;
|
||||||
|
|
@ -338,7 +339,7 @@ export const my_foo = foo + bar;
|
||||||
.expect("Evaluation of the test script should succeed");
|
.expect("Evaluation of the test script should succeed");
|
||||||
|
|
||||||
let my_foo = js_module
|
let my_foo = js_module
|
||||||
.get_module_export(&context, "my_foo")
|
.get_export(&context, "my_foo")
|
||||||
.expect("Retrieving JS export should succeed");
|
.expect("Retrieving JS export should succeed");
|
||||||
|
|
||||||
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("444"));
|
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("444"));
|
||||||
|
|
@ -1,8 +1,29 @@
|
||||||
|
use crate::module::loader::{load_module, DefaultModuleLoader, ModuleLoader};
|
||||||
|
use crate::ContextRef;
|
||||||
use oden_js_sys as sys;
|
use oden_js_sys as sys;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
struct PrivateState {
|
struct PrivateState {
|
||||||
refs: u64,
|
refs: u64,
|
||||||
|
loader: Box<dyn ModuleLoader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivateState {
|
||||||
|
unsafe extern "C" fn module_loader(
|
||||||
|
ctx: *mut sys::JSContext,
|
||||||
|
path: *const i8,
|
||||||
|
opaque: *mut std::os::raw::c_void,
|
||||||
|
) -> *mut sys::JSModuleDef {
|
||||||
|
let path = match CStr::from_ptr(path).to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return std::ptr::null_mut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = opaque as *mut PrivateState;
|
||||||
|
let context = ContextRef::from_raw(ctx);
|
||||||
|
load_module(&context, path, &mut (*state).loader)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -12,10 +33,19 @@ pub struct Runtime {
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
pub fn new() -> Runtime {
|
pub fn new() -> Runtime {
|
||||||
let state = Box::new(RefCell::new(PrivateState { refs: 1 }));
|
Self::with_loader(DefaultModuleLoader::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_loader<TLoader: ModuleLoader + 'static>(loader: TLoader) -> Runtime {
|
||||||
|
let state = Box::new(RefCell::new(PrivateState {
|
||||||
|
refs: 1,
|
||||||
|
loader: Box::new(loader),
|
||||||
|
}));
|
||||||
let rt = unsafe {
|
let rt = unsafe {
|
||||||
let rt = sys::JS_NewRuntime();
|
let rt = sys::JS_NewRuntime();
|
||||||
sys::JS_SetRuntimeOpaque(rt, Box::into_raw(state) as *mut _);
|
let state = Box::into_raw(state) as *mut _;
|
||||||
|
sys::JS_SetRuntimeOpaque(rt, state);
|
||||||
|
sys::JS_SetModuleLoaderFunc(rt, None, Some(PrivateState::module_loader), state);
|
||||||
rt
|
rt
|
||||||
};
|
};
|
||||||
Runtime { rt }
|
Runtime { rt }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction};
|
use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction};
|
||||||
use oden_js_sys as sys;
|
use oden_js_sys as sys;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::CStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
|
@ -312,21 +312,6 @@ impl ValueRef {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_module_export(&self, ctx: &ContextRef, export: &str) -> Result<Value> {
|
|
||||||
if self.value_type() != ValueType::Module {
|
|
||||||
return Err(Error::InvalidType {
|
|
||||||
expected: ValueType::Bool,
|
|
||||||
found: self.value_type(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let c_value = CString::new(export)?;
|
|
||||||
unsafe {
|
|
||||||
let module = sys::JS_VALUE_GET_PTR(self.val) as *mut sys::JSModuleDef;
|
|
||||||
ctx.check_exception(sys::JS_GetModuleExport(ctx.ctx, module, c_value.as_ptr()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call(&self, ctx: &ContextRef) -> Result<Value> {
|
pub fn call(&self, ctx: &ContextRef) -> Result<Value> {
|
||||||
unsafe {
|
unsafe {
|
||||||
ctx.check_exception(sys::JS_Call(
|
ctx.check_exception(sys::JS_Call(
|
||||||
|
|
@ -414,7 +399,7 @@ impl fmt::Debug for Value {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Context, EvalFlags, EvalType, Runtime};
|
use crate::{Context, EvalFlags, Runtime};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn value_type() {
|
fn value_type() {
|
||||||
|
|
@ -438,9 +423,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (expr, expected) in tests.into_iter() {
|
for (expr, expected) in tests.into_iter() {
|
||||||
let val = ctx
|
let val = ctx.eval(expr, "script", EvalFlags::STRICT).unwrap();
|
||||||
.eval(expr, "script", EvalType::Global, EvalFlags::STRICT)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*expected, val.value_type(), "for {}", expr);
|
assert_eq!(*expected, val.value_type(), "for {}", expr);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
src/graphics.js
Normal file
15
src/graphics.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as core from "graphics-core";
|
||||||
|
|
||||||
|
function cls(r, g, b) {
|
||||||
|
core.cls(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function print(...args) {
|
||||||
|
core.print(args.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
function spr(x, y, w, h, sx, sy, sw = undefined, sh = undefined) {
|
||||||
|
sw = sw | w;
|
||||||
|
sh = sh | h;
|
||||||
|
core.spr(xy, w, h, sx, sy, sw, sh);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { cls, print, spr } from "graphics";
|
import { cls, print, spr } from "./src/graphics";
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
print("Hello world!");
|
print("Hello world!");
|
||||||
|
|
@ -8,5 +8,5 @@ export function update() {}
|
||||||
|
|
||||||
export function draw() {
|
export function draw() {
|
||||||
cls(0.1, 0.2, 0.3);
|
cls(0.1, 0.2, 0.3);
|
||||||
spr(0, 0, 0.5, 0.5, 0, 0, 0.5, 0.5);
|
spr(0, 0, 0.5, 0.5, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,17 @@ impl ScriptContext {
|
||||||
|
|
||||||
let js = include_str!("main.js");
|
let js = include_str!("main.js");
|
||||||
let module = context
|
let module = context
|
||||||
.load_module(js, "main.js")
|
.eval_module(js, "main.js")
|
||||||
.expect("Unable to load main");
|
.expect("Unable to load main");
|
||||||
|
|
||||||
let init = module
|
let init = module
|
||||||
.get_module_export(&context, "init")
|
.get_export(&context, "init")
|
||||||
.expect("Unable to fetch init");
|
.expect("Unable to fetch init");
|
||||||
let update = module
|
let update = module
|
||||||
.get_module_export(&context, "update")
|
.get_export(&context, "update")
|
||||||
.expect("Unable to fetch update");
|
.expect("Unable to fetch update");
|
||||||
let draw = module
|
let draw = module
|
||||||
.get_module_export(&context, "draw")
|
.get_export(&context, "draw")
|
||||||
.expect("Unable to fetch draw");
|
.expect("Unable to fetch draw");
|
||||||
|
|
||||||
ScriptContext {
|
ScriptContext {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use oden_js::{module, ContextRef, Value, ValueRef, ValueResult};
|
use oden_js::{module, ContextRef, Value, ValueResult};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -37,13 +37,7 @@ impl GraphicsImpl {
|
||||||
GraphicsImpl { sender }
|
GraphicsImpl { sender }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_fn(&self, ctx: &ContextRef, args: &[&ValueRef]) -> ValueResult {
|
fn print_fn(&self, ctx: &ContextRef, text: String) -> ValueResult {
|
||||||
let mut text = String::with_capacity(128);
|
|
||||||
for arg in args {
|
|
||||||
let v = arg.to_string(ctx)?;
|
|
||||||
text.push_str(&v);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.sender
|
.sender
|
||||||
.send(GraphicsCommand::Print(PrintCommand { text }));
|
.send(GraphicsCommand::Print(PrintCommand { text }));
|
||||||
|
|
@ -67,8 +61,8 @@ impl GraphicsImpl {
|
||||||
h: f32,
|
h: f32,
|
||||||
u: f32,
|
u: f32,
|
||||||
v: f32,
|
v: f32,
|
||||||
sw: Option<f32>,
|
sw: f32,
|
||||||
sh: Option<f32>,
|
sh: f32,
|
||||||
) -> ValueResult {
|
) -> ValueResult {
|
||||||
let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand {
|
let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand {
|
||||||
x,
|
x,
|
||||||
|
|
@ -77,8 +71,8 @@ impl GraphicsImpl {
|
||||||
h,
|
h,
|
||||||
u,
|
u,
|
||||||
v,
|
v,
|
||||||
sw: sw.unwrap_or(w),
|
sw,
|
||||||
sh: sh.unwrap_or(h),
|
sh,
|
||||||
}));
|
}));
|
||||||
Ok(Value::undefined(ctx))
|
Ok(Value::undefined(ctx))
|
||||||
}
|
}
|
||||||
|
|
@ -91,12 +85,12 @@ pub struct GraphicsAPI {
|
||||||
impl GraphicsAPI {
|
impl GraphicsAPI {
|
||||||
pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> {
|
pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> {
|
||||||
let gfx = Arc::new(GraphicsImpl::new(sender));
|
let gfx = Arc::new(GraphicsImpl::new(sender));
|
||||||
let mut builder = module::NativeModuleBuilder::new(ctx);
|
let mut builder = module::native::NativeModuleBuilder::new(ctx);
|
||||||
{
|
{
|
||||||
let gfx = gfx.clone();
|
let gfx = gfx.clone();
|
||||||
builder.export(
|
builder.export(
|
||||||
"print",
|
"print",
|
||||||
ctx.new_dynamic_fn(move |ctx, _, args| gfx.print_fn(ctx, args))?,
|
ctx.new_fn(move |ctx: &ContextRef, t: String| gfx.print_fn(ctx, t))?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
@ -120,14 +114,14 @@ impl GraphicsAPI {
|
||||||
h: f32,
|
h: f32,
|
||||||
u: f32,
|
u: f32,
|
||||||
v: f32,
|
v: f32,
|
||||||
sw: Option<f32>,
|
sw: f32,
|
||||||
sh: Option<f32>| {
|
sh: f32| {
|
||||||
gfx.spr_fn(ctx, x, y, w, h, u, v, sw, sh)
|
gfx.spr_fn(ctx, x, y, w, h, u, v, sw, sh)
|
||||||
},
|
},
|
||||||
)?,
|
)?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
builder.build("graphics")?;
|
builder.build("graphics-core")?;
|
||||||
Ok(GraphicsAPI { gfx })
|
Ok(GraphicsAPI { gfx })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue