use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::time::Duration;
use quickjs_oxide::engine::api::core::{
FunctionOptions, JsString, NativeErrorKind, PromiseCapability, Runtime, RuntimeError, Scope,
Value,
};
struct Counter {
count: Cell<i32>,
}
fn main() -> Result<(), RuntimeError> {
let runtime = Runtime::new();
let context = runtime.context();
context.install_qjs_helpers(&Default::default())?;
let global = context.global_object()?;
context.define_function(&global, "add", 2, |_: &mut Scope<'_>, a: f64, b: f64| {
Ok(a + b)
})?;
let greet = context.new_function(
FunctionOptions::new("greet")
.length(1)
.data(vec![context.eval("'Hello'")?]),
|scope: &mut Scope<'_>| {
let greeting = scope.data(0);
let greeting = scope.to_js_string(&greeting)?.to_utf8_lossy();
let name = scope.arg(0);
let name = scope.to_js_string(&name)?.to_utf8_lossy();
Ok(Value::String(JsString::try_from_utf8(&format!(
"{greeting}, {name}!"
))?))
},
)?;
let key = runtime.intern_property_key("greet")?;
context.set_property(&global, &key, Value::from(greet))?;
let prototype = context.new_object()?;
context.define_function(&prototype, "increment", 0, |scope: &mut Scope<'_>| {
let Some(counter) = scope.this_host_data::<Counter>()? else {
return Err(scope.throw_error(NativeErrorKind::Type, "not a Counter"));
};
counter.count.set(counter.count.get() + 1);
Ok(Value::Int(counter.count.get()))
})?;
let counter = context.new_host_object(
Counter {
count: Cell::new(0),
},
Some(&prototype),
)?;
let key = runtime.intern_property_key("counter")?;
context.set_property(&global, &key, Value::Object(counter))?;
context.define_function(&global, "twice", 1, |scope: &mut Scope<'_>| {
let Value::Object(function) = scope.arg(0) else {
return Err(scope.throw_error(NativeErrorKind::Type, "twice needs a function"));
};
let Some(function) = scope.as_callable(&function)? else {
return Err(scope.throw_error(NativeErrorKind::Type, "twice needs a function"));
};
let once = scope.call(&function, Value::Undefined, &[Value::Int(1)])?;
scope.call(&function, Value::Undefined, &[once])
})?;
let shown = context.eval(
"var lines = [];\n\
lines.push(add(40, 2));\n\
lines.push(greet('embedder'));\n\
counter.increment(); lines.push(counter.increment());\n\
lines.push(twice((x) => x * 10));\n\
try { twice(5); } catch (e) { lines.push(e.name + ': ' + e.message); }\n\
lines.join('\\n')",
)?;
println!("{}", context.to_js_string(&shown)?.to_utf8_lossy());
let pending: Rc<RefCell<Option<PromiseCapability>>> = Rc::default();
let keep = Rc::clone(&pending);
context.define_function(&global, "fetchLater", 0, move |scope: &mut Scope<'_>| {
let capability = scope.new_promise()?;
let promise = capability.promise.clone();
*keep.borrow_mut() = Some(capability);
Ok(Value::Object(promise))
})?;
context.eval("(async () => { print('fetched: ' + await fetchLater()); })()")?;
let capability = pending.borrow_mut().take().expect("the script asked");
let mut context = context;
let data = context.eval("'the data'")?;
capability.resolve(&mut context, data)?;
runtime.run_jobs()?;
let sqrt = context.new_function(
FunctionOptions::new("sqrt").length(1),
|_: &mut Scope<'_>, x: f64| Ok(x.sqrt()),
)?;
context.define_module("host:math", vec![("sqrt".into(), Value::from(sqrt))])?;
let module = context.compile_module(
"import { sqrt } from 'host:math';\n\
print('sqrt(2) = ' + sqrt(2).toFixed(4));",
)?;
context.execute_module(&module)?;
runtime.run_jobs()?;
let handle = runtime.interrupt_handle();
let thread = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(20));
handle.interrupt();
});
let outcome = context.eval("try { while (true) {} } catch (e) { 'caught' }");
thread.join().expect("the interrupting thread");
println!("endless loop: {outcome:?}");
runtime.interrupt_handle().clear();
Ok(())
}