diff --git a/inputs/failing/operators/in.ts b/inputs/failing/operators/in.ts new file mode 100644 index 0000000..edc5e31 --- /dev/null +++ b/inputs/failing/operators/in.ts @@ -0,0 +1,18 @@ +//! test_output([true,false,false,true,true,true,false,true]) + +export default function () { + return [ + "foo" in { foo: "bar" }, + "bar" in { foo: "bar" }, + "foo" in ["foo"], + 0 in ["foo"], + "0" in ["foo"], + "foo" in new C(), + "forEach" in [], + "map" in [], + ]; +} + +class C { + foo() {} +} diff --git a/valuescript_vm/src/builtins/builtin_object.rs b/valuescript_vm/src/builtins/builtin_object.rs index 27cda56..f4d7c5e 100644 --- a/valuescript_vm/src/builtins/builtin_object.rs +++ b/valuescript_vm/src/builtins/builtin_object.rs @@ -70,6 +70,13 @@ where Ok(Self::bo_sub(&key.to_string())) } + fn has(&self, key: &Val) -> Option { + match Self::bo_sub(&key.to_string()) { + Val::Undefined => Some(false), + _ => Some(true), + } + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err(format!("Cannot assign to subscript of {} builtin", Self::bo_name()).to_type_error()) } diff --git a/valuescript_vm/src/generator.rs b/valuescript_vm/src/generator.rs index 70716e7..e1e554f 100644 --- a/valuescript_vm/src/generator.rs +++ b/valuescript_vm/src/generator.rs @@ -92,6 +92,18 @@ impl ValTrait for Generator { Ok(Val::Undefined) } + fn has(&self, key: &Val) -> Option { + if key.to_string() == "next" { + return Some(true); + } + + if let Val::Symbol(VsSymbol::ITERATOR) = key { + return Some(true); + } + + Some(false) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of a generator".to_type_error()) } diff --git a/valuescript_vm/src/iteration/array_entries_iterator.rs b/valuescript_vm/src/iteration/array_entries_iterator.rs index afa4951..e0fde88 100644 --- a/valuescript_vm/src/iteration/array_entries_iterator.rs +++ b/valuescript_vm/src/iteration/array_entries_iterator.rs @@ -12,7 +12,9 @@ use crate::{ LoadFunctionResult, ValTrait, }; -use super::{iteration_result::IterationResult, return_this::RETURN_THIS}; +use super::{ + iteration_result::IterationResult, iterator_has::iterator_has, return_this::RETURN_THIS, +}; #[derive(Clone)] pub struct ArrayEntriesIterator { @@ -87,6 +89,10 @@ impl ValTrait for ArrayEntriesIterator { Ok(Val::Undefined) } + fn has(&self, key: &Val) -> Option { + iterator_has(key) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of array iterator".to_type_error()) } diff --git a/valuescript_vm/src/iteration/array_iterator.rs b/valuescript_vm/src/iteration/array_iterator.rs index edac182..62c5aee 100644 --- a/valuescript_vm/src/iteration/array_iterator.rs +++ b/valuescript_vm/src/iteration/array_iterator.rs @@ -12,7 +12,9 @@ use crate::{ LoadFunctionResult, ValTrait, }; -use super::{iteration_result::IterationResult, return_this::RETURN_THIS}; +use super::{ + iteration_result::IterationResult, iterator_has::iterator_has, return_this::RETURN_THIS, +}; #[derive(Clone)] pub struct ArrayIterator { @@ -87,6 +89,10 @@ impl ValTrait for ArrayIterator { Ok(Val::Undefined) } + fn has(&self, key: &Val) -> Option { + iterator_has(key) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of array iterator".to_type_error()) } diff --git a/valuescript_vm/src/iteration/iteration_result.rs b/valuescript_vm/src/iteration/iteration_result.rs index a4e807c..c97d337 100644 --- a/valuescript_vm/src/iteration/iteration_result.rs +++ b/valuescript_vm/src/iteration/iteration_result.rs @@ -69,6 +69,13 @@ impl ValTrait for IterationResult { }) } + fn has(&self, key: &Val) -> Option { + Some(match key.to_string().as_str() { + "value" | "done" => true, + _ => false, + }) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of iteration result".to_type_error()) } diff --git a/valuescript_vm/src/iteration/iterator_has.rs b/valuescript_vm/src/iteration/iterator_has.rs new file mode 100644 index 0000000..e2bbc02 --- /dev/null +++ b/valuescript_vm/src/iteration/iterator_has.rs @@ -0,0 +1,17 @@ +use crate::{vs_value::Val, VsSymbol}; + +pub fn iterator_has(key: &Val) -> Option { + if key.to_string() == "next" { + return Some(true); + } + + if let Val::Symbol(key) = key { + match key { + VsSymbol::ITERATOR => { + return Some(true); + } + } + } + + Some(false) +} diff --git a/valuescript_vm/src/iteration/mod.rs b/valuescript_vm/src/iteration/mod.rs index 4ee9075..ff0fa73 100644 --- a/valuescript_vm/src/iteration/mod.rs +++ b/valuescript_vm/src/iteration/mod.rs @@ -1,5 +1,6 @@ pub mod array_entries_iterator; pub mod array_iterator; pub mod iteration_result; +mod iterator_has; pub mod return_this; pub mod string_iterator; diff --git a/valuescript_vm/src/iteration/string_iterator.rs b/valuescript_vm/src/iteration/string_iterator.rs index 7843767..8c347b1 100644 --- a/valuescript_vm/src/iteration/string_iterator.rs +++ b/valuescript_vm/src/iteration/string_iterator.rs @@ -12,7 +12,9 @@ use crate::{ LoadFunctionResult, ValTrait, }; -use super::{iteration_result::IterationResult, return_this::RETURN_THIS}; +use super::{ + iteration_result::IterationResult, iterator_has::iterator_has, return_this::RETURN_THIS, +}; #[derive(Clone)] pub struct StringIterator { @@ -123,6 +125,10 @@ impl ValTrait for StringIterator { Ok(Val::Undefined) } + fn has(&self, key: &Val) -> Option { + iterator_has(key) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of string iterator".to_type_error()) } diff --git a/valuescript_vm/src/native_frame_function.rs b/valuescript_vm/src/native_frame_function.rs index 9e82df5..e2d180e 100644 --- a/valuescript_vm/src/native_frame_function.rs +++ b/valuescript_vm/src/native_frame_function.rs @@ -63,6 +63,10 @@ impl ValTrait for NativeFrameFunction { Err("TODO: Subscript native function".to_internal_error()) } + fn has(&self, _key: &Val) -> Option { + Some(false) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of native function".to_type_error()) } diff --git a/valuescript_vm/src/native_function.rs b/valuescript_vm/src/native_function.rs index 96b3808..2684804 100644 --- a/valuescript_vm/src/native_function.rs +++ b/valuescript_vm/src/native_function.rs @@ -84,6 +84,10 @@ impl ValTrait for NativeFunction { Err("TODO: Subscript native function".to_internal_error()) } + fn has(&self, _key: &Val) -> Option { + Some(false) + } + fn submov(&mut self, _key: &Val, _value: Val) -> Result<(), Val> { Err("Cannot assign to subscript of native function".to_type_error()) } diff --git a/valuescript_vm/src/operations.rs b/valuescript_vm/src/operations.rs index c5e3ffb..01af1f6 100644 --- a/valuescript_vm/src/operations.rs +++ b/valuescript_vm/src/operations.rs @@ -1,5 +1,4 @@ use std::collections::BTreeMap; -use std::mem::take; use std::rc::Rc; use std::str::FromStr; @@ -523,8 +522,11 @@ pub fn op_instance_of(_left: &Val, _right: &Val) -> Result { Err("TODO: op_instance_of".to_internal_error()) } -pub fn op_in(_left: &Val, _right: &Val) -> Result { - Err("TODO: op_in".to_internal_error()) +pub fn op_in(left: &Val, right: &Val) -> Result { + match right.has(left) { + Some(found) => Ok(found.to_val()), + None => Err(format!("Can't use `in` with a {}", right.typeof_()).to_type_error()), + } } pub fn op_sub(left: &mut Val, right: &Val) -> Result { @@ -541,34 +543,7 @@ pub fn op_sub(left: &mut Val, right: &Val) -> Result { Val::BigInt(bigint) => Ok(op_sub_bigint(bigint, right)), Val::Symbol(_) => Ok(Val::Undefined), Val::String(string_data) => Ok(op_sub_string(string_data, right)), - Val::Array(array_data) => { - let right_index = match right.to_index() { - None => { - // FIXME: Inefficient to_string() that gets duplicated - // when subscripting the object - if right.to_string() == "length" { - return Ok(Val::Number(array_data.elements.len() as f64)); - } - - return op_sub_array(array_data, right); - } - Some(i) => i, - }; - - if right_index >= array_data.elements.len() { - return Ok(Val::Undefined); - } - - let res = match Rc::get_mut(array_data) { - Some(array_data) => take(&mut array_data.elements[right_index]), - None => array_data.elements[right_index].clone(), - }; - - return Ok(match res { - Val::Void => Val::Undefined, - _ => res, - }); - } + Val::Array(array_data) => op_sub_array(array_data, right), Val::Object(object_data) => Ok(object_data.sub(right)), // TODO: move on single ref Val::Function(_) => Ok(Val::Undefined), Val::Class(class) => op_sub(&mut class.static_.clone(), right), diff --git a/valuescript_vm/src/vs_value.rs b/valuescript_vm/src/vs_value.rs index 0dfea73..65d9be1 100644 --- a/valuescript_vm/src/vs_value.rs +++ b/valuescript_vm/src/vs_value.rs @@ -1,5 +1,6 @@ use core::fmt; use std::any::Any; +use std::fmt::Display; use std::rc::Rc; use std::str::FromStr; @@ -57,6 +58,24 @@ pub enum VsType { Class, } +impl Display for VsType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + VsType::Undefined => f.write_str("undefined"), + VsType::Null => f.write_str("null"), + VsType::Bool => f.write_str("bool"), + VsType::Number => f.write_str("number"), + VsType::BigInt => f.write_str("bigint"), + VsType::Symbol => f.write_str("symbol"), + VsType::String => f.write_str("string"), + VsType::Array => f.write_str("array"), + VsType::Object => f.write_str("object"), + VsType::Function => f.write_str("function"), + VsType::Class => f.write_str("class"), + } + } +} + pub enum LoadFunctionResult { NotAFunction, StackFrame(StackFrame), @@ -80,6 +99,7 @@ pub trait ValTrait: fmt::Display { fn load_function(&self) -> LoadFunctionResult; fn sub(&self, key: &Val) -> Result; + fn has(&self, key: &Val) -> Option; fn submov(&mut self, key: &Val, value: Val) -> Result<(), Val>; fn pretty_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; @@ -355,6 +375,97 @@ impl ValTrait for Val { op_sub(&mut self.clone(), key) } + fn has(&self, key: &Val) -> Option { + match self { + Val::Void + | Val::Undefined + | Val::Null + | Val::Bool(_) + | Val::Number(_) + | Val::BigInt(_) + | Val::Symbol(_) + | Val::String(_) => None, + + Val::Array(array) => { + let index = match key.to_index() { + None => { + return Some(match key.to_string().as_str() { + "at" => true, + "concat" => true, + "copyWithin" => true, + "entries" => true, + "every" => true, + "fill" => true, + "filter" => true, + "find" => true, + "findIndex" => true, + "flat" => true, + "flatMap" => true, + "includes" => true, + "indexOf" => true, + "join" => true, + "keys" => true, + "lastIndexOf" => true, + "length" => true, + "map" => true, + "pop" => true, + "push" => true, + "reduce" => true, + "reduceRight" => true, + "reverse" => true, + "shift" => true, + "slice" => true, + "some" => true, + "sort" => true, + "splice" => true, + "toLocaleString" => true, + "toString" => true, + "unshift" => true, + "values" => true, + + _ => false, + }); + } + Some(i) => i, + }; + + return Some(index < array.elements.len()); + } + Val::Object(object) => match key { + Val::Symbol(symbol) => { + if object.symbol_map.contains_key(symbol) { + return Some(true); + } + + if let Some(proto) = &object.prototype { + return proto.has(key); + } + + return Some(false); + } + _ => { + if object.string_map.contains_key(&key.to_string()) { + return Some(true); + } + + if let Some(proto) = &object.prototype { + return proto.has(key); + } + + return Some(false); + } + }, + Val::Function(_) => Some(false), + Val::Class(class) => class.static_.has(key), + Val::Static(static_) => static_.has(key), + Val::Dynamic(dynamic) => dynamic.has(key), + Val::CopyCounter(_) => Some(match key.to_string().as_str() { + "tag" | "count" => true, + _ => false, + }), + } + } + fn submov(&mut self, key: &Val, value: Val) -> Result<(), Val> { op_submov(self, key, value) }