[oden] Grab my incomplete QuickJS wrapper

This commit is contained in:
John Doty 2023-06-17 20:44:09 -07:00
parent aa70df41a3
commit 898b1fe129
114 changed files with 244181 additions and 0 deletions

488
oden-js/src/context.rs Normal file
View file

@ -0,0 +1,488 @@
use crate::{
callback::new_fn, conversion::RustFunction, Atom, ClassID, Error, Result, Runtime, Value,
ValueRef, ValueResult,
};
use bitflags::bitflags;
use oden_js_sys as sys;
use std::ffi::CString;
use std::marker;
use std::ops::{Deref, DerefMut};
/// Different ways to evaluate JavaScript. See the various `eval` methods on
/// `ContextRef`.
pub enum EvalType {
/// Global code, i.e., just plain old JavaScript running in the most
/// boring, traditional context.
Global,
/// Module code, i.e., code running under the context of an `import`, or
/// referenced in HTML as `type=module`.
Module,
}
bitflags! {
pub struct EvalFlags: u32 {
const NONE = 0;
/// Force evaluation in "strict" mode, as if "use strict" were at the
/// top of the file.
const STRICT = sys::JS_EVAL_FLAG_STRICT;
/// Force "strip" mode.
const STRIP = sys::JS_EVAL_FLAG_STRIP;
/// Compile but do not run. The result is a value with a value type
/// of `ValueType::Module` or `ValueType::FunctionBytecode`, and
/// which can be executed with `value.eval_function`.
const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY;
/// Don't include the stack frames before this eval in the Error()
/// backtraces.
const BACKTRACE_BARRIER = sys::JS_EVAL_FLAG_BACKTRACE_BARRIER;
}
}
#[derive(Debug)]
pub struct ContextRef<'rt> {
pub(crate) ctx: *mut sys::JSContext,
_marker: marker::PhantomData<&'rt Runtime>,
}
impl<'rt> ContextRef<'rt> {
pub(crate) fn from_raw(ctx: *mut sys::JSContext) -> Self {
ContextRef {
ctx,
_marker: marker::PhantomData,
}
}
pub fn is_registered_class(&self, id: &ClassID) -> bool {
let id = id.get();
let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) };
is_registered != 0
}
pub fn add_intrinsic_base_objects(&mut self) {
unsafe { sys::JS_AddIntrinsicBaseObjects(self.ctx) }
}
pub fn add_intrinsic_date(&mut self) {
unsafe { sys::JS_AddIntrinsicDate(self.ctx) }
}
pub fn add_intrinsic_eval(&mut self) {
unsafe { sys::JS_AddIntrinsicEval(self.ctx) }
}
pub fn add_intrinsic_string_normalize(&mut self) {
unsafe { sys::JS_AddIntrinsicStringNormalize(self.ctx) }
}
pub fn add_intrinsic_regexp_compiler(&mut self) {
unsafe { sys::JS_AddIntrinsicRegExpCompiler(self.ctx) }
}
pub fn add_intrinsic_regexp(&mut self) {
unsafe { sys::JS_AddIntrinsicRegExp(self.ctx) }
}
pub fn add_intrinsic_json(&mut self) {
unsafe { sys::JS_AddIntrinsicJSON(self.ctx) }
}
pub fn add_intrinsic_proxy(&mut self) {
unsafe { sys::JS_AddIntrinsicProxy(self.ctx) }
}
pub fn add_intrinsic_map_set(&mut self) {
unsafe { sys::JS_AddIntrinsicMapSet(self.ctx) }
}
pub fn add_intrinsic_typed_arrays(&mut self) {
unsafe { sys::JS_AddIntrinsicTypedArrays(self.ctx) }
}
pub fn add_intrinsic_promise(&mut self) {
unsafe { sys::JS_AddIntrinsicPromise(self.ctx) }
}
pub fn add_intrinsic_bigint(&mut self) {
unsafe { sys::JS_AddIntrinsicBigInt(self.ctx) }
}
pub fn add_intrinsic_bigfloat(&mut self) {
unsafe { sys::JS_AddIntrinsicBigFloat(self.ctx) }
}
pub fn add_intrinsic_bigdecimal(&mut self) {
unsafe { sys::JS_AddIntrinsicBigDecimal(self.ctx) }
}
pub fn add_intrinsic_operators(&mut self) {
unsafe { sys::JS_AddIntrinsicOperators(self.ctx) }
}
pub fn enable_bignum_ext(&mut self, enable: bool) {
unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) }
}
/// Evaluate the specified JavaScript code.
pub fn eval(
&self,
input: &str,
filename: &str,
eval_type: EvalType,
flags: EvalFlags,
) -> ValueResult<'rt> {
let c_input = match CString::new(input) {
Ok(cs) => Ok(cs),
Err(_) => Err(Error::UnexpectedNul),
}?;
let c_filename = match CString::new(filename) {
Ok(cs) => Ok(cs),
Err(_) => Err(Error::UnexpectedNul),
}?;
let eval_type = match eval_type {
EvalType::Global => sys::JS_EVAL_TYPE_GLOBAL,
EvalType::Module => sys::JS_EVAL_TYPE_MODULE,
};
let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap();
unsafe {
self.check_exception(sys::JS_Eval(
self.ctx,
c_input.into_raw(),
input.len(),
c_filename.into_raw(),
flags_bits,
))
}
}
/// Construct a new string atom.
pub fn new_atom(&self, value: &str) -> Result<'rt, Atom<'rt>> {
let c_value = match CString::new(value) {
Ok(cs) => Ok(cs),
Err(_) => Err(Error::UnexpectedNul),
}?;
let atom = unsafe { sys::JS_NewAtomLen(self.ctx, c_value.into_raw(), value.len()) };
if atom == sys::JS_ATOM_NULL {
return Err(Error::Exception(self.exception()));
}
Ok(Atom::from_raw(atom, self))
}
/// Construct a new value of type object.
pub fn new_object(&self) -> ValueResult<'rt> {
self.check_exception(unsafe { sys::JS_NewObject(self.ctx) })
}
/// Construct a new value from a boolean.
pub fn new_bool<T>(&self, value: T) -> ValueResult<'rt>
where
T: Into<bool>,
{
self.check_exception(unsafe { sys::JS_NewBool(self.ctx, value.into()) })
}
/// Construct a new value that wraps a strongly-typed closure.
pub fn new_fn<F>(&self, func: impl RustFunction<'rt, F>) -> ValueResult<'rt> {
self.new_dynamic_fn(|c, _, a| func.call(c, a))
}
/// Construct a new value that wraps a dynamically-typed closure.
pub fn new_dynamic_fn<F>(&self, func: F) -> ValueResult<'rt>
where
F: Fn(&ContextRef<'rt>, &ValueRef<'rt>, &[&ValueRef<'rt>]) -> ValueResult<'rt>,
{
// Constructing a new function is complicated enough that it needs to
// be out of line.
self.check_exception(new_fn(self, func))
}
/// Construct a new value from an int32.
pub fn new_i32<T>(&self, value: T) -> ValueResult<'rt>
where
T: Into<i32>,
{
self.check_exception(unsafe { sys::JS_NewInt32(self.ctx, value.into()) })
}
/// Construct a new value from a u32.
///
/// This returns a value of type Int32 if the value fits into an int32,
/// otherwise it returns a value of type Float64.
pub fn new_u32<T>(&self, value: T) -> ValueResult<'rt>
where
T: Into<u32>,
{
self.check_exception(unsafe { sys::JS_NewUint32(self.ctx, value.into()) })
}
/// Construct a new value from a float64.
pub fn new_f64<T>(&self, value: T) -> ValueResult<'rt>
where
T: Into<f64>,
{
self.check_exception(unsafe { sys::JS_NewFloat64(self.ctx, value.into()) })
}
/// Construct a new BigInt from an i64
pub fn new_i64<T>(&self, value: T) -> ValueResult<'rt>
where
T: Into<i64>,
{
self.check_exception(unsafe { sys::JS_NewBigInt64(self.ctx, value.into()) })
}
/// Construct a new BigInt from an u64
pub fn new_u64<T>(&self, value: T) -> ValueResult<'rt>
where
T: Into<u64>,
{
self.check_exception(unsafe { sys::JS_NewBigUint64(self.ctx, value.into()) })
}
/// Construct a new array value.
pub fn new_array(&self) -> ValueResult<'rt> {
self.check_exception(unsafe { sys::JS_NewArray(self.ctx) })
}
/// Construct a new value from a string.
pub fn new_string(&self, value: &str) -> ValueResult<'rt> {
let c_value = match CString::new(value) {
Ok(cs) => Ok(cs),
Err(_) => Err(Error::UnexpectedNul),
}?;
self.check_exception(unsafe {
sys::JS_NewStringLen(self.ctx, c_value.into_raw(), value.len())
})
}
/// Get the null value.
pub fn null(&self) -> Value<'rt> {
let v = sys::JSValue {
u: sys::JSValueUnion {
ptr: std::ptr::null_mut(),
},
tag: sys::JS_TAG_NULL as i64,
};
Value::from_raw(v, self)
}
/// Get the undefined value.
pub fn undefined(&self) -> Value<'rt> {
let v = sys::JSValue {
u: sys::JSValueUnion {
ptr: std::ptr::null_mut(),
},
tag: sys::JS_TAG_UNDEFINED as i64,
};
Value::from_raw(v, self)
}
/// Fetch the global object for the context.
pub fn global_object(&self) -> ValueResult<'rt> {
self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) })
}
/// Check the value to see if it is the special marker for an exception,
/// and if so grab the exception from the context and return it in an
/// error value. Otherwise, just return success with the value, wrapped.
pub(crate) fn check_exception(&self, val: sys::JSValue) -> ValueResult<'rt> {
if unsafe { sys::JS_ValueGetTag(val) } == sys::JS_TAG_EXCEPTION {
Err(Error::Exception(self.exception()))
} else {
Ok(Value::from_raw(val, self))
}
}
/// Fetch the exception value from the context, if any. This is not
/// public because anything that might raise an exception should be
/// returning a Result<> instead, to separate the exception flow from the
/// value flow.
pub(crate) fn exception(&self) -> Value<'rt> {
Value::from_raw(unsafe { sys::JS_GetException(self.ctx) }, self)
}
}
#[derive(Debug)]
pub struct Context<'rt> {
value: ContextRef<'rt>,
}
impl<'rt> Context<'rt> {
/// Construct a new JavaScript context with many of the useful instrinisc
/// functions defined. Objects created by this context can be shared
/// across contexts belonging to the same runtime.
///
/// This function behaves as if the following methods have been called:
///
/// - `add_intrinsic_base_objects`
/// - `add_intrinsic_date`
/// - `add_intrinsic_eval`
/// - `add_intrinsic_string_normalize`
/// - `add_intrinsic_regexp`
/// - `add_intrinsic_json`
/// - `add_intrinsic_proxy`
/// - `add_intrinsic_map_set`
/// - `add_intrinsic_typed_arrays`
/// - `add_intrinsic_promise`
/// - `add_intrinsic_bignum`
///
/// If you don't want those objects, call `new_raw`, and then add the
/// intrinsics that you want.
pub fn new(runtime: &Runtime) -> Context {
let value = unsafe { ContextRef::from_raw(sys::JS_NewContext(runtime.rt)) };
Context { value }
}
/// Construct a new JavaScript context without any intrinsic
/// objects. Objects created by this context can be shared across
/// contexts belonging to the same runtime.
///
/// You will probably want to call one or more of the `add_intrinsic_`
/// functions to add useful types to the runtime.
pub fn new_raw(runtime: &Runtime) -> Context {
let value = unsafe { ContextRef::from_raw(sys::JS_NewContextRaw(runtime.rt)) };
Context { value }
}
}
impl<'rt> Deref for Context<'rt> {
type Target = ContextRef<'rt>;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<'rt> DerefMut for Context<'rt> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<'rt> Drop for Context<'rt> {
fn drop(&mut self) {
unsafe {
sys::JS_FreeContext(self.value.ctx);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ValueRef, ValueType};
#[test]
fn basic_evaluation() {
let rt = Runtime::new();
let ctx = Context::new(&rt);
let val = ctx
.eval("1+1", "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
assert_eq!(String::from("2"), val.to_string(&ctx).unwrap());
}
#[test]
fn eval_compiled() {
let rt = Runtime::new();
let ctx = Context::new(&rt);
let compiled = ctx
.eval("1 + 1", "script", EvalType::Global, EvalFlags::COMPILE_ONLY)
.expect("Unable to compile code");
let result = compiled
.eval_function(&ctx)
.expect("Unable to execute compiled function");
assert_eq!(String::from("2"), result.to_string(&ctx).unwrap());
}
#[test]
fn global_object() {
let rt = Runtime::new();
let ctx = Context::new(&rt);
let mut go = ctx.global_object().unwrap();
assert_eq!(ValueType::Object, go.value_type());
let val = ctx.new_i32(12).unwrap();
go.set_property(&ctx, "foo", &val).unwrap();
let result = ctx
.eval("foo + 3", "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
assert_eq!(String::from("15"), result.to_string(&ctx).unwrap());
}
fn cb<'c>(
ctx: &ContextRef<'c>,
_this: &ValueRef<'c>,
args: &[&ValueRef<'c>],
) -> ValueResult<'c> {
Ok(args[1].dup(ctx))
}
#[test]
fn new_objects() {
let rt = Runtime::new();
let ctx = Context::new(&rt);
let mut go = ctx.global_object().unwrap();
let tests = &[
(ctx.new_u32(300u32), "val - 2", "298"),
(ctx.new_i32(12), "val + 10", "22"),
(ctx.new_f64(12.3), "val + .2", "12.5"),
(ctx.new_string("asdf"), "val.toUpperCase()", "ASDF"),
(ctx.new_bool(true), "val ? 'yes' : 'no'", "yes"),
(ctx.new_bool(false), "val ? 'yes' : 'no'", "no"),
(ctx.new_i64(-222), "val", "-222"),
(ctx.new_u64(222u64), "val", "222"),
(ctx.new_array(), "val.length", "0"),
(
ctx.new_dynamic_fn(|c, _, a: &[&ValueRef]| Ok(a[1].dup(c))),
"val(1,2)",
"2",
),
(ctx.new_dynamic_fn(cb), "val(1,2)", "2"),
(
ctx.new_fn(|_: &ContextRef, x: i32, y: i32| x * y),
"val(3,3)",
"9",
),
];
for (vr, expr, expected) in tests.into_iter() {
let val = vr.as_ref().unwrap();
go.set_property(&ctx, "val", val).unwrap();
let result = ctx
.eval(expr, "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap());
}
}
#[test]
fn real_closures() {
let rt = Runtime::new();
let ctx = Context::new(&rt);
let return_value = ctx.new_string("unsafe").unwrap();
{
let mut go = ctx.global_object().unwrap();
go.set_property(
&ctx,
"foo",
&ctx.new_dynamic_fn(|c, _, _| Ok(return_value.dup(c)))
.unwrap(),
)
.unwrap();
}
let result = ctx
.eval(
"foo().toUpperCase()",
"script",
EvalType::Global,
EvalFlags::NONE,
)
.unwrap();
assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap());
}
}