extern crate qjs_ng_sys;
use std::ffi::{c_int, CString};
use std::ffi::{c_void, CStr};
use std::marker::PhantomData;
use std::panic::RefUnwindSafe;
use crate::common::{TAG_INT, TAG_NULL};
use crate::convert::{from_js, into_js};
use self::qjs_ng_sys::JS_TAG_UNDEFINED;
use self::qjs_ng_sys::{JSCFunctionData, JSValueUnion, JS_NewCFunctionData};
use self::qjs_ng_sys::JS_SetPropertyStr;
use self::qjs_ng_sys::{
JSCFunctionEnum_JS_CFUNC_generic, JS_Eval, JS_GetGlobalObject, JS_NewCFunction2,
JS_ToCStringLen2,
};
use self::qjs_ng_sys::JS_NewContext;
use self::qjs_ng_sys::JS_NewRuntime;
use self::qjs_ng_sys::JSValue;
use self::qjs_ng_sys::{JSContext, JSRuntime};
fn make_cstring(value: impl Into<Vec<u8>>) -> CString {
CString::new(value).unwrap()
}
type WrappedCallback = dyn Fn(c_int, *mut JSValue) -> JSValue;
#[derive(Debug, Clone)]
pub enum Value {
Undefined,
Null,
Bool(bool),
Int(i32),
Float(f64),
String(String),
Array(Vec<Value>),
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Self::Int(value)
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<()> for Value {
fn from(_: ()) -> Self {
Self::Undefined
}
}
#[derive(Debug, Clone)]
pub struct Context {
ptr: *mut JSContext,
rt: Runtime,
}
#[derive(Debug, Clone)]
pub struct Runtime {
ptr: *mut JSRuntime,
}
#[derive(Debug, Clone)]
pub struct JValue {
v: Value,
ctx: Context,
}
impl Runtime {
pub fn new() -> Self {
let ptr = unsafe { JS_NewRuntime() };
Self { ptr }
}
pub fn new_context(&self) -> Context {
let ptr = unsafe { JS_NewContext(self.ptr) };
Context {
ptr,
rt: self.clone(),
}
}
}
unsafe fn build_closure_trampoline<F>(
closure: F,
) -> ((Box<WrappedCallback>, Box<JSValue>), JSCFunctionData)
where
F: Fn(c_int, *mut JSValue) -> JSValue + 'static,
{
unsafe extern "C" fn trampoline<F>(
_ctx: *mut JSContext,
_this: JSValue,
argc: c_int,
argv: *mut JSValue,
_magic: c_int,
data: *mut JSValue,
) -> JSValue
where
F: Fn(c_int, *mut JSValue) -> JSValue,
{
let closure_ptr = (*data).u.ptr;
println!("closure_ptr:{}", closure_ptr as usize);
let closure: &mut F = &mut *(closure_ptr as *mut F);
(*closure)(argc, argv)
}
let boxed_f = Box::new(closure);
let ptr = (&*boxed_f) as *const F as *mut c_void;
println!("build_closure_trampoline:{}", ptr as usize);
let data = Box::new(JSValue {
u: JSValueUnion { ptr: ptr },
tag: TAG_NULL,
});
((boxed_f, data), Some(trampoline::<F>))
}
pub fn new_undefined() -> JSValue {
return JSValue {
u: JSValueUnion { int32: 0 },
tag: JS_TAG_UNDEFINED as i64,
};
}
impl Context {
pub fn eval(&self, code: &str, filename: &str, eval_flags: std::os::raw::c_int) -> JSValue {
let input = CString::new(code).unwrap();
let input_len = input.as_bytes().len();
let filename = CString::new(filename).unwrap();
unsafe {
let input_ptr = input.as_ptr();
let filename_ptr = filename.as_ptr();
let v = JS_Eval(self.ptr, input_ptr, input_len, filename_ptr, eval_flags);
v
}
}
pub fn set_property(&self, name: &str, this_obj: JSValue, value: JSValue) {
unsafe {
let name = CString::new(name).unwrap();
let name_ptr = name.as_ptr();
JS_SetPropertyStr(self.ptr, this_obj, name_ptr, value);
}
}
pub fn get_global_object(&self) -> JSValue {
unsafe { JS_GetGlobalObject(self.ptr) }
}
fn exec_callback<A>(
ctx: *mut JSContext,
argc: c_int,
argv: *mut JSValue,
callback: &impl Callback<A>,
) -> JSValue {
let result = std::panic::catch_unwind(|| {
let args = callback.get_args(ctx, argc, argv);
let v = callback.apply(args);
println!("exec_callback rt {:?}", v);
let rt: JSValue = into_js(ctx, v);
println!("exec_callback rt222");
rt
})
.unwrap();
println!("exec_callback {}", argc);
result
}
pub fn create_callback<F>(&self, callback: impl Callback<F> + 'static) -> JSValue {
let argcount = callback.get_args_count() as i32;
let context = self.ptr;
let wrapper = move |argc: c_int, argv: *mut JSValue| -> JSValue {
let cb = &callback;
let ptr = cb as *const _ as usize;
println!("wrapper cb {}", ptr);
let ctx_ptr = context;
println!("wrapper ctx_ptr {}", ctx_ptr as usize);
let rt = Self::exec_callback(context, argc, argv, &callback);
rt
};
let wrapper_ptr = &wrapper as *const _ as usize;
println!("wrapper_ptr: {}", wrapper_ptr);
let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
let data = (&*pair.1) as *const JSValue as *mut JSValue;
let obj = unsafe {
let f = JS_NewCFunctionData(self.ptr, trampoline, argcount, 0, 1, data);
f
};
obj
}
pub fn add_callback<F>(&self, name: &str, callback: impl Callback<F> + 'static) -> JSValue {
let cfunc = self.create_callback(callback);
let global = self.get_global_object();
self.set_property(name, global, cfunc);
cfunc
}
pub fn new_undefined(&self) -> JSValue {
new_undefined()
}
pub fn new_number(&self, v: i32) -> JSValue {
let v = JSValue {
u: JSValueUnion { int32: v },
tag: TAG_INT,
};
v
}
}
pub trait Callback<Args, B = ()>: RefUnwindSafe {
fn get_args_count(&self) -> usize;
fn apply(&self, args: Args) -> Value;
fn get_args(&self, ctx: *mut JSContext, argc: i32, argv: *mut JSValue) -> Args;
}
impl From<Value> for String {
fn from(value: Value) -> Self {
match value {
Value::String(s) => s,
_ => "".to_string(),
}
}
}
impl From<Value> for i32 {
fn from(value: Value) -> Self {
match value {
Value::Int(s) => s,
_ => 0,
}
}
}
impl<A1, R, F> Callback<(A1,)> for F
where
F: Sized + RefUnwindSafe + Fn(A1) -> R,
A1: Into<Value> + From<Value>,
R: Into<Value>,
{
fn get_args_count(&self) -> usize {
1
}
fn get_args(&self, ctx: *mut JSContext, argc: i32, argv: *mut JSValue) -> (A1,) {
println!("argc: {}", argc);
let a1 = unsafe { *argv };
let v1: Value = from_js(ctx, &a1).unwrap();
let v1: A1 = v1.into();
(v1,)
}
fn apply(&self, args: (A1,)) -> Value {
let r = self.call(args);
let v: Value = r.into();
println!("apply: {:?}", v);
v
}
}
impl<R, F, A1, A2> Callback<(A1, A2)> for F
where
F: Sized + RefUnwindSafe + Fn(A1, A2) -> R,
A1: Into<Value> + From<Value>,
A2: Into<Value> + From<Value>,
R: Into<Value>,
{
fn get_args_count(&self) -> usize {
1
}
fn get_args(&self, ctx: *mut JSContext, argc: i32, argv: *mut JSValue) -> (A1, A2) {
println!("argc: {}", argc);
let a1 = unsafe { *argv };
let v1: Value = from_js(ctx, &a1).unwrap();
let v1: A1 = v1.into();
let a2 = unsafe { *argv.offset(1) };
let v2: Value = from_js(ctx, &a2).unwrap();
let v2: A2 = v2.into();
(v1, v2)
}
fn apply(&self, args: (A1, A2)) -> Value {
let r = self.call(args);
let v: Value = r.into();
println!("apply: {:?}", v);
v
}
}
#[cfg(test)]
mod test {
use super::qjs_ng_sys::JS_EVAL_TYPE_GLOBAL;
use super::{new_undefined, Runtime};
#[test]
fn test_rt() {
let rt = Runtime::new();
let ctx = rt.new_context();
let code = r#"(1+1).toString()"#;
let v = ctx.eval(code, "<test.js>", JS_EVAL_TYPE_GLOBAL as i32);
}
#[test]
fn test_number() {
let rt = Runtime::new();
let ctx = rt.new_context();
let g = ctx.get_global_object();
let v = ctx.new_number(0);
ctx.set_property("v", g, v);
let code = r#"(typeof globalThis.v).toString()"#;
let v = ctx.eval(code, "<test.js>", JS_EVAL_TYPE_GLOBAL as i32);
}
#[test]
fn test_undefined() {
let rt = Runtime::new();
let ctx = rt.new_context();
let g = ctx.get_global_object();
let v = new_undefined();
ctx.set_property("v", g, v);
let code = r#"(typeof globalThis.v).toString()"#;
let v = ctx.eval(code, "<test.js>", JS_EVAL_TYPE_GLOBAL as i32);
}
#[test]
fn test_fn_str() {
let rt = Runtime::new();
let ctx = rt.new_context();
fn print(s: String) -> String {
println!("print from rs: {}", s);
s + "--from-rs"
}
let code = r#"
let a = print("b");
print(a);
print(a);
"#;
ctx.add_callback("print", print);
ctx.eval(code, "<test.js>", JS_EVAL_TYPE_GLOBAL as i32);
}
#[test]
fn test_fn() {
fn print(s: String) -> String {
println!("print from rs: {}", s);
s + "--from-rs"
}
fn hello(s: String) -> String {
println!("print hello rs: {}", s);
s + "--hello-rs"
}
let rt = Runtime::new();
let ctx = rt.new_context();
let code = r#"
let c = print ( (1+1).toString() );
hello((1).toString())
hello(JSON.stringify(1))
print("a")
"#;
let f = ctx.add_callback("print", print);
let f = ctx.add_callback("hello", hello);
let v = ctx.eval(code, "<test.js>", JS_EVAL_TYPE_GLOBAL as i32);
}
#[test]
fn test_fn_i32() {
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn print(s: String) {
println!("print rs: {}", s);
}
fn inc(a: i32) -> i32 {
println!("inc: {}", a);
add(a, 1)
}
let rt = Runtime::new();
let ctx = rt.new_context();
let code = r#"
let c = add(1, 2);
print('js: '+ c +"");
print('js: '+ inc(c) +"");
"#;
let f = ctx.add_callback("add", add);
let f = ctx.add_callback("inc", inc);
let f = ctx.add_callback("print", print);
let v = ctx.eval(code, "<test.js>", JS_EVAL_TYPE_GLOBAL as i32);
}
}