From f136b6c73882fbed63bb867a3d737944793a536e Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Sat, 26 Feb 2022 20:04:00 +0100 Subject: [PATCH] Feature arrays with empty elements --- boa_engine/src/bytecompiler.rs | 6 +++++ boa_engine/src/syntax/ast/constant.rs | 11 ++++++++++ .../primary/array_initializer/mod.rs | 2 +- .../primary/array_initializer/tests.rs | 8 +++---- boa_engine/src/value/display.rs | 22 ++++++++++--------- boa_engine/src/vm/code_block.rs | 1 + boa_engine/src/vm/mod.rs | 11 ++++++++++ boa_engine/src/vm/opcode.rs | 8 +++++++ 8 files changed, 54 insertions(+), 15 deletions(-) diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 2aca2a1c292..587609e6412 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler.rs @@ -559,6 +559,7 @@ impl<'b> ByteCompiler<'b> { Const::Bool(false) => self.emit(Opcode::PushFalse, &[]), Const::Null => self.emit(Opcode::PushNull, &[]), Const::Undefined => self.emit(Opcode::PushUndefined, &[]), + Const::Elision => unreachable!(), } if !use_expr { @@ -921,6 +922,11 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::PopOnReturnAdd); for element in array.as_ref() { + if let Node::Const(Const::Elision) = element { + self.emit_opcode(Opcode::PushElisionToArray); + continue; + } + self.compile_expr(element, true)?; if let Node::Spread(_) = element { self.emit_opcode(Opcode::InitIterator); diff --git a/boa_engine/src/syntax/ast/constant.rs b/boa_engine/src/syntax/ast/constant.rs index b1672492e7c..0beb3789810 100644 --- a/boa_engine/src/syntax/ast/constant.rs +++ b/boa_engine/src/syntax/ast/constant.rs @@ -111,6 +111,16 @@ pub enum Const { /// [spec]: https://tc39.es/ecma262/#sec-undefined /// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/undefined Undefined, + + /// This represents an empty element of an array initializer. + /// + /// E.g. `[,]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array-initializer + Elision, } impl From for Const { @@ -155,6 +165,7 @@ impl ToInternedString for Const { Self::Bool(v) => v.to_string(), Self::Null => "null".to_owned(), Self::Undefined => "undefined".to_owned(), + Self::Elision => String::new(), } } } diff --git a/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs index b017f779faa..aed033f9fd7 100644 --- a/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs @@ -68,7 +68,7 @@ where loop { // TODO: Support all features. while cursor.next_if(Punctuator::Comma, interner)?.is_some() { - elements.push(Node::Const(Const::Undefined)); + elements.push(Node::Const(Const::Elision)); } if cursor diff --git a/boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs b/boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs index 554cb6e9011..d400be02bd0 100644 --- a/boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs @@ -19,7 +19,7 @@ fn check_empty_slot() { let mut interner = Interner::default(); check_parser( "[,]", - vec![ArrayDecl::from(vec![Const::Undefined.into()]).into()], + vec![ArrayDecl::from(vec![Const::Elision.into()]).into()], &mut interner, ); } @@ -65,7 +65,7 @@ fn check_numeric_array_elision() { vec![ArrayDecl::from(vec![ Const::from(1).into(), Const::from(2).into(), - Const::Undefined.into(), + Const::Elision.into(), Const::from(3).into(), ]) .into()], @@ -82,8 +82,8 @@ fn check_numeric_array_repeated_elision() { vec![ArrayDecl::from(vec![ Const::from(1).into(), Const::from(2).into(), - Const::Undefined.into(), - Const::Undefined.into(), + Const::Elision.into(), + Const::Elision.into(), Const::from(3).into(), ]) .into()], diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 97f2754bbc9..2e6d8855b74 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -134,16 +134,18 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children .map(|i| { // Introduce recursive call to stringify any objects // which are part of the Array - log_string_from( - v.borrow() - .properties() - .get(&i.into()) - // FIXME: handle accessor descriptors - .and_then(PropertyDescriptor::value) - .unwrap_or(&JsValue::Undefined), - print_internals, - false, - ) + + // FIXME: handle accessor descriptors + if let Some(value) = v + .borrow() + .properties() + .get(&i.into()) + .and_then(PropertyDescriptor::value) + { + log_string_from(value, print_internals, false) + } else { + String::from("") + } }) .collect::>() .join(", "); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index cb45df1c3fd..e42c0ee400f 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -317,6 +317,7 @@ impl CodeBlock { | Opcode::PushNewArray | Opcode::PopOnReturnAdd | Opcode::PopOnReturnSub + | Opcode::PushElisionToArray | Opcode::Nop => String::new(), } } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 98c57ff642a..eb736c4c101 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -184,6 +184,17 @@ impl Context { .expect("should be able to create new data property"); self.vm.push(array); } + Opcode::PushElisionToArray => { + let array = self.vm.pop(); + let o = array.as_object().expect("should always be an object"); + + let len = o + .length_of_array_like(self) + .expect("arrays should always have a 'length' property"); + + o.set("length", len + 1, true, self)?; + self.vm.push(array); + } Opcode::PushIteratorToArray => { let next_function = self.vm.pop(); let iterator = self.vm.pop(); diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index cd96a17aa1a..74cbd898c75 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode.rs @@ -145,6 +145,13 @@ pub enum Opcode { /// Stack: array, value **=>** array PushValueToArray, + /// Push an empty element/hole to an array. + /// + /// Operands: + /// + /// Stack: array **=>** array + PushElisionToArray, + /// Push all iterator values to an array. /// /// Operands: @@ -909,6 +916,7 @@ impl Opcode { Opcode::PushEmptyObject => "PushEmptyObject", Opcode::PushNewArray => "PushNewArray", Opcode::PushValueToArray => "PushValueToArray", + Opcode::PushElisionToArray => "PushElisionToArray", Opcode::PushIteratorToArray => "PushIteratorToArray", Opcode::Add => "Add", Opcode::Sub => "Sub",