[oden] Native Modules
This commit is contained in:
parent
3b02faf9b4
commit
c574fd8cb8
8 changed files with 453 additions and 79 deletions
|
|
@ -52,7 +52,9 @@ extern "C" {
|
||||||
fn JS_MakeException_real() -> JSValue;
|
fn JS_MakeException_real() -> JSValue;
|
||||||
fn JS_MakeNull_real() -> JSValue;
|
fn JS_MakeNull_real() -> JSValue;
|
||||||
fn JS_MakeUndefined_real() -> JSValue;
|
fn JS_MakeUndefined_real() -> JSValue;
|
||||||
fn JS_ValueGetPtr_real(v: JSValue) -> *mut ::std::os::raw::c_void;
|
fn JS_VALUE_GET_PTR_real(v: JSValue) -> *mut ::std::os::raw::c_void;
|
||||||
|
fn JS_VALUE_GET_INT_real(v: JSValue) -> ::std::os::raw::c_int;
|
||||||
|
fn JS_VALUE_GET_BOOL_real(v: JSValue) -> ::std::os::raw::c_int;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn JS_ValueGetTag(v: JSValue) -> i32 {
|
pub unsafe fn JS_ValueGetTag(v: JSValue) -> i32 {
|
||||||
|
|
@ -228,6 +230,14 @@ pub unsafe fn JS_MakeUndefined() -> JSValue {
|
||||||
JS_MakeUndefined_real()
|
JS_MakeUndefined_real()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn JS_ValueGetPtr(v: JSValue) -> *mut ::std::os::raw::c_void {
|
pub unsafe fn JS_VALUE_GET_PTR(v: JSValue) -> *mut ::std::os::raw::c_void {
|
||||||
JS_ValueGetPtr_real(v)
|
JS_VALUE_GET_PTR_real(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn JS_VALUE_GET_INT(v: JSValue) -> ::std::os::raw::c_int {
|
||||||
|
JS_VALUE_GET_INT_real(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn JS_VALUE_GET_BOOL(v: JSValue) -> ::std::os::raw::c_int {
|
||||||
|
JS_VALUE_GET_BOOL_real(v)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,15 @@ JSValue JS_MakeUndefined_real() {
|
||||||
return JS_UNDEFINED;
|
return JS_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *JS_ValueGetPtr_real(JSValue val) {
|
void *JS_VALUE_GET_PTR_real(JSValue val) {
|
||||||
return JS_VALUE_GET_PTR(val);
|
return JS_VALUE_GET_PTR(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int JS_VALUE_GET_INT_real(JSValue v) {
|
||||||
|
return JS_VALUE_GET_INT(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
int JS_VALUE_GET_BOOL_real(JSValue v) {
|
||||||
|
return JS_VALUE_GET_BOOL(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{Class, ClassID, ContextRef, Error, ValueRef, ValueResult};
|
use crate::{throw_string, Class, ClassID, ContextRef, Error, ValueRef, ValueResult};
|
||||||
use oden_js_sys as sys;
|
use oden_js_sys as sys;
|
||||||
use std::ffi::{c_int, CString};
|
use std::ffi::c_int;
|
||||||
use std::panic::catch_unwind;
|
use std::panic::catch_unwind;
|
||||||
|
|
||||||
pub trait Callback: Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult {}
|
pub trait Callback: Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult {}
|
||||||
|
|
@ -25,40 +25,6 @@ impl<T: Callback> Class for CallbackObject<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn throw_string(context: &ContextRef, message: String) -> sys::JSValue {
|
|
||||||
let ctx = context.ctx;
|
|
||||||
match context.new_string(&message) {
|
|
||||||
Ok(e) => unsafe {
|
|
||||||
// Because context.new_string yields an owned Value, and will
|
|
||||||
// clean it up on the way out, we need to explicitly DupValue a
|
|
||||||
// reference for the `Throw` to own.
|
|
||||||
let err = sys::JS_NewError(ctx);
|
|
||||||
if sys::JS_ValueGetTag(err) == sys::JS_TAG_EXCEPTION {
|
|
||||||
// GIVE UP; this is out of memory anyway things probably went
|
|
||||||
// wrong because of that.
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
sys::JS_DupValue(ctx, e.val); // SetProperty takes ownership.
|
|
||||||
let prop = CString::new("message").unwrap();
|
|
||||||
if sys::JS_SetPropertyStr(ctx, err, prop.as_ptr(), e.val) == -1 {
|
|
||||||
// Also an out of memory but we need to free the error object
|
|
||||||
// on our way out.
|
|
||||||
sys::JS_FreeValue(ctx, err);
|
|
||||||
return sys::JS_MakeException(); // JS_EXCEPTION
|
|
||||||
}
|
|
||||||
|
|
||||||
sys::JS_Throw(ctx, err)
|
|
||||||
},
|
|
||||||
Err(_) => unsafe {
|
|
||||||
sys::JS_Throw(
|
|
||||||
ctx,
|
|
||||||
sys::JS_NewString(ctx, "Errors within errors: embedded nulls in the description of the error that occurred".as_bytes().as_ptr() as *const i8),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn callback_impl<F>(
|
fn callback_impl<F>(
|
||||||
ctx: *mut sys::JSContext,
|
ctx: *mut sys::JSContext,
|
||||||
_this: sys::JSValue,
|
_this: sys::JSValue,
|
||||||
|
|
@ -103,7 +69,7 @@ where
|
||||||
*ret
|
*ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Error::Exception(e)) => unsafe {
|
Err(Error::Exception(e, _)) => unsafe {
|
||||||
// If we returned `Error::Exception` then we're propagating an
|
// If we returned `Error::Exception` then we're propagating an
|
||||||
// exception through the JS stack, just flip it.
|
// exception through the JS stack, just flip it.
|
||||||
let exc = &e.val;
|
let exc = &e.val;
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ 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.
|
||||||
pub fn load_module(&self, input: &str, filename: &str) -> Result<Value> {
|
pub fn load_module(&self, input: &str, filename: &str) -> Result<Value> {
|
||||||
let val = self.eval(input, filename, EvalType::Module, EvalFlags::COMPILE_ONLY)?;
|
let val = self.eval(input, filename, EvalType::Module, EvalFlags::COMPILE_ONLY)?;
|
||||||
assert!(val.is_module());
|
assert!(val.is_module());
|
||||||
|
|
@ -122,14 +123,8 @@ impl ContextRef {
|
||||||
eval_type: EvalType,
|
eval_type: EvalType,
|
||||||
flags: EvalFlags,
|
flags: EvalFlags,
|
||||||
) -> ValueResult {
|
) -> ValueResult {
|
||||||
let c_input = match CString::new(input) {
|
let c_input = CString::new(input)?;
|
||||||
Ok(cs) => Ok(cs),
|
let c_filename = CString::new(filename)?;
|
||||||
Err(_) => Err(Error::UnexpectedNul),
|
|
||||||
}?;
|
|
||||||
let c_filename = match CString::new(filename) {
|
|
||||||
Ok(cs) => Ok(cs),
|
|
||||||
Err(_) => Err(Error::UnexpectedNul),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let eval_type = match eval_type {
|
let eval_type = match eval_type {
|
||||||
EvalType::Global => sys::JS_EVAL_TYPE_GLOBAL,
|
EvalType::Global => sys::JS_EVAL_TYPE_GLOBAL,
|
||||||
|
|
@ -150,14 +145,11 @@ impl ContextRef {
|
||||||
|
|
||||||
/// 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 = match CString::new(value) {
|
let c_value = CString::new(value)?;
|
||||||
Ok(cs) => Ok(cs),
|
|
||||||
Err(_) => Err(Error::UnexpectedNul),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let atom = unsafe { sys::JS_NewAtomLen(self.ctx, c_value.into_raw(), value.len()) };
|
let atom = unsafe { sys::JS_NewAtomLen(self.ctx, c_value.as_ptr(), value.len()) };
|
||||||
if atom == sys::JS_ATOM_NULL {
|
if atom == sys::JS_ATOM_NULL {
|
||||||
return Err(Error::Exception(self.exception()));
|
return Err(self.exception_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Atom::from_raw(atom, self))
|
Ok(Atom::from_raw(atom, self))
|
||||||
|
|
@ -241,13 +233,9 @@ impl ContextRef {
|
||||||
|
|
||||||
/// Construct a new value from a string.
|
/// Construct a new value from a string.
|
||||||
pub fn new_string(&self, value: &str) -> ValueResult {
|
pub fn new_string(&self, value: &str) -> ValueResult {
|
||||||
let c_value = match CString::new(value) {
|
let c_value = CString::new(value)?;
|
||||||
Ok(cs) => Ok(cs),
|
|
||||||
Err(_) => Err(Error::UnexpectedNul),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
self.check_exception(unsafe {
|
self.check_exception(unsafe {
|
||||||
sys::JS_NewStringLen(self.ctx, c_value.into_raw(), value.len())
|
sys::JS_NewStringLen(self.ctx, c_value.as_ptr(), value.len())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,7 +271,7 @@ impl ContextRef {
|
||||||
/// error value. Otherwise, just return success with the value, wrapped.
|
/// error value. Otherwise, just return success with the value, wrapped.
|
||||||
pub(crate) fn check_exception(&self, val: sys::JSValue) -> ValueResult {
|
pub(crate) fn check_exception(&self, val: sys::JSValue) -> ValueResult {
|
||||||
if unsafe { sys::JS_ValueGetTag(val) } == sys::JS_TAG_EXCEPTION {
|
if unsafe { sys::JS_ValueGetTag(val) } == sys::JS_TAG_EXCEPTION {
|
||||||
Err(Error::Exception(self.exception()))
|
Err(self.exception_error())
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::from_raw(val, self))
|
Ok(Value::from_raw(val, self))
|
||||||
}
|
}
|
||||||
|
|
@ -296,6 +284,16 @@ impl ContextRef {
|
||||||
pub(crate) fn exception(&self) -> Value {
|
pub(crate) fn exception(&self) -> Value {
|
||||||
Value::from_raw(unsafe { sys::JS_GetException(self.ctx) }, self)
|
Value::from_raw(unsafe { sys::JS_GetException(self.ctx) }, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch the exception value from the context, if any. This is not
|
||||||
|
/// public because anything that might raise an exception should be
|
||||||
|
/// returning a Result<> instead, to separate the exception flow from the
|
||||||
|
/// value flow.
|
||||||
|
pub(crate) fn exception_error(&self) -> Error {
|
||||||
|
let exc = self.exception();
|
||||||
|
let desc = exc.to_string(&self).unwrap_or_else(|_| String::new());
|
||||||
|
Error::Exception(exc, desc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,8 @@ impl TryIntoValue for Error {
|
||||||
}
|
}
|
||||||
Error::ConversionError(e) => Err(Error::ConversionError(e)),
|
Error::ConversionError(e) => Err(Error::ConversionError(e)),
|
||||||
Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
|
Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
|
||||||
Error::Exception(v) => Err(Error::Exception(v.dup(ctx))),
|
Error::Exception(v, d) => Err(Error::Exception(v.dup(ctx), d)),
|
||||||
|
Error::OutOfMemory => Err(Error::OutOfMemory),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use oden_js_sys as sys;
|
||||||
|
use std::ffi::{CString, NulError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod atom;
|
mod atom;
|
||||||
|
|
@ -5,6 +7,7 @@ mod callback;
|
||||||
mod class;
|
mod class;
|
||||||
mod context;
|
mod context;
|
||||||
mod conversion;
|
mod conversion;
|
||||||
|
pub mod module;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
|
@ -36,8 +39,51 @@ pub enum Error {
|
||||||
ConversionError(String),
|
ConversionError(String),
|
||||||
#[error("an error occurred calling a rust function: {0}")]
|
#[error("an error occurred calling a rust function: {0}")]
|
||||||
RustFunctionError(String),
|
RustFunctionError(String),
|
||||||
#[error("an exception was thrown during evaluation")]
|
#[error("an exception was thrown during evaluation: {1}")]
|
||||||
Exception(Value),
|
Exception(Value, String),
|
||||||
|
#[error("out of memory")]
|
||||||
|
OutOfMemory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NulError> for Error {
|
||||||
|
fn from(_: NulError) -> Self {
|
||||||
|
Error::UnexpectedNul
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
let ctx = context.ctx;
|
||||||
|
match context.new_string(&message) {
|
||||||
|
Ok(e) => unsafe {
|
||||||
|
// Because context.new_string yields an owned Value, and will
|
||||||
|
// clean it up on the way out, we need to explicitly DupValue a
|
||||||
|
// reference for the `Throw` to own.
|
||||||
|
let err = sys::JS_NewError(ctx);
|
||||||
|
if sys::JS_ValueGetTag(err) == sys::JS_TAG_EXCEPTION {
|
||||||
|
// GIVE UP; this is out of memory anyway things probably went
|
||||||
|
// wrong because of that.
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys::JS_DupValue(ctx, e.val); // SetProperty takes ownership.
|
||||||
|
let prop = CString::new("message").unwrap();
|
||||||
|
if sys::JS_SetPropertyStr(ctx, err, prop.as_ptr(), e.val) == -1 {
|
||||||
|
// Also an out of memory but we need to free the error object
|
||||||
|
// on our way out.
|
||||||
|
sys::JS_FreeValue(ctx, err);
|
||||||
|
return sys::JS_MakeException(); // JS_EXCEPTION
|
||||||
|
}
|
||||||
|
|
||||||
|
sys::JS_Throw(ctx, err)
|
||||||
|
},
|
||||||
|
Err(_) => unsafe {
|
||||||
|
sys::JS_Throw(
|
||||||
|
ctx,
|
||||||
|
sys::JS_NewString(ctx, "Errors within errors: embedded nulls in the description of the error that occurred".as_bytes().as_ptr() as *const i8),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
346
oden-js/src/module.rs
Normal file
346
oden-js/src/module.rs
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
use crate::{
|
||||||
|
throw_string, Class, ClassID, ContextRef, Error, Result, TryIntoValue, Value, ValueRef,
|
||||||
|
};
|
||||||
|
use oden_js_sys as sys;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
/// A helper structure for declaring values. Use this in the implementation
|
||||||
|
/// of a NativeModule. (See the documentation for `NativeModule` for more
|
||||||
|
/// information.)
|
||||||
|
pub struct Declarations {
|
||||||
|
declarations: HashSet<CString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Declarations {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Declarations {
|
||||||
|
declarations: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declare that a native module will eventually export a value named
|
||||||
|
/// `name`.
|
||||||
|
pub fn declare<S>(&mut self, name: S) -> Result<&mut Self>
|
||||||
|
where
|
||||||
|
S: Into<Vec<u8>>,
|
||||||
|
{
|
||||||
|
self.declarations.insert(CString::new(name)?);
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply all the declarations to the module. (Don't let user code see
|
||||||
|
/// the JSModuleDef, right?)
|
||||||
|
pub(crate) unsafe fn apply(self, ctx: &ContextRef, m: *mut sys::JSModuleDef) -> Result<()> {
|
||||||
|
for k in self.declarations {
|
||||||
|
let res = unsafe { sys::JS_AddModuleExport(ctx.ctx, m, k.into_raw()) };
|
||||||
|
if res < 0 {
|
||||||
|
return Err(Error::OutOfMemory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Export {
|
||||||
|
name: CString,
|
||||||
|
value: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper structure for exporting values. Use this in the implementation
|
||||||
|
/// of a NativeModule. (See the documentation for `NativeModule` for more
|
||||||
|
/// information.)
|
||||||
|
pub struct Exports<'ctx> {
|
||||||
|
context: &'ctx ContextRef,
|
||||||
|
exports: Vec<Export>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> Exports<'ctx> {
|
||||||
|
pub(crate) fn new(context: &'ctx ContextRef) -> Self {
|
||||||
|
Exports {
|
||||||
|
context,
|
||||||
|
exports: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export a value named `name` from the current module. You *must* have
|
||||||
|
/// provided the same name in a previous call to `Declarations::declare`.
|
||||||
|
pub fn export<N: Into<Vec<u8>>, T: TryIntoValue>(
|
||||||
|
&mut self,
|
||||||
|
name: N,
|
||||||
|
value: T,
|
||||||
|
) -> Result<&mut Self> {
|
||||||
|
let name = CString::new(name.into())?;
|
||||||
|
let value = value.try_into_value(&self.context)?;
|
||||||
|
self.export_value(name, &value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export a value named `name` from the current module. You *must* have
|
||||||
|
/// provided the same name in a previous call to `Declarations::declare`.
|
||||||
|
pub fn export_value(&mut self, name: CString, value: &ValueRef) -> Result<&mut Self> {
|
||||||
|
self.exports.push(Export {
|
||||||
|
name,
|
||||||
|
value: value.dup(self.context),
|
||||||
|
});
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actually export the values in the module. (Don't let user code see
|
||||||
|
/// the JSModuleDef!)
|
||||||
|
pub(crate) fn apply(self, module: *mut sys::JSModuleDef) -> Result<()> {
|
||||||
|
for export in self.exports {
|
||||||
|
let name = export.name;
|
||||||
|
let value = export.value;
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
// Ownership of name is retained
|
||||||
|
// Ownership of value is transfered.
|
||||||
|
sys::JS_DupValue(self.context.ctx, value.val);
|
||||||
|
sys::JS_SetModuleExport(self.context.ctx, module, name.as_ref().as_ptr(), value.val)
|
||||||
|
};
|
||||||
|
|
||||||
|
if res < 0 {
|
||||||
|
return Err(Error::OutOfMemory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement this trait to implement a JavaScript module in Rust.
|
||||||
|
///
|
||||||
|
/// JavaScript modules proceed in two phases. First, we resolve all the
|
||||||
|
/// imports, and once the import graph has been determined, then "execute"
|
||||||
|
/// the body of the modules to actually generate values. We reflect these
|
||||||
|
/// two phases here, in the `declare` and `define` methods.
|
||||||
|
///
|
||||||
|
/// (You might find the `NativeModuleBuilder` structure easier to use.)
|
||||||
|
pub trait NativeModule {
|
||||||
|
/// Phase 1: Declare all the names you're going to export. Call
|
||||||
|
/// `declarations.declare` once for each value you will eventually export
|
||||||
|
/// in phase 2.
|
||||||
|
fn declare(&self, declarations: &mut Declarations) -> Result<()>;
|
||||||
|
|
||||||
|
/// Phase 2: Define all the values you're going to define. Call
|
||||||
|
/// `exports.export` once for each value you declared in phase 1.
|
||||||
|
fn define<'ctx>(&self, context: &'ctx ContextRef, exports: &mut Exports<'ctx>) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NativeModuleState<T: NativeModule> {
|
||||||
|
module: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: NativeModule> NativeModuleState<T> {
|
||||||
|
fn new(module: T) -> Self {
|
||||||
|
NativeModuleState { module }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define(context: &ContextRef, m: *mut sys::JSModuleDef) -> Result<()> {
|
||||||
|
let import_meta =
|
||||||
|
context.check_exception(unsafe { sys::JS_GetImportMeta(context.ctx, m) })?;
|
||||||
|
let native_value = import_meta.get_property(context, "native_module")?;
|
||||||
|
let native = Self::try_from_value(&native_value)?;
|
||||||
|
|
||||||
|
let mut exports = Exports::new(context);
|
||||||
|
native
|
||||||
|
.module
|
||||||
|
.define(context, &mut exports)
|
||||||
|
.and_then(|_| exports.apply(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: NativeModule> Class for NativeModuleState<T> {
|
||||||
|
fn class_id() -> &'static ClassID {
|
||||||
|
static ID: ClassID = ClassID::new("NativeModuleState");
|
||||||
|
&ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn init_func<T: NativeModule>(
|
||||||
|
ctx: *mut sys::JSContext,
|
||||||
|
m: *mut sys::JSModuleDef,
|
||||||
|
) -> std::os::raw::c_int {
|
||||||
|
let context = ContextRef::from_raw(ctx);
|
||||||
|
match NativeModuleState::<T>::define(&context, m) {
|
||||||
|
Ok(_) => 0,
|
||||||
|
Err(Error::Exception(e, _)) => unsafe {
|
||||||
|
// If we returned `Error::Exception` then we're propagating an
|
||||||
|
// exception through the JS stack, just flip it.
|
||||||
|
let exc = &e.val;
|
||||||
|
sys::JS_DupValue(ctx, *exc);
|
||||||
|
sys::JS_Throw(ctx, *exc);
|
||||||
|
-1
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
throw_string(&context, err.to_string());
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a new native module, with the provided name and
|
||||||
|
/// implementation. Once this succeeds, the specified module is ready to be
|
||||||
|
/// consumed by javascript, although the `define` method will not be called
|
||||||
|
/// until the first time it gets successfully imported somewhere.
|
||||||
|
pub fn define_native_module<T: NativeModule>(
|
||||||
|
ctx: &ContextRef,
|
||||||
|
name: &str,
|
||||||
|
module: T,
|
||||||
|
) -> Result<()> {
|
||||||
|
let c_name = CString::new(name)?;
|
||||||
|
let m = unsafe { sys::JS_NewCModule(ctx.ctx, c_name.as_ptr(), Some(init_func::<T>)) };
|
||||||
|
if m.is_null() {
|
||||||
|
return Err(ctx.exception_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut declarations = Declarations::new();
|
||||||
|
module
|
||||||
|
.declare(&mut declarations)
|
||||||
|
.and_then(|_| unsafe { declarations.apply(&ctx, m) })?;
|
||||||
|
|
||||||
|
let native_value = NativeModuleState::new(module).into_value(ctx)?;
|
||||||
|
let mut import_meta = ctx.check_exception(unsafe { sys::JS_GetImportMeta(ctx.ctx, m) })?;
|
||||||
|
import_meta.set_property(ctx, "native_module", &native_value)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GenericNativeModule {
|
||||||
|
exports: Vec<Export>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenericNativeModule {
|
||||||
|
fn new(exports: Vec<Export>) -> Self {
|
||||||
|
GenericNativeModule { exports }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NativeModule for GenericNativeModule {
|
||||||
|
fn declare(&self, declarations: &mut Declarations) -> Result<()> {
|
||||||
|
for e in self.exports.iter() {
|
||||||
|
declarations.declare(e.name.clone())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define<'ctx>(&self, _: &'ctx ContextRef, exports: &mut Exports<'ctx>) -> Result<()> {
|
||||||
|
for e in self.exports.iter() {
|
||||||
|
exports.export_value(e.name.clone(), &e.value)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper to define a native module, by defining it export-by-export.
|
||||||
|
pub struct NativeModuleBuilder<'ctx> {
|
||||||
|
exports: Exports<'ctx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx> NativeModuleBuilder<'ctx> {
|
||||||
|
/// Construct a new native module builder, which will use the given
|
||||||
|
/// context to define members.
|
||||||
|
pub fn new(context: &'ctx ContextRef) -> Self {
|
||||||
|
NativeModuleBuilder {
|
||||||
|
exports: Exports::new(context),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a new export that will be in the native module.
|
||||||
|
pub fn export<N: Into<Vec<u8>>, T: TryIntoValue>(
|
||||||
|
&mut self,
|
||||||
|
name: N,
|
||||||
|
value: T,
|
||||||
|
) -> Result<&mut Self> {
|
||||||
|
self.exports.export(name, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish constructing the native module.
|
||||||
|
pub fn build(&self, name: &str) -> Result<()> {
|
||||||
|
let context = self.exports.context;
|
||||||
|
let mut exports = Vec::new();
|
||||||
|
for export in self.exports.exports.iter() {
|
||||||
|
exports.push(Export {
|
||||||
|
name: export.name.clone(),
|
||||||
|
value: export.value.dup(context),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let generic_module = GenericNativeModule::new(exports);
|
||||||
|
define_native_module(context, name, generic_module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{Context, Runtime};
|
||||||
|
|
||||||
|
struct TestingNativeModule {}
|
||||||
|
|
||||||
|
impl NativeModule for TestingNativeModule {
|
||||||
|
fn declare(&self, declarations: &mut Declarations) -> Result<()> {
|
||||||
|
declarations.declare("foo")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define<'ctx>(&self, _: &'ctx ContextRef, exports: &mut Exports<'ctx>) -> Result<()> {
|
||||||
|
exports.export("foo", 23)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_define_native_module() {
|
||||||
|
let runtime = Runtime::new();
|
||||||
|
let context = Context::new(runtime);
|
||||||
|
|
||||||
|
define_native_module(&context, "the_test", TestingNativeModule {})
|
||||||
|
.expect("Module load should succeed");
|
||||||
|
|
||||||
|
let js_module = context
|
||||||
|
.load_module(
|
||||||
|
r#"
|
||||||
|
import { foo } from "the_test";
|
||||||
|
export const my_foo = foo;
|
||||||
|
"#,
|
||||||
|
"test",
|
||||||
|
)
|
||||||
|
.expect("Evaluation of the test script should succeed");
|
||||||
|
|
||||||
|
let my_foo = js_module
|
||||||
|
.get_module_export(&context, "my_foo")
|
||||||
|
.expect("Retrieving JS export should succeed");
|
||||||
|
|
||||||
|
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("23"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_native_module_builder() {
|
||||||
|
let runtime = Runtime::new();
|
||||||
|
let context = Context::new(runtime);
|
||||||
|
|
||||||
|
NativeModuleBuilder::new(&context)
|
||||||
|
.export("foo", 123)
|
||||||
|
.expect("define should succeed")
|
||||||
|
.export("bar", 321)
|
||||||
|
.expect("define should succeed")
|
||||||
|
.build("the_test")
|
||||||
|
.expect("Module build should succeed");
|
||||||
|
|
||||||
|
let js_module = context
|
||||||
|
.load_module(
|
||||||
|
r#"
|
||||||
|
import { foo, bar } from "the_test";
|
||||||
|
export const my_foo = foo + bar;
|
||||||
|
"#,
|
||||||
|
"test",
|
||||||
|
)
|
||||||
|
.expect("Evaluation of the test script should succeed");
|
||||||
|
|
||||||
|
let my_foo = js_module
|
||||||
|
.get_module_export(&context, "my_foo")
|
||||||
|
.expect("Retrieving JS export should succeed");
|
||||||
|
|
||||||
|
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("444"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -125,7 +125,7 @@ impl ValueRef {
|
||||||
let mut res: i32 = 0;
|
let mut res: i32 = 0;
|
||||||
let ret = sys::JS_ToInt32(ctx.ctx, &mut res, self.val);
|
let ret = sys::JS_ToInt32(ctx.ctx, &mut res, self.val);
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
Err(Error::Exception(ctx.exception()))
|
Err(ctx.exception_error())
|
||||||
} else {
|
} else {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +137,9 @@ impl ValueRef {
|
||||||
let mut res: u32 = 0;
|
let mut res: u32 = 0;
|
||||||
let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val);
|
let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val);
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
Err(Error::Exception(ctx.exception()))
|
let exc = ctx.exception();
|
||||||
|
let desc = exc.to_string(&ctx).unwrap_or_else(|_| String::new());
|
||||||
|
Err(Error::Exception(exc, desc))
|
||||||
} else {
|
} else {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +151,7 @@ impl ValueRef {
|
||||||
let mut res: i64 = 0;
|
let mut res: i64 = 0;
|
||||||
let ret = sys::JS_ToInt64(ctx.ctx, &mut res, self.val);
|
let ret = sys::JS_ToInt64(ctx.ctx, &mut res, self.val);
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
Err(Error::Exception(ctx.exception()))
|
Err(ctx.exception_error())
|
||||||
} else {
|
} else {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +191,7 @@ impl ValueRef {
|
||||||
let mut res: f64 = 0.0;
|
let mut res: f64 = 0.0;
|
||||||
let ret = sys::JS_ToFloat64(ctx.ctx, &mut res, self.val);
|
let ret = sys::JS_ToFloat64(ctx.ctx, &mut res, self.val);
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
Err(Error::Exception(ctx.exception()))
|
Err(ctx.exception_error())
|
||||||
} else {
|
} else {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
@ -257,7 +259,7 @@ impl ValueRef {
|
||||||
sys::JS_DupValue(ctx.ctx, val.val);
|
sys::JS_DupValue(ctx.ctx, val.val);
|
||||||
let result = sys::JS_SetProperty(ctx.ctx, self.val, prop.atom, val.val);
|
let result = sys::JS_SetProperty(ctx.ctx, self.val, prop.atom, val.val);
|
||||||
if result == -1 {
|
if result == -1 {
|
||||||
Err(Error::Exception(ctx.exception()))
|
Err(ctx.exception_error())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +292,7 @@ impl ValueRef {
|
||||||
let cstr = unsafe {
|
let cstr = unsafe {
|
||||||
let ptr = sys::JS_ToCStringLen2(ctx.ctx, std::ptr::null_mut(), self.val, 0);
|
let ptr = sys::JS_ToCStringLen2(ctx.ctx, std::ptr::null_mut(), self.val, 0);
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
return Err(Error::Exception(ctx.exception()));
|
return Err(ctx.exception_error());
|
||||||
}
|
}
|
||||||
CStr::from_ptr(ptr)
|
CStr::from_ptr(ptr)
|
||||||
};
|
};
|
||||||
|
|
@ -318,14 +320,10 @@ impl ValueRef {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let c_value = match CString::new(export) {
|
let c_value = CString::new(export)?;
|
||||||
Ok(cs) => Ok(cs),
|
|
||||||
Err(_) => Err(Error::UnexpectedNul),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let module = sys::JS_ValueGetPtr(self.val) as *mut sys::JSModuleDef;
|
let module = sys::JS_VALUE_GET_PTR(self.val) as *mut sys::JSModuleDef;
|
||||||
ctx.check_exception(sys::JS_GetModuleExport(ctx.ctx, module, c_value.into_raw()))
|
ctx.check_exception(sys::JS_GetModuleExport(ctx.ctx, module, c_value.as_ptr()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,7 +340,7 @@ impl ValueRef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> fmt::Debug for ValueRef {
|
impl fmt::Debug for ValueRef {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Value")
|
f.debug_struct("Value")
|
||||||
.field("v", &self.val)
|
.field("v", &self.val)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue