oden/oden-js/src/value.rs

429 lines
13 KiB
Rust

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<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 {
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<i32> {
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<u32> {
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<i64> {
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<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) -> Result<f64> {
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<Value> {
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<Value> {
let atom = ctx.new_atom(prop)?;
self.get_property_atom(ctx, &atom)
}
pub fn get_property_atom(&self, ctx: &ContextRef, prop: &AtomRef) -> Result<Value> {
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<F>(
&mut self,
ctx: &ContextRef,
prop: &str,
func: impl RustFunction<F> + 'static,
) -> Result<()> {
let vr: Value = ctx.new_fn(func)?;
self.set_property(ctx, prop, &vr)
}
pub fn set_dynamic_method<F>(&mut self, ctx: &ContextRef, prop: &str, func: F) -> Result<()>
where
F: 'static + Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> Result<Value>,
{
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<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(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<Value> {
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);
}
}
}