use alloc::string::ToString;
use alloc::vec;
use alloc::{boxed::Box, ffi::CString, vec::Vec};
use core::ffi::{c_char, c_int};
use core::ptr::null_mut;
use core::{ffi::c_void, ptr::NonNull};
use rmquickjs_sys::{
JS_AddGCRef, JS_EVAL_JSON, JS_EVAL_REGEXP, JS_EVAL_REPL, JS_EVAL_RETVAL, JS_EVAL_STRIP_COL,
JS_NewCFunctionParams, JS_Parse, JSCFunctionEnum_JS_CFUNCTION_USER, JSContext, JSGCRef,
JSValue, js_stdlib,
};
use crate::{Array, Error, Function, Object, Opaque, Result, Value};
pub struct Context {
mem: Option<Vec<u8>>,
ctx: NonNull<rmquickjs_sys::JSContext>,
}
unsafe extern "C" fn host_callback(
ctx: *mut JSContext,
this_val: *mut JSValue,
argc: i32,
argv: *mut JSValue,
params: JSValue,
) -> JSValue {
unsafe {
if argc < 0 {
return Value::exception().into_raw();
}
let args_raw = core::slice::from_raw_parts(argv, argc as usize);
let mut args = Vec::with_capacity(args_raw.len());
for arg in args_raw {
args.push(Value::from_raw(*arg));
}
let ctx = Context::from_raw(ctx);
let this = if this_val.is_null() {
None
} else {
Some(Value::from_raw(*this_val))
};
let opaque = ctx.get_opaque();
let Some(index) = Value::from_raw(params).to_i32(&ctx) else {
return Value::exception().into_raw();
};
let Some(func) = opaque.funcs.get(index as usize) else {
return Value::exception().into_raw();
};
#[cfg(feature = "std")]
{
let result =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| func(&ctx, this, &args)));
match result {
Ok(result) => match result {
Ok(value) => value,
Err(error) => error.exception,
},
Err(_) => {
ctx.throw_internal_error("callback panicked")
.unwrap_err()
.exception
}
}
.into_raw()
}
#[cfg(not(feature = "std"))]
{
let result = func(&ctx, this, &args);
match result {
Ok(value) => value,
Err(error) => error.exception,
}
.into_raw()
}
}
}
unsafe extern "C" fn interrupt_callback(ctx: *mut JSContext, _: *mut c_void) -> c_int {
let ctx = Context::from_raw(ctx);
let opaque = ctx.get_opaque();
if let Some(handler) = &opaque.interrupt_handler {
#[cfg(feature = "std")]
let failed =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handler(&ctx))).is_err();
#[cfg(not(feature = "std"))]
let failed = handler(&ctx);
if failed {
return 1;
}
}
0
}
#[derive(Default)]
pub struct EvalFlags(u32);
impl EvalFlags {
pub fn inner(&self) -> u32 {
self.0
}
pub fn with_retval(self) -> Self {
Self(self.0 | JS_EVAL_RETVAL)
}
pub fn with_repl(self) -> Self {
Self(self.0 | JS_EVAL_REPL)
}
pub fn with_strip_col(self) -> Self {
Self(self.0 | JS_EVAL_STRIP_COL)
}
pub fn with_regexp(self) -> Self {
Self(self.0 | JS_EVAL_REGEXP)
}
pub fn with_json(self) -> Self {
Self(self.0 | JS_EVAL_JSON)
}
}
pub struct EvalOptions<'file_name> {
file_name: Option<&'file_name str>,
flags: EvalFlags,
}
impl Default for EvalOptions<'_> {
fn default() -> Self {
Self {
file_name: Default::default(),
flags: EvalFlags::default().with_retval(),
}
}
}
impl Context {
pub fn new() -> Self {
const DEFAULT_SIZE: usize = 65536;
Self::with_memory_size(DEFAULT_SIZE)
}
pub fn with_memory_size(memory_size: usize) -> Self {
unsafe {
let mut mem = vec![0; memory_size];
let ctx = rmquickjs_sys::JS_NewContext(
mem.as_mut_ptr() as *mut c_void,
memory_size,
&js_stdlib,
);
let opaque = Box::into_raw(Opaque::default().into());
rmquickjs_sys::JS_SetContextOpaque(ctx, opaque as *mut c_void);
rmquickjs_sys::JS_SetHostCallback(Some(host_callback));
Self {
mem: Some(mem),
ctx: NonNull::new(ctx).expect("ctx should not be null"),
}
}
}
pub fn with_prepare_compilation(memory_size: usize, prepare_compilation: bool) -> Self {
unsafe {
let mut mem = vec![0; memory_size];
let ctx = rmquickjs_sys::JS_NewContext2(
mem.as_mut_ptr() as *mut c_void,
memory_size,
&js_stdlib,
prepare_compilation as i32,
);
let opaque = Box::into_raw(Opaque::default().into());
rmquickjs_sys::JS_SetContextOpaque(ctx, opaque as *mut c_void);
rmquickjs_sys::JS_SetHostCallback(Some(host_callback));
Self {
mem: Some(mem),
ctx: NonNull::new(ctx).expect("ctx should not be null"),
}
}
}
pub(crate) fn from_raw(ctx: *mut rmquickjs_sys::JSContext) -> Self {
Self {
mem: None,
ctx: NonNull::new(ctx).expect("ctx should not be null"),
}
}
pub fn as_ptr(&self) -> *mut rmquickjs_sys::JSContext {
self.ctx.as_ptr()
}
pub(crate) fn get_opaque(&self) -> &mut Opaque {
unsafe { &mut *(rmquickjs_sys::JS_GetContextOpaque(self.ctx.as_ptr()) as *mut Opaque) }
}
pub fn globals<'ctx>(&'ctx self) -> Object<'ctx> {
let mut gc_ref = Box::new(JSGCRef {
val: 0,
prev: null_mut(),
});
unsafe {
let slot = rmquickjs_sys::JS_AddGCRef(self.ctx.as_ptr(), &mut *gc_ref);
*slot = rmquickjs_sys::JS_GetGlobalObject(self.ctx.as_ptr());
Object::new(gc_ref, self)
}
}
pub fn eval(&self, input: &str) -> Result<Value> {
self.eval_with_options(input, &EvalOptions::default())
}
pub fn eval_with_options(&self, input: &str, options: &EvalOptions<'_>) -> Result<Value> {
let c_input = CString::new(input).unwrap();
let c_filename = CString::new(options.file_name.unwrap_or("index.js")).unwrap();
let result = unsafe {
Value::from_raw(rmquickjs_sys::JS_Eval(
self.ctx.as_ptr(),
c_input.as_ptr(),
input.len(),
c_filename.as_ptr(),
options.flags.inner().try_into().unwrap(),
))
};
if result.is_exception() {
let exception = self.get_exception().unwrap();
let message = exception.to_string(self);
Err(Error { message, exception })
} else {
Ok(result)
}
}
pub fn parse(&self, input: &str) -> Result<Value> {
self.parse_with_options(input, &EvalOptions::default())
}
pub fn parse_with_options(&self, input: &str, options: &EvalOptions<'_>) -> Result<Value> {
let c_input = CString::new(input).unwrap();
let c_filename = CString::new(options.file_name.unwrap_or("index.js")).unwrap();
let result = unsafe {
Value::from_raw(JS_Parse(
self.ctx.as_ptr(),
c_input.as_ptr(),
input.len(),
c_filename.as_ptr(),
options.flags.inner().try_into().unwrap(),
))
};
if result.is_exception() {
let exception = self.get_exception().unwrap();
let message = exception.to_string(self);
Err(Error { message, exception })
} else {
Ok(result)
}
}
pub fn relocate_bytecode(&self, bytecode: &mut [u8]) -> Result<()> {
unsafe {
let code = rmquickjs_sys::JS_RelocateBytecode(
self.ctx.as_ptr(),
bytecode.as_mut_ptr(),
bytecode.len() as u32,
);
if code != 0 {
Err(Error {
message: "Could not relocate bytecode".to_string(),
exception: Value::exception(),
})
} else {
Ok(())
}
}
}
pub fn load_bytecode(&self, bytecode: &[u8]) -> Result<Value> {
unsafe {
let result = Value::from_raw(rmquickjs_sys::JS_LoadBytecode(
self.ctx.as_ptr(),
bytecode.as_ptr(),
));
if result.is_exception() {
let exception = self.get_exception().unwrap();
let message = exception.to_string(self);
Err(Error { message, exception })
} else {
Ok(result)
}
}
}
pub fn run(&self, bytecode: Value) -> Result<Value> {
unsafe {
let result = Value::from_raw(rmquickjs_sys::JS_Run(
self.ctx.as_ptr(),
bytecode.into_raw(),
));
if result.is_exception() {
let exception = self.get_exception().unwrap();
let message = exception.to_string(self);
Err(Error { message, exception })
} else {
Ok(result)
}
}
}
pub fn gc(&self) {
unsafe {
rmquickjs_sys::JS_GC(self.as_ptr());
}
}
pub fn get_exception(&self) -> Option<Value> {
unsafe {
let ex = Value::from_raw(rmquickjs_sys::JS_GetException(self.ctx.as_ptr()));
if ex.is_undefined() { None } else { Some(ex) }
}
}
pub fn throw(&self, value: Value) -> Result<Value> {
unsafe {
Value::from_raw(rmquickjs_sys::JS_Throw(self.ctx.as_ptr(), value.into_raw()))
.to_result(self)
}
}
fn throw_error(&self, class: rmquickjs_sys::JSObjectClassEnum, message: &str) -> Result<Value> {
Value::from_raw(unsafe {
rmquickjs_sys::JS_ThrowError(
self.ctx.as_ptr(),
class,
CString::new(message).unwrap().as_ptr(),
)
})
.to_result(self)
}
pub fn throw_type_error(&self, message: &str) -> Result<Value> {
self.throw_error(
rmquickjs_sys::JSObjectClassEnum_JS_CLASS_TYPE_ERROR,
message,
)
}
pub fn throw_reference_error(&self, message: &str) -> Result<Value> {
self.throw_error(
rmquickjs_sys::JSObjectClassEnum_JS_CLASS_REFERENCE_ERROR,
message,
)
}
pub fn throw_internal_error(&self, message: &str) -> Result<Value> {
self.throw_error(
rmquickjs_sys::JSObjectClassEnum_JS_CLASS_INTERNAL_ERROR,
message,
)
}
pub fn throw_range_error(&self, message: &str) -> Result<Value> {
self.throw_error(
rmquickjs_sys::JSObjectClassEnum_JS_CLASS_RANGE_ERROR,
message,
)
}
pub fn throw_syntax_error(&self, message: &str) -> Result<Value> {
self.throw_error(
rmquickjs_sys::JSObjectClassEnum_JS_CLASS_SYNTAX_ERROR,
message,
)
}
pub fn throw_out_of_memory(&self) -> Result<Value> {
unsafe {
Value::from_raw(rmquickjs_sys::JS_ThrowOutOfMemory(self.as_ptr())).to_result(self)
}
}
pub fn new_i32(&self, value: i32) -> Value {
unsafe { Value::from_raw(rmquickjs_sys::JS_NewInt32(self.as_ptr(), value)) }
}
pub fn new_i64(&self, value: i64) -> Value {
unsafe { Value::from_raw(rmquickjs_sys::JS_NewInt64(self.as_ptr(), value)) }
}
pub fn new_f64(&self, value: f64) -> Value {
unsafe { Value::from_raw(rmquickjs_sys::JS_NewFloat64(self.as_ptr(), value)) }
}
pub fn new_u32(&self, value: u32) -> Value {
unsafe { Value::from_raw(rmquickjs_sys::JS_NewUint32(self.as_ptr(), value)) }
}
pub fn new_string(&self, value: &str) -> Value {
unsafe {
Value::from_raw(rmquickjs_sys::JS_NewStringLen(
self.as_ptr(),
value.as_ptr() as *const c_char,
value.len(),
))
}
}
pub fn new_array<'ctx>(&'ctx self, len: usize) -> Result<Array<'ctx>> {
unsafe {
let mut gc_ref = Box::new(JSGCRef {
val: 0,
prev: null_mut(),
});
let slot = rmquickjs_sys::JS_AddGCRef(self.as_ptr(), &mut *gc_ref);
gc_ref.val = rmquickjs_sys::JS_NewArray(self.as_ptr(), len as i32);
*slot = gc_ref.val.into();
let value = Value::from_raw(*slot);
if value.is_exception() {
rmquickjs_sys::JS_DeleteGCRef(self.as_ptr(), &mut *gc_ref);
Err(Error {
message: value.to_string(self),
exception: value,
})
} else {
Ok(Array::new(gc_ref, self))
}
}
}
pub fn new_object<'ctx>(&'ctx self) -> Result<Object<'ctx>> {
unsafe {
let mut gc_ref = Box::new(JSGCRef {
val: 0,
prev: null_mut(),
});
let slot = rmquickjs_sys::JS_AddGCRef(self.as_ptr(), &mut *gc_ref);
gc_ref.val = rmquickjs_sys::JS_NewObject(self.as_ptr());
*slot = gc_ref.val.into();
let value = Value::from_raw(*slot);
if value.is_exception() {
rmquickjs_sys::JS_DeleteGCRef(self.as_ptr(), &mut *gc_ref);
Err(Error {
message: value.to_string(self),
exception: value,
})
} else {
Ok(Object::new(gc_ref, self))
}
}
}
pub fn new_function<'ctx, F>(&'ctx self, func: F) -> Result<Function<'ctx>>
where
F: Fn(&Context, Option<Value>, &[Value]) -> Result<Value> + 'static,
{
unsafe {
let opaque = self.get_opaque();
let index = opaque.funcs.len();
opaque.funcs.push(Box::new(func));
let mut gc_ref = Box::new(JSGCRef {
val: 0,
prev: null_mut(),
});
let slot = JS_AddGCRef(self.as_ptr(), &mut *gc_ref);
gc_ref.val = JS_NewCFunctionParams(
self.as_ptr(),
(JSCFunctionEnum_JS_CFUNCTION_USER + 0) as c_int, self.new_u32(index as u32).into_raw(),
);
*slot = gc_ref.val;
let value = Value::from_raw(*slot);
if value.is_exception() {
rmquickjs_sys::JS_DeleteGCRef(self.as_ptr(), &mut *gc_ref);
Err(Error {
message: value.to_string(self),
exception: value,
})
} else {
Ok(Function::new(gc_ref, self))
}
}
}
pub fn stack_check(&self, len: u32) -> bool {
unsafe { rmquickjs_sys::JS_StackCheck(self.as_ptr(), len) == 0 }
}
pub fn set_random_seed(&self, seed: u64) {
unsafe { rmquickjs_sys::JS_SetRandomSeed(self.as_ptr(), seed) }
}
pub fn set_interrupt_handler<F>(&self, handler: F)
where
F: Fn(&Context) -> bool + 'static,
{
unsafe {
let opaque = self.get_opaque();
opaque.interrupt_handler = Some(Box::new(handler));
rmquickjs_sys::JS_SetInterruptHandler(self.as_ptr(), Some(interrupt_callback));
}
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
if let Some(_) = self.mem.take() {
let ctx = self.ctx.as_ptr();
let opaque = self.get_opaque();
rmquickjs_sys::JS_FreeContext(ctx);
drop(Box::from_raw(opaque));
}
};
}
}