use std::fmt;
use std::rc::{Rc, Weak};
use oxide_core::Handle;
use super::RuntimeError;
use super::runtime::Shared;
use crate::engine::value::JsString;
use crate::engine::value::bigint::JsBigInt;
#[derive(Clone)]
pub enum Value {
Undefined,
Null,
Bool(bool),
Int(i32),
Float(f64),
BigInt(JsBigInt),
String(JsString),
Symbol(SymbolRef),
Object(ObjectRef),
}
impl Value {
pub fn try_clone(&self) -> Result<Self, RuntimeError> {
Ok(self.clone())
}
pub fn number(value: f64) -> Self {
if value.fract() == 0.0
&& value >= f64::from(i32::MIN)
&& value <= f64::from(i32::MAX)
&& !(value == 0.0 && value.is_sign_negative())
{
Value::Int(value as i32)
} else {
Value::Float(value)
}
}
pub const fn as_number(&self) -> Option<f64> {
match self {
Value::Int(value) => Some(*value as f64),
Value::Float(value) => Some(*value),
_ => None,
}
}
pub fn as_object(&self) -> Option<&ObjectRef> {
match self {
Value::Object(object) => Some(object),
_ => None,
}
}
pub fn to_boolean(&self) -> bool {
match self {
Value::Undefined | Value::Null => false,
Value::Bool(value) => *value,
Value::Int(value) => *value != 0,
Value::Float(value) => *value != 0.0 && !value.is_nan(),
Value::BigInt(value) => !value.is_zero(),
Value::String(value) => !value.is_empty(),
Value::Symbol(_) | Value::Object(_) => true,
}
}
pub const fn type_of(&self) -> &'static str {
match self {
Value::Undefined => "undefined",
Value::Null | Value::Object(_) => "object",
Value::Bool(_) => "boolean",
Value::Int(_) | Value::Float(_) => "number",
Value::BigInt(_) => "bigint",
Value::String(_) => "string",
Value::Symbol(_) => "symbol",
}
}
pub fn strict_equal(&self, other: &Self) -> bool {
match (self.as_number(), other.as_number()) {
(Some(x), Some(y)) => x == y,
_ => self.same_value(other),
}
}
pub fn same_value(&self, other: &Self) -> bool {
match (self, other) {
(Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::BigInt(x), Value::BigInt(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y,
(Value::Symbol(x), Value::Symbol(y)) => x == y,
(Value::Object(x), Value::Object(y)) => x == y,
_ => match (self.as_number(), other.as_number()) {
(Some(x), Some(y)) => {
(x.is_nan() && y.is_nan())
|| (x == y && x.is_sign_negative() == y.is_sign_negative())
}
_ => false,
},
}
}
pub fn same_value_zero(&self, other: &Self) -> bool {
match (self.as_number(), other.as_number()) {
(Some(x), Some(y)) => (x.is_nan() && y.is_nan()) || x == y,
_ => self.same_value(other),
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Undefined, Value::Undefined) | (Value::Null, Value::Null) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Int(x), Value::Int(y)) => x == y,
(Value::Float(x), Value::Float(y)) => x == y,
(Value::BigInt(x), Value::BigInt(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y,
(Value::Symbol(x), Value::Symbol(y)) => x == y,
(Value::Object(x), Value::Object(y)) => x == y,
_ => false,
}
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Undefined => f.write_str("Undefined"),
Value::Null => f.write_str("Null"),
Value::Bool(value) => write!(f, "Bool({value})"),
Value::Int(value) => write!(f, "Int({value})"),
Value::Float(value) => write!(f, "Float({value:?})"),
Value::BigInt(value) => write!(f, "BigInt({value})"),
Value::String(value) => write!(f, "String({:?})", value.to_utf8_lossy()),
Value::Symbol(value) => write!(f, "Symbol({value:?})"),
Value::Object(value) => write!(f, "Object({value:?})"),
}
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::Bool(value)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Value::Int(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Value::number(value)
}
}
impl From<JsString> for Value {
fn from(value: JsString) -> Self {
Value::String(value)
}
}
impl From<ObjectRef> for Value {
fn from(value: ObjectRef) -> Self {
Value::Object(value)
}
}
impl From<CallableRef> for Value {
fn from(value: CallableRef) -> Self {
Value::Object(value.0)
}
}
pub(crate) struct Slot {
pub(crate) shared: Weak<Shared>,
pub(crate) handle: Handle,
pub(crate) value: oxide_core::Value,
}
impl Drop for Slot {
fn drop(&mut self) {
if let Some(shared) = self.shared.upgrade() {
shared.release(self.handle);
}
}
}
impl Slot {
pub(crate) fn of(&self, shared: &Rc<Shared>) -> bool {
std::ptr::eq(self.shared.as_ptr(), Rc::as_ptr(shared))
}
}
#[derive(Clone)]
pub struct ObjectRef {
pub(crate) slot: Rc<Slot>,
}
#[derive(Clone)]
pub struct SymbolRef {
pub(crate) slot: Rc<Slot>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct CallableRef(pub(crate) ObjectRef);
macro_rules! reference {
($name:ident) => {
impl $name {
pub fn try_clone(&self) -> Result<Self, RuntimeError> {
Ok(self.clone())
}
pub fn belongs_to(&self, runtime: &super::Runtime) -> bool {
self.slot.of(&runtime.shared)
}
pub(crate) fn core_value(&self) -> oxide_core::Value {
self.slot.value
}
}
impl PartialEq for $name {
fn eq(&self, other: &Self) -> bool {
Weak::ptr_eq(&self.slot.shared, &other.slot.shared)
&& self.slot.value == other.slot.value
}
}
impl Eq for $name {}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({:?})", stringify!($name), self.slot.value)
}
}
};
}
reference!(ObjectRef);
reference!(SymbolRef);
impl CallableRef {
pub fn try_clone(&self) -> Result<Self, RuntimeError> {
Ok(self.clone())
}
pub fn as_object(&self) -> &ObjectRef {
&self.0
}
pub fn into_object(self) -> ObjectRef {
self.0
}
pub fn belongs_to(&self, runtime: &super::Runtime) -> bool {
self.0.belongs_to(runtime)
}
}
impl std::ops::Deref for CallableRef {
type Target = ObjectRef;
fn deref(&self) -> &ObjectRef {
&self.0
}
}