Browse Source

Feature arrays with empty elements (#1870)

This PR adds support for arrays with empty elements e.g. `["", false, , , ]`. Before we were filling the empty places with `undefined`, but this is wrong according to [spec](https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation) there shouldn't be undefined with a index at that place, instead only `length` is incremented. So `[,,,].length == 3` and operations like `[,,,,].indexOf(undefined) == -1`,  `[,,,,].lastIndexOf(undefined) == -1` etc.
pull/1866/head
Halid Odat 3 years ago
parent
commit
ada4ca895f
  1. 5
      boa_engine/src/bytecompiler.rs
  2. 4
      boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs
  3. 10
      boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs
  4. 22
      boa_engine/src/value/display.rs
  5. 1
      boa_engine/src/vm/code_block.rs
  6. 11
      boa_engine/src/vm/mod.rs
  7. 8
      boa_engine/src/vm/opcode.rs

5
boa_engine/src/bytecompiler.rs

@ -933,6 +933,11 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::PopOnReturnAdd); self.emit_opcode(Opcode::PopOnReturnAdd);
for element in array.as_ref() { for element in array.as_ref() {
if let Node::Empty = element {
self.emit_opcode(Opcode::PushElisionToArray);
continue;
}
self.compile_expr(element, true)?; self.compile_expr(element, true)?;
if let Node::Spread(_) = element { if let Node::Spread(_) = element {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::InitIterator);

4
boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs

@ -13,7 +13,7 @@ mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{ ast::{
node::{ArrayDecl, Node, Spread}, node::{ArrayDecl, Node, Spread},
Const, Punctuator, Punctuator,
}, },
parser::{ parser::{
expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser,
@ -68,7 +68,7 @@ where
loop { loop {
// TODO: Support all features. // TODO: Support all features.
while cursor.next_if(Punctuator::Comma, interner)?.is_some() { while cursor.next_if(Punctuator::Comma, interner)?.is_some() {
elements.push(Node::Const(Const::Undefined)); elements.push(Node::Empty);
} }
if cursor if cursor

10
boa_engine/src/syntax/parser/expression/primary/array_initializer/tests.rs

@ -1,7 +1,7 @@
// ! Tests for array initializer parsing. // ! Tests for array initializer parsing.
use crate::syntax::{ use crate::syntax::{
ast::{node::ArrayDecl, Const}, ast::{node::ArrayDecl, Const, Node},
parser::tests::check_parser, parser::tests::check_parser,
}; };
use boa_interner::{Interner, Sym}; use boa_interner::{Interner, Sym};
@ -19,7 +19,7 @@ fn check_empty_slot() {
let mut interner = Interner::default(); let mut interner = Interner::default();
check_parser( check_parser(
"[,]", "[,]",
vec![ArrayDecl::from(vec![Const::Undefined.into()]).into()], vec![ArrayDecl::from(vec![Node::Empty]).into()],
&mut interner, &mut interner,
); );
} }
@ -65,7 +65,7 @@ fn check_numeric_array_elision() {
vec![ArrayDecl::from(vec![ vec![ArrayDecl::from(vec![
Const::from(1).into(), Const::from(1).into(),
Const::from(2).into(), Const::from(2).into(),
Const::Undefined.into(), Node::Empty,
Const::from(3).into(), Const::from(3).into(),
]) ])
.into()], .into()],
@ -82,8 +82,8 @@ fn check_numeric_array_repeated_elision() {
vec![ArrayDecl::from(vec![ vec![ArrayDecl::from(vec![
Const::from(1).into(), Const::from(1).into(),
Const::from(2).into(), Const::from(2).into(),
Const::Undefined.into(), Node::Empty,
Const::Undefined.into(), Node::Empty,
Const::from(3).into(), Const::from(3).into(),
]) ])
.into()], .into()],

22
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| { .map(|i| {
// Introduce recursive call to stringify any objects // Introduce recursive call to stringify any objects
// which are part of the Array // which are part of the Array
log_string_from(
v.borrow() // FIXME: handle accessor descriptors
.properties() if let Some(value) = v
.get(&i.into()) .borrow()
// FIXME: handle accessor descriptors .properties()
.and_then(PropertyDescriptor::value) .get(&i.into())
.unwrap_or(&JsValue::Undefined), .and_then(PropertyDescriptor::value)
print_internals, {
false, log_string_from(value, print_internals, false)
) } else {
String::from("<empty>")
}
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");

1
boa_engine/src/vm/code_block.rs

@ -318,6 +318,7 @@ impl CodeBlock {
| Opcode::RestParameterInit | Opcode::RestParameterInit
| Opcode::RestParameterPop | Opcode::RestParameterPop
| Opcode::PushValueToArray | Opcode::PushValueToArray
| Opcode::PushElisionToArray
| Opcode::PushIteratorToArray | Opcode::PushIteratorToArray
| Opcode::PushNewArray | Opcode::PushNewArray
| Opcode::PopOnReturnAdd | Opcode::PopOnReturnAdd

11
boa_engine/src/vm/mod.rs

@ -203,6 +203,17 @@ impl Context {
.expect("should be able to create new data property"); .expect("should be able to create new data property");
self.vm.push(array); 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 => { Opcode::PushIteratorToArray => {
let next_function = self.vm.pop(); let next_function = self.vm.pop();
let iterator = self.vm.pop(); let iterator = self.vm.pop();

8
boa_engine/src/vm/opcode.rs

@ -145,6 +145,13 @@ pub enum Opcode {
/// Stack: array, value **=>** array /// Stack: array, value **=>** array
PushValueToArray, PushValueToArray,
/// Push an empty element/hole to an array.
///
/// Operands:
///
/// Stack: array **=>** array
PushElisionToArray,
/// Push all iterator values to an array. /// Push all iterator values to an array.
/// ///
/// Operands: /// Operands:
@ -937,6 +944,7 @@ impl Opcode {
Opcode::PushEmptyObject => "PushEmptyObject", Opcode::PushEmptyObject => "PushEmptyObject",
Opcode::PushNewArray => "PushNewArray", Opcode::PushNewArray => "PushNewArray",
Opcode::PushValueToArray => "PushValueToArray", Opcode::PushValueToArray => "PushValueToArray",
Opcode::PushElisionToArray => "PushElisionToArray",
Opcode::PushIteratorToArray => "PushIteratorToArray", Opcode::PushIteratorToArray => "PushIteratorToArray",
Opcode::Add => "Add", Opcode::Add => "Add",
Opcode::Sub => "Sub", Opcode::Sub => "Sub",

Loading…
Cancel
Save