use crate::{
get_exception, handle_exception, qjs, Atom, Context, Ctx, Error, FromAtom, FromJs, IntoJs,
Result, Value,
};
use std::{
ffi::{CStr, CString},
marker::PhantomData,
mem::MaybeUninit,
ptr::null_mut,
slice::from_raw_parts,
};
pub struct Script;
pub struct Native;
pub struct Created;
pub struct Loaded<S = ()>(S);
pub struct Evaluated;
pub trait ModuleDef {
fn load<'js>(_ctx: Ctx<'js>, _module: &Module<'js, Created>) -> Result<()> {
Ok(())
}
fn eval<'js>(_ctx: Ctx<'js>, _module: &Module<'js, Loaded<Native>>) -> Result<()> {
Ok(())
}
}
macro_rules! module_def_impls {
($($($t:ident)*,)*) => {
$(
impl<$($t),*> ModuleDef for ($($t,)*)
where
$($t: ModuleDef,)*
{
fn load<'js>(_ctx: Ctx<'js>, _module: &Module<'js, Created>) -> Result<()> {
$($t::load(_ctx, _module)?;)*
Ok(())
}
fn eval<'js>(_ctx: Ctx<'js>, _module: &Module<'js, Loaded<Native>>) -> Result<()> {
$($t::eval(_ctx, _module)?;)*
Ok(())
}
}
)*
};
}
module_def_impls! {
,
A,
A B,
A B C,
A B C D,
A B C D E,
A B C D E F,
A B C D E F G,
A B C D E F G H,
A B C D E F G H I,
A B C D E F G H I J,
A B C D E F G H I J K,
A B C D E F G H I J K L,
A B C D E F G H I J K L M,
A B C D E F G H I J K L M N,
A B C D E F G H I J K L M N O,
A B C D E F G H I J K L M N O P,
}
#[derive(Debug, PartialEq)]
pub struct Module<'js, S = Evaluated>(pub(crate) Value<'js>, pub(crate) PhantomData<S>);
impl<'js, S> Clone for Module<'js, S> {
fn clone(&self) -> Self {
Module(self.0.clone(), PhantomData)
}
}
impl<'js, S> Module<'js, S> {
pub(crate) unsafe fn from_module_def(ctx: Ctx<'js>, ptr: *mut qjs::JSModuleDef) -> Self {
Self(
Value::new_ptr(ctx, qjs::JS_TAG_MODULE, ptr as _),
PhantomData,
)
}
pub(crate) unsafe fn from_module_def_const(ctx: Ctx<'js>, ptr: *mut qjs::JSModuleDef) -> Self {
Self(
Value::new_ptr_const(ctx, qjs::JS_TAG_MODULE, ptr as _),
PhantomData,
)
}
pub(crate) fn as_module_def(&self) -> *mut qjs::JSModuleDef {
unsafe { self.0.get_ptr() as _ }
}
pub(crate) fn into_module_def(self) -> *mut qjs::JSModuleDef {
unsafe { self.0.into_ptr() as _ }
}
}
impl<'js> Module<'js> {
pub fn name<N>(&self) -> Result<N>
where
N: FromAtom<'js>,
{
let ctx = self.0.ctx;
let name = unsafe {
Atom::from_atom_val(ctx, qjs::JS_GetModuleName(ctx.ctx, self.as_module_def()))
};
N::from_atom(name)
}
pub fn meta<T>(&self) -> Result<T>
where
T: FromJs<'js>,
{
let ctx = self.0.ctx;
let meta = unsafe {
Value::from_js_value(
ctx,
handle_exception(ctx, qjs::JS_GetImportMeta(ctx.ctx, self.as_module_def()))?,
)
};
T::from_js(ctx, meta)
}
}
#[macro_export]
macro_rules! module_init {
($type:ty) => {
$crate::module_init!(js_init_module: $type);
};
($name:ident: $type:ty) => {
#[no_mangle]
pub unsafe extern "C" fn $name(
ctx: *mut $crate::qjs::JSContext,
module_name: *const $crate::qjs::c_char,
) -> *mut $crate::qjs::JSModuleDef {
$crate::Module::init_raw::<$type>(ctx, module_name)
}
};
}
pub type ModuleLoadFn =
unsafe extern "C" fn(*mut qjs::JSContext, *const qjs::c_char) -> *mut qjs::JSModuleDef;
impl<'js> Module<'js> {
#[allow(clippy::new_ret_no_self)]
pub fn new<N, S>(ctx: Ctx<'js>, name: N, source: S) -> Result<Module<'js, Loaded<Script>>>
where
N: Into<Vec<u8>>,
S: Into<Vec<u8>>,
{
let name = CString::new(name)?;
let flag =
qjs::JS_EVAL_TYPE_MODULE | qjs::JS_EVAL_FLAG_STRICT | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
Ok(Module(
unsafe {
let value = Value::from_js_value_const(
ctx,
ctx.eval_raw(source, name.as_c_str(), flag as _)?,
);
debug_assert!(value.is_module());
value
},
PhantomData,
))
}
#[allow(clippy::new_ret_no_self)]
pub fn new_def<D, N>(ctx: Ctx<'js>, name: N) -> Result<Module<'js, Loaded<Native>>>
where
D: ModuleDef,
N: Into<Vec<u8>>,
{
let name = CString::new(name)?;
let ptr = unsafe {
qjs::JS_NewCModule(
ctx.ctx,
name.as_ptr(),
Some(Module::<Loaded<Native>>::eval_fn::<D>),
)
};
if ptr.is_null() {
return Err(Error::Allocation);
}
let module = unsafe { Module::<Created>::from_module_def_const(ctx, ptr) };
D::load(ctx, &module)?;
Ok(Module(module.0, PhantomData))
}
#[allow(clippy::new_ret_no_self)]
pub unsafe fn new_raw<N>(
ctx: Ctx<'js>,
name: N,
load: ModuleLoadFn,
) -> Result<Module<'js, Loaded<Native>>>
where
N: Into<Vec<u8>>,
{
let name = CString::new(name)?;
let ptr = load(ctx.ctx, name.as_ptr());
if ptr.is_null() {
Err(Error::Unknown)
} else {
Ok(Module::from_module_def(ctx, ptr))
}
}
pub unsafe extern "C" fn init_raw<D>(
ctx: *mut qjs::JSContext,
name: *const qjs::c_char,
) -> *mut qjs::JSModuleDef
where
D: ModuleDef,
{
Context::init_raw(ctx);
let ctx = Ctx::from_ptr(ctx);
let name = CStr::from_ptr(name);
match Self::_init::<D>(ctx, name) {
Ok(module) => module.into_module_def(),
Err(error) => {
error.throw(ctx);
null_mut() as _
}
}
}
fn _init<D>(ctx: Ctx<'js>, name: &CStr) -> Result<Module<'js, Loaded>>
where
D: ModuleDef,
{
let name = name.to_str()?;
Ok(Module::new_def::<D, _>(ctx, name)?.into_loaded())
}
}
impl<'js> Module<'js, Loaded<Script>> {
pub fn read_object<B: AsRef<[u8]>>(ctx: Ctx<'js>, buf: B) -> Result<Self> {
Self::read_object_raw(ctx, buf.as_ref(), qjs::JS_READ_OBJ_BYTECODE as _)
}
pub fn read_object_const(ctx: Ctx<'js>, buf: &'static [u8]) -> Result<Self> {
Self::read_object_raw(
ctx,
buf,
(qjs::JS_READ_OBJ_BYTECODE | qjs::JS_READ_OBJ_ROM_DATA) as _,
)
}
fn read_object_raw(ctx: Ctx<'js>, buf: &[u8], flags: qjs::c_int) -> Result<Self> {
let value = unsafe {
Value::from_js_value(
ctx,
handle_exception(
ctx,
qjs::JS_ReadObject(ctx.ctx, buf.as_ptr(), buf.len() as _, flags),
)?,
)
};
Ok(Self(value, PhantomData))
}
pub fn write_object(&self, byte_swap: bool) -> Result<Vec<u8>> {
let ctx = self.0.ctx;
let mut len = MaybeUninit::uninit();
let mut flags = qjs::JS_WRITE_OBJ_BYTECODE;
if byte_swap {
flags |= qjs::JS_WRITE_OBJ_BSWAP;
}
let buf = unsafe {
qjs::JS_WriteObject(ctx.ctx, len.as_mut_ptr(), self.0.as_js_value(), flags as _)
};
if buf.is_null() {
return Err(unsafe { get_exception(ctx) });
}
let len = unsafe { len.assume_init() };
let obj = unsafe { from_raw_parts(buf, len as _) };
let obj = Vec::from(obj);
unsafe { qjs::js_free(ctx.ctx, buf as _) };
Ok(obj)
}
}
impl<'js> Module<'js, Loaded<Native>> {
pub fn set<N, T>(&self, name: N, value: T) -> Result<()>
where
N: AsRef<str>,
T: IntoJs<'js>,
{
let name = CString::new(name.as_ref())?;
let ctx = self.0.ctx;
let value = value.into_js(ctx)?;
let value = unsafe { qjs::JS_DupValue(value.as_js_value()) };
if unsafe { qjs::JS_SetModuleExport(ctx.ctx, self.as_module_def(), name.as_ptr(), value) }
< 0
{
unsafe { qjs::JS_FreeValue(ctx.ctx, value) };
return Err(unsafe { get_exception(ctx) });
}
Ok(())
}
unsafe extern "C" fn eval_fn<D>(
ctx: *mut qjs::JSContext,
ptr: *mut qjs::JSModuleDef,
) -> qjs::c_int
where
D: ModuleDef,
{
let ctx = Ctx::from_ptr(ctx);
let module = Self::from_module_def_const(ctx, ptr);
match D::eval(ctx, &module) {
Ok(_) => 0,
Err(error) => {
error.throw(ctx);
-1
}
}
}
}
impl<'js, S> Module<'js, Loaded<S>> {
pub fn eval(self) -> Result<Module<'js, Evaluated>> {
let ctx = self.0.ctx;
unsafe {
let ret = qjs::JS_EvalFunction(ctx.ctx, qjs::JS_DupValue(self.0.value));
handle_exception(ctx, ret)?;
}
Ok(Module(self.0, PhantomData))
}
pub fn into_loaded(self) -> Module<'js, Loaded> {
Module(self.0, PhantomData)
}
}
impl<'js> Module<'js, Created> {
pub fn add<N>(&self, name: N) -> Result<()>
where
N: AsRef<str>,
{
let ctx = self.0.ctx;
let name = CString::new(name.as_ref())?;
unsafe {
qjs::JS_AddModuleExport(ctx.ctx, self.as_module_def(), name.as_ptr());
}
Ok(())
}
}
#[cfg(feature = "exports")]
impl<'js> Module<'js> {
pub fn get<N, T>(&self, name: N) -> Result<T>
where
N: AsRef<str>,
T: FromJs<'js>,
{
let ctx = self.0.ctx;
let name = CString::new(name.as_ref())?;
let value = unsafe {
Value::from_js_value(
ctx,
handle_exception(
ctx,
qjs::JS_GetModuleExport(ctx.ctx, self.as_module_def(), name.as_ptr()),
)?,
)
};
T::from_js(ctx, value)
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub fn names<N>(&self) -> ExportNamesIter<'js, N>
where
N: FromAtom<'js>,
{
ExportNamesIter {
module: self.clone(),
count: unsafe { qjs::JS_GetModuleExportEntriesCount(self.as_module_def()) },
index: 0,
marker: PhantomData,
}
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub fn entries<N, T>(&self) -> ExportEntriesIter<'js, N, T>
where
N: FromAtom<'js>,
T: FromJs<'js>,
{
ExportEntriesIter {
module: self.clone(),
count: unsafe { qjs::JS_GetModuleExportEntriesCount(self.as_module_def()) },
index: 0,
marker: PhantomData,
}
}
#[doc(hidden)]
pub unsafe fn dump_exports(&self) {
let ctx = self.0.ctx;
let ptr = self.as_module_def();
let count = qjs::JS_GetModuleExportEntriesCount(ptr);
for i in 0..count {
let atom_name =
Atom::from_atom_val(ctx, qjs::JS_GetModuleExportEntryName(ctx.ctx, ptr, i));
println!("{}", atom_name.to_string().unwrap());
}
}
}
#[cfg(feature = "exports")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub struct ExportNamesIter<'js, N> {
module: Module<'js>,
count: i32,
index: i32,
marker: PhantomData<N>,
}
#[cfg(feature = "exports")]
impl<'js, N> Iterator for ExportNamesIter<'js, N>
where
N: FromAtom<'js>,
{
type Item = Result<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let ctx = self.module.0.ctx;
let ptr = self.module.as_module_def();
let atom = unsafe {
let atom_val = qjs::JS_GetModuleExportEntryName(ctx.ctx, ptr, self.index);
Atom::from_atom_val(ctx, atom_val)
};
self.index += 1;
Some(N::from_atom(atom))
}
}
#[cfg(feature = "exports")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "exports")))]
pub struct ExportEntriesIter<'js, N, T> {
module: Module<'js>,
count: i32,
index: i32,
marker: PhantomData<(N, T)>,
}
#[cfg(feature = "exports")]
impl<'js, N, T> Iterator for ExportEntriesIter<'js, N, T>
where
N: FromAtom<'js>,
T: FromJs<'js>,
{
type Item = Result<(N, T)>;
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let ctx = self.module.0.ctx;
let ptr = self.module.as_module_def();
let name = unsafe {
let atom_val = qjs::JS_GetModuleExportEntryName(ctx.ctx, ptr, self.index);
Atom::from_atom_val(ctx, atom_val)
};
let value = unsafe {
let js_val = qjs::JS_GetModuleExportEntry(ctx.ctx, ptr, self.index);
Value::from_js_value(ctx, js_val)
};
self.index += 1;
Some(N::from_atom(name).and_then(|name| T::from_js(ctx, value).map(|value| (name, value))))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
pub struct RustModule;
impl ModuleDef for RustModule {
fn load<'js>(_ctx: Ctx<'js>, _module: &Module<'js, Created>) -> Result<()> {
Ok(())
}
fn eval<'js>(_ctx: Ctx<'js>, _module: &Module<'js, Loaded<Native>>) -> Result<()> {
Ok(())
}
}
#[test]
fn from_rust_def() {
test_with(|ctx| {
Module::new_def::<RustModule, _>(ctx, "rust_mod").unwrap();
})
}
#[test]
fn from_javascript() {
test_with(|ctx| {
let module: Module = ctx
.compile(
"Test",
r#"
export var a = 2;
export function foo(){ return "bar"}
export class Baz{
quel = 3;
constructor(){
}
}
"#,
)
.unwrap();
assert_eq!(module.name::<StdString>().unwrap(), "Test");
let _ = module.meta::<Object>().unwrap();
#[cfg(feature = "exports")]
{
let names = module.names().collect::<Result<Vec<StdString>>>().unwrap();
assert_eq!(names[0], "a");
assert_eq!(names[1], "foo");
assert_eq!(names[2], "Baz");
let entries = module
.entries()
.collect::<Result<Vec<(StdString, Value)>>>()
.unwrap();
assert_eq!(entries[0].0, "a");
assert_eq!(i32::from_js(ctx, entries[0].1.clone()).unwrap(), 2);
assert_eq!(entries[1].0, "foo");
assert_eq!(entries[2].0, "Baz");
}
});
}
}