use super::{ctx::RefCountHeader, intrinsic, r#ref::ContextRef, ContextBuilder, Intrinsic};
use crate::{qjs, Ctx, Error, Result, Runtime};
use std::{mem, ptr::NonNull};
pub(crate) struct Inner {
pub(crate) ctx: NonNull<qjs::JSContext>,
pub(crate) rt: Runtime,
}
impl Clone for Inner {
fn clone(&self) -> Inner {
let ctx = unsafe { NonNull::new_unchecked(qjs::JS_DupContext(self.ctx.as_ptr())) };
let rt = self.rt.clone();
Self { ctx, rt }
}
}
#[derive(Clone)]
pub struct Context(pub(crate) ContextRef<Inner>);
impl Context {
pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: Runtime) -> Self {
Context(ContextRef::new(Inner { ctx, rt }))
}
pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
self.0.ctx
}
pub fn base(runtime: &Runtime) -> Result<Self> {
Self::custom::<intrinsic::None>(runtime)
}
pub fn custom<I: Intrinsic>(runtime: &Runtime) -> Result<Self> {
let guard = runtime.inner.lock();
let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.rt.as_ptr()) })
.ok_or_else(|| Error::Allocation)?;
unsafe { qjs::JS_AddIntrinsicBaseObjects(ctx.as_ptr()) };
unsafe { I::add_intrinsic(ctx) };
let res = Inner {
ctx,
rt: runtime.clone(),
};
mem::drop(guard);
Ok(Context(ContextRef::new(res)))
}
pub fn full(runtime: &Runtime) -> Result<Self> {
let guard = runtime.inner.lock();
let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.rt.as_ptr()) })
.ok_or_else(|| Error::Allocation)?;
let res = Inner {
ctx,
rt: runtime.clone(),
};
mem::drop(guard);
Ok(Context(ContextRef::new(res)))
}
pub fn builder() -> ContextBuilder<()> {
ContextBuilder::default()
}
pub fn runtime(&self) -> &Runtime {
&self.0.rt
}
#[allow(dead_code)]
pub fn get_runtime_ptr(&self) -> *mut qjs::JSRuntime {
unsafe { qjs::JS_GetRuntime(self.0.ctx.as_ptr()) }
}
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(Ctx) -> R,
{
let guard = self.0.rt.inner.lock();
guard.update_stack_top();
let ctx = unsafe { Ctx::new(self) };
f(ctx)
}
}
impl Drop for Context {
fn drop(&mut self) {
let guard = match self.0.rt.inner.try_lock() {
Some(x) => x,
None => {
let p = unsafe { &mut *(self.0.ctx.as_ptr() as *mut RefCountHeader) };
if p.ref_count <= 1 {
assert!(std::thread::panicking());
}
unsafe { qjs::JS_FreeContext(self.0.ctx.as_ptr()) }
return;
}
};
guard.update_stack_top();
unsafe { qjs::JS_FreeContext(self.0.ctx.as_ptr()) }
mem::drop(guard);
}
}
#[cfg(feature = "parallel")]
unsafe impl Send for Context {}
#[cfg(feature = "parallel")]
unsafe impl Sync for Context {}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
fn basic() {
test_with(|ctx| {
let val: Value = ctx.eval(r#"1+1"#).unwrap();
assert_eq!(val.type_of(), Type::Int);
assert_eq!(i32::from_js(&ctx, val).unwrap(), 2);
println!("{:?}", ctx.globals());
});
}
#[test]
fn minimal() {
let rt = Runtime::new().unwrap();
let ctx = Context::builder()
.with::<intrinsic::Eval>()
.build(&rt)
.unwrap();
ctx.with(|ctx| {
let val: i32 = ctx.eval(r#"1+1"#).unwrap();
assert_eq!(val, 2);
println!("{:?}", ctx.globals());
});
}
#[test]
fn base() {
let rt = Runtime::new().unwrap();
let _ = Context::base(&rt).unwrap();
}
#[test]
fn module() {
test_with(|ctx| {
Module::evaluate(
ctx,
"test_mod",
r#"
let t = "3";
let b = (a) => a + 3;
export { b, t}
"#,
)
.unwrap()
.finish::<()>()
.unwrap();
});
}
#[test]
#[cfg(feature = "parallel")]
fn parallel() {
use std::thread;
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
let _: () = ctx.eval("this.foo = 42").unwrap();
});
thread::spawn(move || {
ctx.with(|ctx| {
let i: i32 = ctx.eval("foo + 8").unwrap();
assert_eq!(i, 50);
});
})
.join()
.unwrap();
}
#[test]
#[cfg(feature = "parallel")]
fn parallel_drop() {
use std::{
sync::{Arc, Barrier},
thread,
};
let wait_for_entry = Arc::new(Barrier::new(2));
let rt = Runtime::new().unwrap();
let ctx_1 = Context::full(&rt).unwrap();
let ctx_2 = Context::full(&rt).unwrap();
let wait_for_entry_c = wait_for_entry.clone();
thread::spawn(move || {
wait_for_entry_c.wait();
std::mem::drop(ctx_1);
println!("done");
});
ctx_2.with(|ctx| {
wait_for_entry.wait();
let i: i32 = ctx.eval("2 + 8").unwrap();
assert_eq!(i, 10);
});
println!("done");
}
#[test]
#[should_panic(
expected = "Error: invalid first character of private name\n at eval_script:1:1\n"
)]
fn exception() {
test_with(|ctx| {
let val = ctx.eval::<(), _>("bla?#@!@ ").catch(&ctx);
if let Err(e) = val {
assert!(e.is_exception());
panic!("{}", e);
}
});
}
}