[oden] Grab my incomplete QuickJS wrapper
This commit is contained in:
parent
aa70df41a3
commit
898b1fe129
114 changed files with 244181 additions and 0 deletions
471
oden-js/src/value.rs
Normal file
471
oden-js/src/value.rs
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction};
|
||||
use oden_js_sys as sys;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::marker;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ValueType {
|
||||
BigDecimal,
|
||||
BigInt,
|
||||
BigFloat,
|
||||
Symbol,
|
||||
Module,
|
||||
FunctionBytecode,
|
||||
String,
|
||||
Object,
|
||||
Int,
|
||||
Bool,
|
||||
Null,
|
||||
Undefined,
|
||||
Float64,
|
||||
}
|
||||
|
||||
impl From<i32> for ValueType {
|
||||
fn from(v: i32) -> Self {
|
||||
match v {
|
||||
sys::JS_TAG_BIG_DECIMAL => ValueType::BigDecimal,
|
||||
sys::JS_TAG_BIG_INT => ValueType::BigInt,
|
||||
sys::JS_TAG_BIG_FLOAT => ValueType::BigFloat,
|
||||
sys::JS_TAG_SYMBOL => ValueType::Symbol,
|
||||
sys::JS_TAG_MODULE => ValueType::Module,
|
||||
sys::JS_TAG_FUNCTION_BYTECODE => ValueType::FunctionBytecode,
|
||||
sys::JS_TAG_STRING => ValueType::String,
|
||||
sys::JS_TAG_OBJECT => ValueType::Object,
|
||||
sys::JS_TAG_INT => ValueType::Int,
|
||||
sys::JS_TAG_BOOL => ValueType::Bool,
|
||||
sys::JS_TAG_NULL => ValueType::Null,
|
||||
sys::JS_TAG_UNDEFINED => ValueType::Undefined,
|
||||
// sys::JS_TAG_UNINITIALIZED => ValueType::Uninitialized,
|
||||
// sys::JS_TAG_CATCH_OFFSET => ValueType::CatchOffset,
|
||||
// sys::JS_TAG_EXCEPTION => ValueType::Exception,
|
||||
sys::JS_TAG_FLOAT64 => ValueType::Float64,
|
||||
_ => panic!("Unknown or unsupported value type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A borrowed JavaScript value.
|
||||
///
|
||||
/// Contrast this structure to `Value`, which is the owned equivalent. Mostly
|
||||
/// you will want to create and manipulate `Value`, but you will occasionally
|
||||
/// receive `ValueRef` in calls that originate from JavaScript
|
||||
/// code. (`ValueRef` is to `Value` as `str` is to `String`, if that helps.)
|
||||
pub struct ValueRef<'r> {
|
||||
pub(crate) val: sys::JSValue,
|
||||
_phantom: marker::PhantomData<&'r Runtime>,
|
||||
}
|
||||
|
||||
// pub type Callback<'r, 'ctx> = fn(
|
||||
// ctx: &'ctx Context<'r>,
|
||||
// this_val: &ValueRef<'r, 'ctx>,
|
||||
// argc: usize,
|
||||
// argvc: &[&ValueRef<'r, 'ctx>],
|
||||
// ) -> Value<'r, 'ctx>;
|
||||
|
||||
impl<'r> ValueRef<'r> {
|
||||
/// Wrap a value in a specific context.
|
||||
///
|
||||
/// **WARNING**: The specified value *must* be associated with at least
|
||||
/// the runtime of the specified context, if not the context itself. This
|
||||
/// function makes no attempt to validate this.
|
||||
pub fn from_raw(val: sys::JSValue, _ctx: &ContextRef<'r>) -> Self {
|
||||
ValueRef {
|
||||
val,
|
||||
_phantom: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type of JavaScript value that this is.
|
||||
pub fn value_type(&self) -> ValueType {
|
||||
// SAFETY: Our lifetime guarantees ensure that this value is still live.
|
||||
unsafe { sys::JS_ValueGetTag(self.val) }.into()
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Null.
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.value_type() == ValueType::Null
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::BigDecimal.
|
||||
pub fn is_big_decimal(&self) -> bool {
|
||||
self.value_type() == ValueType::BigDecimal
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::BigInt.
|
||||
pub fn is_big_int(&self) -> bool {
|
||||
self.value_type() == ValueType::BigInt
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::BigFloat.
|
||||
pub fn is_big_float(&self) -> bool {
|
||||
self.value_type() == ValueType::BigFloat
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Symbol.
|
||||
pub fn is_symbol(&self) -> bool {
|
||||
self.value_type() == ValueType::Symbol
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Module.
|
||||
pub fn is_module(&self) -> bool {
|
||||
self.value_type() == ValueType::Module
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::FunctionBytecode.
|
||||
pub fn is_functionbytecode(&self) -> bool {
|
||||
self.value_type() == ValueType::FunctionBytecode
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::String.
|
||||
pub fn is_string(&self) -> bool {
|
||||
self.value_type() == ValueType::String
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Object.
|
||||
///
|
||||
/// NOTE! This also returns true for arrays and functions the like; it
|
||||
/// has all the problems of JavaScript's typeof() operator. I wish I
|
||||
/// could fix the world but I can't.
|
||||
pub fn is_object(&self) -> bool {
|
||||
self.value_type() == ValueType::Object
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Int.
|
||||
pub fn is_int(&self) -> bool {
|
||||
self.value_type() == ValueType::Int
|
||||
}
|
||||
|
||||
pub fn to_i32(&self, ctx: &ContextRef<'r>) -> Result<'r, i32> {
|
||||
unsafe {
|
||||
let mut res: i32 = 0;
|
||||
let ret = sys::JS_ToInt32(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_u32(&self, ctx: &ContextRef<'r>) -> Result<'r, u32> {
|
||||
unsafe {
|
||||
let mut res: u32 = 0;
|
||||
let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_i64(&self, ctx: &ContextRef<'r>) -> Result<'r, i64> {
|
||||
unsafe {
|
||||
let mut res: i64 = 0;
|
||||
let ret = sys::JS_ToInt64(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: tou64.
|
||||
|
||||
/// True if ValueType is ValueType::Bool.
|
||||
pub fn is_bool(&self) -> bool {
|
||||
self.value_type() == ValueType::Bool
|
||||
}
|
||||
|
||||
pub fn to_bool(&self) -> Result<'r, bool> {
|
||||
if self.value_type() == ValueType::Bool {
|
||||
Ok(unsafe { self.val.u.int32 } > 0)
|
||||
} else {
|
||||
Err(Error::InvalidType {
|
||||
expected: ValueType::Bool,
|
||||
found: self.value_type(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Undefined.
|
||||
pub fn is_undefined(&self) -> bool {
|
||||
self.value_type() == ValueType::Undefined
|
||||
}
|
||||
|
||||
/// True if ValueType is ValueType::Float64.
|
||||
pub fn is_float64(&self) -> bool {
|
||||
self.value_type() == ValueType::Float64
|
||||
}
|
||||
|
||||
pub fn to_float64(&self, ctx: &ContextRef<'r>) -> Result<'r, f64> {
|
||||
unsafe {
|
||||
let mut res: f64 = 0.0;
|
||||
let ret = sys::JS_ToFloat64(ctx.ctx, &mut res, self.val);
|
||||
if ret < 0 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a reference to this value so that it outlives this current reference.
|
||||
///
|
||||
/// (This is a very cheap operation: at most it increments a reference count.)
|
||||
pub fn dup(&self, ctx: &ContextRef<'r>) -> Value<'r> {
|
||||
unsafe {
|
||||
sys::JS_DupValue(ctx.ctx, self.val);
|
||||
}
|
||||
Value::from_raw(self.val, ctx)
|
||||
}
|
||||
|
||||
/// Evaluate a previously-compiled function or module. This value must be
|
||||
/// of type `FunctionBytecode` or `Module`, otherwise an error will be
|
||||
/// returned.
|
||||
///
|
||||
/// JavsScript exceptions are are returned as values where `value_type`
|
||||
/// is `ValueType::Exception`.
|
||||
pub fn eval_function(&self, ctx: &ContextRef<'r>) -> Result<'r, Value<'r>> {
|
||||
match self.value_type() {
|
||||
ValueType::Module | ValueType::FunctionBytecode => (),
|
||||
found => {
|
||||
return Err(Error::InvalidType {
|
||||
expected: ValueType::FunctionBytecode,
|
||||
found,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
// NOTE: JS_EvalFunction consumes its value but that's not what
|
||||
// we want here.
|
||||
sys::JS_DupValue(ctx.ctx, self.val);
|
||||
sys::JS_EvalFunction(ctx.ctx, self.val)
|
||||
};
|
||||
Ok(Value::from_raw(result, ctx))
|
||||
}
|
||||
|
||||
pub fn get_property(&self, ctx: &ContextRef<'r>, prop: &str) -> Result<'r, Value<'r>> {
|
||||
let atom = ctx.new_atom(prop)?;
|
||||
self.get_property_atom(ctx, &atom)
|
||||
}
|
||||
|
||||
pub fn get_property_atom(
|
||||
&self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &AtomRef<'r>,
|
||||
) -> Result<'r, Value<'r>> {
|
||||
ctx.check_exception(unsafe { sys::JS_GetProperty(ctx.ctx, self.val, prop.atom) })
|
||||
}
|
||||
|
||||
pub fn set_property(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &str,
|
||||
val: &ValueRef,
|
||||
) -> Result<'r, ()> {
|
||||
// TODO: Consume API
|
||||
let atom = ctx.new_atom(prop)?;
|
||||
self.set_property_atom(ctx, &atom, val)
|
||||
}
|
||||
|
||||
pub fn set_property_atom(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &AtomRef<'r>,
|
||||
val: &ValueRef,
|
||||
) -> Result<'r, ()> {
|
||||
unsafe {
|
||||
sys::JS_DupValue(ctx.ctx, val.val);
|
||||
let result = sys::JS_SetProperty(ctx.ctx, self.val, prop.atom, val.val);
|
||||
if result == -1 {
|
||||
Err(Error::Exception(ctx.exception()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fn<F>(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &str,
|
||||
func: impl RustFunction<'r, F>,
|
||||
) -> Result<'r, ()> {
|
||||
let vr: Value<'r> = ctx.new_fn(func)?;
|
||||
self.set_property(ctx, prop, &vr)
|
||||
}
|
||||
|
||||
pub fn set_dynamic_method<F>(
|
||||
&mut self,
|
||||
ctx: &ContextRef<'r>,
|
||||
prop: &str,
|
||||
func: F,
|
||||
) -> Result<'r, ()>
|
||||
where
|
||||
F: Fn(&ContextRef<'r>, &ValueRef<'r>, &[&ValueRef<'r>]) -> Result<'r, Value<'r>>,
|
||||
{
|
||||
let vr: Value<'r> = ctx.new_dynamic_fn(func)?;
|
||||
self.set_property(ctx, prop, &vr)
|
||||
}
|
||||
|
||||
/// Convert this value into a string representation of the same value.
|
||||
pub fn to_string(&self, ctx: &ContextRef<'r>) -> Result<'r, String> {
|
||||
// SAFETY: ctx's life is bound by the lifetime of our Context, and
|
||||
// sys::JS_ToCStringLen2 will return non-null, unless we're out of
|
||||
// memory.
|
||||
let cstr = unsafe {
|
||||
let ptr = sys::JS_ToCStringLen2(ctx.ctx, std::ptr::null_mut(), self.val, 0);
|
||||
if ptr.is_null() {
|
||||
return Err(Error::Exception(ctx.exception()));
|
||||
}
|
||||
CStr::from_ptr(ptr)
|
||||
};
|
||||
|
||||
// NOTE: Javascript only traffics in unicode strings, and QuickJS
|
||||
// ensures that the result here will be UTF-8, so we can safely
|
||||
// "unwrap" as there's no JavaScript value that should cause it to
|
||||
// fail.
|
||||
let result: String = String::from(cstr.to_str().unwrap());
|
||||
|
||||
// SAFETY: ctx's life is bound by the lifetime of our Context, and
|
||||
// cstr just came from the call above.
|
||||
unsafe {
|
||||
sys::JS_FreeCString(ctx.ctx, cstr.as_ptr());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> fmt::Debug for ValueRef<'ctx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Value")
|
||||
.field("v", &self.val)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned Value from JavaScript. Unlike ValueRef, this type stands in for
|
||||
/// values that you construct in Rust code; otherwise they are identical.
|
||||
pub struct Value<'r> {
|
||||
value: ValueRef<'r>,
|
||||
rt: *mut sys::JSRuntime,
|
||||
_phantom: marker::PhantomData<&'r Runtime>,
|
||||
}
|
||||
|
||||
impl<'r> Value<'r> {
|
||||
/// Take ownership of a value in a specific context.
|
||||
///
|
||||
/// **WARNING**: The specified value *must* be associated with at least
|
||||
/// the runtime of the specified context, if not the context itself. This
|
||||
/// function makes no attempt to validate this.
|
||||
pub(crate) fn from_raw(val: sys::JSValue, ctx: &ContextRef<'r>) -> Self {
|
||||
Value {
|
||||
value: ValueRef::from_raw(val, ctx),
|
||||
rt: unsafe { sys::JS_GetRuntime(ctx.ctx) },
|
||||
_phantom: marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Deref for Value<'r> {
|
||||
type Target = ValueRef<'r>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> DerefMut for Value<'r> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Drop for Value<'r> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
sys::JS_FreeValueRT(self.rt, self.val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> fmt::Debug for Value<'r> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.value.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Context, EvalFlags, EvalType, Runtime};
|
||||
|
||||
#[test]
|
||||
fn value_type() {
|
||||
let rt = Runtime::new();
|
||||
let mut ctx = Context::new(&rt);
|
||||
ctx.add_intrinsic_bigfloat();
|
||||
ctx.add_intrinsic_bigdecimal();
|
||||
|
||||
let tests = &[
|
||||
("123.333m", ValueType::BigDecimal),
|
||||
("BigInt(9007199254740991)", ValueType::BigInt),
|
||||
("222.23444l", ValueType::BigFloat),
|
||||
("Symbol('hello')", ValueType::Symbol),
|
||||
("\"hello\"", ValueType::String),
|
||||
("(function() { return {'a':123}; })()", ValueType::Object),
|
||||
("123", ValueType::Int),
|
||||
("true", ValueType::Bool),
|
||||
("null", ValueType::Null),
|
||||
("undefined", ValueType::Undefined),
|
||||
("123.45", ValueType::Float64),
|
||||
];
|
||||
|
||||
for (expr, expected) in tests.into_iter() {
|
||||
let val = ctx
|
||||
.eval(expr, "script", EvalType::Global, EvalFlags::STRICT)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(*expected, val.value_type(), "for {}", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: We can happily create and return values, like this:
|
||||
/// ```
|
||||
/// use oden_js::*;
|
||||
/// fn my_test<'a>(ctx: &'a Context<'a>) -> Value<'a> {
|
||||
/// ctx.new_i32(123).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// But our lifetime bounds on values keep them from outliving their runtimes:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use oden_js::*;
|
||||
/// fn my_test<'r>() -> oden_js::Value<'r> {
|
||||
/// let rt = Runtime::new();
|
||||
/// let ctx = Context::new(&rt);
|
||||
///
|
||||
/// ctx.new_i32(123).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Contexts are fundamentally transient, though:
|
||||
///
|
||||
/// ```
|
||||
/// use oden_js::*;
|
||||
/// fn my_test<'r>(runtime: &'r Runtime) -> sheetland_js::Value<'r> {
|
||||
/// let ctx = Context::new(runtime);
|
||||
///
|
||||
/// ctx.new_i32(123).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(doctest)]
|
||||
pub struct ValuesObeyLifetimes {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue