use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction}; use oden_js_sys as sys; use std::ffi::CStr; use std::fmt; 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 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 { pub(crate) val: sys::JSValue, } impl ValueRef { pub fn from_raw(val: sys::JSValue) -> Self { ValueRef { val } } /// 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) -> Result { unsafe { let mut res: i32 = 0; let ret = sys::JS_ToInt32(ctx.ctx, &mut res, self.val); if ret < 0 { Err(ctx.exception_error()) } else { Ok(res) } } } pub fn to_u32(&self, ctx: &ContextRef) -> Result { unsafe { let mut res: u32 = 0; let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val); if ret < 0 { Err(ctx.exception_error()) } else { Ok(res) } } } pub fn to_i64(&self, ctx: &ContextRef) -> Result { unsafe { let mut res: i64 = 0; let ret = sys::JS_ToInt64(ctx.ctx, &mut res, self.val); if ret < 0 { Err(ctx.exception_error()) } 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 { 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) -> Result { unsafe { let mut res: f64 = 0.0; let ret = sys::JS_ToFloat64(ctx.ctx, &mut res, self.val); if ret < 0 { Err(ctx.exception_error()) } 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) -> Value { 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) -> Result { 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, prop: &str) -> Result { let atom = ctx.new_atom(prop)?; self.get_property_atom(ctx, &atom) } pub fn get_property_atom(&self, ctx: &ContextRef, prop: &AtomRef) -> Result { ctx.check_exception(unsafe { sys::JS_GetProperty(ctx.ctx, self.val, prop.atom) }) } pub fn set_property(&mut self, ctx: &ContextRef, prop: &str, val: &ValueRef) -> Result<()> { // 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, prop: &AtomRef, val: &ValueRef, ) -> Result<()> { 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(ctx.exception_error()) } else { Ok(()) } } } pub fn set_fn( &mut self, ctx: &ContextRef, prop: &str, func: impl RustFunction + 'static, ) -> Result<()> { let vr: Value = ctx.new_fn(func)?; self.set_property(ctx, prop, &vr) } pub fn set_dynamic_method(&mut self, ctx: &ContextRef, prop: &str, func: F) -> Result<()> where F: 'static + Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> Result, { let vr: Value = 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) -> Result { // 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(ctx.exception_error()); } 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) } pub fn call(&self, ctx: &ContextRef) -> Result { unsafe { ctx.check_exception(sys::JS_Call( ctx.ctx, self.val, sys::JS_MakeUndefined(), 0, std::ptr::null_mut(), )) } } } impl fmt::Debug for ValueRef { 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 { value: ValueRef, rt: Runtime, } impl Value { /// 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) -> Self { Value { value: ValueRef::from_raw(val), rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }), } } pub fn null(ctx: &ContextRef) -> Self { Value { value: ValueRef::from_raw(unsafe { sys::JS_MakeNull() }), rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }), } } pub fn undefined(ctx: &ContextRef) -> Self { Value { value: ValueRef::from_raw(unsafe { sys::JS_MakeUndefined() }), rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }), } } } impl Deref for Value { type Target = ValueRef; fn deref(&self) -> &Self::Target { &self.value } } impl DerefMut for Value { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } impl Drop for Value { fn drop(&mut self) { unsafe { sys::JS_FreeValueRT(self.rt.rt, self.val); } } } impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.value.fmt(f) } } #[cfg(test)] mod tests { use super::*; use crate::{Context, EvalFlags, 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", EvalFlags::STRICT).unwrap(); assert_eq!(*expected, val.value_type(), "for {}", expr); } } }