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, 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 {
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 index = Value::from_raw(params).to_i32(&ctx).unwrap();
let func = &opaque.funcs[index as usize];
let result = func(&ctx, this, &args);
match result {
Ok(value) => value,
Err(error) => error.exception,
}
.into_raw()
}
}
#[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_size(DEFAULT_SIZE)
}
pub fn with_size(size: usize) -> Self {
unsafe {
let mut mem = vec![0; size];
let ctx =
rmquickjs_sys::JS_NewContext(mem.as_mut_ptr() as *mut c_void, 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(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 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 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 set_random_seed(&self, seed: u64) {
unsafe { rmquickjs_sys::JS_SetRandomSeed(self.as_ptr(), seed) }
}
}
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));
}
};
}
}