extern crate alloc;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::ffi::{CStr, c_char, c_void};
use core::ptr;
use crate::JsString;
use crate::error::JsError;
use crate::value::{CheapClone, Guarded, JsValue, PropertyKey};
use super::{
NativeCallbackWrapper, TsRunContext, TsRunNativeFn, TsRunResult, TsRunValue, TsRunValueResult,
};
#[unsafe(no_mangle)]
pub extern "C" fn tsrun_native_function(
ctx: *mut TsRunContext,
name: *const c_char,
func: TsRunNativeFn,
arity: usize,
userdata: *mut c_void,
) -> TsRunValueResult {
let ctx = match unsafe { ctx.as_mut() } {
Some(c) => c,
None => {
return TsRunValueResult {
value: ptr::null_mut(),
error: c"NULL context".as_ptr(),
};
}
};
let name_str = if name.is_null() {
"anonymous"
} else {
match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => {
return TsRunValueResult::err(ctx, "Invalid function name encoding".to_string());
}
}
};
let ffi_id = ctx.next_ffi_id;
ctx.next_ffi_id += 1;
let wrapper = NativeCallbackWrapper {
callback: func,
userdata,
};
ctx.native_callbacks.insert(ffi_id, wrapper);
let guard = ctx.interp.heap.create_guard();
let fn_obj = ctx
.interp
.create_native_fn(&guard, name_str, native_callback_trampoline, arity);
{
use crate::value::{ExoticObject, JsFunction};
let mut fn_ref = fn_obj.borrow_mut();
if let ExoticObject::Function(JsFunction::Native(ref mut native)) = fn_ref.exotic {
native.ffi_id = ffi_id;
}
}
TsRunValueResult::ok(Box::new(TsRunValue {
inner: crate::RuntimeValue::with_guard(JsValue::Object(fn_obj), guard),
}))
}
fn native_callback_trampoline(
interp: &mut crate::Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let ffi_id = interp.current_ffi_id;
if ffi_id == 0 {
return Err(JsError::internal_error(
"Native callback called without FFI ID",
));
}
let ctx_ptr = interp.ffi_context as *mut TsRunContext;
if ctx_ptr.is_null() {
return Err(JsError::internal_error(
"Native callback called without context",
));
}
let ctx = unsafe { &mut *ctx_ptr };
let wrapper = ctx
.native_callbacks
.get(&ffi_id)
.ok_or_else(|| JsError::internal_error("Native callback not found"))?;
let this_handle = Box::into_raw(TsRunValue::from_js_value(&mut ctx.interp, this.clone()));
let mut arg_handles: Vec<*mut TsRunValue> = args
.iter()
.map(|arg| Box::into_raw(TsRunValue::from_js_value(&mut ctx.interp, arg.clone())))
.collect();
let args_ptr = if arg_handles.is_empty() {
ptr::null_mut()
} else {
arg_handles.as_mut_ptr()
};
let mut error_out: *const c_char = ptr::null();
let result = (wrapper.callback)(
ctx_ptr,
this_handle,
args_ptr,
args.len(),
wrapper.userdata,
&mut error_out,
);
unsafe {
drop(Box::from_raw(this_handle));
for handle in arg_handles {
drop(Box::from_raw(handle));
}
}
if !error_out.is_null() {
let error_str = unsafe { CStr::from_ptr(error_out) }
.to_str()
.unwrap_or("Unknown error");
return Err(JsError::type_error(error_str));
}
if result.is_null() {
Ok(Guarded::unguarded(JsValue::Undefined))
} else {
let result_val = unsafe { Box::from_raw(result) };
if let JsValue::Object(obj) = result_val.inner.value() {
let guard = interp.heap.create_guard();
guard.guard(obj.cheap_clone());
Ok(Guarded::with_guard(result_val.inner.value().clone(), guard))
} else {
Ok(Guarded::unguarded(result_val.inner.value().clone()))
}
}
}
pub struct TsRunInternalModule {
specifier: String,
exports: Vec<(String, InternalExportKind)>,
}
enum InternalExportKind {
Function {
func: TsRunNativeFn,
arity: usize,
userdata: *mut c_void,
},
Value(*mut TsRunValue),
}
#[unsafe(no_mangle)]
pub extern "C" fn tsrun_internal_module_new(specifier: *const c_char) -> *mut TsRunInternalModule {
let spec_str = match unsafe { super::c_str_to_str(specifier) } {
Some(s) => s.to_string(),
None => return ptr::null_mut(),
};
Box::into_raw(Box::new(TsRunInternalModule {
specifier: spec_str,
exports: Vec::new(),
}))
}
#[unsafe(no_mangle)]
pub extern "C" fn tsrun_internal_module_add_function(
module: *mut TsRunInternalModule,
name: *const c_char,
func: TsRunNativeFn,
arity: usize,
userdata: *mut c_void,
) {
let module = match unsafe { module.as_mut() } {
Some(m) => m,
None => return,
};
let name_str = match unsafe { super::c_str_to_str(name) } {
Some(s) => s.to_string(),
None => return,
};
module.exports.push((
name_str,
InternalExportKind::Function {
func,
arity,
userdata,
},
));
}
#[unsafe(no_mangle)]
pub extern "C" fn tsrun_internal_module_add_value(
module: *mut TsRunInternalModule,
name: *const c_char,
value: *mut TsRunValue,
) {
let module = match unsafe { module.as_mut() } {
Some(m) => m,
None => return,
};
let name_str = match unsafe { super::c_str_to_str(name) } {
Some(s) => s.to_string(),
None => return,
};
module
.exports
.push((name_str, InternalExportKind::Value(value)));
}
#[unsafe(no_mangle)]
pub extern "C" fn tsrun_register_internal_module(
ctx: *mut TsRunContext,
module: *mut TsRunInternalModule,
) -> TsRunResult {
let ctx = match unsafe { ctx.as_mut() } {
Some(c) => c,
None => {
return TsRunResult {
ok: false,
error: c"NULL context".as_ptr(),
};
}
};
if module.is_null() {
return TsRunResult::err(ctx, "NULL module".to_string());
}
let module = unsafe { Box::from_raw(module) };
let guard = ctx.interp.heap.create_guard();
let module_obj = ctx.interp.create_object(&guard);
for (name, export) in module.exports {
let key = PropertyKey::String(JsString::from(name.as_str()));
match export {
InternalExportKind::Function {
func,
arity,
userdata,
} => {
let ffi_id = ctx.next_ffi_id;
ctx.next_ffi_id += 1;
let wrapper = NativeCallbackWrapper {
callback: func,
userdata,
};
ctx.native_callbacks.insert(ffi_id, wrapper);
let fn_obj = ctx.interp.create_ffi_native_fn(
&guard,
&name,
native_callback_trampoline,
arity,
ffi_id,
);
module_obj
.borrow_mut()
.set_property(key, JsValue::Object(fn_obj));
}
InternalExportKind::Value(value_ptr) => {
let value = if value_ptr.is_null() {
JsValue::Undefined
} else {
unsafe { &*value_ptr }.value().clone()
};
module_obj.borrow_mut().set_property(key, value);
if !value_ptr.is_null() {
unsafe { drop(Box::from_raw(value_ptr)) };
}
}
}
}
ctx.interp
.register_ffi_module(&module.specifier, module_obj);
TsRunResult::success()
}