use libquickjs_sys as q;
use crate::{ExecutionError, JsValue, ValueError};
use super::make_cstring;
use crate::bindings::ContextWrapper;
#[repr(i32)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum JsTag {
Int = q::JS_TAG_INT,
Bool = q::JS_TAG_BOOL,
Null = q::JS_TAG_NULL,
Module = q::JS_TAG_MODULE,
Object = q::JS_TAG_OBJECT,
String = q::JS_TAG_STRING,
Symbol = q::JS_TAG_SYMBOL,
#[cfg(feature = "bigint")]
BigInt = q::JS_TAG_BIG_INT,
Float64 = q::JS_TAG_FLOAT64,
Exception = q::JS_TAG_EXCEPTION,
Undefined = q::JS_TAG_UNDEFINED,
CatchOffset = q::JS_TAG_CATCH_OFFSET,
Uninitialized = q::JS_TAG_UNINITIALIZED,
FunctionBytecode = q::JS_TAG_FUNCTION_BYTECODE,
}
impl JsTag {
#[inline]
pub(super) fn from_c(value: &q::JSValue) -> JsTag {
let inner = unsafe { q::JS_VALUE_GET_TAG(*value) };
match inner {
q::JS_TAG_INT => JsTag::Int,
q::JS_TAG_BOOL => JsTag::Bool,
q::JS_TAG_NULL => JsTag::Null,
q::JS_TAG_MODULE => JsTag::Module,
q::JS_TAG_OBJECT => JsTag::Object,
q::JS_TAG_STRING => JsTag::String,
q::JS_TAG_SYMBOL => JsTag::Symbol,
q::JS_TAG_FLOAT64 => JsTag::Float64,
q::JS_TAG_EXCEPTION => JsTag::Exception,
q::JS_TAG_UNDEFINED => JsTag::Undefined,
q::JS_TAG_CATCH_OFFSET => JsTag::CatchOffset,
q::JS_TAG_UNINITIALIZED => JsTag::Uninitialized,
q::JS_TAG_FUNCTION_BYTECODE => JsTag::FunctionBytecode,
#[cfg(feature = "bigint")]
q::JS_TAG_BIG_INT => JsTag::BigInt,
_other => {
unreachable!()
}
}
}
pub(super) fn to_c(self) -> i32 {
match self {
JsTag::Int => q::JS_TAG_INT,
JsTag::Bool => q::JS_TAG_BOOL,
JsTag::Null => q::JS_TAG_NULL,
JsTag::Module => q::JS_TAG_MODULE,
JsTag::Object => q::JS_TAG_OBJECT,
JsTag::String => q::JS_TAG_STRING,
JsTag::Symbol => q::JS_TAG_SYMBOL,
JsTag::Float64 => q::JS_TAG_FLOAT64,
JsTag::Exception => q::JS_TAG_EXCEPTION,
JsTag::Undefined => q::JS_TAG_UNDEFINED,
JsTag::CatchOffset => q::JS_TAG_CATCH_OFFSET,
JsTag::Uninitialized => q::JS_TAG_UNINITIALIZED,
JsTag::FunctionBytecode => q::JS_TAG_FUNCTION_BYTECODE,
#[cfg(feature = "bigint")]
JsTag::BigInt => q::JS_TAG_FUNCTION_BYTECODE,
}
}
#[inline]
pub fn is_undefined(&self) -> bool {
matches!(self, Self::Undefined)
}
#[inline]
pub fn is_object(&self) -> bool {
matches!(self, Self::Object)
}
#[inline]
pub fn is_exception(&self) -> bool {
matches!(self, Self::Exception)
}
#[inline]
pub fn is_int(&self) -> bool {
matches!(self, Self::Int)
}
#[inline]
pub fn is_bool(&self) -> bool {
matches!(self, Self::Bool)
}
#[inline]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[inline]
pub fn is_module(&self) -> bool {
matches!(self, Self::Module)
}
#[inline]
pub fn is_string(&self) -> bool {
matches!(self, Self::String)
}
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol)
}
#[cfg(feature = "bigint")]
#[inline]
pub fn is_big_int(&self) -> bool {
matches!(self, Self::BigInt)
}
#[inline]
pub fn is_float64(&self) -> bool {
matches!(self, Self::Float64)
}
}
pub struct OwnedJsAtom<'a> {
context: &'a ContextWrapper,
value: q::JSAtom,
}
impl<'a> OwnedJsAtom<'a> {
#[inline]
pub fn new(context: &'a ContextWrapper, value: q::JSAtom) -> Self {
Self { context, value }
}
}
impl<'a> Drop for OwnedJsAtom<'a> {
fn drop(&mut self) {
unsafe {
q::JS_FreeAtom(self.context.context, self.value);
}
}
}
impl<'a> Clone for OwnedJsAtom<'a> {
fn clone(&self) -> Self {
unsafe { q::JS_DupAtom(self.context.context, self.value) };
Self {
context: self.context,
value: self.value,
}
}
}
pub struct OwnedJsValue<'a> {
context: &'a ContextWrapper,
pub(crate) value: q::JSValue,
}
impl<'a> OwnedJsValue<'a> {
#[inline]
pub(crate) fn context(&self) -> &ContextWrapper {
self.context
}
#[inline]
pub(crate) fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self {
Self { context, value }
}
#[inline]
pub(crate) fn tag(&self) -> JsTag {
JsTag::from_c(&self.value)
}
pub(super) unsafe fn as_inner(&self) -> &q::JSValue {
&self.value
}
pub(super) unsafe fn extract(self) -> q::JSValue {
let v = self.value;
std::mem::forget(self);
v
}
#[inline]
pub fn is_null(&self) -> bool {
self.tag().is_null()
}
#[inline]
pub fn is_undefined(&self) -> bool {
self.tag() == JsTag::Undefined
}
#[inline]
pub fn is_bool(&self) -> bool {
self.tag() == JsTag::Bool
}
#[inline]
pub fn is_exception(&self) -> bool {
self.tag() == JsTag::Exception
}
#[inline]
pub fn is_object(&self) -> bool {
self.tag() == JsTag::Object
}
#[inline]
pub fn is_array(&self) -> bool {
unsafe { q::JS_IsArray(self.context.context, self.value) == 1 }
}
#[inline]
pub fn is_function(&self) -> bool {
unsafe { q::JS_IsFunction(self.context.context, self.value) == 1 }
}
#[inline]
pub fn is_module(&self) -> bool {
self.tag().is_module()
}
#[inline]
pub fn is_string(&self) -> bool {
self.tag() == JsTag::String
}
#[inline]
pub fn is_compiled_function(&self) -> bool {
self.tag() == JsTag::FunctionBytecode
}
pub fn to_value(&self) -> Result<JsValue, ValueError> {
self.context.to_value(&self.value)
}
pub(crate) fn to_bool(&self) -> Result<bool, ValueError> {
match self.to_value()? {
JsValue::Bool(b) => Ok(b),
_ => Err(ValueError::UnexpectedType),
}
}
pub(crate) fn try_into_object(self) -> Result<OwnedJsObject<'a>, ValueError> {
OwnedJsObject::try_from_value(self)
}
pub(crate) fn try_into_function(self) -> Result<JsFunction<'a>, ValueError> {
JsFunction::try_from_value(self)
}
pub(crate) fn try_into_compiled_function(self) -> Result<JsCompiledFunction<'a>, ValueError> {
JsCompiledFunction::try_from_value(self)
}
pub(crate) fn try_into_module(self) -> Result<JsModule<'a>, ValueError> {
JsModule::try_from_value(self)
}
pub(crate) fn js_to_string(&self) -> Result<String, ExecutionError> {
let value = if self.is_string() {
self.to_value()?
} else {
let raw = unsafe { q::JS_ToString(self.context.context, self.value) };
let value = OwnedJsValue::new(self.context, raw);
if !value.is_string() {
return Err(ExecutionError::Exception(
"Could not convert value to string".into(),
));
}
value.to_value()?
};
Ok(value.as_str().unwrap().to_string())
}
#[cfg(test)]
pub(crate) fn get_ref_count(&self) -> i32 {
if self.value.tag < 0 {
let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader };
let pref: &mut q::JSRefCountHeader = &mut unsafe { *ptr };
pref.ref_count
} else {
-1
}
}
}
impl<'a> Drop for OwnedJsValue<'a> {
fn drop(&mut self) {
unsafe {
q::JS_FreeValue(self.context.context, self.value);
}
}
}
impl<'a> Clone for OwnedJsValue<'a> {
fn clone(&self) -> Self {
unsafe { q::JS_DupValue(self.context.context, self.value) };
Self {
context: self.context,
value: self.value,
}
}
}
impl<'a> std::fmt::Debug for OwnedJsValue<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}(_)", self.tag())
}
}
pub struct OwnedJsArray<'a> {
value: OwnedJsValue<'a>,
}
impl<'a> OwnedJsArray<'a> {
pub fn new(value: OwnedJsValue<'a>) -> Option<Self> {
if value.is_array() {
Some(Self { value })
} else {
None
}
}
}
#[derive(Clone, Debug)]
pub struct OwnedJsObject<'a> {
value: OwnedJsValue<'a>,
}
impl<'a> OwnedJsObject<'a> {
pub fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
if !value.is_object() {
Err(ValueError::Internal("Expected an object".into()))
} else {
Ok(Self { value })
}
}
pub fn into_value(self) -> OwnedJsValue<'a> {
self.value
}
pub fn property(&self, name: &str) -> Result<Option<OwnedJsValue<'a>>, ExecutionError> {
let cname = make_cstring(name)?;
let value = {
let raw = unsafe {
q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
};
OwnedJsValue::new(self.value.context, raw)
};
let tag = value.tag();
if tag.is_exception() {
Err(ExecutionError::Internal(format!(
"Exception while getting property '{}'",
name
)))
} else if tag.is_undefined() {
Ok(None)
} else {
Ok(Some(value))
}
}
pub fn property_require(&self, name: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
self.property(name)?
.ok_or_else(|| ExecutionError::Internal(format!("Property '{}' not found", name)))
}
pub fn is_promise(&self) -> Result<bool, ExecutionError> {
if let Some(p) = self.property("then")? {
if p.is_function() {
return Ok(true);
}
}
if let Some(p) = self.property("catch")? {
if p.is_function() {
return Ok(true);
}
}
Ok(false)
}
pub fn set_property(&self, name: &str, value: OwnedJsValue<'a>) -> Result<(), ExecutionError> {
let cname = make_cstring(name)?;
unsafe {
let ret = q::JS_SetPropertyStr(
self.value.context.context,
self.value.value,
cname.as_ptr(),
value.value,
);
if ret < 0 {
Err(ExecutionError::Exception("Could not set property".into()))
} else {
std::mem::forget(value);
Ok(())
}
}
}
}
#[derive(Clone, Debug)]
pub struct JsFunction<'a> {
value: OwnedJsValue<'a>,
}
impl<'a> JsFunction<'a> {
pub fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
if !value.is_function() {
Err(ValueError::Internal(format!(
"Expected a function, got {:?}",
value.tag()
)))
} else {
Ok(Self { value })
}
}
pub fn into_value(self) -> OwnedJsValue<'a> {
self.value
}
pub fn call(&self, args: Vec<OwnedJsValue<'a>>) -> Result<OwnedJsValue<'a>, ExecutionError> {
let mut qargs = args.iter().map(|arg| arg.value).collect::<Vec<_>>();
let qres_raw = unsafe {
q::JS_Call(
self.value.context.context,
self.value.value,
q::JSValue {
u: q::JSValueUnion { int32: 0 },
tag: JsTag::Null as i64,
},
qargs.len() as i32,
qargs.as_mut_ptr(),
)
};
Ok(OwnedJsValue::new(self.value.context, qres_raw))
}
}
#[derive(Clone, Debug)]
pub struct JsCompiledFunction<'a> {
value: OwnedJsValue<'a>,
}
impl<'a> JsCompiledFunction<'a> {
pub(crate) fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
if !value.is_compiled_function() {
Err(ValueError::Internal(format!(
"Expected a compiled function, got {:?}",
value.tag()
)))
} else {
Ok(Self { value })
}
}
pub(crate) fn as_value(&self) -> &OwnedJsValue<'_> {
&self.value
}
pub(crate) fn into_value(self) -> OwnedJsValue<'a> {
self.value
}
pub fn eval(&'a self) -> Result<OwnedJsValue<'a>, ExecutionError> {
super::compile::run_compiled_function(self)
}
pub fn to_bytecode(&self) -> Result<Vec<u8>, ExecutionError> {
Ok(super::compile::to_bytecode(self.value.context, self))
}
}
pub struct JsModule<'a> {
value: OwnedJsValue<'a>,
}
impl<'a> JsModule<'a> {
pub fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
if !value.is_module() {
Err(ValueError::Internal(format!(
"Expected a compiled function, got {:?}",
value.tag()
)))
} else {
Ok(Self { value })
}
}
pub fn into_value(self) -> OwnedJsValue<'a> {
self.value
}
}
pub enum JsCompiledValue<'a> {
Function(JsCompiledFunction<'a>),
Module(JsModule<'a>),
}