mirror of
https://github.com/voltrevo/ValueScript.git
synced 2026-01-14 07:57:57 -05:00
716 lines
16 KiB
Rust
716 lines
16 KiB
Rust
use core::fmt;
|
|
use std::any::Any;
|
|
use std::rc::Rc;
|
|
use std::str::FromStr;
|
|
|
|
use num_bigint::BigInt;
|
|
use num_traits::cast::ToPrimitive;
|
|
use num_traits::Zero;
|
|
|
|
use crate::copy_counter::CopyCounter;
|
|
use crate::native_function::ThisWrapper;
|
|
use crate::operations::{op_sub, op_submov};
|
|
use crate::stack_frame::StackFrame;
|
|
use crate::vs_array::VsArray;
|
|
use crate::vs_class::VsClass;
|
|
use crate::vs_function::VsFunction;
|
|
use crate::vs_object::VsObject;
|
|
use crate::vs_symbol::{symbol_to_name, VsSymbol};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Val {
|
|
Void,
|
|
Undefined,
|
|
Null,
|
|
Bool(bool),
|
|
Number(f64),
|
|
BigInt(BigInt),
|
|
Symbol(VsSymbol),
|
|
String(Rc<String>),
|
|
Array(Rc<VsArray>),
|
|
Object(Rc<VsObject>),
|
|
Function(Rc<VsFunction>),
|
|
Class(Rc<VsClass>),
|
|
Static(&'static dyn ValTrait),
|
|
Dynamic(Rc<dyn DynValTrait>),
|
|
CopyCounter(Box<CopyCounter>),
|
|
}
|
|
|
|
impl Default for Val {
|
|
fn default() -> Self {
|
|
Val::Void
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
pub enum VsType {
|
|
Undefined,
|
|
Null,
|
|
Bool,
|
|
Number,
|
|
BigInt,
|
|
Symbol,
|
|
String,
|
|
Array,
|
|
Object,
|
|
Function,
|
|
Class,
|
|
}
|
|
|
|
pub enum LoadFunctionResult {
|
|
NotAFunction,
|
|
StackFrame(StackFrame),
|
|
NativeFunction(fn(this: ThisWrapper, params: Vec<Val>) -> Result<Val, Val>),
|
|
}
|
|
|
|
pub trait ValTrait: fmt::Display {
|
|
fn typeof_(&self) -> VsType;
|
|
fn to_number(&self) -> f64;
|
|
fn to_index(&self) -> Option<usize>;
|
|
fn is_primitive(&self) -> bool;
|
|
fn is_truthy(&self) -> bool;
|
|
fn is_nullish(&self) -> bool;
|
|
|
|
fn bind(&self, params: Vec<Val>) -> Option<Val>;
|
|
|
|
fn as_bigint_data(&self) -> Option<BigInt>;
|
|
fn as_array_data(&self) -> Option<Rc<VsArray>>;
|
|
fn as_class_data(&self) -> Option<Rc<VsClass>>;
|
|
|
|
fn load_function(&self) -> LoadFunctionResult;
|
|
|
|
fn sub(&self, key: &Val) -> Result<Val, Val>;
|
|
fn submov(&mut self, key: &Val, value: Val) -> Result<(), Val>;
|
|
|
|
fn pretty_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
|
fn codify(&self) -> String;
|
|
}
|
|
|
|
pub trait DynValTrait: ValTrait {
|
|
fn clone_interior(&self) -> Rc<dyn DynValTrait>;
|
|
fn as_any(&self) -> &dyn Any;
|
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
|
}
|
|
|
|
impl<T> DynValTrait for T
|
|
where
|
|
T: 'static + ValTrait + Clone,
|
|
{
|
|
fn clone_interior(&self) -> Rc<dyn DynValTrait> {
|
|
Rc::new(self.clone())
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
pub fn dynamic_make_mut(rc: &mut Rc<dyn DynValTrait>) -> &mut dyn DynValTrait {
|
|
if Rc::get_mut(rc).is_none() {
|
|
*rc = rc.clone_interior();
|
|
}
|
|
|
|
Rc::get_mut(rc).unwrap()
|
|
}
|
|
|
|
impl fmt::Debug for dyn ValTrait {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "(dyn ValTrait)(")?;
|
|
self.pretty_fmt(f)?;
|
|
write!(f, ")")
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for dyn DynValTrait {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "(dyn DynValTrait)(")?;
|
|
self.pretty_fmt(f)?;
|
|
write!(f, ")")
|
|
}
|
|
}
|
|
|
|
impl Val {
|
|
pub fn to_primitive(&self) -> Val {
|
|
if self.is_primitive() {
|
|
self.clone()
|
|
} else {
|
|
self.clone().to_val_string()
|
|
}
|
|
}
|
|
|
|
pub fn to_val_string(self) -> Val {
|
|
match self {
|
|
Val::String(_) => self,
|
|
_ => self.to_string().to_val(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ValTrait for Val {
|
|
fn typeof_(&self) -> VsType {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Void => VsType::Undefined,
|
|
Undefined => VsType::Undefined,
|
|
Null => VsType::Null,
|
|
Bool(_) => VsType::Bool,
|
|
Number(_) => VsType::Number,
|
|
BigInt(_) => VsType::BigInt,
|
|
Symbol(_) => VsType::Symbol,
|
|
String(_) => VsType::String,
|
|
Array(_) => VsType::Array,
|
|
Object(_) => VsType::Object,
|
|
Function(_) => VsType::Function,
|
|
Class(_) => VsType::Class,
|
|
Static(val) => val.typeof_(),
|
|
Dynamic(val) => val.typeof_(),
|
|
CopyCounter(_) => VsType::Object,
|
|
};
|
|
}
|
|
|
|
fn to_number(&self) -> f64 {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Void => f64::NAN,
|
|
Undefined => f64::NAN,
|
|
Null => 0_f64,
|
|
Bool(b) => *b as u8 as f64,
|
|
Number(x) => *x,
|
|
BigInt(x) => x.to_f64().unwrap_or(f64::NAN),
|
|
Symbol(_) => f64::NAN, // TODO: Should be TypeError
|
|
String(s) => f64::from_str(s).unwrap_or(f64::NAN),
|
|
Array(vals) => match vals.elements.len() {
|
|
0 => 0_f64,
|
|
1 => vals.elements[0].to_number(),
|
|
_ => f64::NAN,
|
|
},
|
|
Object(_) => f64::NAN,
|
|
Function(_) => f64::NAN,
|
|
Class(_) => f64::NAN,
|
|
Static(val) => val.to_number(),
|
|
Dynamic(val) => val.to_number(),
|
|
CopyCounter(_) => f64::NAN,
|
|
};
|
|
}
|
|
|
|
fn to_index(&self) -> Option<usize> {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Void => panic!("Shouldn't happen"),
|
|
Undefined => None,
|
|
Null => None,
|
|
Bool(_) => None,
|
|
Number(x) => number_to_index(*x),
|
|
BigInt(b) => number_to_index(b.to_f64().unwrap_or(f64::NAN)),
|
|
Symbol(_) => None,
|
|
String(s) => match f64::from_str(s) {
|
|
Ok(x) => number_to_index(x),
|
|
Err(_) => None,
|
|
},
|
|
Array(_) => None,
|
|
Object(_) => None,
|
|
Function(_) => None,
|
|
Class(_) => None,
|
|
Static(val) => val.to_index(),
|
|
Dynamic(val) => val.to_index(),
|
|
CopyCounter(_) => None,
|
|
};
|
|
}
|
|
|
|
fn is_primitive(&self) -> bool {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Void => true,
|
|
Undefined => true,
|
|
Null => true,
|
|
Bool(_) => true,
|
|
Number(_) => true,
|
|
BigInt(_) => true,
|
|
Symbol(_) => true,
|
|
String(_) => true,
|
|
Array(_) => false,
|
|
Object(_) => false,
|
|
Function(_) => false,
|
|
Class(_) => false,
|
|
Static(val) => val.is_primitive(), // TODO: false?
|
|
Dynamic(val) => val.is_primitive(),
|
|
CopyCounter(_) => false,
|
|
};
|
|
}
|
|
|
|
fn is_truthy(&self) -> bool {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Void => false,
|
|
Undefined => false,
|
|
Null => false,
|
|
Bool(b) => *b,
|
|
Number(x) => *x != 0_f64 && !x.is_nan(),
|
|
BigInt(x) => !x.is_zero(),
|
|
Symbol(_) => true,
|
|
String(s) => s.len() > 0,
|
|
Array(_) => true,
|
|
Object(_) => true,
|
|
Function(_) => true,
|
|
Class(_) => true,
|
|
Static(val) => val.is_truthy(), // TODO: true?
|
|
Dynamic(val) => val.is_truthy(),
|
|
CopyCounter(_) => true,
|
|
};
|
|
}
|
|
|
|
fn is_nullish(&self) -> bool {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Void => panic!("Shouldn't happen"), // TODO: Or just true?
|
|
Undefined => true,
|
|
Null => true,
|
|
Bool(_) => false,
|
|
Number(_) => false,
|
|
BigInt(_) => false,
|
|
Symbol(_) => false,
|
|
String(_) => false,
|
|
Array(_) => false,
|
|
Object(_) => false,
|
|
Function(_) => false,
|
|
Class(_) => false,
|
|
Static(_) => false,
|
|
Dynamic(val) => val.is_nullish(),
|
|
CopyCounter(_) => false,
|
|
};
|
|
}
|
|
|
|
fn bind(&self, params: Vec<Val>) -> Option<Val> {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Function(f) => Some(f.bind(params).to_val()),
|
|
Static(val) => val.bind(params),
|
|
Dynamic(val) => val.bind(params),
|
|
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
// TODO: &BigInt ?
|
|
fn as_bigint_data(&self) -> Option<BigInt> {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
BigInt(b) => Some(b.clone()),
|
|
// TODO: Static? Others too?
|
|
Dynamic(val) => val.as_bigint_data(),
|
|
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
fn as_array_data(&self) -> Option<Rc<VsArray>> {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Array(a) => Some(a.clone()),
|
|
Dynamic(val) => val.as_array_data(),
|
|
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
fn as_class_data(&self) -> Option<Rc<VsClass>> {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Class(class) => Some(class.clone()),
|
|
Static(s) => s.as_class_data(),
|
|
Dynamic(val) => val.as_class_data(),
|
|
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
fn load_function(&self) -> LoadFunctionResult {
|
|
use Val::*;
|
|
|
|
return match self {
|
|
Function(f) => LoadFunctionResult::StackFrame(f.make_frame()),
|
|
Static(s) => s.load_function(),
|
|
Dynamic(val) => val.load_function(),
|
|
|
|
_ => LoadFunctionResult::NotAFunction,
|
|
};
|
|
}
|
|
|
|
fn sub(&self, key: &Val) -> Result<Val, Val> {
|
|
// TODO: mut version?
|
|
op_sub(&mut self.clone(), key)
|
|
}
|
|
|
|
fn submov(&mut self, key: &Val, value: Val) -> Result<(), Val> {
|
|
op_submov(self, key, value)
|
|
}
|
|
|
|
fn pretty_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
std::fmt::Display::fmt(&self.pretty(), f)
|
|
}
|
|
|
|
fn codify(&self) -> String {
|
|
match self {
|
|
Val::Void => "".into(),
|
|
Val::Undefined => "undefined".into(),
|
|
Val::Null => "null".into(),
|
|
Val::Bool(_) => self.to_string(),
|
|
Val::Number(_) => self.to_string(),
|
|
Val::BigInt(_) => self.to_string() + "n",
|
|
Val::Symbol(s) => format!("Symbol.{}", symbol_to_name(s.clone())),
|
|
Val::String(str) => stringify_string(str),
|
|
Val::Array(vals) => {
|
|
if vals.elements.len() == 0 {
|
|
"[]".to_string()
|
|
} else if vals.elements.len() == 1 {
|
|
"[".to_string() + vals.elements[0].codify().as_str() + "]"
|
|
} else {
|
|
let mut iter = vals.elements.iter();
|
|
let mut res: String = "[".into();
|
|
res += iter.next().unwrap().codify().as_str();
|
|
|
|
for val in iter {
|
|
res += ",";
|
|
res += &val.codify();
|
|
}
|
|
|
|
res += "]";
|
|
|
|
res
|
|
}
|
|
}
|
|
Val::Object(object) => {
|
|
let mut res = String::new();
|
|
|
|
if let Some(proto) = &object.prototype {
|
|
match proto.sub(&"name".to_val()) {
|
|
Ok(name) => {
|
|
if name.typeof_() == VsType::String {
|
|
res += &name.to_string();
|
|
}
|
|
}
|
|
Err(_) => {}
|
|
}
|
|
}
|
|
|
|
if object.string_map.len() == 0 {
|
|
res += "{}";
|
|
return res;
|
|
}
|
|
|
|
res += "{";
|
|
|
|
let mut first = true;
|
|
|
|
for (k, v) in &object.string_map {
|
|
if first {
|
|
first = false;
|
|
} else {
|
|
res += ",";
|
|
}
|
|
|
|
res += stringify_string(k).as_str();
|
|
res += ":";
|
|
res += v.codify().as_str();
|
|
}
|
|
|
|
res += "}";
|
|
|
|
res
|
|
}
|
|
Val::Function(_) => "() => { [unavailable] }".to_string(),
|
|
Val::Class(_) => "class { [unavailable] }".to_string(),
|
|
Val::Static(val) => val.codify(),
|
|
Val::Dynamic(val) => val.codify(),
|
|
Val::CopyCounter(cc) => format!(
|
|
"CopyCounter {{ tag: {}, count: {} }}",
|
|
cc.tag.codify(),
|
|
cc.count.borrow()
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Val {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
use Val::*;
|
|
|
|
match self {
|
|
Void => Ok(()),
|
|
Undefined => write!(f, "undefined"),
|
|
Null => write!(f, "null"),
|
|
Bool(b) => b.fmt(f),
|
|
Number(x) => {
|
|
if x.is_infinite() {
|
|
if x.is_sign_positive() {
|
|
write!(f, "Infinity")
|
|
} else {
|
|
write!(f, "-Infinity")
|
|
}
|
|
} else {
|
|
x.fmt(f)
|
|
}
|
|
} // TODO: Match js's number string format
|
|
BigInt(x) => x.fmt(f),
|
|
Symbol(s) => write!(f, "Symbol(Symbol.{})", symbol_to_name(s.clone())),
|
|
String(s) => s.fmt(f),
|
|
Array(vals) => {
|
|
if vals.elements.len() == 0 {
|
|
Ok(())
|
|
} else if vals.elements.len() == 1 {
|
|
vals.elements[0].fmt(f)
|
|
} else {
|
|
let mut iter = vals.elements.iter();
|
|
iter.next().unwrap().fmt(f)?;
|
|
|
|
for val in iter {
|
|
write!(f, ",")?;
|
|
|
|
match val.typeof_() {
|
|
VsType::Undefined => {}
|
|
_ => {
|
|
val.fmt(f)?;
|
|
}
|
|
};
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
Object(_) => write!(f, "[object Object]"),
|
|
Function(_) => write!(f, "[function]"),
|
|
Class(_) => write!(f, "[class]"),
|
|
Static(val) => val.fmt(f),
|
|
Dynamic(val) => val.fmt(f),
|
|
CopyCounter(cc) => write!(
|
|
f,
|
|
"CopyCounter {{ tag: {}, count: {} }}",
|
|
cc.tag,
|
|
(*cc.count.borrow() as f64).to_val()
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait ToVal {
|
|
fn to_val(self) -> Val;
|
|
}
|
|
|
|
impl<T> From<T> for Val
|
|
where
|
|
T: ToVal,
|
|
{
|
|
fn from(value: T) -> Val {
|
|
value.to_val()
|
|
}
|
|
}
|
|
|
|
impl ToVal for char {
|
|
fn to_val(self) -> Val {
|
|
self.to_string().to_val()
|
|
}
|
|
}
|
|
|
|
impl ToVal for &str {
|
|
fn to_val(self) -> Val {
|
|
Val::String(Rc::new(self.to_string()))
|
|
}
|
|
}
|
|
|
|
impl ToVal for String {
|
|
fn to_val(self) -> Val {
|
|
Val::String(Rc::new(self))
|
|
}
|
|
}
|
|
|
|
impl ToVal for f64 {
|
|
fn to_val(self) -> Val {
|
|
Val::Number(self)
|
|
}
|
|
}
|
|
|
|
impl ToVal for bool {
|
|
fn to_val(self) -> Val {
|
|
Val::Bool(self)
|
|
}
|
|
}
|
|
|
|
impl ToVal for BigInt {
|
|
fn to_val(self) -> Val {
|
|
Val::BigInt(self)
|
|
}
|
|
}
|
|
|
|
impl ToVal for Vec<Val> {
|
|
fn to_val(self) -> Val {
|
|
Val::Array(Rc::new(VsArray::from(self)))
|
|
}
|
|
}
|
|
|
|
impl<T> ToVal for &'static T
|
|
where
|
|
T: ValTrait,
|
|
{
|
|
fn to_val(self) -> Val {
|
|
Val::Static(self)
|
|
}
|
|
}
|
|
|
|
pub trait ToDynamicVal {
|
|
fn to_dynamic_val(self) -> Val;
|
|
}
|
|
|
|
impl<T> ToDynamicVal for T
|
|
where
|
|
T: DynValTrait + 'static,
|
|
{
|
|
fn to_dynamic_val(self) -> Val {
|
|
Val::Dynamic(Rc::new(self))
|
|
}
|
|
}
|
|
|
|
pub struct PrettyVal<'a> {
|
|
val: &'a Val,
|
|
}
|
|
|
|
impl<'a> Val {
|
|
pub fn pretty(&'a self) -> PrettyVal<'a> {
|
|
PrettyVal { val: self }
|
|
}
|
|
}
|
|
|
|
impl<'a> std::fmt::Display for PrettyVal<'a> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self.val {
|
|
Val::Void => write!(f, "void"),
|
|
Val::Undefined => write!(f, "\x1b[90mundefined\x1b[39m"),
|
|
Val::Null => write!(f, "\x1b[1mnull\x1b[22m"),
|
|
Val::Bool(_) => write!(f, "\x1b[33m{}\x1b[39m", self.val),
|
|
Val::Number(_) => write!(f, "\x1b[33m{}\x1b[39m", self.val),
|
|
Val::BigInt(_) => write!(f, "\x1b[33m{}n\x1b[39m", self.val),
|
|
Val::Symbol(_) => write!(f, "\x1b[32m{}\x1b[39m", self.val.codify()),
|
|
Val::String(_) => write!(f, "\x1b[32m{}\x1b[39m", self.val.codify()),
|
|
Val::Array(array) => {
|
|
if array.elements.len() == 0 {
|
|
return write!(f, "[]");
|
|
}
|
|
|
|
write!(f, "[ ")?;
|
|
|
|
let mut first = true;
|
|
|
|
for elem in &array.elements {
|
|
if first {
|
|
first = false;
|
|
} else {
|
|
write!(f, ", ")?;
|
|
}
|
|
|
|
write!(f, "{}", elem.pretty())?;
|
|
}
|
|
|
|
write!(f, " ]")
|
|
}
|
|
Val::Object(object) => {
|
|
if let Some(proto) = &object.prototype {
|
|
match proto.sub(&"name".to_val()) {
|
|
Ok(name) => {
|
|
if name.typeof_() == VsType::String {
|
|
write!(f, "{} ", name)?;
|
|
}
|
|
}
|
|
Err(_) => {}
|
|
}
|
|
}
|
|
|
|
if object.string_map.len() == 0 {
|
|
return f.write_str("{}");
|
|
}
|
|
|
|
match f.write_str("{ ") {
|
|
Ok(_) => {}
|
|
Err(e) => {
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
let mut first = true;
|
|
|
|
for (k, v) in &object.string_map {
|
|
if first {
|
|
first = false;
|
|
} else {
|
|
write!(f, ", ")?;
|
|
}
|
|
|
|
write!(f, "{}: {}", k, v.pretty())?;
|
|
}
|
|
|
|
f.write_str(" }")
|
|
}
|
|
Val::Function(_) => write!(f, "\x1b[36m[Function]\x1b[39m"),
|
|
Val::Class(_) => write!(f, "\x1b[36m[Class]\x1b[39m"),
|
|
|
|
// TODO: Improve printing these
|
|
Val::Static(s) => s.pretty_fmt(f),
|
|
Val::Dynamic(c) => c.pretty_fmt(f),
|
|
|
|
Val::CopyCounter(cc) => write!(
|
|
f,
|
|
"\x1b[36mCopyCounter\x1b[39m {{ tag: {}, count: {} }}",
|
|
cc.tag.pretty(),
|
|
(*cc.count.borrow() as f64).to_val().pretty()
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn number_to_index(x: f64) -> Option<usize> {
|
|
if x < 0_f64 || x != x.floor() {
|
|
return None;
|
|
}
|
|
|
|
return Some(x as usize);
|
|
}
|
|
|
|
fn stringify_string(str: &String) -> String {
|
|
let mut res: String = "\"".into();
|
|
|
|
for c in str.chars() {
|
|
let escape_seq = match c {
|
|
'\r' => Some("\\r"),
|
|
'\n' => Some("\\n"),
|
|
'\t' => Some("\\t"),
|
|
'"' => Some("\\\""),
|
|
_ => None,
|
|
};
|
|
|
|
match escape_seq {
|
|
Some(seq) => {
|
|
res += seq;
|
|
}
|
|
None => {
|
|
res.push(c);
|
|
}
|
|
};
|
|
}
|
|
|
|
res += "\"";
|
|
|
|
res
|
|
}
|