From d638746637bd47bdbd378f27f8e8100d361837b8 Mon Sep 17 00:00:00 2001 From: Iban Eguia Moraza Date: Sun, 10 May 2020 18:03:24 +0200 Subject: [PATCH 1/2] The interpreter is now modular. --- .vscode/launch.json | 23 +- Cargo.lock | 18 +- boa/Cargo.toml | 4 +- boa/src/builtins/array/mod.rs | 16 +- boa/src/builtins/array/tests.rs | 50 +- boa/src/builtins/boolean/tests.rs | 10 +- boa/src/builtins/function/mod.rs | 7 +- boa/src/builtins/json/tests.rs | 4 +- boa/src/builtins/math/tests.rs | 56 +- boa/src/builtins/number/tests.rs | 18 +- boa/src/builtins/regexp/tests.rs | 14 +- boa/src/builtins/string/tests.rs | 24 +- boa/src/builtins/symbol/tests.rs | 8 +- boa/src/exec/array.rs | 29 + boa/src/exec/arrow_function.rs | 42 + boa/src/exec/block.rs | 36 + boa/src/exec/mod.rs | 935 ++++++++---------- boa/src/exec/operator.rs | 160 +++ boa/src/exec/tests.rs | 14 +- boa/src/lib.rs | 8 +- boa/src/syntax/ast/node/array.rs | 70 ++ boa/src/syntax/ast/node/arrow_function.rs | 62 ++ boa/src/syntax/ast/node/block.rs | 86 ++ boa/src/syntax/ast/{node.rs => node/mod.rs} | 216 +--- boa/src/syntax/ast/node/operator.rs | 118 +++ boa/src/syntax/ast/op.rs | 44 +- .../expression/assignment/arrow_function.rs | 8 +- .../expression/assignment/exponentiation.rs | 10 +- .../parser/expression/assignment/mod.rs | 16 +- .../expression/left_hand_side/arguments.rs | 2 +- boa/src/syntax/parser/expression/mod.rs | 15 +- .../primary/array_initializer/mod.rs | 15 +- .../primary/array_initializer/tests.rs | 39 +- .../syntax/parser/expression/primary/mod.rs | 4 +- boa/src/syntax/parser/expression/tests.rs | 250 +++-- boa/src/syntax/parser/function/tests.rs | 40 +- boa/src/syntax/parser/statement/block.rs | 29 +- .../parser/statement/break_stm/tests.rs | 17 +- .../parser/statement/continue_stm/tests.rs | 17 +- .../parser/statement/declaration/lexical.rs | 2 +- .../statement/iteration/for_statement.rs | 12 +- .../parser/statement/iteration/tests.rs | 30 +- boa/src/syntax/parser/statement/mod.rs | 3 +- .../syntax/parser/statement/try_stm/catch.rs | 10 +- .../parser/statement/try_stm/finally.rs | 8 +- .../syntax/parser/statement/try_stm/mod.rs | 17 +- .../syntax/parser/statement/try_stm/tests.rs | 104 +- boa/src/syntax/parser/tests.rs | 13 +- boa_cli/Cargo.toml | 2 +- boa_cli/src/main.rs | 4 +- boa_wasm/Cargo.toml | 2 +- boa_wasm/src/lib.rs | 6 +- 52 files changed, 1602 insertions(+), 1145 deletions(-) create mode 100644 boa/src/exec/array.rs create mode 100644 boa/src/exec/arrow_function.rs create mode 100644 boa/src/exec/block.rs create mode 100644 boa/src/exec/operator.rs create mode 100644 boa/src/syntax/ast/node/array.rs create mode 100644 boa/src/syntax/ast/node/arrow_function.rs create mode 100644 boa/src/syntax/ast/node/block.rs rename boa/src/syntax/ast/{node.rs => node/mod.rs} (87%) create mode 100644 boa/src/syntax/ast/node/operator.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index a86cf58f86b..c1f754200dc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,7 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { "type": "lldb", "request": "launch", @@ -37,16 +36,16 @@ "symbolSearchPath": "https://msdl.microsoft.com/download/symbols" }, { - "name": "(Windows) Run Test Debugger", - "type": "cppvsdbg", - "request": "launch", - "program": "${workspaceFolder}/target/debug/boa-ea5ed1ef3ee0cbe1.exe", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": true, - "preLaunchTask": "Cargo Test Build", -} + "name": "(Windows) Run Test Debugger", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/boa-ea5ed1ef3ee0cbe1.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "preLaunchTask": "Cargo Test Build", + } ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b205cea5d19..dd9a1dda145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ [[package]] name = "boa_wasm" -version = "0.1.0" +version = "0.7.0" dependencies = [ "Boa", "wasm-bindgen", @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" dependencies = [ "lazy_static", "memchr", @@ -619,18 +619,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2", "quote", @@ -639,9 +639,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.52" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "itoa", "ryu", diff --git a/boa/Cargo.toml b/boa/Cargo.toml index dfd749beb7f..4ac6b80b228 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -12,14 +12,14 @@ edition = "2018" [dependencies] gc = { version = "0.3.4", features = ["derive"] } -serde_json = "1.0.52" +serde_json = "1.0.53" rand = "0.7.3" num-traits = "0.2.11" regex = "1.3.7" rustc-hash = "1.1.0" # Optional Dependencies -serde = { version = "1.0.106", features = ["derive"], optional = true } +serde = { version = "1.0.110", features = ["derive"], optional = true } [dev-dependencies] criterion = "0.3.2" diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 110e7547d26..ad22998abce 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -20,15 +20,17 @@ use crate::{ }, exec::Interpreter, }; -use std::borrow::Borrow; -use std::cmp::{max, min}; -use std::ops::Deref; +use std::{ + borrow::Borrow, + cmp::{max, min}, + ops::Deref, +}; /// Creates a new `Array` instance. pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { let array = Value::new_object(Some( &interpreter - .get_realm() + .realm() .environment .get_global_object() .expect("Could not get global object"), @@ -37,7 +39,7 @@ pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { array.borrow().set_internal_slot( INSTANCE_PROTOTYPE, interpreter - .get_realm() + .realm() .environment .get_binding_value("Array") .borrow() @@ -541,7 +543,7 @@ pub fn map(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> R let length = i32::from(&this.get_field_slice("length")); - let new = new_array(&interpreter)?; + let new = new_array(interpreter)?; let values: Vec = (0..length) .map(|idx| { @@ -879,7 +881,7 @@ pub fn filter(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) - let length = i32::from(&this.get_field_slice("length")); - let new = new_array(&interpreter)?; + let new = new_array(interpreter)?; let values = (0..length) .filter_map(|idx| { diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index 3319aa94fa2..3794361817f 100644 --- a/boa/src/builtins/array/tests.rs +++ b/boa/src/builtins/array/tests.rs @@ -1,11 +1,9 @@ -use crate::exec::Executor; -use crate::forward; -use crate::realm::Realm; +use crate::{exec::Interpreter, forward, realm::Realm}; #[test] fn is_array() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = []; var new_arr = new Array(); @@ -46,7 +44,7 @@ fn is_array() { fn concat() { //TODO: array display formatter // let realm = Realm::create(); - // let mut engine = Executor::new(realm); + // let mut engine = Interpreter::new(realm); // let init = r#" // var empty = new Array(); // var one = new Array(1); @@ -69,7 +67,7 @@ fn concat() { #[test] fn join() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = ["a"]; @@ -90,7 +88,7 @@ fn join() { #[test] fn to_string() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = ["a"]; @@ -111,7 +109,7 @@ fn to_string() { #[test] fn every() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every let init = r#" var empty = []; @@ -156,7 +154,7 @@ fn every() { #[test] fn find() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" function comp(a) { return a == "a"; @@ -171,7 +169,7 @@ fn find() { #[test] fn find_index() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let code = r#" function comp(item) { @@ -197,7 +195,7 @@ fn find_index() { #[test] fn push() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var arr = [1, 2]; "#; @@ -212,7 +210,7 @@ fn push() { #[test] fn pop() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = [1]; @@ -234,7 +232,7 @@ fn pop() { #[test] fn shift() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = [1]; @@ -256,7 +254,7 @@ fn shift() { #[test] fn unshift() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var arr = [3, 4]; "#; @@ -271,7 +269,7 @@ fn unshift() { #[test] fn reverse() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var arr = [1, 2]; var reversed = arr.reverse(); @@ -286,7 +284,7 @@ fn reverse() { #[test] fn index_of() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = ["a"]; @@ -350,7 +348,7 @@ fn index_of() { #[test] fn last_index_of() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = ["a"]; @@ -414,7 +412,7 @@ fn last_index_of() { #[test] fn fill() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); forward(&mut engine, "var a = [1, 2, 3];"); assert_eq!( @@ -511,7 +509,7 @@ fn fill() { #[test] fn includes_value() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ]; var one = ["a"]; @@ -550,7 +548,7 @@ fn includes_value() { #[test] fn map() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let js = r#" var empty = []; @@ -611,7 +609,7 @@ fn map() { #[test] fn slice() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = [ ].slice(); var one = ["a"].slice(); @@ -635,7 +633,7 @@ fn slice() { #[test] fn for_each() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = [2, 3, 4, 5]; var sum = 0; @@ -658,7 +656,7 @@ fn for_each() { #[test] fn for_each_push_value() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = [1, 2, 3, 4]; function callingCallback(item, index, list) { @@ -679,7 +677,7 @@ fn for_each_push_value() { #[test] fn filter() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let js = r#" var empty = []; @@ -746,7 +744,7 @@ fn filter() { #[test] fn some() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = []; @@ -795,7 +793,7 @@ fn some() { #[test] fn call_array_constructor_with_one_argument() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = new Array(0); diff --git a/boa/src/builtins/boolean/tests.rs b/boa/src/builtins/boolean/tests.rs index 69c3599a5b2..9041ddf40c9 100644 --- a/boa/src/builtins/boolean/tests.rs +++ b/boa/src/builtins/boolean/tests.rs @@ -1,7 +1,5 @@ use super::*; -use crate::exec::Executor; -use crate::realm::Realm; -use crate::{builtins::value::same_value, forward, forward_val}; +use crate::{builtins::value::same_value, exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_boolean_constructor_is_function() { @@ -15,7 +13,7 @@ fn check_boolean_constructor_is_function() { #[test] fn construct_and_call() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var one = new Boolean(1); var zero = Boolean(0); @@ -31,7 +29,7 @@ fn construct_and_call() { #[test] fn constructor_gives_true_instance() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var trueVal = new Boolean(true); var trueNum = new Boolean(1); @@ -61,7 +59,7 @@ fn constructor_gives_true_instance() { #[test] fn instances_have_correct_proto_set() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var boolInstance = new Boolean(true); var boolProto = Boolean.prototype; diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index e0c7a346bc4..3b29d58fd6c 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -19,9 +19,8 @@ use crate::{ value::{ResultValue, Value}, }, environment::lexical_environment::{new_function_environment, Environment}, - exec::Executor, + exec::Interpreter, syntax::ast::node::{FormalParameter, Node}, - Interpreter, }; use gc::{unsafe_empty_trace, Finalize, Trace}; use std::fmt::{self, Debug}; @@ -192,7 +191,7 @@ impl Function { // Call body should be set before reaching here let result = match &self.body { - FunctionBody::Ordinary(ref body) => interpreter.run(body), + FunctionBody::Ordinary(ref body) => interpreter.exec(body), _ => panic!("Ordinary function should not have BuiltIn Function body"), }; @@ -251,7 +250,7 @@ impl Function { // Call body should be set before reaching here let result = match &self.body { - FunctionBody::Ordinary(ref body) => interpreter.run(body), + FunctionBody::Ordinary(ref body) => interpreter.exec(body), _ => panic!("Ordinary function should not have BuiltIn Function body"), }; diff --git a/boa/src/builtins/json/tests.rs b/boa/src/builtins/json/tests.rs index 5143da8cc80..ba8535884ee 100644 --- a/boa/src/builtins/json/tests.rs +++ b/boa/src/builtins/json/tests.rs @@ -1,9 +1,9 @@ -use crate::{exec::Executor, forward, realm::Realm}; +use crate::{exec::Interpreter, forward, realm::Realm}; #[test] fn json_sanity() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); assert_eq!( forward(&mut engine, r#"JSON.parse('{"aaa":"bbb"}').aaa == 'bbb'"#), "true" diff --git a/boa/src/builtins/math/tests.rs b/boa/src/builtins/math/tests.rs index 45ad0b13b13..a733946d690 100644 --- a/boa/src/builtins/math/tests.rs +++ b/boa/src/builtins/math/tests.rs @@ -1,12 +1,12 @@ #![allow(clippy::float_cmp)] -use crate::{exec::Executor, forward, forward_val, realm::Realm}; +use crate::{exec::Interpreter, forward, forward_val, realm::Realm}; use std::f64; #[test] fn abs() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.abs(3 - 5); var b = Math.abs(1.23456 - 7.89012); @@ -24,7 +24,7 @@ fn abs() { #[test] fn acos() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.acos(8 / 10); var b = Math.acos(5 / 3); @@ -48,7 +48,7 @@ fn acos() { #[test] fn acosh() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.acosh(2); var b = Math.acosh(-1); @@ -69,7 +69,7 @@ fn acosh() { #[test] fn asin() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.asin(6 / 10); var b = Math.asin(5 / 3); @@ -87,7 +87,7 @@ fn asin() { #[test] fn asinh() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.asinh(1); var b = Math.asinh(0); @@ -105,7 +105,7 @@ fn asinh() { #[test] fn atan() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.atan(1); var b = Math.atan(0); @@ -126,7 +126,7 @@ fn atan() { #[test] fn atan2() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.atan2(90, 15); var b = Math.atan2(15, 90); @@ -144,7 +144,7 @@ fn atan2() { #[test] fn cbrt() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.cbrt(64); var b = Math.cbrt(-1); @@ -165,7 +165,7 @@ fn cbrt() { #[test] fn ceil() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.ceil(1.95); var b = Math.ceil(4); @@ -186,7 +186,7 @@ fn ceil() { #[test] fn cos() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.cos(0); var b = Math.cos(1); @@ -204,7 +204,7 @@ fn cos() { #[test] fn cosh() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.cosh(0); var b = Math.cosh(1); @@ -225,7 +225,7 @@ fn cosh() { #[test] fn exp() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.exp(0); var b = Math.exp(-1); @@ -246,7 +246,7 @@ fn exp() { #[test] fn floor() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.floor(1.95); var b = Math.floor(-3.01); @@ -267,7 +267,7 @@ fn floor() { #[test] fn log() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.log(1); var b = Math.log(10); @@ -288,7 +288,7 @@ fn log() { #[test] fn log10() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.log10(2); var b = Math.log10(1); @@ -309,7 +309,7 @@ fn log10() { #[test] fn log2() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.log2(3); var b = Math.log2(1); @@ -330,7 +330,7 @@ fn log2() { #[test] fn max() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.max(10, 20); var b = Math.max(-10, -20); @@ -351,7 +351,7 @@ fn max() { #[test] fn min() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.min(10, 20); var b = Math.min(-10, -20); @@ -372,7 +372,7 @@ fn min() { #[test] fn pow() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.pow(2, 10); var b = Math.pow(-7, 2); @@ -396,7 +396,7 @@ fn pow() { #[test] fn round() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.round(20.5); var b = Math.round(-20.3); @@ -414,7 +414,7 @@ fn round() { #[test] fn sign() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.sign(3); var b = Math.sign(-3); @@ -435,7 +435,7 @@ fn sign() { #[test] fn sin() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.sin(0); var b = Math.sin(1); @@ -453,7 +453,7 @@ fn sin() { #[test] fn sinh() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.sinh(0); var b = Math.sinh(1); @@ -471,7 +471,7 @@ fn sinh() { #[test] fn sqrt() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.sqrt(0); var b = Math.sqrt(2); @@ -494,7 +494,7 @@ fn sqrt() { // #[test] // fn tan() { // let realm = Realm::create(); -// let mut engine = Executor::new(realm); +// let mut engine = Interpreter::new(realm); // let init = r#" // var a = Math.tan(1.1); // "#; @@ -509,7 +509,7 @@ fn sqrt() { #[test] fn tanh() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.tanh(1); var b = Math.tanh(0); @@ -527,7 +527,7 @@ fn tanh() { #[test] fn trunc() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = Math.trunc(13.37); var b = Math.trunc(0.123); diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index e4cb7a4d2ad..c9547dfc78d 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -1,6 +1,6 @@ #![allow(clippy::float_cmp)] -use crate::{builtins::value::Value, exec::Executor, forward, forward_val, realm::Realm}; +use crate::{builtins::value::Value, exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_number_constructor_is_function() { @@ -12,7 +12,7 @@ fn check_number_constructor_is_function() { #[test] fn call_number() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var default_zero = Number(); var int_one = Number(1); @@ -47,7 +47,7 @@ fn call_number() { #[test] fn to_exponential() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var default_exp = Number().toExponential(); var int_exp = Number(5).toExponential(); @@ -76,7 +76,7 @@ fn to_exponential() { #[test] fn to_fixed() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var default_fixed = Number().toFixed(); var pos_fixed = Number("3.456e+4").toFixed(); @@ -102,7 +102,7 @@ fn to_fixed() { #[test] fn to_locale_string() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var default_locale = Number().toLocaleString(); var small_locale = Number(5).toLocaleString(); @@ -129,7 +129,7 @@ fn to_locale_string() { #[ignore] fn to_precision() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var default_precision = Number().toPrecision(); var low_precision = Number(123456789).toPrecision(1); @@ -161,7 +161,7 @@ fn to_precision() { #[test] fn to_string() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); assert_eq!("NaN", &forward(&mut engine, "Number(NaN).toString()")); assert_eq!("Infinity", &forward(&mut engine, "Number(1/0).toString()")); @@ -328,7 +328,7 @@ fn to_string() { // https://github.com/jasonwilliams/boa/pull/381#discussion_r422458544 fn num_to_string_exponential() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); assert_eq!( String::from("111111111111111110000"), @@ -371,7 +371,7 @@ fn num_to_string_exponential() { #[test] fn value_of() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); // TODO: In addition to parsing numbers from strings, parse them bare As of October 2019 // the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation. let init = r#" diff --git a/boa/src/builtins/regexp/tests.rs b/boa/src/builtins/regexp/tests.rs index dd2fbfc0cb0..fbf388fc04b 100644 --- a/boa/src/builtins/regexp/tests.rs +++ b/boa/src/builtins/regexp/tests.rs @@ -1,12 +1,10 @@ use super::*; -use crate::exec::Executor; -use crate::forward; -use crate::realm::Realm; +use crate::{exec::Interpreter, forward, realm::Realm}; #[test] fn constructors() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var constructed = new RegExp("[0-9]+(\\.[0-9]+)?"); var literal = /[0-9]+(\.[0-9]+)?/; @@ -30,7 +28,7 @@ fn check_regexp_constructor_is_function() { // #[test] // fn flags() { -// let mut engine = Executor::new(); +// let mut engine = Interpreter::new(); // let init = r#" // var re_gi = /test/gi; // var re_sm = /test/sm; @@ -57,7 +55,7 @@ fn check_regexp_constructor_is_function() { #[test] fn last_index() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var regex = /[0-9]+(\.[0-9]+)?/g; "#; @@ -73,7 +71,7 @@ fn last_index() { #[test] fn exec() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var re = /quick\s(brown).+?(jumps)/ig; var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); @@ -93,7 +91,7 @@ fn exec() { #[test] fn to_string() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); assert_eq!( forward(&mut engine, "(new RegExp('a+b+c')).toString()"), diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index e5f8d21fbe0..886aa1d2327 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -1,7 +1,5 @@ use super::*; -use crate::exec::Executor; -use crate::realm::Realm; -use crate::{forward, forward_val}; +use crate::{exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_string_constructor_is_function() { @@ -14,7 +12,7 @@ fn check_string_constructor_is_function() { // TODO: re-enable when getProperty() is finished; // fn length() { // //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js -// let mut engine = Executor::new(); +// let mut engine = Interpreter::new(); // let init = r#" // const a = new String(' '); // const b = new String('\ud834\udf06'); @@ -39,7 +37,7 @@ fn check_string_constructor_is_function() { #[test] fn concat() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var hello = new String('Hello, '); var world = new String('world! '); @@ -59,7 +57,7 @@ fn concat() { /// Test the correct type is returned from call and construct fn construct_and_call() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var hello = new String('Hello'); var world = String('world'); @@ -75,7 +73,7 @@ fn construct_and_call() { #[test] fn repeat() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = new String(''); var en = new String('english'); @@ -103,7 +101,7 @@ fn repeat() { #[test] fn replace() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = "abc"; a = a.replace("a", "2"); @@ -118,7 +116,7 @@ fn replace() { #[test] fn replace_with_function() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var a = "ecmascript is cool"; var p1, p2, p3; @@ -146,7 +144,7 @@ fn replace_with_function() { #[test] fn starts_with() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = new String(''); var en = new String('english'); @@ -170,7 +168,7 @@ fn starts_with() { #[test] fn ends_with() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var empty = new String(''); var en = new String('english'); @@ -194,7 +192,7 @@ fn ends_with() { #[test] fn match_all() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); assert_eq!( forward(&mut engine, "'aa'.matchAll(null).length"), @@ -267,7 +265,7 @@ fn match_all() { #[test] fn test_match() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog'); var result1 = str.match(/quick\s(brown).+?(jumps)/i); diff --git a/boa/src/builtins/symbol/tests.rs b/boa/src/builtins/symbol/tests.rs index 31b78dc16c4..ca72edb1f53 100644 --- a/boa/src/builtins/symbol/tests.rs +++ b/boa/src/builtins/symbol/tests.rs @@ -1,7 +1,5 @@ use super::*; -use crate::exec::Executor; -use crate::realm::Realm; -use crate::{forward, forward_val}; +use crate::{exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_symbol_constructor_is_function() { @@ -13,7 +11,7 @@ fn check_symbol_constructor_is_function() { #[test] fn call_symbol_and_check_return_type() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var sym = Symbol(); "#; @@ -25,7 +23,7 @@ fn call_symbol_and_check_return_type() { #[test] fn print_symbol_expect_description() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let init = r#" var sym = Symbol("Hello"); "#; diff --git a/boa/src/exec/array.rs b/boa/src/exec/array.rs new file mode 100644 index 00000000000..d29038bd58b --- /dev/null +++ b/boa/src/exec/array.rs @@ -0,0 +1,29 @@ +//! Array declaration execution. + +use super::{Executable, Interpreter}; +use crate::{ + builtins::{ + array::{add_to_array_object, new_array}, + value::ResultValue, + }, + syntax::ast::node::{ArrayDecl, Node}, +}; + +impl Executable for ArrayDecl { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let array = new_array(interpreter)?; + let mut elements = Vec::new(); + for elem in self.as_ref() { + if let Node::Spread(ref x) = elem { + let val = interpreter.exec(x)?; + let mut vals = interpreter.extract_array_properties(&val).unwrap(); + elements.append(&mut vals); + continue; // Don't push array after spread + } + elements.push(interpreter.exec(&elem)?); + } + add_to_array_object(&array, &elements)?; + + Ok(array) + } +} diff --git a/boa/src/exec/arrow_function.rs b/boa/src/exec/arrow_function.rs new file mode 100644 index 00000000000..4fb9520f1d4 --- /dev/null +++ b/boa/src/exec/arrow_function.rs @@ -0,0 +1,42 @@ +//! Arrow function execution. + +use super::{Executable, Interpreter}; +use crate::{ + builtins::{ + function::{Function as FunctionObject, FunctionBody, ThisMode}, + object::Object, + value::{ResultValue, Value}, + }, + syntax::ast::node::ArrowFunctionDecl, +}; + +impl Executable for ArrowFunctionDecl { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype + // let proto = &self + // .realm + // .environment + // .get_global_object() + // .expect("Could not get the global object") + // .get_field_slice("Object") + // .get_field_slice("Prototype"); + + let func = FunctionObject::create_ordinary( + self.params.clone(), // TODO: args shouldn't need to be a reference it should be passed by value + interpreter + .realm_mut() + .environment + .get_current_environment() + .clone(), + FunctionBody::Ordinary(*self.body.clone()), + ThisMode::Lexical, + ); + + let mut new_func = Object::function(); + new_func.set_call(func); + let val = Value::from(new_func); + val.set_field_slice("length", Value::from(self.params.len())); + + Ok(val) + } +} diff --git a/boa/src/exec/block.rs b/boa/src/exec/block.rs new file mode 100644 index 00000000000..148b8c585a9 --- /dev/null +++ b/boa/src/exec/block.rs @@ -0,0 +1,36 @@ +use super::{Executable, Interpreter}; +use crate::{ + builtins::value::{ResultValue, Value}, + environment::lexical_environment::new_declarative_environment, + syntax::ast::node::Block, +}; + +impl Executable for Block { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + + let mut obj = Value::null(); + for hoistable in self.hoistable() { + obj = interpreter.exec(hoistable)?; + } + + for statement in self.statements() { + obj = interpreter.exec(statement)?; + + // early return + if interpreter.is_return { + break; + } + } + + // pop the block env + let _ = interpreter.realm_mut().environment.pop(); + + Ok(obj) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index f5e4deb83aa..43e67663243 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -1,11 +1,14 @@ //! Execution of the AST, this is where the interpreter actually runs +mod array; +mod arrow_function; +mod block; +mod operator; #[cfg(test)] mod tests; use crate::{ builtins::{ - array, function::{Function as FunctionObject, FunctionBody, ThisMode}, object::{ internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, @@ -19,57 +22,301 @@ use crate::{ syntax::ast::{ constant::Const, node::{MethodDefinitionKind, Node, PropertyDefinition}, - op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, + op::UnaryOp, }, }; -use std::{ - borrow::{Borrow, BorrowMut}, - ops::Deref, -}; +use std::{borrow::Borrow, ops::Deref}; -/// An execution engine -pub trait Executor { - /// Make a new execution engine - fn new(realm: Realm) -> Self; - /// Run an expression - fn run(&mut self, expr: &Node) -> ResultValue; +pub trait Executable { + /// Runs this executable in the given executor. + fn run(&self, interpreter: &mut Interpreter) -> ResultValue; } /// A Javascript intepreter #[derive(Debug)] pub struct Interpreter { + /// Wether it's running a return statement. is_return: bool, /// realm holds both the global object and the environment pub realm: Realm, } -fn exec_assign_op(op: &AssignOp, v_a: Value, v_b: Value) -> Value { - match *op { - AssignOp::Add => v_a + v_b, - AssignOp::Sub => v_a - v_b, - AssignOp::Mul => v_a * v_b, - AssignOp::Exp => v_a.as_num_to_power(v_b), - AssignOp::Div => v_a / v_b, - AssignOp::Mod => v_a % v_b, - AssignOp::And => v_a & v_b, - AssignOp::Or => v_a | v_b, - AssignOp::Xor => v_a ^ v_b, - AssignOp::Shl => v_a << v_b, - AssignOp::Shr => v_a << v_b, - } -} - -impl Executor for Interpreter { - fn new(realm: Realm) -> Self { +impl Interpreter { + /// Creates a new interpreter. + pub fn new(realm: Realm) -> Self { Self { realm, is_return: false, } } - #[allow(clippy::match_same_arms)] - fn run(&mut self, node: &Node) -> ResultValue { - match *node { + /// Retrieves the `Realm` of this executor. + pub fn realm(&self) -> &Realm { + &self.realm + } + + /// Retrieves the `Realm` of this executor as a mutable reference. + pub fn realm_mut(&mut self) -> &mut Realm { + &mut self.realm + } + + /// Run an expression. + pub fn exec(&mut self, node: &Node) -> ResultValue { + node.run(self) + } + + /// + pub(crate) fn call( + &mut self, + f: &Value, + this: &mut Value, + arguments_list: &[Value], + ) -> ResultValue { + // All functions should be objects, and eventually will be. + // During this transition call will support both native functions and function objects + match (*f).deref() { + ValueData::Object(ref obj) => match (*obj).deref().borrow().call { + Some(ref func) => func.call(&mut f.clone(), arguments_list, self, this), + None => panic!("Expected function"), + }, + _ => Err(Value::undefined()), + } + } + + /// Converts a value into a rust heap allocated string. + pub(crate) fn value_to_rust_string(&mut self, value: &Value) -> String { + match *value.deref().borrow() { + ValueData::Null => String::from("null"), + ValueData::Boolean(ref boolean) => boolean.to_string(), + ValueData::Rational(ref num) => num.to_string(), + ValueData::Integer(ref num) => num.to_string(), + ValueData::String(ref string) => string.clone(), + ValueData::Object(_) => { + let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); + self.to_string(&prim_value).to_string() + } + _ => String::from("undefined"), + } + } + + /// Converts an array object into a rust vector of values. + /// + /// This is useful for the spread operator, for any other object an `Err` is returned + pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result, ()> { + if let ValueData::Object(ref x) = *value.deref().borrow() { + // Check if object is array + if x.deref().borrow().kind == ObjectKind::Array { + let length: i32 = + self.value_to_rust_number(&value.get_field_slice("length")) as i32; + let values: Vec = (0..length) + .map(|idx| value.get_field_slice(&idx.to_string())) + .collect(); + return Ok(values); + } + + return Err(()); + } + + Err(()) + } + + /// + pub(crate) fn ordinary_to_primitive(&mut self, o: &mut Value, hint: &str) -> Value { + debug_assert!(o.get_type() == "object"); + debug_assert!(hint == "string" || hint == "number"); + let method_names: Vec<&str> = if hint == "string" { + vec!["toString", "valueOf"] + } else { + vec!["valueOf", "toString"] + }; + for name in method_names.iter() { + let method: Value = o.get_field_slice(name); + if method.is_function() { + let result = self.call(&method, o, &[]); + match result { + Ok(val) => { + if val.is_object() { + // TODO: throw exception + continue; + } else { + return val; + } + } + Err(_) => continue, + } + } + } + + Value::undefined() + } + + /// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. + /// + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_primitive( + &mut self, + input: &mut Value, + preferred_type: Option<&str>, + ) -> Value { + let mut hint: &str; + match (*input).deref() { + ValueData::Object(_) => { + hint = match preferred_type { + None => "default", + Some(pt) => match pt { + "string" => "string", + "number" => "number", + _ => "default", + }, + }; + + // Skip d, e we don't support Symbols yet + // TODO: add when symbols are supported + if hint == "default" { + hint = "number"; + }; + + self.ordinary_to_primitive(input, hint) + } + _ => input.clone(), + } + } + /// to_string() converts a value into a String + /// https://tc39.es/ecma262/#sec-tostring + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(&mut self, value: &Value) -> Value { + match *value.deref().borrow() { + ValueData::Undefined => Value::from("undefined"), + ValueData::Null => Value::from("null"), + ValueData::Boolean(ref boolean) => Value::from(boolean.to_string()), + ValueData::Rational(ref num) => Value::from(num.to_string()), + ValueData::Integer(ref num) => Value::from(num.to_string()), + ValueData::String(ref string) => Value::from(string.clone()), + ValueData::Object(_) => { + let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); + self.to_string(&prim_value) + } + _ => Value::from("function(){...}"), + } + } + + /// The abstract operation ToPropertyKey takes argument argument. It converts argument to a value that can be used as a property key. + /// https://tc39.es/ecma262/#sec-topropertykey + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_property_key(&mut self, value: &mut Value) -> Value { + let key = self.to_primitive(value, Some("string")); + if key.is_symbol() { + key + } else { + self.to_string(&key) + } + } + + /// https://tc39.es/ecma262/#sec-hasproperty + pub(crate) fn has_property(&self, obj: &mut Value, key: &Value) -> bool { + if let Some(obj) = obj.as_object() { + if !Property::is_property_key(key) { + false + } else { + obj.has_property(key) + } + } else { + false + } + } + + /// The abstract operation ToObject converts argument to a value of type Object + /// https://tc39.es/ecma262/#sec-toobject + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue { + match *value.deref().borrow() { + ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => { + Err(Value::undefined()) + } + ValueData::Boolean(_) => { + let proto = self + .realm + .environment + .get_binding_value("Boolean") + .get_field_slice(PROTOTYPE); + + let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); + bool_obj.set_internal_slot("BooleanData", value.clone()); + Ok(bool_obj) + } + ValueData::Rational(_) => { + let proto = self + .realm + .environment + .get_binding_value("Number") + .get_field_slice(PROTOTYPE); + let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); + number_obj.set_internal_slot("NumberData", value.clone()); + Ok(number_obj) + } + ValueData::String(_) => { + let proto = self + .realm + .environment + .get_binding_value("String") + .get_field_slice(PROTOTYPE); + let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String); + string_obj.set_internal_slot("StringData", value.clone()); + Ok(string_obj) + } + ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), + } + } + + pub(crate) fn value_to_rust_number(&mut self, value: &Value) -> f64 { + match *value.deref().borrow() { + ValueData::Null => f64::from(0), + ValueData::Boolean(boolean) => { + if boolean { + f64::from(1) + } else { + f64::from(0) + } + } + ValueData::Rational(num) => num, + ValueData::Integer(num) => f64::from(num), + ValueData::String(ref string) => string.parse::().unwrap(), + ValueData::Object(_) => { + let prim_value = self.to_primitive(&mut (value.clone()), Some("number")); + self.to_string(&prim_value) + .to_string() + .parse::() + .expect("cannot parse valur to x64") + } + _ => { + // TODO: Make undefined? + f64::from(0) + } + } + } + + fn set_value(&mut self, node: &Node, value: Value) -> ResultValue { + match node { + Node::Local(ref name) => { + self.realm + .environment + .set_mutable_binding(name, value.clone(), true); + Ok(value) + } + Node::GetConstField(ref obj, ref field) => { + Ok(self.exec(obj)?.set_field_slice(field, value)) + } + Node::GetField(ref obj, ref field) => { + Ok(self.exec(obj)?.set_field(self.exec(field)?, value)) + } + _ => panic!("TypeError: invalid assignment to {}", node), + } + } +} + +impl Executable for Node { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + match *self { Node::Const(Const::Null) => Ok(Value::null()), Node::Const(Const::Undefined) => Ok(Value::undefined()), Node::Const(Const::Num(num)) => Ok(Value::rational(num)), @@ -79,43 +326,18 @@ impl Executor for Interpreter { // Do Const values need to be garbage collected? We no longer need them once we've generated Values Node::Const(Const::String(ref value)) => Ok(Value::string(value.to_string())), Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)), - Node::Block(ref es) => { - { - let env = &mut self.realm.environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } - - let mut obj = Value::null(); - for e in es.iter() { - let val = self.run(e)?; - // early return - if self.is_return { - obj = val; - break; - } - if e == es.last().expect("unable to get last value") { - obj = val; - } - } - - // pop the block env - let _ = self.realm.environment.pop(); - - Ok(obj) - } + Node::Block(ref block) => block.run(interpreter), Node::Local(ref name) => { - let val = self.realm.environment.get_binding_value(name); + let val = interpreter.realm().environment.get_binding_value(name); Ok(val) } Node::GetConstField(ref obj, ref field) => { - let val_obj = self.run(obj)?; + let val_obj = interpreter.exec(obj)?; Ok(val_obj.borrow().get_field_slice(field)) } Node::GetField(ref obj, ref field) => { - let val_obj = self.run(obj)?; - let val_field = self.run(field)?; + let val_obj = interpreter.exec(obj)?; + let val_field = interpreter.exec(field)?; Ok(val_obj .borrow() .get_field_slice(&val_field.borrow().to_string())) @@ -123,97 +345,104 @@ impl Executor for Interpreter { Node::Call(ref callee, ref args) => { let (mut this, func) = match callee.deref() { Node::GetConstField(ref obj, ref field) => { - let mut obj = self.run(obj)?; + let mut obj = interpreter.exec(obj)?; if obj.get_type() != "object" || obj.get_type() != "symbol" { - obj = self.to_object(&obj).expect("failed to convert to object"); + obj = interpreter + .to_object(&obj) + .expect("failed to convert to object"); } (obj.clone(), obj.borrow().get_field_slice(field)) } Node::GetField(ref obj, ref field) => { - let obj = self.run(obj)?; - let field = self.run(field)?; + let obj = interpreter.exec(obj)?; + let field = interpreter.exec(field)?; ( obj.clone(), obj.borrow().get_field_slice(&field.borrow().to_string()), ) } - _ => (self.realm.global_obj.clone(), self.run(&callee.clone())?), // 'this' binding should come from the function's self-contained environment + _ => ( + interpreter.realm().global_obj.clone(), + interpreter.exec(&callee.clone())?, + ), // 'this' binding should come from the function's self-contained environment }; let mut v_args = Vec::with_capacity(args.len()); for arg in args.iter() { if let Node::Spread(ref x) = arg.deref() { - let val = self.run(x)?; - let mut vals = self.extract_array_properties(&val).unwrap(); + let val = interpreter.exec(x)?; + let mut vals = interpreter.extract_array_properties(&val).unwrap(); v_args.append(&mut vals); break; // after spread we don't accept any new arguments } - v_args.push(self.run(arg)?); + v_args.push(interpreter.exec(arg)?); } // execute the function call itself - let fnct_result = self.call(&func, &mut this, &v_args); + let fnct_result = interpreter.call(&func, &mut this, &v_args); // unset the early return flag - self.is_return = false; + interpreter.is_return = false; fnct_result } Node::WhileLoop(ref cond, ref expr) => { let mut result = Value::undefined(); - while self.run(cond)?.borrow().is_true() { - result = self.run(expr)?; + while interpreter.exec(cond)?.borrow().is_true() { + result = interpreter.exec(expr)?; } Ok(result) } Node::DoWhileLoop(ref body, ref cond) => { - let mut result = self.run(body)?; - while self.run(cond)?.borrow().is_true() { - result = self.run(body)?; + let mut result = interpreter.exec(body)?; + while interpreter.exec(cond)?.borrow().is_true() { + result = interpreter.exec(body)?; } Ok(result) } Node::ForLoop(ref init, ref cond, ref step, ref body) => { if let Some(init) = init { - self.run(init)?; + interpreter.exec(init)?; } while match cond { - Some(cond) => self.run(cond)?.borrow().is_true(), + Some(cond) => interpreter.exec(cond)?.borrow().is_true(), None => true, } { - self.run(body)?; + interpreter.exec(body)?; if let Some(step) = step { - self.run(step)?; + interpreter.exec(step)?; } } Ok(Value::undefined()) } - Node::If(ref cond, ref expr, None) => Ok(if self.run(cond)?.borrow().is_true() { - self.run(expr)? - } else { - Value::undefined() - }), + Node::If(ref cond, ref expr, None) => { + Ok(if interpreter.exec(cond)?.borrow().is_true() { + interpreter.exec(expr)? + } else { + Value::undefined() + }) + } Node::If(ref cond, ref expr, Some(ref else_e)) => { - Ok(if self.run(cond)?.borrow().is_true() { - self.run(expr)? + Ok(if interpreter.exec(cond)?.borrow().is_true() { + interpreter.exec(expr)? } else { - self.run(else_e)? + interpreter.exec(else_e)? }) } Node::Switch(ref val_e, ref vals, ref default) => { - let val = self.run(val_e)?; + let val = interpreter.exec(val_e)?; let mut result = Value::null(); let mut matched = false; for tup in vals.iter() { let cond = &tup.0; let block = &tup.1; - if val == self.run(cond)? { + if val == interpreter.exec(cond)? { matched = true; let last_expr = block.last().expect("Block has no expressions"); for expr in block.iter() { - let e_result = self.run(expr)?; + let e_result = interpreter.exec(expr)?; if expr == last_expr { result = e_result; } @@ -221,7 +450,7 @@ impl Executor for Interpreter { } } if !matched && default.is_some() { - result = self.run( + result = interpreter.exec( default .as_ref() .expect("Could not get default as reference"), @@ -230,8 +459,8 @@ impl Executor for Interpreter { Ok(result) } Node::Object(ref properties) => { - let global_val = &self - .realm + let global_val = &interpreter + .realm() .environment .get_global_object() .expect("Could not get the global object"); @@ -241,11 +470,13 @@ impl Executor for Interpreter { for property in properties.iter() { match property { PropertyDefinition::Property(key, value) => { - obj.borrow().set_field_slice(&key.clone(), self.run(value)?); + obj.borrow() + .set_field_slice(&key.clone(), interpreter.exec(value)?); } PropertyDefinition::MethodDefinition(kind, name, func) => { if let MethodDefinitionKind::Ordinary = kind { - obj.borrow().set_field_slice(&name.clone(), self.run(func)?); + obj.borrow() + .set_field_slice(&name.clone(), interpreter.exec(func)?); } else { // TODO: Implement other types of MethodDefinitionKinds. unimplemented!("other types of property method definitions."); @@ -257,21 +488,7 @@ impl Executor for Interpreter { Ok(obj) } - Node::ArrayDecl(ref arr) => { - let array = array::new_array(self)?; - let mut elements = Vec::new(); - for elem in arr.iter() { - if let Node::Spread(ref x) = elem.deref() { - let val = self.run(x)?; - let mut vals = self.extract_array_properties(&val).unwrap(); - elements.append(&mut vals); - continue; // Don't push array after spread - } - elements.push(self.run(elem)?); - } - array::add_to_array_object(&array, &elements)?; - Ok(array) - } + Node::ArrayDecl(ref arr) => arr.run(interpreter), // Node::FunctionDecl(ref name, ref args, ref expr) => { // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype @@ -285,7 +502,11 @@ impl Executor for Interpreter { let func = FunctionObject::create_ordinary( args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value - self.realm.environment.get_current_environment().clone(), + interpreter + .realm_mut() + .environment + .get_current_environment() + .clone(), FunctionBody::Ordinary(*expr.clone()), ThisMode::NonLexical, ); @@ -297,13 +518,16 @@ impl Executor for Interpreter { // Set the name and assign it in the current environment val.set_field_slice("name", Value::from(name.clone())); - self.realm.environment.create_mutable_binding( + interpreter.realm_mut().environment.create_mutable_binding( name.clone(), false, VariableScope::Function, ); - self.realm.environment.initialize_binding(name, val.clone()); + interpreter + .realm_mut() + .environment + .initialize_binding(name, val.clone()); Ok(val) } @@ -320,7 +544,11 @@ impl Executor for Interpreter { let func = FunctionObject::create_ordinary( args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value - self.realm.environment.get_current_environment().clone(), + interpreter + .realm_mut() + .environment + .get_current_environment() + .clone(), FunctionBody::Ordinary(*expr.clone()), ThisMode::NonLexical, ); @@ -336,62 +564,28 @@ impl Executor for Interpreter { Ok(val) } - Node::ArrowFunctionDecl(ref args, ref expr) => { - // Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype - // let proto = &self - // .realm - // .environment - // .get_global_object() - // .expect("Could not get the global object") - // .get_field_slice("Object") - // .get_field_slice("Prototype"); - - let func = FunctionObject::create_ordinary( - args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value - self.realm.environment.get_current_environment().clone(), - FunctionBody::Ordinary(*expr.clone()), - ThisMode::Lexical, - ); - - let mut new_func = Object::function(); - new_func.set_call(func); - let val = Value::from(new_func); - val.set_field_slice("length", Value::from(args.len())); - - Ok(val) - } - Node::BinOp(BinOp::Num(ref op), ref a, ref b) => { - let v_a = self.run(a)?; - let v_b = self.run(b)?; - Ok(match *op { - NumOp::Add => v_a + v_b, - NumOp::Sub => v_a - v_b, - NumOp::Mul => v_a * v_b, - NumOp::Exp => v_a.as_num_to_power(v_b), - NumOp::Div => v_a / v_b, - NumOp::Mod => v_a % v_b, - }) - } + Node::ArrowFunctionDecl(ref decl) => decl.run(interpreter), + Node::BinOp(ref op) => op.run(interpreter), Node::UnaryOp(ref op, ref a) => { - let v_a = self.run(a)?; + let v_a = interpreter.exec(a)?; Ok(match *op { UnaryOp::Minus => Value::from(-v_a.to_number()), UnaryOp::Plus => Value::from(v_a.to_number()), UnaryOp::IncrementPost => { let ret = v_a.clone(); - self.set_value(a, Value::from(v_a.to_number() + 1.0))?; + interpreter.set_value(a, Value::from(v_a.to_number() + 1.0))?; ret } UnaryOp::IncrementPre => { - self.set_value(a, Value::from(v_a.to_number() + 1.0))? + interpreter.set_value(a, Value::from(v_a.to_number() + 1.0))? } UnaryOp::DecrementPost => { let ret = v_a.clone(); - self.set_value(a, Value::from(v_a.to_number() - 1.0))?; + interpreter.set_value(a, Value::from(v_a.to_number() - 1.0))?; ret } UnaryOp::DecrementPre => { - self.set_value(a, Value::from(v_a.to_number() - 1.0))? + interpreter.set_value(a, Value::from(v_a.to_number() - 1.0))? } UnaryOp::Not => !v_a, UnaryOp::Tilde => { @@ -406,11 +600,12 @@ impl Executor for Interpreter { UnaryOp::Void => Value::undefined(), UnaryOp::Delete => match a.deref() { Node::GetConstField(ref obj, ref field) => { - Value::boolean(self.run(obj)?.remove_property(field)) + Value::boolean(interpreter.exec(obj)?.remove_property(field)) } Node::GetField(ref obj, ref field) => Value::boolean( - self.run(obj)? - .remove_property(&self.run(field)?.to_string()), + interpreter + .exec(obj)? + .remove_property(&interpreter.exec(field)?.to_string()), ), Node::Local(_) => Value::boolean(false), Node::ArrayDecl(_) @@ -422,91 +617,21 @@ impl Executor for Interpreter { | Node::Object(_) | Node::TypeOf(_) | Node::UnaryOp(_, _) => Value::boolean(true), - _ => panic!("SyntaxError: wrong delete argument {}", node), - }, - _ => unimplemented!(), - }) - } - Node::BinOp(BinOp::Bit(ref op), ref a, ref b) => { - let v_a = self.run(a)?; - let v_b = self.run(b)?; - Ok(match *op { - BitOp::And => v_a & v_b, - BitOp::Or => v_a | v_b, - BitOp::Xor => v_a ^ v_b, - BitOp::Shl => v_a << v_b, - BitOp::Shr => v_a >> v_b, - // TODO Fix - BitOp::UShr => v_a >> v_b, - }) - } - Node::BinOp(BinOp::Comp(ref op), ref a, ref b) => { - let mut v_r_a = self.run(a)?; - let mut v_r_b = self.run(b)?; - let mut v_a = v_r_a.borrow_mut(); - let mut v_b = v_r_b.borrow_mut(); - Ok(Value::from(match *op { - CompOp::Equal if v_a.is_object() => v_r_a == v_r_b, - CompOp::Equal => v_a == v_b, - CompOp::NotEqual if v_a.is_object() => v_r_a != v_r_b, - CompOp::NotEqual => v_a != v_b, - CompOp::StrictEqual if v_a.is_object() => v_r_a == v_r_b, - CompOp::StrictEqual => v_a == v_b, - CompOp::StrictNotEqual if v_a.is_object() => v_r_a != v_r_b, - CompOp::StrictNotEqual => v_a != v_b, - CompOp::GreaterThan => v_a.to_number() > v_b.to_number(), - CompOp::GreaterThanOrEqual => v_a.to_number() >= v_b.to_number(), - CompOp::LessThan => v_a.to_number() < v_b.to_number(), - CompOp::LessThanOrEqual => v_a.to_number() <= v_b.to_number(), - CompOp::In => { - if !v_b.is_object() { - panic!("TypeError: {} is not an Object.", v_b); - } - let key = self.to_property_key(&mut v_a); - self.has_property(&mut v_b, &key) - } - })) - } - Node::BinOp(BinOp::Log(ref op), ref a, ref b) => { - // turn a `Value` into a `bool` - let to_bool = |value| bool::from(&value); - Ok(match *op { - LogOp::And => Value::from(to_bool(self.run(a)?) && to_bool(self.run(b)?)), - LogOp::Or => Value::from(to_bool(self.run(a)?) || to_bool(self.run(b)?)), + _ => panic!("SyntaxError: wrong delete argument {}", self), + }, + _ => unimplemented!(), }) } - Node::BinOp(BinOp::Assign(ref op), ref a, ref b) => match a.deref() { - Node::Local(ref name) => { - let v_a = self.realm.environment.get_binding_value(&name); - let v_b = self.run(b)?; - let value = exec_assign_op(op, v_a, v_b); - self.realm - .environment - .set_mutable_binding(&name, value.clone(), true); - Ok(value) - } - Node::GetConstField(ref obj, ref field) => { - let v_r_a = self.run(obj)?; - let v_a = v_r_a.get_field_slice(field); - let v_b = self.run(b)?; - let value = exec_assign_op(op, v_a, v_b); - v_r_a - .borrow() - .set_field_slice(&field.clone(), value.clone()); - Ok(value) - } - _ => Ok(Value::undefined()), - }, Node::New(ref call) => { let (callee, args) = match call.as_ref() { Node::Call(callee, args) => (callee, args), _ => unreachable!("Node::New(ref call): 'call' must only be Node::Call type."), }; - let func_object = self.run(callee)?; + let func_object = interpreter.exec(callee)?; let mut v_args = Vec::with_capacity(args.len()); for arg in args.iter() { - v_args.push(self.run(arg)?); + v_args.push(interpreter.exec(arg)?); } let mut this = Value::new_object(None); // Create a blank object, then set its __proto__ property to the [Constructor].prototype @@ -520,66 +645,37 @@ impl Executor for Interpreter { .construct .as_ref() .unwrap() - .construct(&mut func_object.clone(), &v_args, self, &mut this), + .construct(&mut func_object.clone(), &v_args, interpreter, &mut this), _ => Ok(Value::undefined()), } } Node::Return(ref ret) => { let result = match *ret { - Some(ref v) => self.run(v), + Some(ref v) => interpreter.exec(v), None => Ok(Value::undefined()), }; // Set flag for return - self.is_return = true; + interpreter.is_return = true; result } - Node::Throw(ref ex) => Err(self.run(ex)?), - Node::Assign(ref ref_e, ref val_e) => { - let val = self.run(val_e)?; - match ref_e.deref() { - Node::Local(ref name) => { - if self.realm.environment.has_binding(name) { - // Binding already exists - self.realm - .environment - .set_mutable_binding(&name, val.clone(), true); - } else { - self.realm.environment.create_mutable_binding( - name.clone(), - true, - VariableScope::Function, - ); - self.realm.environment.initialize_binding(name, val.clone()); - } - } - Node::GetConstField(ref obj, ref field) => { - let val_obj = self.run(obj)?; - val_obj - .borrow() - .set_field_slice(&field.clone(), val.clone()); - } - Node::GetField(ref obj, ref field) => { - let val_obj = self.run(obj)?; - let val_field = self.run(field)?; - val_obj.borrow().set_field(val_field, val.clone()); - } - _ => (), - } - Ok(val) - } + Node::Throw(ref ex) => Err(interpreter.exec(ex)?), + Node::Assign(ref op) => op.run(interpreter), Node::VarDecl(ref vars) => { for var in vars.iter() { let (name, value) = var.clone(); let val = match value { - Some(v) => self.run(&v)?, + Some(v) => interpreter.exec(&v)?, None => Value::undefined(), }; - self.realm.environment.create_mutable_binding( + interpreter.realm_mut().environment.create_mutable_binding( name.clone(), false, VariableScope::Function, ); - self.realm.environment.initialize_binding(&name, val); + interpreter + .realm_mut() + .environment + .initialize_binding(&name, val); } Ok(Value::undefined()) } @@ -587,32 +683,37 @@ impl Executor for Interpreter { for var in vars.iter() { let (name, value) = var.clone(); let val = match value { - Some(v) => self.run(&v)?, + Some(v) => interpreter.exec(&v)?, None => Value::undefined(), }; - self.realm.environment.create_mutable_binding( + interpreter.realm_mut().environment.create_mutable_binding( name.clone(), false, VariableScope::Block, ); - self.realm.environment.initialize_binding(&name, val); + interpreter + .realm_mut() + .environment + .initialize_binding(&name, val); } Ok(Value::undefined()) } Node::ConstDecl(ref vars) => { for (name, value) in vars.iter() { - self.realm.environment.create_immutable_binding( - name.clone(), - false, - VariableScope::Block, - ); - let val = self.run(&value)?; - self.realm.environment.initialize_binding(&name, val); + interpreter + .realm_mut() + .environment + .create_immutable_binding(name.clone(), false, VariableScope::Block); + let val = interpreter.exec(&value)?; + interpreter + .realm_mut() + .environment + .initialize_binding(&name, val); } Ok(Value::undefined()) } Node::TypeOf(ref val_e) => { - let val = self.run(val_e)?; + let val = interpreter.exec(val_e)?; Ok(Value::from(match *val { ValueData::Undefined => "undefined", ValueData::Symbol(_) => "symbol", @@ -631,7 +732,7 @@ impl Executor for Interpreter { } Node::StatementList(ref list) => { { - let env = &mut self.realm.environment; + let env = &mut interpreter.realm_mut().environment; env.push(new_declarative_environment(Some( env.get_current_environment_ref().clone(), ))); @@ -639,9 +740,9 @@ impl Executor for Interpreter { let mut obj = Value::null(); for (i, item) in list.iter().enumerate() { - let val = self.run(item)?; + let val = interpreter.exec(item)?; // early return - if self.is_return { + if interpreter.is_return { obj = val; break; } @@ -651,265 +752,15 @@ impl Executor for Interpreter { } // pop the block env - let _ = self.realm.environment.pop(); + let _ = interpreter.realm_mut().environment.pop(); Ok(obj) } Node::Spread(ref node) => { // TODO: for now we can do nothing but return the value as-is - self.run(node) + interpreter.exec(node) } ref i => unimplemented!("{}", i), } } } - -impl Interpreter { - /// Get the Interpreter's realm - pub(crate) fn get_realm(&self) -> &Realm { - &self.realm - } - - /// https://tc39.es/ecma262/#sec-call - pub(crate) fn call( - &mut self, - f: &Value, - this: &mut Value, - arguments_list: &[Value], - ) -> ResultValue { - // All functions should be objects, and eventually will be. - // During this transition call will support both native functions and function objects - match (*f).deref() { - ValueData::Object(ref obj) => match (*obj).deref().borrow().call { - Some(ref func) => func.call(&mut f.clone(), arguments_list, self, this), - None => panic!("Expected function"), - }, - _ => Err(Value::undefined()), - } - } - - /// https://tc39.es/ecma262/#sec-ordinarytoprimitive - fn ordinary_to_primitive(&mut self, o: &mut Value, hint: &str) -> Value { - debug_assert!(o.get_type() == "object"); - debug_assert!(hint == "string" || hint == "number"); - let method_names: Vec<&str> = if hint == "string" { - vec!["toString", "valueOf"] - } else { - vec!["valueOf", "toString"] - }; - for name in method_names.iter() { - let method: Value = o.get_field_slice(name); - if method.is_function() { - let result = self.call(&method, o, &[]); - match result { - Ok(val) => { - if val.is_object() { - // TODO: throw exception - continue; - } else { - return val; - } - } - Err(_) => continue, - } - } - } - - Value::undefined() - } - - /// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. - /// https://tc39.es/ecma262/#sec-toprimitive - #[allow(clippy::wrong_self_convention)] - pub fn to_primitive(&mut self, input: &mut Value, preferred_type: Option<&str>) -> Value { - let mut hint: &str; - match (*input).deref() { - ValueData::Object(_) => { - hint = match preferred_type { - None => "default", - Some(pt) => match pt { - "string" => "string", - "number" => "number", - _ => "default", - }, - }; - - // Skip d, e we don't support Symbols yet - // TODO: add when symbols are supported - if hint == "default" { - hint = "number"; - }; - - self.ordinary_to_primitive(input, hint) - } - _ => input.clone(), - } - } - /// to_string() converts a value into a String - /// https://tc39.es/ecma262/#sec-tostring - #[allow(clippy::wrong_self_convention)] - pub fn to_string(&mut self, value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Undefined => Value::from("undefined"), - ValueData::Null => Value::from("null"), - ValueData::Boolean(ref boolean) => Value::from(boolean.to_string()), - ValueData::Rational(ref num) => Value::from(num.to_string()), - ValueData::Integer(ref num) => Value::from(num.to_string()), - ValueData::String(ref string) => Value::from(string.clone()), - ValueData::Object(_) => { - let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); - self.to_string(&prim_value) - } - _ => Value::from("function(){...}"), - } - } - - /// The abstract operation ToPropertyKey takes argument argument. It converts argument to a value that can be used as a property key. - /// https://tc39.es/ecma262/#sec-topropertykey - #[allow(clippy::wrong_self_convention)] - pub fn to_property_key(&mut self, value: &mut Value) -> Value { - let key = self.to_primitive(value, Some("string")); - if key.is_symbol() { - key - } else { - self.to_string(&key) - } - } - - /// https://tc39.es/ecma262/#sec-hasproperty - pub fn has_property(&self, obj: &mut Value, key: &Value) -> bool { - if let Some(obj) = obj.as_object() { - if !Property::is_property_key(key) { - false - } else { - obj.has_property(key) - } - } else { - false - } - } - - /// The abstract operation ToObject converts argument to a value of type Object - /// https://tc39.es/ecma262/#sec-toobject - #[allow(clippy::wrong_self_convention)] - pub fn to_object(&mut self, value: &Value) -> ResultValue { - match *value.deref().borrow() { - ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => { - Err(Value::undefined()) - } - ValueData::Boolean(_) => { - let proto = self - .realm - .environment - .get_binding_value("Boolean") - .get_field_slice(PROTOTYPE); - - let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); - bool_obj.set_internal_slot("BooleanData", value.clone()); - Ok(bool_obj) - } - ValueData::Rational(_) => { - let proto = self - .realm - .environment - .get_binding_value("Number") - .get_field_slice(PROTOTYPE); - let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); - number_obj.set_internal_slot("NumberData", value.clone()); - Ok(number_obj) - } - ValueData::String(_) => { - let proto = self - .realm - .environment - .get_binding_value("String") - .get_field_slice(PROTOTYPE); - let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String); - string_obj.set_internal_slot("StringData", value.clone()); - Ok(string_obj) - } - ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), - } - } - - /// value_to_rust_string() converts a value into a rust heap allocated string - pub fn value_to_rust_string(&mut self, value: &Value) -> String { - match *value.deref().borrow() { - ValueData::Null => String::from("null"), - ValueData::Boolean(ref boolean) => boolean.to_string(), - ValueData::Rational(ref num) => num.to_string(), - ValueData::Integer(ref num) => num.to_string(), - ValueData::String(ref string) => string.clone(), - ValueData::Object(_) => { - let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); - self.to_string(&prim_value).to_string() - } - _ => String::from("undefined"), - } - } - - pub fn value_to_rust_number(&mut self, value: &Value) -> f64 { - match *value.deref().borrow() { - ValueData::Null => f64::from(0), - ValueData::Boolean(boolean) => { - if boolean { - f64::from(1) - } else { - f64::from(0) - } - } - ValueData::Rational(num) => num, - ValueData::Integer(num) => f64::from(num), - ValueData::String(ref string) => string.parse::().unwrap(), - ValueData::Object(_) => { - let prim_value = self.to_primitive(&mut (value.clone()), Some("number")); - self.to_string(&prim_value) - .to_string() - .parse::() - .expect("cannot parse valur to x64") - } - _ => { - // TODO: Make undefined? - f64::from(0) - } - } - } - - /// `extract_array_properties` converts an array object into a rust vector of Values. - /// This is useful for the spread operator, for any other object an `Err` is returned - fn extract_array_properties(&mut self, value: &Value) -> Result, ()> { - if let ValueData::Object(ref x) = *value.deref().borrow() { - // Check if object is array - if x.deref().borrow().kind == ObjectKind::Array { - let length: i32 = - self.value_to_rust_number(&value.get_field_slice("length")) as i32; - let values: Vec = (0..length) - .map(|idx| value.get_field_slice(&idx.to_string())) - .collect(); - return Ok(values); - } - - return Err(()); - } - - Err(()) - } - - fn set_value(&mut self, node: &Node, value: Value) -> ResultValue { - match node { - Node::Local(ref name) => { - self.realm - .environment - .set_mutable_binding(name, value.clone(), true); - Ok(value) - } - Node::GetConstField(ref obj, ref field) => { - Ok(self.run(obj)?.set_field_slice(field, value)) - } - Node::GetField(ref obj, ref field) => { - Ok(self.run(obj)?.set_field(self.run(field)?, value)) - } - _ => panic!("TypeError: invalid assignment to {}", node), - } - } -} diff --git a/boa/src/exec/operator.rs b/boa/src/exec/operator.rs new file mode 100644 index 00000000000..266b4dd86cc --- /dev/null +++ b/boa/src/exec/operator.rs @@ -0,0 +1,160 @@ +//! Operator execution. + +use super::{Executable, Interpreter}; +use crate::{ + builtins::value::{ResultValue, Value}, + environment::lexical_environment::VariableScope, + syntax::ast::{ + node::{Assign, BinOp, Node}, + op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp}, + }, +}; + +impl Executable for Assign { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + let val = interpreter.exec(self.rhs())?; + match self.lhs() { + Node::Local(ref name) => { + if interpreter.realm().environment.has_binding(name) { + // Binding already exists + interpreter.realm_mut().environment.set_mutable_binding( + &name, + val.clone(), + true, + ); + } else { + interpreter.realm_mut().environment.create_mutable_binding( + name.clone(), + true, + VariableScope::Function, + ); + interpreter + .realm_mut() + .environment + .initialize_binding(name, val.clone()); + } + } + Node::GetConstField(ref obj, ref field) => { + let val_obj = interpreter.exec(obj)?; + val_obj.set_field_slice(&field.clone(), val.clone()); + } + Node::GetField(ref obj, ref field) => { + let val_obj = interpreter.exec(obj)?; + let val_field = interpreter.exec(field)?; + val_obj.set_field(val_field, val.clone()); + } + _ => (), + } + Ok(val) + } +} + +impl Executable for BinOp { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + match self.op() { + op::BinOp::Num(op) => { + let v_a = interpreter.exec(self.lhs())?; + let v_b = interpreter.exec(self.rhs())?; + Ok(match op { + NumOp::Add => v_a + v_b, + NumOp::Sub => v_a - v_b, + NumOp::Mul => v_a * v_b, + NumOp::Exp => v_a.as_num_to_power(v_b), + NumOp::Div => v_a / v_b, + NumOp::Mod => v_a % v_b, + }) + } + op::BinOp::Bit(op) => { + let v_a = interpreter.exec(self.lhs())?; + let v_b = interpreter.exec(self.rhs())?; + Ok(match op { + BitOp::And => v_a & v_b, + BitOp::Or => v_a | v_b, + BitOp::Xor => v_a ^ v_b, + BitOp::Shl => v_a << v_b, + BitOp::Shr => v_a >> v_b, + // TODO Fix + BitOp::UShr => v_a >> v_b, + }) + } + op::BinOp::Comp(op) => { + let mut v_a = interpreter.exec(self.lhs())?; + let mut v_b = interpreter.exec(self.rhs())?; + Ok(Value::from(match op { + CompOp::Equal if v_a.is_object() => v_a == v_b, + CompOp::Equal => v_a == v_b, + CompOp::NotEqual if v_a.is_object() => v_a != v_b, + CompOp::NotEqual => v_a != v_b, + CompOp::StrictEqual if v_a.is_object() => v_a == v_b, + CompOp::StrictEqual => v_a == v_b, + CompOp::StrictNotEqual if v_a.is_object() => v_a != v_b, + CompOp::StrictNotEqual => v_a != v_b, + CompOp::GreaterThan => v_a.to_number() > v_b.to_number(), + CompOp::GreaterThanOrEqual => v_a.to_number() >= v_b.to_number(), + CompOp::LessThan => v_a.to_number() < v_b.to_number(), + CompOp::LessThanOrEqual => v_a.to_number() <= v_b.to_number(), + CompOp::In => { + if !v_b.is_object() { + panic!("TypeError: {} is not an Object.", v_b); + } + let key = interpreter.to_property_key(&mut v_a); + interpreter.has_property(&mut v_b, &key) + } + })) + } + op::BinOp::Log(op) => { + // turn a `Value` into a `bool` + let to_bool = |value| bool::from(&value); + Ok(match op { + LogOp::And => Value::from( + to_bool(interpreter.exec(self.lhs())?) + && to_bool(interpreter.exec(self.rhs())?), + ), + LogOp::Or => Value::from( + to_bool(interpreter.exec(self.lhs())?) + || to_bool(interpreter.exec(self.rhs())?), + ), + }) + } + op::BinOp::Assign(op) => match self.lhs() { + Node::Local(ref name) => { + let v_a = interpreter.realm().environment.get_binding_value(&name); + let v_b = interpreter.exec(self.rhs())?; + let value = Self::run_assign(op, v_a, v_b); + interpreter + .realm + .environment + .set_mutable_binding(&name, value.clone(), true); + Ok(value) + } + Node::GetConstField(ref obj, ref field) => { + let v_r_a = interpreter.exec(obj)?; + let v_a = v_r_a.get_field_slice(field); + let v_b = interpreter.exec(self.rhs())?; + let value = Self::run_assign(op, v_a, v_b); + v_r_a.set_field_slice(&field.clone(), value.clone()); + Ok(value) + } + _ => Ok(Value::undefined()), + }, + } + } +} + +impl BinOp { + fn run_assign(op: AssignOp, v_a: Value, v_b: Value) -> Value { + match op { + AssignOp::Add => v_a + v_b, + AssignOp::Sub => v_a - v_b, + AssignOp::Mul => v_a * v_b, + AssignOp::Exp => v_a.as_num_to_power(v_b), + AssignOp::Div => v_a / v_b, + AssignOp::Mod => v_a % v_b, + AssignOp::And => v_a & v_b, + AssignOp::Or => v_a | v_b, + AssignOp::Xor => v_a ^ v_b, + AssignOp::Shl => v_a << v_b, + AssignOp::Shr => v_a << v_b, + } + } +} diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 7f3b6f78125..a05753bebb8 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -1,7 +1,4 @@ -use crate::exec; -use crate::exec::Executor; -use crate::forward; -use crate::realm::Realm; +use crate::{exec, exec::Interpreter, forward, realm::Realm}; #[test] fn empty_let_decl_undefined() { @@ -47,7 +44,7 @@ fn object_field_set() { #[test] fn spread_with_arguments() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let scenario = r#" const a = [1, "test", 3, 4]; @@ -74,7 +71,7 @@ fn spread_with_arguments() { #[test] fn array_rest_with_arguments() { let realm = Realm::create(); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); let scenario = r#" var b = [4, 5, 6] @@ -329,10 +326,7 @@ fn test_for_loop() { a "#; - assert_eq!( - exec(body_should_not_execute_on_false_condition), - String::from("0") - ); + assert_eq!(&exec(body_should_not_execute_on_false_condition), "0"); let inner_scope = r#" for (let i = 0;false;) {} diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 8f9bfda549d..62321d4de06 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -40,7 +40,7 @@ pub mod realm; pub mod syntax; use crate::{ builtins::value::ResultValue, - exec::{Executor, Interpreter}, + exec::Interpreter, realm::Realm, syntax::{ast::node::Node, lexer::Lexer, parser::Parser}, }; @@ -64,7 +64,7 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String { return error_string; } }; - let result = engine.run(&expr); + let result = engine.exec(&expr); match result { Ok(v) => v.to_string(), Err(v) => format!("{}: {}", "Error", v.to_string()), @@ -78,7 +78,7 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String { pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { // Setup executor match parser_expr(src) { - Ok(expr) => engine.run(&expr), + Ok(expr) => engine.exec(&expr), Err(e) => { eprintln!("{}", e); std::process::exit(1); @@ -90,6 +90,6 @@ pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue { pub fn exec(src: &str) -> String { // Create new Realm let realm = Realm::create(); - let mut engine: Interpreter = Executor::new(realm); + let mut engine = Interpreter::new(realm); forward(&mut engine, src) } diff --git a/boa/src/syntax/ast/node/array.rs b/boa/src/syntax/ast/node/array.rs new file mode 100644 index 00000000000..f214163e077 --- /dev/null +++ b/boa/src/syntax/ast/node/array.rs @@ -0,0 +1,70 @@ +//! Array declaration node. + +use super::{join_nodes, Node}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An array is an ordered collection of data (either primitive or object depending upon the +/// language). +/// +/// Arrays are used to store multiple values in a single variable. +/// This is compared to a variable that can store only one value. +/// +/// Each item in an array has a number attached to it, called a numeric index, that allows you +/// to access it. In JavaScript, arrays start at index zero and can be manipulated with various +/// methods. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ArrayDecl { + arr: Box<[Node]>, +} + +impl ArrayDecl { + /// Creates an `ArrayDecl` AST node. + pub fn array_decl(nodes: N) -> Self + where + N: Into>, + { + Self { arr: nodes.into() } + } +} + +impl AsRef<[Node]> for ArrayDecl { + fn as_ref(&self) -> &[Node] { + &self.arr + } +} + +impl From for ArrayDecl +where + T: Into>, +{ + fn from(decl: T) -> Self { + Self { arr: decl.into() } + } +} + +impl fmt::Display for ArrayDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + join_nodes(f, &self.arr)?; + f.write_str("]") + } +} + +impl From for Node { + fn from(arr: ArrayDecl) -> Self { + Self::ArrayDecl(arr) + } +} diff --git a/boa/src/syntax/ast/node/arrow_function.rs b/boa/src/syntax/ast/node/arrow_function.rs new file mode 100644 index 00000000000..926448a8010 --- /dev/null +++ b/boa/src/syntax/ast/node/arrow_function.rs @@ -0,0 +1,62 @@ +//! Arrow function declaration node. + +use super::{join_nodes, FormalParameter, Node}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An arrow function expression is a syntactically compact alternative to a regular function +/// expression. +/// +/// Arrow function expressions are ill suited as methods, and they cannot be used as +/// constructors. Arrow functions cannot be used as constructors and will throw an error when +/// used with new. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ArrowFunctionDecl { + pub(crate) params: Box<[FormalParameter]>, + pub(crate) body: Box, +} + +impl ArrowFunctionDecl { + /// Creates a new `ArrowFunctionDecl` AST node. + pub(crate) fn new(params: P, body: B) -> Self + where + P: Into>, + B: Into>, + { + Self { + params: params.into(), + body: body.into(), + } + } + + /// Implements the display formatting with indentation. + pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + write!(f, "(")?; + join_nodes(f, &self.params)?; + f.write_str(") => ")?; + self.body.display(f, indentation) + } +} + +impl fmt::Display for ArrowFunctionDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(decl: ArrowFunctionDecl) -> Self { + Self::ArrowFunctionDecl(decl) + } +} diff --git a/boa/src/syntax/ast/node/block.rs b/boa/src/syntax/ast/node/block.rs new file mode 100644 index 00000000000..804f3a7c4a3 --- /dev/null +++ b/boa/src/syntax/ast/node/block.rs @@ -0,0 +1,86 @@ +//! Block AST node. + +use super::Node; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// A `block` statement (or compound statement in other languages) is used to group zero or +/// more statements. +/// +/// The block statement is often called compound statement in other languages. +/// It allows you to use multiple statements where JavaScript expects only one statement. +/// Combining statements into blocks is a common practice in JavaScript. The opposite behavior +/// is possible using an empty statement, where you provide no statement, although one is +/// required. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Block { + hoistable: Box<[Node]>, + statements: Box<[Node]>, +} + +impl Block { + /// Creates a `Block` AST node. + pub fn new(hoistable: H, statements: S) -> Self + where + H: Into>, + S: Into>, + { + Self { + hoistable: hoistable.into(), + statements: statements.into(), + } + } + + /// Gets the list of hoistable statements. + pub fn hoistable(&self) -> &[Node] { + &self.hoistable + } + + /// Gets the list of non-hoistable statements. + pub fn statements(&self) -> &[Node] { + &self.statements + } + + /// Implements the display formatting with indentation. + pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + writeln!(f, "{{")?; + for node in self.hoistable.iter().chain(self.statements.iter()) { + node.display(f, indentation + 1)?; + + match node { + Node::Block(_) + | Node::If(_, _, _) + | Node::Switch(_, _, _) + | Node::FunctionDecl(_, _, _) + | Node::WhileLoop(_, _) + | Node::StatementList(_) => {} + _ => write!(f, ";")?, + } + writeln!(f)?; + } + write!(f, "{}}}", " ".repeat(indentation)) + } +} + +impl fmt::Display for Block { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(block: Block) -> Self { + Self::Block(block) + } +} diff --git a/boa/src/syntax/ast/node.rs b/boa/src/syntax/ast/node/mod.rs similarity index 87% rename from boa/src/syntax/ast/node.rs rename to boa/src/syntax/ast/node/mod.rs index b2288af8ea3..a66afb571c1 100644 --- a/boa/src/syntax/ast/node.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -1,11 +1,22 @@ //! This module implements the `Node` structure, which composes the AST. +pub mod array; +pub mod arrow_function; +pub mod block; +pub mod operator; + +pub use self::{ + array::ArrayDecl, + arrow_function::ArrowFunctionDecl, + block::Block, + operator::{Assign, BinOp}, +}; use crate::syntax::ast::{ constant::Const, - op::{BinOp, Operator, UnaryOp}, + op::{Operator, UnaryOp}, }; use gc::{Finalize, Trace}; -use std::fmt; +use std::fmt::{self, Display}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -14,76 +25,20 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub enum Node { - /// An array is an ordered collection of data (either primitive or object depending upon the - /// language). - /// - /// Arrays are used to store multiple values in a single variable. - /// This is compared to a variable that can store only one value. - /// - /// Each item in an array has a number attached to it, called a numeric index, that allows you - /// to access it. In JavaScript, arrays start at index zero and can be manipulated with various - /// methods. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array - ArrayDecl(Box<[Node]>), + /// Array declaration node. [More information](./array/struct.ArrayDecl.html). + ArrayDecl(ArrayDecl), - /// An arrow function expression is a syntactically compact alternative to a regular function - /// expression. - /// - /// Arrow function expressions are ill suited as methods, and they cannot be used as - /// constructors. Arrow functions cannot be used as constructors and will throw an error when - /// used with new. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions - ArrowFunctionDecl(Box<[FormalParameter]>, Box), + /// An arrow function expression node. [More information](./arrow_function/struct.ArrowFunctionDecl.html). + ArrowFunctionDecl(ArrowFunctionDecl), - /// An assignment operator assigns a value to its left operand based on the value of its right - /// operand. - /// - /// Assignment operator (`=`), assigns the value of its right operand to its left operand. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators - Assign(Box, Box), + /// An assignment operator node. [More information](./operator/struct.Assign.html). + Assign(Assign), - /// Binary operators requires two operands, one before the operator and one after the operator. - /// - /// More information: - /// - [MDN documentation][mdn] - /// - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators - BinOp(BinOp, Box, Box), + /// A binary operator node. [More information](./operator/struct.BinOp.html). + BinOp(BinOp), - /// A `block` statement (or compound statement in other languages) is used to group zero or - /// more statements. - /// - /// The block statement is often called compound statement in other languages. - /// It allows you to use multiple statements where JavaScript expects only one statement. - /// Combining statements into blocks is a common practice in JavaScript. The opposite behavior - /// is possible using an empty statement, where you provide no statement, although one is - /// required. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-BlockStatement - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block - Block(Box<[Node]>), + /// A Block node. [More information](./block/struct.Block.html). + Block(Block), /// The `break` statement terminates the current loop, switch, or label statement and transfers /// program control to the statement following the terminated statement. @@ -496,12 +451,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#prod-TryStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch - Try( - Box, - Option>, - Option>, - Option>, - ), + Try(Block, Option<(Option>, Block)>, Option), /// The JavaScript `this` keyword refers to the object it belongs to. /// @@ -564,7 +514,7 @@ pub enum Node { impl Operator for Node { fn get_assoc(&self) -> bool { match *self { - Self::UnaryOp(_, _) | Self::TypeOf(_) | Self::If(_, _, _) | Self::Assign(_, _) => false, + Self::UnaryOp(_, _) | Self::TypeOf(_) | Self::If(_, _, _) | Self::Assign(_) => false, _ => true, } } @@ -581,64 +531,29 @@ impl Operator for Node { | Self::UnaryOp(UnaryOp::Tilde, _) | Self::UnaryOp(UnaryOp::Minus, _) | Self::TypeOf(_) => 4, - Self::BinOp(op, _, _) => op.get_precedence(), + Self::BinOp(inner) => inner.op().get_precedence(), Self::If(_, _, _) => 15, // 16 should be yield - Self::Assign(_, _) => 17, + Self::Assign(_) => 17, _ => 19, } } } -impl fmt::Display for Node { +impl Display for Node { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display(f, 0) } } impl Node { - /// Creates an `ArrayDecl` AST node. - pub fn array_decl(nodes: N) -> Self - where - N: Into>, - { - Self::ArrayDecl(nodes.into()) - } - - /// Creates an `ArraowFunctionDecl` AST node. - pub fn arrow_function_decl(params: P, body: B) -> Self - where - P: Into>, - B: Into>, - { - Self::ArrowFunctionDecl(params.into(), body.into()) - } - - /// Creates an `Assign` AST node. - pub fn assign(lhs: L, rhs: R) -> Self - where - L: Into>, - R: Into>, - { - Self::Assign(lhs.into(), rhs.into()) - } - - /// Creates a `BinOp` AST node. - pub fn bin_op(op: O, lhs: L, rhs: R) -> Self - where - O: Into, - L: Into>, - R: Into>, - { - Self::BinOp(op.into(), lhs.into(), rhs.into()) - } - - /// Creates a `Block` AST node. - pub fn block(nodes: N) -> Self - where - N: Into>, - { - Self::Block(nodes.into()) + /// Checks if the current node is a hoistable node. + pub(crate) fn is_hoistable(&self) -> bool { + match self { + Node::FunctionDecl(_, _, _) => true, + Node::VarDecl(decl) if decl.is_empty() => true, + _ => false, + } } /// Creates a `Break` AST node. @@ -857,25 +772,20 @@ impl Node { } /// Creates a `Try` AST node. - pub fn try_node(try_node: T, catch: OC, param: OP, finally: OF) -> Self + pub fn try_node(try_node: Block, catch: OC, finally: OF) -> Self where - T: Into>, - OC: Into>, - OP: Into>, - OF: Into>, - C: Into>, - P: Into>, - F: Into>, + OC: Into>, Block)>>, + OF: Into>, { - let catch = catch.into().map(C::into); - let finally = finally.into().map(F::into); + let catch = catch.into(); + let finally = finally.into(); debug_assert!( catch.is_some() || finally.is_some(), "try/catch must have a catch or a finally block" ); - Self::Try(try_node.into(), catch, param.into().map(P::into), finally) + Self::Try(try_node, catch, finally) } /// Creates a `This` AST node. @@ -923,7 +833,7 @@ impl Node { } Self::ForLoop(_, _, _, _) => write!(f, "for loop"), // TODO Self::This => write!(f, "this"), - Self::Try(_, _, _, _) => write!(f, "try/catch/finally"), // TODO + Self::Try(_, _, _) => write!(f, "try/catch/finally"), // TODO Self::Break(ref l) => write!( f, "break{}", @@ -943,24 +853,7 @@ impl Node { } ), Self::Spread(ref node) => write!(f, "...{}", node), - Self::Block(ref block) => { - writeln!(f, "{{")?; - for node in block.iter() { - node.display(f, indentation + 1)?; - - match node { - Self::Block(_) - | Self::If(_, _, _) - | Self::Switch(_, _, _) - | Self::FunctionDecl(_, _, _) - | Self::WhileLoop(_, _) - | Self::StatementList(_) => {} - _ => write!(f, ";")?, - } - writeln!(f)?; - } - write!(f, "{}}}", indent) - } + Self::Block(ref block) => block.display(f, indentation), Self::StatementList(ref list) => { for node in list.iter() { node.display(f, indentation + 1)?; @@ -1062,11 +955,7 @@ impl Node { } f.write_str("}") } - Self::ArrayDecl(ref arr) => { - f.write_str("[")?; - join_nodes(f, arr)?; - f.write_str("]") - } + Self::ArrayDecl(ref arr) => Display::fmt(arr, f), Self::FunctionDecl(ref name, ref _args, ref node) => { write!(f, "function {} {{", name)?; //join_nodes(f, args)?; TODO: port @@ -1083,18 +972,13 @@ impl Node { f.write_str("} ")?; node.display(f, indentation + 1) } - Self::ArrowFunctionDecl(ref args, ref node) => { - write!(f, "(")?; - join_nodes(f, args)?; - f.write_str(") => ")?; - node.display(f, indentation) - } - Self::BinOp(ref op, ref a, ref b) => write!(f, "{} {} {}", a, op, b), + Self::ArrowFunctionDecl(ref decl) => decl.display(f, indentation), + Self::BinOp(ref op) => Display::fmt(op, f), Self::UnaryOp(ref op, ref a) => write!(f, "{}{}", op, a), Self::Return(Some(ref ex)) => write!(f, "return {}", ex), Self::Return(None) => write!(f, "return"), Self::Throw(ref ex) => write!(f, "throw {}", ex), - Self::Assign(ref ref_e, ref val) => write!(f, "{} = {}", ref_e, val), + Self::Assign(ref op) => Display::fmt(op, f), Self::VarDecl(ref vars) | Self::LetDecl(ref vars) => { if let Self::VarDecl(_) = *self { f.write_str("var ")?; @@ -1124,7 +1008,7 @@ impl Node { /// Utility to join multiple Nodes into a single string. fn join_nodes(f: &mut fmt::Formatter<'_>, nodes: &[N]) -> fmt::Result where - N: fmt::Display, + N: Display, { let mut first = true; for e in nodes { @@ -1132,7 +1016,7 @@ where f.write_str(", ")?; } first = false; - write!(f, "{}", e)?; + Display::fmt(e, f)?; } Ok(()) } @@ -1173,7 +1057,7 @@ impl FormalParameter { } } -impl fmt::Display for FormalParameter { +impl Display for FormalParameter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_rest_param { write!(f, "...")?; diff --git a/boa/src/syntax/ast/node/operator.rs b/boa/src/syntax/ast/node/operator.rs new file mode 100644 index 00000000000..67b961fde7a --- /dev/null +++ b/boa/src/syntax/ast/node/operator.rs @@ -0,0 +1,118 @@ +use super::Node; +use crate::syntax::ast::op; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An assignment operator assigns a value to its left operand based on the value of its right +/// operand. +/// +/// Assignment operator (`=`), assigns the value of its right operand to its left operand. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Assign { + lhs: Box, + rhs: Box, +} + +impl Assign { + /// Creates an `Assign` AST node. + pub fn new(lhs: L, rhs: R) -> Self + where + L: Into, + R: Into, + { + Self { + lhs: Box::new(lhs.into()), + rhs: Box::new(rhs.into()), + } + } + + /// Gets the left hand side of the assignment operation. + pub fn lhs(&self) -> &Node { + &self.lhs + } + + /// Gets the right hand side of the assignment operation. + pub fn rhs(&self) -> &Node { + &self.rhs + } +} + +impl fmt::Display for Assign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} = {}", self.lhs, self.rhs) + } +} + +impl From for Node { + fn from(op: Assign) -> Self { + Self::Assign(op) + } +} + +/// Binary operators requires two operands, one before the operator and one after the operator. +/// +/// More information: +/// - [MDN documentation][mdn] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct BinOp { + op: op::BinOp, + lhs: Box, + rhs: Box, +} + +impl BinOp { + /// Creates a `BinOp` AST node. + pub fn new(op: O, lhs: L, rhs: R) -> Self + where + O: Into, + L: Into, + R: Into, + { + Self { + op: op.into(), + lhs: Box::new(lhs.into()), + rhs: Box::new(rhs.into()), + } + } + + /// Gets the binary operation of the node. + pub fn op(&self) -> op::BinOp { + self.op + } + + /// Gets the left hand side of the binary operation. + pub fn lhs(&self) -> &Node { + &self.lhs + } + + /// Gets the right hand side of the binary operation. + pub fn rhs(&self) -> &Node { + &self.rhs + } +} + +impl fmt::Display for BinOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.lhs, self.op, self.rhs) + } +} + +impl From for Node { + fn from(op: BinOp) -> Self { + Self::BinOp(op) + } +} diff --git a/boa/src/syntax/ast/op.rs b/boa/src/syntax/ast/op.rs index baac3949df8..5eede0f44a3 100644 --- a/boa/src/syntax/ast/op.rs +++ b/boa/src/syntax/ast/op.rs @@ -1,6 +1,6 @@ //! This module implements various structure for logic handling. -use gc::{Finalize, Trace}; +use gc::{unsafe_empty_trace, Finalize, Trace}; use std::fmt::{Display, Formatter, Result}; #[cfg(feature = "serde")] @@ -26,7 +26,7 @@ pub trait Operator { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Arithmetic #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum NumOp { /// The addition operator produces the sum of numeric operands or string concatenation. /// @@ -124,6 +124,10 @@ impl Display for NumOp { } } +unsafe impl Trace for NumOp { + unsafe_empty_trace!(); +} + /// A unary operator is one that takes a single operand/argument and performs an operation. /// /// A unary operation is an operation with only one operand. This operand comes either @@ -137,7 +141,7 @@ impl Display for NumOp { /// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum UnaryOp { /// The increment operator increments (adds one to) its operand and returns a value. /// @@ -336,6 +340,10 @@ impl Display for UnaryOp { } } +unsafe impl Trace for UnaryOp { + unsafe_empty_trace!(); +} + /// A bitwise operator is an operator used to perform bitwise operations /// on bit patterns or binary numerals that involve the manipulation of individual bits. /// @@ -344,7 +352,7 @@ impl Display for UnaryOp { /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum BitOp { /// Performs the AND operation on each pair of bits. a AND b yields 1 only if both a and b are 1. /// @@ -447,6 +455,10 @@ impl Display for BitOp { } } +unsafe impl Trace for BitOp { + unsafe_empty_trace!(); +} + /// A comparison operator compares its operands and returns a logical value based on whether the comparison is true. /// /// The operands can be numerical, string, logical, or object values. Strings are compared based on standard @@ -463,7 +475,7 @@ impl Display for BitOp { /// [spec]: tc39.es/ecma262/#sec-testing-and-comparison-operations /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Comparison #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum CompOp { /// The equality operator converts the operands if they are not of the same type, then applies strict comparison. /// @@ -614,6 +626,10 @@ impl Display for CompOp { } } +unsafe impl Trace for CompOp { + unsafe_empty_trace!(); +} + /// Logical operators are typically used with Boolean (logical) values; when they are, they return a Boolean value. /// /// However, the `&&` and `||` operators actually return the value of one of the specified operands, @@ -626,7 +642,7 @@ impl Display for CompOp { /// [spec]: https://tc39.es/ecma262/#sec-binary-logical-operators /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Logical #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum LogOp { /// The logical AND operator returns the value of the first operand if it can be coerced into `false`; /// otherwise, it returns the second operand. @@ -668,9 +684,13 @@ impl Display for LogOp { } } +unsafe impl Trace for LogOp { + unsafe_empty_trace!(); +} + /// This represents a binary operation between two values. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum BinOp { /// Numeric operation. /// @@ -773,6 +793,10 @@ impl Display for BinOp { } } +unsafe impl Trace for BinOp { + unsafe_empty_trace!(); +} + /// An assignment operator assigns a value to its left operand based on the value of its right operand. /// /// The simple assignment operator is equal (`=`), which assigns the value of its right operand to its @@ -787,7 +811,7 @@ impl Display for BinOp { /// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +#[derive(Clone, Copy, Debug, Finalize, PartialEq)] pub enum AssignOp { /// The addition assignment operator adds the value of the right operand to a variable and assigns the result to the variable. /// @@ -928,6 +952,10 @@ pub enum AssignOp { // TODO: Add UShl (unsigned shift left). } +unsafe impl Trace for AssignOp { + unsafe_empty_trace!(); +} + impl Display for AssignOp { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!( diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index b8166fe97da..275cdaab723 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -10,7 +10,7 @@ use super::AssignmentExpression; use crate::syntax::{ ast::{ - node::{FormalParameter, Node}, + node::{ArrowFunctionDecl, FormalParameter, Node}, punc::Punctuator, token::TokenKind, }, @@ -57,9 +57,9 @@ impl ArrowFunction { } impl TokenParser for ArrowFunction { - type Output = Node; + type Output = ArrowFunctionDecl; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; let params = if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind { // CoverParenthesizedExpressionAndArrowParameterList @@ -90,7 +90,7 @@ impl TokenParser for ArrowFunction { let body = ConciseBody::new(self.allow_in).parse(cursor)?; - Ok(Node::arrow_function_decl(params, body)) + Ok(ArrowFunctionDecl::new(params, body)) } } diff --git a/boa/src/syntax/parser/expression/assignment/exponentiation.rs b/boa/src/syntax/parser/expression/assignment/exponentiation.rs index 5da2b310326..8acc765ebbd 100644 --- a/boa/src/syntax/parser/expression/assignment/exponentiation.rs +++ b/boa/src/syntax/parser/expression/assignment/exponentiation.rs @@ -10,8 +10,8 @@ use crate::syntax::{ ast::{ keyword::Keyword, - node::Node, - op::{BinOp, NumOp}, + node::{BinOp, Node}, + op::NumOp, punc::Punctuator, token::TokenKind, }, @@ -80,11 +80,7 @@ impl TokenParser for ExponentiationExpression { let lhs = UpdateExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; if let Some(tok) = cursor.next() { if let TokenKind::Punctuator(Punctuator::Exp) = tok.kind { - return Ok(Node::bin_op( - BinOp::Num(NumOp::Exp), - lhs, - self.parse(cursor)?, - )); + return Ok(Node::from(BinOp::new(NumOp::Exp, lhs, self.parse(cursor)?))); } else { cursor.back(); } diff --git a/boa/src/syntax/parser/expression/assignment/mod.rs b/boa/src/syntax/parser/expression/assignment/mod.rs index bcc3079f772..e11e1b82b89 100644 --- a/boa/src/syntax/parser/expression/assignment/mod.rs +++ b/boa/src/syntax/parser/expression/assignment/mod.rs @@ -13,7 +13,12 @@ mod exponentiation; use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{ + keyword::Keyword, + node::{Assign, BinOp, Node}, + punc::Punctuator, + token::TokenKind, + }, parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; pub(super) use exponentiation::ExponentiationExpression; @@ -85,7 +90,8 @@ impl TokenParser for AssignmentExpression { self.allow_yield, self.allow_await, ) - .parse(cursor); + .parse(cursor) + .map(Node::ArrowFunctionDecl); } } } @@ -94,6 +100,7 @@ impl TokenParser for AssignmentExpression { if let Some(node) = ArrowFunction::new(self.allow_in, self.allow_yield, self.allow_await) .try_parse(cursor) + .map(Node::ArrowFunctionDecl) { return Ok(node); } @@ -103,17 +110,16 @@ impl TokenParser for AssignmentExpression { let mut lhs = ConditionalExpression::new(self.allow_in, self.allow_yield, self.allow_await) .parse(cursor)?; - // let mut lhs = self.read_block()?; if let Some(tok) = cursor.next() { match tok.kind { TokenKind::Punctuator(Punctuator::Assign) => { - lhs = Node::assign(lhs, self.parse(cursor)?) + lhs = Node::from(Assign::new(lhs, self.parse(cursor)?)); } TokenKind::Punctuator(p) if p.as_binop().is_some() => { let expr = self.parse(cursor)?; let binop = p.as_binop().expect("binop disappeared"); - lhs = Node::bin_op(binop, lhs, expr); + lhs = Node::from(BinOp::new(binop, lhs, expr)); } _ => { cursor.back(); diff --git a/boa/src/syntax/parser/expression/left_hand_side/arguments.rs b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs index 177e9d3ad1a..330b804c7dc 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/arguments.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs @@ -45,7 +45,7 @@ impl Arguments { impl TokenParser for Arguments { type Output = Vec; - fn parse(self, cursor: &mut Cursor<'_>) -> Result, ParseError> { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Punctuator::OpenParen, "arguments")?; let mut args = Vec::new(); loop { diff --git a/boa/src/syntax/parser/expression/mod.rs b/boa/src/syntax/parser/expression/mod.rs index 464784449b5..6d33ea4a07c 100644 --- a/boa/src/syntax/parser/expression/mod.rs +++ b/boa/src/syntax/parser/expression/mod.rs @@ -18,7 +18,12 @@ mod update; use self::assignment::ExponentiationExpression; pub(super) use self::{assignment::AssignmentExpression, primary::Initializer}; use super::{AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser}; -use crate::syntax::ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}; +use crate::syntax::ast::{ + keyword::Keyword, + node::{BinOp, Node}, + punc::Punctuator, + token::TokenKind, +}; // For use in the expression! macro to allow for both Punctuator and Keyword parameters. // Always returns false. @@ -53,19 +58,19 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo match tok.kind { TokenKind::Punctuator(op) if $( op == $op )||* => { let _ = cursor.next().expect("token disappeared"); - lhs = Node::bin_op( + lhs = Node::from(BinOp::new( op.as_binop().expect("Could not get binary operation."), lhs, $lower::new($( self.$low_param ),*).parse(cursor)? - ) + )); } TokenKind::Keyword(op) if $( op == $op )||* => { let _ = cursor.next().expect("token disappeared"); - lhs = Node::bin_op( + lhs = Node::from(BinOp::new( op.as_binop().expect("Could not get binary operation."), lhs, $lower::new($( self.$low_param ),*).parse(cursor)? - ) + )); } _ => break } diff --git a/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs index 2dc7847e01b..e34ba511ac0 100644 --- a/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs @@ -11,10 +11,13 @@ mod tests; use crate::syntax::{ - ast::{constant::Const, node::Node, punc::Punctuator}, + ast::{ + constant::Const, + node::{ArrayDecl, Node}, + punc::Punctuator, + }, parser::{ - expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, - TokenParser, + expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -47,9 +50,9 @@ impl ArrayLiteral { } impl TokenParser for ArrayLiteral { - type Output = Node; + type Output = ArrayDecl; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { let mut elements = Vec::new(); loop { @@ -77,6 +80,6 @@ impl TokenParser for ArrayLiteral { cursor.next_if(Punctuator::Comma); } - Ok(Node::array_decl(elements)) + Ok(elements.into()) } } diff --git a/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs index 1da63882b69..7b59d16bfaf 100644 --- a/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs @@ -1,14 +1,17 @@ // ! Tests for array initializer parsing. use crate::syntax::{ - ast::{constant::Const, node::Node}, + ast::{ + constant::Const, + node::{ArrayDecl, Node}, + }, parser::tests::check_parser, }; /// Checks an empty array. #[test] fn check_empty() { - check_parser("[]", vec![Node::array_decl(Vec::new())]); + check_parser("[]", vec![ArrayDecl::from(Vec::new()).into()]); } /// Checks an array with empty slot. @@ -16,7 +19,7 @@ fn check_empty() { fn check_empty_slot() { check_parser( "[,]", - vec![Node::array_decl(vec![Node::Const(Const::Undefined)])], + vec![ArrayDecl::from(vec![Node::Const(Const::Undefined)]).into()], ); } @@ -25,11 +28,12 @@ fn check_empty_slot() { fn check_numeric_array() { check_parser( "[1, 2, 3]", - vec![Node::array_decl(vec![ + vec![ArrayDecl::from(vec![ Node::const_node(1), Node::const_node(2), Node::const_node(3), - ])], + ]) + .into()], ); } @@ -38,11 +42,12 @@ fn check_numeric_array() { fn check_numeric_array_trailing() { check_parser( "[1, 2, 3,]", - vec![Node::array_decl(vec![ + vec![ArrayDecl::from(vec![ Node::const_node(1), Node::const_node(2), Node::const_node(3), - ])], + ]) + .into()], ); } @@ -51,12 +56,13 @@ fn check_numeric_array_trailing() { fn check_numeric_array_elision() { check_parser( "[1, 2, , 3]", - vec![Node::array_decl(vec![ + vec![ArrayDecl::from(vec![ Node::const_node(1), Node::const_node(2), Node::Const(Const::Undefined), Node::const_node(3), - ])], + ]) + .into()], ); } @@ -65,13 +71,14 @@ fn check_numeric_array_elision() { fn check_numeric_array_repeated_elision() { check_parser( "[1, 2, ,, 3]", - vec![Node::array_decl(vec![ + vec![ArrayDecl::from(vec![ Node::const_node(1), Node::const_node(2), Node::Const(Const::Undefined), Node::Const(Const::Undefined), Node::const_node(3), - ])], + ]) + .into()], ); } @@ -80,11 +87,12 @@ fn check_numeric_array_repeated_elision() { fn check_combined() { check_parser( "[1, \"a\", 2]", - vec![Node::array_decl(vec![ + vec![ArrayDecl::from(vec![ Node::const_node(1), Node::const_node("a"), Node::const_node(2), - ])], + ]) + .into()], ); } @@ -93,10 +101,11 @@ fn check_combined() { fn check_combined_empty_str() { check_parser( "[1, \"\", 2]", - vec![Node::array_decl(vec![ + vec![ArrayDecl::from(vec![ Node::const_node(1), Node::const_node(""), Node::const_node(2), - ])], + ]) + .into()], ); } diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index 0022ddacc6c..4153fdcafb1 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -72,7 +72,9 @@ impl TokenParser for PrimaryExpression { Ok(expr) } TokenKind::Punctuator(Punctuator::OpenBracket) => { - ArrayLiteral::new(self.allow_yield, self.allow_await).parse(cursor) + ArrayLiteral::new(self.allow_yield, self.allow_await) + .parse(cursor) + .map(Node::ArrayDecl) } TokenKind::Punctuator(Punctuator::OpenBlock) => { ObjectLiteral::new(self.allow_yield, self.allow_await).parse(cursor) diff --git a/boa/src/syntax/parser/expression/tests.rs b/boa/src/syntax/parser/expression/tests.rs index 864783e05e6..456b7f05b0d 100644 --- a/boa/src/syntax/parser/expression/tests.rs +++ b/boa/src/syntax/parser/expression/tests.rs @@ -1,6 +1,6 @@ use crate::syntax::{ - ast::node::Node, - ast::op::{AssignOp, BinOp, BitOp, CompOp, NumOp}, + ast::node::{BinOp, Node}, + ast::op::{AssignOp, BitOp, CompOp, NumOp}, parser::tests::check_parser, }; @@ -9,75 +9,99 @@ use crate::syntax::{ fn check_numeric_operations() { check_parser( "a + b", - vec![Node::bin_op(NumOp::Add, Node::local("a"), Node::local("b"))], + vec![Node::from(BinOp::new( + NumOp::Add, + Node::local("a"), + Node::local("b"), + ))], ); check_parser( "a+1", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Add, Node::local("a"), Node::const_node(1), - )], + ))], ); check_parser( "a - b", - vec![Node::bin_op(NumOp::Sub, Node::local("a"), Node::local("b"))], + vec![Node::from(BinOp::new( + NumOp::Sub, + Node::local("a"), + Node::local("b"), + ))], ); check_parser( "a-1", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Sub, Node::local("a"), Node::const_node(1), - )], + ))], ); check_parser( "a / b", - vec![Node::bin_op(NumOp::Div, Node::local("a"), Node::local("b"))], + vec![Node::from(BinOp::new( + NumOp::Div, + Node::local("a"), + Node::local("b"), + ))], ); check_parser( "a/2", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Div, Node::local("a"), Node::const_node(2), - )], + ))], ); check_parser( "a * b", - vec![Node::bin_op(NumOp::Mul, Node::local("a"), Node::local("b"))], + vec![Node::from(BinOp::new( + NumOp::Mul, + Node::local("a"), + Node::local("b"), + ))], ); check_parser( "a*2", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Mul, Node::local("a"), Node::const_node(2), - )], + ))], ); check_parser( "a ** b", - vec![Node::bin_op(NumOp::Exp, Node::local("a"), Node::local("b"))], + vec![Node::from(BinOp::new( + NumOp::Exp, + Node::local("a"), + Node::local("b"), + ))], ); check_parser( "a**2", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Exp, Node::local("a"), Node::const_node(2), - )], + ))], ); check_parser( "a % b", - vec![Node::bin_op(NumOp::Mod, Node::local("a"), Node::local("b"))], + vec![Node::from(BinOp::new( + NumOp::Mod, + Node::local("a"), + Node::local("b"), + ))], ); check_parser( "a%2", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Mod, Node::local("a"), Node::const_node(2), - )], + ))], ); } @@ -86,19 +110,23 @@ fn check_numeric_operations() { fn check_complex_numeric_operations() { check_parser( "a + d*(b-3)+1", - vec![Node::bin_op( + vec![Node::from(BinOp::new( NumOp::Add, - Node::bin_op( + Node::from(BinOp::new( NumOp::Add, Node::local("a"), - Node::bin_op( + Node::from(BinOp::new( NumOp::Mul, Node::local("d"), - Node::bin_op(NumOp::Sub, Node::local("b"), Node::const_node(3)), - ), - ), + Node::from(BinOp::new( + NumOp::Sub, + Node::local("b"), + Node::const_node(3), + )), + )), + )), Node::const_node(1), - )], + ))], ); } @@ -107,87 +135,87 @@ fn check_complex_numeric_operations() { fn check_bitwise_operations() { check_parser( "a & b", - vec![Node::bin_op( - BinOp::Bit(BitOp::And), + vec![Node::from(BinOp::new( + BitOp::And, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a&b", - vec![Node::bin_op( - BinOp::Bit(BitOp::And), + vec![Node::from(BinOp::new( + BitOp::And, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a | b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Or), + vec![Node::from(BinOp::new( + BitOp::Or, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a|b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Or), + vec![Node::from(BinOp::new( + BitOp::Or, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a ^ b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Xor), + vec![Node::from(BinOp::new( + BitOp::Xor, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a^b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Xor), + vec![Node::from(BinOp::new( + BitOp::Xor, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a << b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Shl), + vec![Node::from(BinOp::new( + BitOp::Shl, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a<> b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Shr), + vec![Node::from(BinOp::new( + BitOp::Shr, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a>>b", - vec![Node::bin_op( - BinOp::Bit(BitOp::Shr), + vec![Node::from(BinOp::new( + BitOp::Shr, Node::local("a"), Node::local("b"), - )], + ))], ); } @@ -196,99 +224,103 @@ fn check_bitwise_operations() { fn check_assign_operations() { check_parser( "a += b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Add), + vec![Node::from(BinOp::new( + AssignOp::Add, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a -= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Sub), + vec![Node::from(BinOp::new( + AssignOp::Sub, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a *= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Mul), + vec![Node::from(BinOp::new( + AssignOp::Mul, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a **= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Exp), + vec![Node::from(BinOp::new( + AssignOp::Exp, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a /= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Div), + vec![Node::from(BinOp::new( + AssignOp::Div, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a %= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Mod), + vec![Node::from(BinOp::new( + AssignOp::Mod, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a &= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::And), + vec![Node::from(BinOp::new( + AssignOp::And, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a |= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Or), + vec![Node::from(BinOp::new( + AssignOp::Or, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a ^= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Xor), + vec![Node::from(BinOp::new( + AssignOp::Xor, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a <<= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Shl), + vec![Node::from(BinOp::new( + AssignOp::Shl, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a >>= b", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Shr), + vec![Node::from(BinOp::new( + AssignOp::Shr, Node::local("a"), Node::local("b"), - )], + ))], ); check_parser( "a %= 10 / 2", - vec![Node::bin_op( - BinOp::Assign(AssignOp::Mod), + vec![Node::from(BinOp::new( + AssignOp::Mod, Node::local("a"), - Node::bin_op(NumOp::Div, Node::const_node(10), Node::const_node(2)), - )], + Node::from(BinOp::new( + NumOp::Div, + Node::const_node(10), + Node::const_node(2), + )), + ))], ); } @@ -296,42 +328,42 @@ fn check_assign_operations() { fn check_relational_operations() { check_parser( "a < b", - vec![Node::bin_op( - BinOp::Comp(CompOp::LessThan), + vec![Node::from(BinOp::new( + CompOp::LessThan, Node::Local(String::from("a")), Node::Local(String::from("b")), - )], + ))], ); check_parser( "a > b", - vec![Node::bin_op( - BinOp::Comp(CompOp::GreaterThan), + vec![Node::from(BinOp::new( + CompOp::GreaterThan, Node::Local(String::from("a")), Node::Local(String::from("b")), - )], + ))], ); check_parser( "a <= b", - vec![Node::bin_op( - BinOp::Comp(CompOp::LessThanOrEqual), + vec![Node::from(BinOp::new( + CompOp::LessThanOrEqual, Node::Local(String::from("a")), Node::Local(String::from("b")), - )], + ))], ); check_parser( "a >= b", - vec![Node::bin_op( - BinOp::Comp(CompOp::GreaterThanOrEqual), + vec![Node::from(BinOp::new( + CompOp::GreaterThanOrEqual, Node::Local(String::from("a")), Node::Local(String::from("b")), - )], + ))], ); check_parser( "p in o", - vec![Node::bin_op( - BinOp::Comp(CompOp::In), + vec![Node::from(BinOp::new( + CompOp::In, Node::Local(String::from("p")), Node::Local(String::from("o")), - )], + ))], ); } diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs index cbc9c09fb0b..c6576a19985 100644 --- a/boa/src/syntax/parser/function/tests.rs +++ b/boa/src/syntax/parser/function/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::{FormalParameter, Node}, + ast::node::{ArrowFunctionDecl, BinOp, FormalParameter, Node}, ast::op::NumOp, parser::tests::check_parser, }; @@ -77,10 +77,11 @@ fn check_rest_operator() { fn check_arrow_only_rest() { check_parser( "(...a) => {}", - vec![Node::arrow_function_decl( + vec![ArrowFunctionDecl::new( vec![FormalParameter::new("a", None, true)], Node::StatementList(Box::new([])), - )], + ) + .into()], ); } @@ -89,14 +90,15 @@ fn check_arrow_only_rest() { fn check_arrow_rest() { check_parser( "(a, b, ...c) => {}", - vec![Node::arrow_function_decl( + vec![ArrowFunctionDecl::new( vec![ FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), FormalParameter::new("c", None, true), ], Node::StatementList(Box::new([])), - )], + ) + .into()], ); } @@ -105,17 +107,18 @@ fn check_arrow_rest() { fn check_arrow() { check_parser( "(a, b) => { return a + b; }", - vec![Node::arrow_function_decl( + vec![ArrowFunctionDecl::new( vec![ FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - Node::statement_list(vec![Node::return_node(Node::bin_op( + Node::statement_list(vec![Node::return_node(Node::from(BinOp::new( NumOp::Add, Node::local("a"), Node::local("b"), - ))]), - )], + )))]), + ) + .into()], ); } @@ -124,17 +127,18 @@ fn check_arrow() { fn check_arrow_semicolon_insertion() { check_parser( "(a, b) => { return a + b }", - vec![Node::arrow_function_decl( + vec![ArrowFunctionDecl::new( vec![ FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - Node::statement_list(vec![Node::return_node(Node::bin_op( + Node::statement_list(vec![Node::return_node(Node::from(BinOp::new( NumOp::Add, Node::local("a"), Node::local("b"), - ))]), - )], + )))]), + ) + .into()], ); } @@ -143,13 +147,14 @@ fn check_arrow_semicolon_insertion() { fn check_arrow_epty_return() { check_parser( "(a, b) => { return; }", - vec![Node::arrow_function_decl( + vec![ArrowFunctionDecl::new( vec![ FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], Node::statement_list(vec![Node::Return(None)]), - )], + ) + .into()], ); } @@ -158,12 +163,13 @@ fn check_arrow_epty_return() { fn check_arrow_empty_return_semicolon_insertion() { check_parser( "(a, b) => { return }", - vec![Node::arrow_function_decl( + vec![ArrowFunctionDecl::new( vec![ FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], Node::statement_list(vec![Node::Return(None)]), - )], + ) + .into()], ); } diff --git a/boa/src/syntax/parser/statement/block.rs b/boa/src/syntax/parser/statement/block.rs index fbb3c47e86c..bcfa29b54df 100644 --- a/boa/src/syntax/parser/statement/block.rs +++ b/boa/src/syntax/parser/statement/block.rs @@ -9,7 +9,12 @@ use super::{declaration::Declaration, Statement}; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{ + keyword::Keyword, + node::{self, Node}, + punc::Punctuator, + token::TokenKind, + }, parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; @@ -53,21 +58,21 @@ impl Block { } impl TokenParser for Block { - type Output = Node; + type Output = node::Block; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Punctuator::OpenBlock, "block")?; if let Some(tk) = cursor.peek(0) { if tk.kind == TokenKind::Punctuator(Punctuator::CloseBlock) { cursor.next(); - return Ok(Node::Block(Box::new([]))); + return Ok(node::Block::new(vec![], vec![])); } } let statement_list = StatementList::new(self.allow_yield, self.allow_await, self.allow_return, true) .parse(cursor) - .map(Node::block)?; + .map(|(hoistable, statements)| node::Block::new(hoistable, statements))?; cursor.expect(Punctuator::CloseBlock, "block")?; Ok(statement_list) @@ -113,10 +118,11 @@ impl StatementList { } impl TokenParser for StatementList { - type Output = Vec; + type Output = (Box<[Node]>, Box<[Node]>); fn parse(self, cursor: &mut Cursor<'_>) -> Result { - let mut items = Vec::new(); + let mut hoistable = Vec::new(); + let mut statements = Vec::new(); loop { match cursor.peek(0) { @@ -140,13 +146,18 @@ impl TokenParser for StatementList { let item = StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor)?; - items.push(item); + + if item.is_hoistable() { + hoistable.push(item); + } else { + statements.push(item); + } // move the cursor forward for any consecutive semicolon. while cursor.next_if(Punctuator::Semicolon).is_some() {} } - Ok(items) + Ok((hoistable.into_boxed_slice(), statements.into_boxed_slice())) } } diff --git a/boa/src/syntax/parser/statement/break_stm/tests.rs b/boa/src/syntax/parser/statement/break_stm/tests.rs index 1284e15d1ba..801fc811eed 100644 --- a/boa/src/syntax/parser/statement/break_stm/tests.rs +++ b/boa/src/syntax/parser/statement/break_stm/tests.rs @@ -1,4 +1,7 @@ -use crate::syntax::{ast::node::Node, parser::tests::check_parser}; +use crate::syntax::{ + ast::node::{Block, Node}, + parser::tests::check_parser, +}; #[test] fn check_inline() { @@ -23,7 +26,7 @@ fn check_inline_block_semicolon_insertion() { "while (true) {break}", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Break(None)]), + Node::from(Block::new(vec![], vec![Node::Break(None)])), )], ); } @@ -36,7 +39,7 @@ fn check_new_line_semicolon_insertion() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::break_node("test")]), + Node::from(Block::new(vec![], vec![Node::break_node("test")])), )], ); } @@ -47,7 +50,7 @@ fn check_inline_block() { "while (true) {break;}", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Break(None)]), + Node::from(Block::new(vec![], vec![Node::Break(None)])), )], ); } @@ -60,7 +63,7 @@ fn check_new_line_block() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::break_node("test")]), + Node::from(Block::new(vec![], vec![Node::break_node("test")])), )], ); } @@ -73,7 +76,7 @@ fn check_new_line_block_empty() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Break(None)]), + Node::from(Block::new(vec![], vec![Node::Break(None)])), )], ); } @@ -86,7 +89,7 @@ fn check_new_line_block_empty_semicolon_insertion() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Break(None)]), + Node::from(Block::new(vec![], vec![Node::Break(None)])), )], ); } diff --git a/boa/src/syntax/parser/statement/continue_stm/tests.rs b/boa/src/syntax/parser/statement/continue_stm/tests.rs index f3a1472304a..dc36040ecf2 100644 --- a/boa/src/syntax/parser/statement/continue_stm/tests.rs +++ b/boa/src/syntax/parser/statement/continue_stm/tests.rs @@ -1,4 +1,7 @@ -use crate::syntax::{ast::node::Node, parser::tests::check_parser}; +use crate::syntax::{ + ast::node::{Block, Node}, + parser::tests::check_parser, +}; #[test] fn check_inline() { @@ -29,7 +32,7 @@ fn check_inline_block_semicolon_insertion() { "while (true) {continue}", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Continue(None)]), + Node::from(Block::new(vec![], vec![Node::Continue(None)])), )], ); } @@ -42,7 +45,7 @@ fn check_new_line_semicolon_insertion() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::continue_node("test")]), + Node::from(Block::new(vec![], vec![Node::continue_node("test")])), )], ); } @@ -53,7 +56,7 @@ fn check_inline_block() { "while (true) {continue;}", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Continue(None)]), + Node::from(Block::new(vec![], vec![Node::Continue(None)])), )], ); } @@ -66,7 +69,7 @@ fn check_new_line_block() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::continue_node("test")]), + Node::from(Block::new(vec![], vec![Node::continue_node("test")])), )], ); } @@ -79,7 +82,7 @@ fn check_new_line_block_empty() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Continue(None)]), + Node::from(Block::new(vec![], vec![Node::Continue(None)])), )], ); } @@ -92,7 +95,7 @@ fn check_new_line_block_empty_semicolon_insertion() { }", vec![Node::while_loop( Node::const_node(true), - Node::block(vec![Node::Continue(None)]), + Node::from(Block::new(vec![], vec![Node::Continue(None)])), )], ); } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index be0a0eb8525..14d2877256b 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -183,7 +183,7 @@ impl LexicalBinding { impl TokenParser for LexicalBinding { type Output = (String, Option); - fn parse(self, cursor: &mut Cursor<'_>) -> Result<(String, Option), ParseError> { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { let ident = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; let initializer = Initializer::new(self.allow_in, self.allow_yield, self.allow_await).try_parse(cursor); diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 7e818ee169e..3446db6edd7 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -8,7 +8,12 @@ //! [spec]: https://tc39.es/ecma262/#sec-for-statement use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{ + keyword::Keyword, + node::{Block, Node}, + punc::Punctuator, + token::TokenKind, + }, parser::{ expression::Expression, statement::declaration::Declaration, @@ -95,8 +100,9 @@ impl TokenParser for ForStatement { let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; - let for_node = Node::for_loop::<_, _, _, Node, Node, Node, _>(init, cond, step, body); + let for_loop = Node::for_loop::<_, _, _, Node, Node, Node, _>(init, cond, step, body); - Ok(Node::Block(Box::new([for_node]))) + // TODO: do not encapsulate the `for` in a block just to have an inner scope. + Ok(Node::from(Block::new(vec![], vec![for_loop]))) } } diff --git a/boa/src/syntax/parser/statement/iteration/tests.rs b/boa/src/syntax/parser/statement/iteration/tests.rs index 9c71ae2248e..0728c4e4795 100644 --- a/boa/src/syntax/parser/statement/iteration/tests.rs +++ b/boa/src/syntax/parser/statement/iteration/tests.rs @@ -1,6 +1,6 @@ use crate::syntax::{ - ast::node::Node, - ast::op::{AssignOp, BinOp, CompOp, UnaryOp}, + ast::node::{BinOp, Block, Node}, + ast::op::{AssignOp, CompOp, UnaryOp}, parser::tests::check_parser, }; @@ -12,11 +12,10 @@ fn check_do_while() { a += 1; } while (true)"#, vec![Node::do_while_loop( - Node::block(vec![Node::bin_op( - BinOp::Assign(AssignOp::Add), - Node::local("a"), - Node::const_node(1), - )]), + Node::from(Block::new( + vec![], + vec![BinOp::new(AssignOp::Add, Node::local("a"), Node::const_node(1)).into()], + )), Node::const_node(true), )], ); @@ -31,15 +30,18 @@ fn check_do_while_semicolon_insertion() { vec![ Node::var_decl(vec![(String::from("i"), Some(Node::const_node(0)))]), Node::do_while_loop( - Node::block(vec![Node::call( - Node::get_const_field(Node::local("console"), "log"), - vec![Node::const_node("hello")], - )]), - Node::bin_op( - BinOp::Comp(CompOp::LessThan), + Node::from(Block::new( + vec![], + vec![Node::call( + Node::get_const_field(Node::local("console"), "log"), + vec![Node::const_node("hello")], + )], + )), + Node::from(BinOp::new( + CompOp::LessThan, Node::unary_op(UnaryOp::IncrementPost, Node::local("i")), Node::const_node(10), - ), + )), ), Node::call( Node::get_const_field(Node::local("console"), "log"), diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 368ad3cb9f7..ab23b8eb8cf 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -140,6 +140,7 @@ impl TokenParser for Statement { TokenKind::Punctuator(Punctuator::OpenBlock) => { BlockStatement::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor) + .map(Node::from) } // TODO: https://tc39.es/ecma262/#prod-LabelledStatement // TokenKind::Punctuator(Punctuator::Semicolon) => { @@ -191,7 +192,7 @@ impl StatementList { impl TokenParser for StatementList { type Output = Vec; - fn parse(self, cursor: &mut Cursor<'_>) -> Result, ParseError> { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { let mut items = Vec::new(); loop { diff --git a/boa/src/syntax/parser/statement/try_stm/catch.rs b/boa/src/syntax/parser/statement/try_stm/catch.rs index addf8a88804..9810b3867d8 100644 --- a/boa/src/syntax/parser/statement/try_stm/catch.rs +++ b/boa/src/syntax/parser/statement/try_stm/catch.rs @@ -1,5 +1,9 @@ use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator}, + ast::{ + keyword::Keyword, + node::{self, Node}, + punc::Punctuator, + }, parser::{ statement::{block::Block, BindingIdentifier}, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -38,7 +42,7 @@ impl Catch { } impl TokenParser for Catch { - type Output = (Option, Option); + type Output = (Option, node::Block); fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Keyword::Catch, "try statement")?; @@ -53,8 +57,8 @@ impl TokenParser for Catch { // Catch block Ok(( - Some(Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?), catch_param, + Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?, )) } } diff --git a/boa/src/syntax/parser/statement/try_stm/finally.rs b/boa/src/syntax/parser/statement/try_stm/finally.rs index 5a7037becab..d187c3c1381 100644 --- a/boa/src/syntax/parser/statement/try_stm/finally.rs +++ b/boa/src/syntax/parser/statement/try_stm/finally.rs @@ -1,7 +1,7 @@ use crate::syntax::{ - ast::{keyword::Keyword, node::Node}, + ast::{keyword::Keyword, node}, parser::{ - statement::block::Block, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, + statement::block::Block, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -38,9 +38,9 @@ impl Finally { } impl TokenParser for Finally { - type Output = Node; + type Output = node::Block; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Keyword::Finally, "try statement")?; Ok(Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?) } diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs index aa015c00817..7646b88c99e 100644 --- a/boa/src/syntax/parser/statement/try_stm/mod.rs +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -68,10 +68,14 @@ impl TokenParser for TryStatement { )); } - let (catch, param) = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { - Catch::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)? + let catch = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { + Some( + Catch::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + .map(|(param, block)| (param.map(Box::new), block))?, + ) } else { - (None, None) + None }; let next_token = cursor.peek(0); @@ -87,11 +91,6 @@ impl TokenParser for TryStatement { None => None, }; - Ok(Node::try_node::<_, _, _, _, Node, Node, Node>( - try_clause, - catch, - param, - finally_block, - )) + Ok(Node::try_node(try_clause, catch, finally_block)) } } diff --git a/boa/src/syntax/parser/statement/try_stm/tests.rs b/boa/src/syntax/parser/statement/try_stm/tests.rs index cecf9bb1448..384436ccc66 100644 --- a/boa/src/syntax/parser/statement/try_stm/tests.rs +++ b/boa/src/syntax/parser/statement/try_stm/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::Node, + ast::node::{Block, Node}, parser::tests::{check_invalid, check_parser}, }; @@ -7,10 +7,9 @@ use crate::syntax::{ fn check_inline_with_empty_try_catch() { check_parser( "try { } catch(e) {}", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![]), - Node::block(vec![]), - Node::local("e"), + vec![Node::try_node( + Block::new(vec![], vec![]), + Some((Some(Box::new(Node::local("e"))), Block::new(vec![], vec![]))), None, )], ); @@ -20,13 +19,15 @@ fn check_inline_with_empty_try_catch() { fn check_inline_with_var_decl_inside_try() { check_parser( "try { var x = 1; } catch(e) {}", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])]), - Node::block(vec![]), - Node::local("e"), + vec![Node::try_node( + Block::new( + vec![], + vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])], + ), + Some((Some(Box::new(Node::local("e"))), Block::new(vec![], vec![]))), None, )], ); @@ -36,16 +37,24 @@ fn check_inline_with_var_decl_inside_try() { fn check_inline_with_var_decl_inside_catch() { check_parser( "try { var x = 1; } catch(e) { var x = 1; }", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])]), - Node::block(vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])]), - Node::local("e"), + vec![Node::try_node( + Block::new( + vec![], + vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])], + ), + Some(( + Some(Box::new(Node::local("e"))), + Block::new( + vec![], + vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])], + ), + )), None, )], ); @@ -55,11 +64,10 @@ fn check_inline_with_var_decl_inside_catch() { fn check_inline_with_empty_try_catch_finally() { check_parser( "try {} catch(e) {} finally {}", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![]), - Node::block(vec![]), - Node::local("e"), - Node::block(vec![]), + vec![Node::try_node( + Block::new(vec![], vec![]), + Some((Some(Box::new(Node::local("e"))), Block::new(vec![], vec![]))), + Block::new(vec![], vec![]), )], ); } @@ -68,11 +76,10 @@ fn check_inline_with_empty_try_catch_finally() { fn check_inline_with_empty_try_finally() { check_parser( "try {} finally {}", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![]), + vec![Node::try_node( + Block::new(vec![], vec![]), None, - None, - Node::block(vec![]), + Block::new(vec![], vec![]), )], ); } @@ -81,14 +88,16 @@ fn check_inline_with_empty_try_finally() { fn check_inline_with_empty_try_var_decl_in_finally() { check_parser( "try {} finally { var x = 1; }", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![]), - None, + vec![Node::try_node( + Block::new(vec![], vec![]), None, - Node::block(vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])]), + Block::new( + vec![], + vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])], + ), )], ); } @@ -97,13 +106,18 @@ fn check_inline_with_empty_try_var_decl_in_finally() { fn check_inline_empty_try_paramless_catch() { check_parser( "try {} catch { var x = 1; }", - vec![Node::try_node::<_, _, _, _, Node, Node, Node>( - Node::block(vec![]), - Node::block(vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])]), - None, + vec![Node::try_node( + Block::new(vec![], vec![]), + Some(( + None, + Block::new( + vec![], + vec![Node::var_decl(vec![( + String::from("x"), + Some(Node::const_node(1)), + )])], + ), + )), None, )], ); diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index ee613ff59f7..94470804d31 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -1,7 +1,11 @@ //! Tests for the parser. use super::Parser; -use crate::syntax::{ast::node::Node, ast::op::NumOp, lexer::Lexer}; +use crate::syntax::{ + ast::node::{Assign, BinOp, Node}, + ast::op::NumOp, + lexer::Lexer, +}; #[allow(clippy::result_unwrap_used)] pub(super) fn check_parser(js: &str, expr: L) @@ -45,9 +49,10 @@ fn check_construct_call_precedence() { fn assign_operator_precedence() { check_parser( "a = a + 1", - vec![Node::assign( + vec![Assign::new( Node::local("a"), - Node::bin_op(NumOp::Add, Node::local("a"), Node::const_node(1)), - )], + BinOp::new(NumOp::Add, Node::local("a"), Node::const_node(1)), + ) + .into()], ); } diff --git a/boa_cli/Cargo.toml b/boa_cli/Cargo.toml index 867db599e53..c841f1e07b3 100644 --- a/boa_cli/Cargo.toml +++ b/boa_cli/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" [dependencies] Boa = { path = "../boa", features = ["serde"] } structopt = "0.3.14" -serde_json = "1.0.52" +serde_json = "1.0.53" [target.x86_64-unknown-linux-gnu.dependencies] jemallocator = "0.3.2" diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 03dac27604e..08c480643a3 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -27,7 +27,7 @@ use boa::{ builtins::console::log, - exec::Executor, + exec::Interpreter, forward_val, realm::Realm, syntax::ast::{node::Node, token::Token}, @@ -177,7 +177,7 @@ pub fn main() -> Result<(), std::io::Error> { let realm = Realm::create().register_global_func("print", log); - let mut engine = Executor::new(realm); + let mut engine = Interpreter::new(realm); for file in &args.files { let buffer = read_to_string(file)?; diff --git a/boa_wasm/Cargo.toml b/boa_wasm/Cargo.toml index 12073267025..8b790ea1bef 100644 --- a/boa_wasm/Cargo.toml +++ b/boa_wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "boa_wasm" -version = "0.1.0" +version = "0.7.0" authors = ["Jason Williams "] description = "Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language." repository = "https://github.com/jasonwilliams/boa" diff --git a/boa_wasm/src/lib.rs b/boa_wasm/src/lib.rs index c8e88726193..809956e4404 100644 --- a/boa_wasm/src/lib.rs +++ b/boa_wasm/src/lib.rs @@ -1,5 +1,5 @@ use boa::{ - exec::{Executor, Interpreter}, + exec::Interpreter, realm::Realm, syntax::{ast::node::Node, lexer::Lexer, parser::Parser}, }; @@ -38,8 +38,8 @@ pub fn evaluate(src: &str) -> String { } // Create new Realm let realm = Realm::create(); - let mut engine: Interpreter = Executor::new(realm); - let result = engine.run(&node); + let mut engine = Interpreter::new(realm); + let result = engine.exec(&node); match result { Ok(v) => v.to_string(), Err(v) => format!("{}: {}", "error", v.to_string()), From 463285d97b0990e612fde57e39ca299898e154f5 Mon Sep 17 00:00:00 2001 From: Iban Eguia Moraza Date: Tue, 12 May 2020 17:33:05 +0200 Subject: [PATCH 2/2] Added some extra tests, still adding tests for hoisting, that seems to need a bit of rework --- Cargo.lock | 14 +- boa/Cargo.toml | 2 +- boa/src/builtins/function/mod.rs | 12 +- boa/src/builtins/value/conversions.rs | 6 + boa/src/exec/block.rs | 6 +- boa/src/exec/mod.rs | 27 ++-- boa/src/exec/operator.rs | 22 +-- boa/src/exec/tests.rs | 69 ++++++++ boa/src/syntax/ast/keyword.rs | 102 ++++++------ boa/src/syntax/ast/node/array.rs | 10 -- boa/src/syntax/ast/node/block.rs | 42 +++-- boa/src/syntax/ast/node/local.rs | 58 +++++++ boa/src/syntax/ast/node/mod.rs | 134 ++++++++-------- .../expression/assignment/arrow_function.rs | 6 +- .../parser/expression/left_hand_side/call.rs | 2 +- .../expression/left_hand_side/member.rs | 4 +- .../expression/primary/function_expression.rs | 2 +- .../syntax/parser/expression/primary/mod.rs | 10 +- .../primary/object_initializer/mod.rs | 4 +- .../primary/object_initializer/tests.rs | 10 +- boa/src/syntax/parser/expression/tests.rs | 150 +++++++++--------- boa/src/syntax/parser/function/mod.rs | 2 +- boa/src/syntax/parser/function/tests.rs | 14 +- .../statement/{block.rs => block/mod.rs} | 20 +-- .../syntax/parser/statement/block/tests.rs | 95 +++++++++++ .../syntax/parser/statement/break_stm/mod.rs | 26 ++- .../parser/statement/break_stm/tests.rs | 51 ++++-- .../parser/statement/continue_stm/mod.rs | 26 ++- .../parser/statement/continue_stm/tests.rs | 51 ++++-- .../parser/statement/declaration/lexical.rs | 2 +- .../parser/statement/declaration/tests.rs | 56 +++---- .../statement/iteration/for_statement.rs | 2 +- .../parser/statement/iteration/tests.rs | 28 ++-- boa/src/syntax/parser/statement/mod.rs | 20 ++- .../syntax/parser/statement/try_stm/catch.rs | 12 +- .../syntax/parser/statement/try_stm/mod.rs | 6 +- .../syntax/parser/statement/try_stm/tests.rs | 79 ++++----- boa/src/syntax/parser/statement/variable.rs | 2 +- boa/src/syntax/parser/tests.rs | 12 +- 39 files changed, 714 insertions(+), 482 deletions(-) create mode 100644 boa/src/syntax/ast/node/local.rs rename boa/src/syntax/parser/statement/{block.rs => block/mod.rs} (92%) create mode 100644 boa/src/syntax/parser/statement/block/tests.rs diff --git a/Cargo.lock b/Cargo.lock index dd9a1dda145..acc1be69e29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" -version = "2.33.0" +version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" dependencies = [ "ansi_term", "atty", @@ -253,8 +253,7 @@ checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" [[package]] name = "gc" version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4917b7233397091baf9136eec3c669c8551b097d69ca2b00a2606e5f07641d1" +source = "git+https://github.com/Razican/rust-gc.git?branch=box_str#fadf8eb29b55c27ef973ecc3395bd3c18de849bb" dependencies = [ "gc_derive", ] @@ -262,8 +261,7 @@ dependencies = [ [[package]] name = "gc_derive" version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5b968c8044a119af2671a52de57689cbf502d6686847abd9e6252ee4c39313" +source = "git+https://github.com/Razican/rust-gc.git?branch=box_str#fadf8eb29b55c27ef973ecc3395bd3c18de849bb" dependencies = [ "proc-macro2", "quote", @@ -680,9 +678,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7" +checksum = "dd1b5e337360b1fae433c59fcafa0c6b77c605e92540afa5221a7b81a9eca91d" dependencies = [ "proc-macro2", "quote", diff --git a/boa/Cargo.toml b/boa/Cargo.toml index 4ac6b80b228..1b4608e7112 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -11,7 +11,7 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"] edition = "2018" [dependencies] -gc = { version = "0.3.4", features = ["derive"] } +gc = { version = "0.3.4", features = ["derive"], git = "https://github.com/Razican/rust-gc.git", branch = "box_str" } serde_json = "1.0.53" rand = "0.7.3" num-traits = "0.2.11" diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 3b29d58fd6c..b1dae529dfd 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -169,7 +169,7 @@ impl Function { for i in 0..self.params.len() { let param = self.params.get(i).expect("Could not get param"); // Rest Parameters - if param.is_rest_param { + if param.is_rest_param() { self.add_rest_param(param, i, args_list, interpreter, &local_env); break; } @@ -228,7 +228,7 @@ impl Function { // Add argument bindings to the function environment for (i, param) in self.params.iter().enumerate() { // Rest Parameters - if param.is_rest_param { + if param.is_rest_param() { self.add_rest_param(param, i, args_list, interpreter, &local_env); break; } @@ -277,12 +277,12 @@ impl Function { // Create binding local_env .borrow_mut() - .create_mutable_binding(param.name.clone(), false); + .create_mutable_binding(param.name().to_owned(), false); // Set Binding to value local_env .borrow_mut() - .initialize_binding(¶m.name, array); + .initialize_binding(param.name(), array); } // Adds an argument to the environment @@ -295,12 +295,12 @@ impl Function { // Create binding local_env .borrow_mut() - .create_mutable_binding(param.name.clone(), false); + .create_mutable_binding(param.name().to_owned(), false); // Set Binding to value local_env .borrow_mut() - .initialize_binding(¶m.name, value); + .initialize_binding(param.name(), value); } } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 72ab5da5c63..5152aaa30a3 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -13,6 +13,12 @@ impl From for Value { } } +impl From> for Value { + fn from(value: Box) -> Self { + Self::string(value) + } +} + impl From<&Value> for String { fn from(value: &Value) -> Self { value.to_string() diff --git a/boa/src/exec/block.rs b/boa/src/exec/block.rs index 148b8c585a9..521e922024e 100644 --- a/boa/src/exec/block.rs +++ b/boa/src/exec/block.rs @@ -15,11 +15,7 @@ impl Executable for Block { } let mut obj = Value::null(); - for hoistable in self.hoistable() { - obj = interpreter.exec(hoistable)?; - } - - for statement in self.statements() { + for statement in self.as_ref() { obj = interpreter.exec(statement)?; // early return diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 43e67663243..3a722bd8327 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -300,7 +300,7 @@ impl Interpreter { Node::Local(ref name) => { self.realm .environment - .set_mutable_binding(name, value.clone(), true); + .set_mutable_binding(name.as_ref(), value.clone(), true); Ok(value) } Node::GetConstField(ref obj, ref field) => { @@ -328,7 +328,10 @@ impl Executable for Node { Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)), Node::Block(ref block) => block.run(interpreter), Node::Local(ref name) => { - let val = interpreter.realm().environment.get_binding_value(name); + let val = interpreter + .realm() + .environment + .get_binding_value(name.as_ref()); Ok(val) } Node::GetConstField(ref obj, ref field) => { @@ -517,9 +520,9 @@ impl Executable for Node { val.set_field_slice("length", Value::from(args.len())); // Set the name and assign it in the current environment - val.set_field_slice("name", Value::from(name.clone())); + val.set_field_slice("name", Value::from(name.as_ref())); interpreter.realm_mut().environment.create_mutable_binding( - name.clone(), + name.as_ref().to_owned(), false, VariableScope::Function, ); @@ -559,7 +562,7 @@ impl Executable for Node { val.set_field_slice("length", Value::from(args.len())); if let Some(name) = name { - val.set_field_slice("name", Value::from(name.clone())); + val.set_field_slice("name", Value::string(name.as_ref())); } Ok(val) @@ -619,7 +622,7 @@ impl Executable for Node { | Node::UnaryOp(_, _) => Value::boolean(true), _ => panic!("SyntaxError: wrong delete argument {}", self), }, - _ => unimplemented!(), + _ => unimplemented!("{:?}", op), }) } Node::New(ref call) => { @@ -668,7 +671,7 @@ impl Executable for Node { None => Value::undefined(), }; interpreter.realm_mut().environment.create_mutable_binding( - name.clone(), + name.as_ref().to_owned(), false, VariableScope::Function, ); @@ -687,7 +690,7 @@ impl Executable for Node { None => Value::undefined(), }; interpreter.realm_mut().environment.create_mutable_binding( - name.clone(), + name.as_ref().to_owned(), false, VariableScope::Block, ); @@ -703,7 +706,11 @@ impl Executable for Node { interpreter .realm_mut() .environment - .create_immutable_binding(name.clone(), false, VariableScope::Block); + .create_immutable_binding( + name.as_ref().to_owned(), + false, + VariableScope::Block, + ); let val = interpreter.exec(&value)?; interpreter .realm_mut() @@ -760,7 +767,7 @@ impl Executable for Node { // TODO: for now we can do nothing but return the value as-is interpreter.exec(node) } - ref i => unimplemented!("{}", i), + ref i => unimplemented!("{:?}", i), } } } diff --git a/boa/src/exec/operator.rs b/boa/src/exec/operator.rs index 266b4dd86cc..e4617bb59b5 100644 --- a/boa/src/exec/operator.rs +++ b/boa/src/exec/operator.rs @@ -15,23 +15,23 @@ impl Executable for Assign { let val = interpreter.exec(self.rhs())?; match self.lhs() { Node::Local(ref name) => { - if interpreter.realm().environment.has_binding(name) { + if interpreter.realm().environment.has_binding(name.as_ref()) { // Binding already exists interpreter.realm_mut().environment.set_mutable_binding( - &name, + name.as_ref(), val.clone(), true, ); } else { interpreter.realm_mut().environment.create_mutable_binding( - name.clone(), + name.as_ref().to_owned(), true, VariableScope::Function, ); interpreter .realm_mut() .environment - .initialize_binding(name, val.clone()); + .initialize_binding(name.as_ref(), val.clone()); } } Node::GetConstField(ref obj, ref field) => { @@ -118,13 +118,17 @@ impl Executable for BinOp { } op::BinOp::Assign(op) => match self.lhs() { Node::Local(ref name) => { - let v_a = interpreter.realm().environment.get_binding_value(&name); + let v_a = interpreter + .realm() + .environment + .get_binding_value(name.as_ref()); let v_b = interpreter.exec(self.rhs())?; let value = Self::run_assign(op, v_a, v_b); - interpreter - .realm - .environment - .set_mutable_binding(&name, value.clone(), true); + interpreter.realm.environment.set_mutable_binding( + name.as_ref(), + value.clone(), + true, + ); Ok(value) } Node::GetConstField(ref obj, ref field) => { diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index a05753bebb8..9ae7de6d21f 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -559,3 +559,72 @@ mod in_operator { exec(scenario); } } + +#[test] +fn var_decl_hoisting() { + let scenario = r#" + x = 5; + + var x; + x; + "#; + assert_eq!(&exec(scenario), "5"); + + let scenario = r#" + x = 5; + + var x = 10; + x; + "#; + assert_eq!(&exec(scenario), "10"); + + let scenario = r#" + x = y; + + var x = 10; + var y = 5; + + x; + "#; + assert_eq!(&exec(scenario), "10"); + + let scenario = r#" + var x = y; + + var y = 5; + x; + "#; + assert_eq!(&exec(scenario), "undefined"); + + let scenario = r#" + let y = x; + x = 5; + + var x = 10; + y; + "#; + assert_eq!(&exec(scenario), "undefined"); +} + +#[test] +fn function_decl_hoisting() { + let scenario = r#" + let a = hello(); + function hello() { return 5 } + + a; + "#; + assert_eq!(&exec(scenario), "5"); + + let scenario = r#" + x = y; + + var x; + var y = hello(); + + function hello() {return 5} + + x; + "#; + assert_eq!(&exec(scenario), "undefined"); +} diff --git a/boa/src/syntax/ast/keyword.rs b/boa/src/syntax/ast/keyword.rs index a76195e5a8c..69bfba4fb2b 100644 --- a/boa/src/syntax/ast/keyword.rs +++ b/boa/src/syntax/ast/keyword.rs @@ -8,12 +8,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords use crate::syntax::ast::op::{BinOp, CompOp}; -use std::{ - convert::TryInto, - error, - fmt::{Display, Error, Formatter}, - str::FromStr, -}; +use std::{convert::TryInto, error, fmt, str::FromStr}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -436,12 +431,55 @@ pub enum Keyword { } impl Keyword { + /// Gets the keyword as a binary operation, if this keyword is the `in` keyword. pub fn as_binop(self) -> Option { match self { Keyword::In => Some(BinOp::Comp(CompOp::In)), _ => None, } } + + /// Gets the keyword as a string. + pub fn as_str(self) -> &'static str { + match self { + Self::Await => "await", + Self::Break => "break", + Self::Case => "case", + Self::Catch => "catch", + Self::Class => "class", + Self::Continue => "continue", + Self::Const => "const", + Self::Debugger => "debugger", + Self::Default => "default", + Self::Delete => "delete", + Self::Do => "do", + Self::Else => "else", + Self::Enum => "enum", + Self::Extends => "extends", + Self::Export => "export", + Self::Finally => "finally", + Self::For => "for", + Self::Function => "function", + Self::If => "if", + Self::In => "in", + Self::InstanceOf => "instanceof", + Self::Import => "import", + Self::Let => "let", + Self::New => "new", + Self::Return => "return", + Self::Super => "super", + Self::Switch => "switch", + Self::This => "this", + Self::Throw => "throw", + Self::Try => "try", + Self::TypeOf => "typeof", + Self::Var => "var", + Self::Void => "void", + Self::While => "while", + Self::With => "with", + Self::Yield => "yield", + } + } } impl TryInto for Keyword { @@ -454,8 +492,8 @@ impl TryInto for Keyword { #[derive(Debug, Clone, Copy)] pub struct KeywordError; -impl Display for KeywordError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { +impl fmt::Display for KeywordError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "invalid token") } } @@ -515,49 +553,9 @@ impl FromStr for Keyword { } } } -impl Display for Keyword { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!( - f, - "{}", - match *self { - Self::Await => "await", - Self::Break => "break", - Self::Case => "case", - Self::Catch => "catch", - Self::Class => "class", - Self::Continue => "continue", - Self::Const => "const", - Self::Debugger => "debugger", - Self::Default => "default", - Self::Delete => "delete", - Self::Do => "do", - Self::Else => "else", - Self::Enum => "enum", - Self::Extends => "extends", - Self::Export => "export", - Self::Finally => "finally", - Self::For => "for", - Self::Function => "function", - Self::If => "if", - Self::In => "in", - Self::InstanceOf => "instanceof", - Self::Import => "import", - Self::Let => "let", - Self::New => "new", - Self::Return => "return", - Self::Super => "super", - Self::Switch => "switch", - Self::This => "this", - Self::Throw => "throw", - Self::Try => "try", - Self::TypeOf => "typeof", - Self::Var => "var", - Self::Void => "void", - Self::While => "while", - Self::With => "with", - Self::Yield => "yield", - } - ) + +impl fmt::Display for Keyword { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.as_str(), f) } } diff --git a/boa/src/syntax/ast/node/array.rs b/boa/src/syntax/ast/node/array.rs index f214163e077..55763df7c01 100644 --- a/boa/src/syntax/ast/node/array.rs +++ b/boa/src/syntax/ast/node/array.rs @@ -30,16 +30,6 @@ pub struct ArrayDecl { arr: Box<[Node]>, } -impl ArrayDecl { - /// Creates an `ArrayDecl` AST node. - pub fn array_decl(nodes: N) -> Self - where - N: Into>, - { - Self { arr: nodes.into() } - } -} - impl AsRef<[Node]> for ArrayDecl { fn as_ref(&self) -> &[Node] { &self.arr diff --git a/boa/src/syntax/ast/node/block.rs b/boa/src/syntax/ast/node/block.rs index 804f3a7c4a3..1360094b34a 100644 --- a/boa/src/syntax/ast/node/block.rs +++ b/boa/src/syntax/ast/node/block.rs @@ -25,37 +25,14 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct Block { - hoistable: Box<[Node]>, statements: Box<[Node]>, } impl Block { - /// Creates a `Block` AST node. - pub fn new(hoistable: H, statements: S) -> Self - where - H: Into>, - S: Into>, - { - Self { - hoistable: hoistable.into(), - statements: statements.into(), - } - } - - /// Gets the list of hoistable statements. - pub fn hoistable(&self) -> &[Node] { - &self.hoistable - } - - /// Gets the list of non-hoistable statements. - pub fn statements(&self) -> &[Node] { - &self.statements - } - /// Implements the display formatting with indentation. pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { writeln!(f, "{{")?; - for node in self.hoistable.iter().chain(self.statements.iter()) { + for node in self.statements.iter() { node.display(f, indentation + 1)?; match node { @@ -79,6 +56,23 @@ impl fmt::Display for Block { } } +impl AsRef<[Node]> for Block { + fn as_ref(&self) -> &[Node] { + &self.statements + } +} + +impl From for Block +where + T: Into>, +{ + fn from(stm: T) -> Self { + Self { + statements: stm.into(), + } + } +} + impl From for Node { fn from(block: Block) -> Self { Self::Block(block) diff --git a/boa/src/syntax/ast/node/local.rs b/boa/src/syntax/ast/node/local.rs new file mode 100644 index 00000000000..65f857a64f0 --- /dev/null +++ b/boa/src/syntax/ast/node/local.rs @@ -0,0 +1,58 @@ +//! Local identifier node. + +use super::Node; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An `identifier` is a sequence of characters in the code that identifies a variable, +/// function, or property. +/// +/// In JavaScript, identifiers are case-sensitive and can contain Unicode letters, $, _, and +/// digits (0-9), but may not start with a digit. +/// +/// An identifier differs from a string in that a string is data, while an identifier is part +/// of the code. In JavaScript, there is no way to convert identifiers to strings, but +/// sometimes it is possible to parse strings into identifiers. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-Identifier +/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Local { + ident: Box, +} + +impl fmt::Display for Local { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.ident, f) + } +} + +impl AsRef for Local { + fn as_ref(&self) -> &str { + &self.ident + } +} + +impl From for Local +where + T: Into>, +{ + fn from(stm: T) -> Self { + Self { ident: stm.into() } + } +} + +impl From for Node { + fn from(local: Local) -> Self { + Self::Local(local) + } +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index a66afb571c1..e841c71e7d2 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -3,12 +3,14 @@ pub mod array; pub mod arrow_function; pub mod block; +pub mod local; pub mod operator; pub use self::{ array::ArrayDecl, arrow_function::ArrowFunctionDecl, block::Block, + local::Local, operator::{Assign, BinOp}, }; use crate::syntax::ast::{ @@ -16,7 +18,10 @@ use crate::syntax::ast::{ op::{Operator, UnaryOp}, }; use gc::{Finalize, Trace}; -use std::fmt::{self, Display}; +use std::{ + cmp::Ordering, + fmt::{self, Display}, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -54,7 +59,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#prod-BreakStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break - Break(Option), + Break(Option>), /// Calling the function actually performs the specified actions with the indicated parameters. /// @@ -118,7 +123,7 @@ pub enum Node { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const /// [identifier]: https://developer.mozilla.org/en-US/docs/Glossary/identifier /// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions - ConstDecl(Box<[(String, Node)]>), + ConstDecl(Box<[(Box, Node)]>), /// The `continue` statement terminates execution of the statements in the current iteration of /// the current or labeled loop, and continues execution of the loop with the next iteration. @@ -133,7 +138,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue - Continue(Option), + Continue(Option>), /// The `do...while` statement creates a loop that executes a specified statement until the /// test condition evaluates to false. @@ -166,7 +171,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function - FunctionDecl(String, Box<[FormalParameter]>, Box), + FunctionDecl(Box, Box<[FormalParameter]>, Box), /// The `function` expression defines a function with the specified parameters. /// @@ -184,7 +189,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function - FunctionExpr(Option, Box<[FormalParameter]>, Box), + FunctionExpr(Option>, Box<[FormalParameter]>, Box), /// This property accessor provides access to an object's properties by using the /// [dot notation][mdn]. @@ -207,7 +212,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#sec-property-accessors /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Dot_notation - GetConstField(Box, String), + GetConstField(Box, Box), /// This property accessor provides access to an object's properties by using the /// [bracket notation][mdn]. @@ -286,25 +291,10 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let - LetDecl(Box<[(String, Option)]>), + LetDecl(Box<[(Box, Option)]>), - /// An `identifier` is a sequence of characters in the code that identifies a variable, - /// function, or property. - /// - /// In JavaScript, identifiers are case-sensitive and can contain Unicode letters, $, _, and - /// digits (0-9), but may not start with a digit. - /// - /// An identifier differs from a string in that a string is data, while an identifier is part - /// of the code. In JavaScript, there is no way to convert identifiers to strings, but - /// sometimes it is possible to parse strings into identifiers. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-Identifier - /// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier - Local(String), + /// A local identifier node. [More information](./local/struct.Local.html). + Local(Local), /// The `new` operator lets developers create an instance of a user-defined object type or of /// one of the built-in object types that has a constructor function. @@ -451,7 +441,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#prod-TryStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch - Try(Block, Option<(Option>, Block)>, Option), + Try(Block, Option<(Option, Block)>, Option), /// The JavaScript `this` keyword refers to the object it belongs to. /// @@ -495,7 +485,7 @@ pub enum Node { /// /// [spec]: https://tc39.es/ecma262/#prod-VariableStatement /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var - VarDecl(Box<[(String, Option)]>), + VarDecl(Box<[(Box, Option)]>), /// The `while` statement creates a loop that executes a specified statement as long as the /// test condition evaluates to `true`. @@ -547,19 +537,25 @@ impl Display for Node { } impl Node { - /// Checks if the current node is a hoistable node. - pub(crate) fn is_hoistable(&self) -> bool { - match self { - Node::FunctionDecl(_, _, _) => true, - Node::VarDecl(decl) if decl.is_empty() => true, - _ => false, + /// Returns a node ordering based on the hoistability of each node. + pub(crate) fn hoistable_order(a: &Node, b: &Node) -> Ordering { + match (a, b) { + (Node::FunctionDecl(_, _, _), Node::FunctionDecl(_, _, _)) => Ordering::Equal, + (_, Node::FunctionDecl(_, _, _)) => Ordering::Greater, + (Node::FunctionDecl(_, _, _), _) => Ordering::Less, + + (Node::VarDecl(_), Node::VarDecl(_)) => Ordering::Equal, + (_, Node::VarDecl(_)) => Ordering::Greater, + (Node::VarDecl(_), _) => Ordering::Less, + + (_, _) => Ordering::Equal, } } /// Creates a `Break` AST node. pub fn break_node(label: OL) -> Self where - L: Into, + L: Into>, OL: Into>, { Self::Break(label.into().map(L::into)) @@ -595,7 +591,7 @@ impl Node { /// Creates a `ConstDecl` AST node. pub fn const_decl(decl: D) -> Self where - D: Into>, + D: Into, Self)]>>, { Self::ConstDecl(decl.into()) } @@ -603,7 +599,7 @@ impl Node { /// Creates a `Continue` AST node. pub fn continue_node(label: OL) -> Self where - L: Into, + L: Into>, OL: Into>, { Self::Continue(label.into().map(L::into)) @@ -621,7 +617,7 @@ impl Node { /// Creates a `FunctionDecl` AST node. pub fn function_decl(name: N, params: P, body: B) -> Self where - N: Into, + N: Into>, P: Into>, B: Into>, { @@ -631,7 +627,7 @@ impl Node { /// Creates a `FunctionDecl` AST node. pub fn function_expr(name: ON, params: P, body: B) -> Self where - N: Into, + N: Into>, ON: Into>, P: Into>, B: Into>, @@ -643,7 +639,7 @@ impl Node { pub fn get_const_field(value: V, label: L) -> Self where V: Into>, - L: Into, + L: Into>, { Self::GetConstField(value.into(), label.into()) } @@ -690,19 +686,11 @@ impl Node { /// Creates a `LetDecl` AST node. pub fn let_decl(init: I) -> Self where - I: Into)]>>, + I: Into, Option)]>>, { Self::LetDecl(init.into()) } - /// Creates a `Local` AST node. - pub fn local(name: N) -> Self - where - N: Into, - { - Self::Local(name.into()) - } - /// Creates a `New` AST node. pub fn new(node: N) -> Self where @@ -774,7 +762,7 @@ impl Node { /// Creates a `Try` AST node. pub fn try_node(try_node: Block, catch: OC, finally: OF) -> Self where - OC: Into>, Block)>>, + OC: Into, Block)>>, OF: Into>, { let catch = catch.into(); @@ -804,7 +792,7 @@ impl Node { /// Creates a `VarDecl` AST node. pub fn var_decl(init: I) -> Self where - I: Into)]>>, + I: Into, Option)]>>, { Self::VarDecl(init.into()) } @@ -871,13 +859,13 @@ impl Node { } Ok(()) } - Self::Local(ref s) => write!(f, "{}", s), + Self::Local(ref s) => Display::fmt(s, f), Self::GetConstField(ref ex, ref field) => write!(f, "{}.{}", ex, field), Self::GetField(ref ex, ref field) => write!(f, "{}[{}]", ex, field), Self::Call(ref ex, ref args) => { write!(f, "{}(", ex)?; - let arg_strs: Box<[String]> = args.iter().map(ToString::to_string).collect(); - write!(f, "{})", arg_strs.join(", ")) + join_nodes(f, args)?; + f.write_str(")") } Self::New(ref call) => { let (func, args) = match call.as_ref() { @@ -1026,7 +1014,7 @@ where /// In the declaration of a function, the parameters must be identifiers, /// not any value like numbers, strings, or objects. ///```text -///function foo(formalParametar1, formalParametar2) { +///function foo(formalParameter1, formalParameter2) { ///} ///``` /// @@ -1039,15 +1027,16 @@ where #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug, PartialEq, Trace, Finalize)] pub struct FormalParameter { - pub name: String, - pub init: Option>, - pub is_rest_param: bool, + name: Box, + init: Option, + is_rest_param: bool, } impl FormalParameter { - pub fn new(name: N, init: Option>, is_rest_param: bool) -> Self + /// Creates a new formal parameter. + pub fn new(name: N, init: Option, is_rest_param: bool) -> Self where - N: Into, + N: Into>, { Self { name: name.into(), @@ -1055,6 +1044,21 @@ impl FormalParameter { is_rest_param, } } + + /// Gets the name of the formal parameter. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the initialization node of the formal parameter, if any. + pub fn init(&self) -> Option<&Node> { + self.init.as_ref() + } + + /// Gets wether the parameter is a rest parameter. + pub fn is_rest_param(&self) -> bool { + self.is_rest_param + } } impl Display for FormalParameter { @@ -1094,7 +1098,7 @@ pub enum PropertyDefinition { /// /// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions - IdentifierReference(String), + IdentifierReference(Box), /// Binds a property name to a JavaScript value. /// @@ -1104,7 +1108,7 @@ pub enum PropertyDefinition { /// /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions - Property(String, Node), + Property(Box, Node), /// A property of an object can also refer to a function or a getter or setter method. /// @@ -1114,7 +1118,7 @@ pub enum PropertyDefinition { /// /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions - MethodDefinition(MethodDefinitionKind, String, Node), + MethodDefinition(MethodDefinitionKind, Box, Node), /// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. /// It copies own enumerable properties from a provided object onto a new object. @@ -1134,7 +1138,7 @@ impl PropertyDefinition { /// Creates an `IdentifierReference` property definition. pub fn identifier_reference(ident: I) -> Self where - I: Into, + I: Into>, { Self::IdentifierReference(ident.into()) } @@ -1142,7 +1146,7 @@ impl PropertyDefinition { /// Creates a `Property` definition. pub fn property(name: N, value: V) -> Self where - N: Into, + N: Into>, V: Into, { Self::Property(name.into(), value.into()) @@ -1151,7 +1155,7 @@ impl PropertyDefinition { /// Creates a `MethodDefinition`. pub fn method_definition(kind: MethodDefinitionKind, name: N, body: B) -> Self where - N: Into, + N: Into>, B: Into, { Self::MethodDefinition(kind, name.into(), body.into()) diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index 275cdaab723..2d44f6cedb2 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -77,11 +77,7 @@ impl TokenParser for ArrowFunction { } e => e, })?; - Box::new([FormalParameter { - init: None, - name: param, - is_rest_param: false, - }]) + Box::new([FormalParameter::new(param, None, false)]) }; cursor.peek_expect_no_lineterminator(0, "arrow function")?; diff --git a/boa/src/syntax/parser/expression/left_hand_side/call.rs b/boa/src/syntax/parser/expression/left_hand_side/call.rs index ce7fd8e47ff..274512c3827 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/call.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/call.rs @@ -73,7 +73,7 @@ impl TokenParser for CallExpression { let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor. match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind { TokenKind::Identifier(name) => { - lhs = Node::get_const_field(lhs, name); + lhs = Node::get_const_field(lhs, name.clone().into_boxed_str()); } TokenKind::Keyword(kw) => { lhs = Node::get_const_field(lhs, kw.to_string()); diff --git a/boa/src/syntax/parser/expression/left_hand_side/member.rs b/boa/src/syntax/parser/expression/left_hand_side/member.rs index 932387730f8..5c3362ffb83 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/member.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/member.rs @@ -61,7 +61,9 @@ impl TokenParser for MemberExpression { TokenKind::Punctuator(Punctuator::Dot) => { let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor forward. match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind { - TokenKind::Identifier(name) => lhs = Node::get_const_field(lhs, name), + TokenKind::Identifier(name) => { + lhs = Node::get_const_field(lhs, name.clone().into_boxed_str()) + } TokenKind::Keyword(kw) => lhs = Node::get_const_field(lhs, kw.to_string()), _ => { return Err(ParseError::Expected( diff --git a/boa/src/syntax/parser/expression/primary/function_expression.rs b/boa/src/syntax/parser/expression/primary/function_expression.rs index 0f52e10a61a..dc2a6307c6d 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression.rs @@ -46,6 +46,6 @@ impl TokenParser for FunctionExpression { cursor.expect(Punctuator::CloseBlock, "function expression")?; - Ok(Node::function_expr::<_, String, _, _>(name, params, body)) + Ok(Node::function_expr::<_, Box<_>, _, _>(name, params, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index 4153fdcafb1..627d5e2ac48 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -20,7 +20,11 @@ use self::{ use super::Expression; use crate::syntax::{ ast::{ - constant::Const, keyword::Keyword, node::Node, punc::Punctuator, token::NumericLiteral, + constant::Const, + keyword::Keyword, + node::{Local, Node}, + punc::Punctuator, + token::NumericLiteral, token::TokenKind, }, parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, @@ -83,12 +87,12 @@ impl TokenParser for PrimaryExpression { // TODO: ADD TokenKind::UndefinedLiteral TokenKind::Identifier(ref i) if i == "undefined" => Ok(Node::Const(Const::Undefined)), TokenKind::NullLiteral => Ok(Node::Const(Const::Null)), - TokenKind::Identifier(ident) => Ok(Node::local(ident)), // TODO: IdentifierReference + TokenKind::Identifier(ident) => Ok(Local::from(ident.as_str()).into()), // TODO: IdentifierReference TokenKind::StringLiteral(s) => Ok(Node::const_node(s)), TokenKind::NumericLiteral(NumericLiteral::Integer(num)) => Ok(Node::const_node(*num)), TokenKind::NumericLiteral(NumericLiteral::Rational(num)) => Ok(Node::const_node(*num)), TokenKind::RegularExpressionLiteral(body, flags) => Ok(Node::new(Node::call( - Node::local("RegExp"), + Node::from(Local::from("RegExp")), vec![Node::const_node(body), Node::const_node(flags)], ))), _ => Err(ParseError::Unexpected( diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs index d3638e328ac..a4e22f630d1 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -129,7 +129,7 @@ impl TokenParser for PropertyDefinition { if cursor.next_if(Punctuator::Colon).is_some() { let val = AssignmentExpression::new(true, self.allow_yield, self.allow_await) .parse(cursor)?; - return Ok(node::PropertyDefinition::Property(prop_name, val)); + return Ok(node::PropertyDefinition::property(prop_name, val)); } if cursor @@ -239,7 +239,7 @@ impl TokenParser for MethodDefinition { "property method definition", )?; - Ok(node::PropertyDefinition::MethodDefinition( + Ok(node::PropertyDefinition::method_definition( methodkind, prop_name, Node::function_expr::<_, String, _, _>(None, params, body), diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs index 54e82a5592e..acf8a6ef3cf 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -18,7 +18,7 @@ fn check_object_literal() { }; ", vec![Node::const_decl(vec![( - String::from("x"), + "x".into(), Node::object(object_properties), )])], ); @@ -43,7 +43,7 @@ fn check_object_short_function() { }; ", vec![Node::const_decl(vec![( - String::from("x"), + "x".into(), Node::object(object_properties), )])], ); @@ -72,7 +72,7 @@ fn check_object_short_function_arguments() { }; ", vec![Node::const_decl(vec![( - String::from("x"), + "x".into(), Node::object(object_properties), )])], ); @@ -100,7 +100,7 @@ fn check_object_getter() { }; ", vec![Node::const_decl(vec![( - String::from("x"), + "x".into(), Node::object(object_properties), )])], ); @@ -128,7 +128,7 @@ fn check_object_setter() { }; ", vec![Node::const_decl(vec![( - String::from("x"), + "x".into(), Node::object(object_properties), )])], ); diff --git a/boa/src/syntax/parser/expression/tests.rs b/boa/src/syntax/parser/expression/tests.rs index 456b7f05b0d..73b169cc4ca 100644 --- a/boa/src/syntax/parser/expression/tests.rs +++ b/boa/src/syntax/parser/expression/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::{BinOp, Node}, + ast::node::{BinOp, Local, Node}, ast::op::{AssignOp, BitOp, CompOp, NumOp}, parser::tests::check_parser, }; @@ -11,15 +11,15 @@ fn check_numeric_operations() { "a + b", vec![Node::from(BinOp::new( NumOp::Add, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a+1", vec![Node::from(BinOp::new( NumOp::Add, - Node::local("a"), + Local::from("a"), Node::const_node(1), ))], ); @@ -27,15 +27,15 @@ fn check_numeric_operations() { "a - b", vec![Node::from(BinOp::new( NumOp::Sub, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a-1", vec![Node::from(BinOp::new( NumOp::Sub, - Node::local("a"), + Local::from("a"), Node::const_node(1), ))], ); @@ -43,15 +43,15 @@ fn check_numeric_operations() { "a / b", vec![Node::from(BinOp::new( NumOp::Div, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a/2", vec![Node::from(BinOp::new( NumOp::Div, - Node::local("a"), + Local::from("a"), Node::const_node(2), ))], ); @@ -59,15 +59,15 @@ fn check_numeric_operations() { "a * b", vec![Node::from(BinOp::new( NumOp::Mul, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a*2", vec![Node::from(BinOp::new( NumOp::Mul, - Node::local("a"), + Local::from("a"), Node::const_node(2), ))], ); @@ -75,15 +75,15 @@ fn check_numeric_operations() { "a ** b", vec![Node::from(BinOp::new( NumOp::Exp, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a**2", vec![Node::from(BinOp::new( NumOp::Exp, - Node::local("a"), + Local::from("a"), Node::const_node(2), ))], ); @@ -91,15 +91,15 @@ fn check_numeric_operations() { "a % b", vec![Node::from(BinOp::new( NumOp::Mod, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a%2", vec![Node::from(BinOp::new( NumOp::Mod, - Node::local("a"), + Local::from("a"), Node::const_node(2), ))], ); @@ -114,13 +114,13 @@ fn check_complex_numeric_operations() { NumOp::Add, Node::from(BinOp::new( NumOp::Add, - Node::local("a"), + Local::from("a"), Node::from(BinOp::new( NumOp::Mul, - Node::local("d"), + Local::from("d"), Node::from(BinOp::new( NumOp::Sub, - Node::local("b"), + Local::from("b"), Node::const_node(3), )), )), @@ -137,16 +137,16 @@ fn check_bitwise_operations() { "a & b", vec![Node::from(BinOp::new( BitOp::And, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a&b", vec![Node::from(BinOp::new( BitOp::And, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); @@ -154,16 +154,16 @@ fn check_bitwise_operations() { "a | b", vec![Node::from(BinOp::new( BitOp::Or, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a|b", vec![Node::from(BinOp::new( BitOp::Or, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); @@ -171,16 +171,16 @@ fn check_bitwise_operations() { "a ^ b", vec![Node::from(BinOp::new( BitOp::Xor, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a^b", vec![Node::from(BinOp::new( BitOp::Xor, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); @@ -188,16 +188,16 @@ fn check_bitwise_operations() { "a << b", vec![Node::from(BinOp::new( BitOp::Shl, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a<> b", vec![Node::from(BinOp::new( BitOp::Shr, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a>>b", vec![Node::from(BinOp::new( BitOp::Shr, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); } @@ -226,95 +226,95 @@ fn check_assign_operations() { "a += b", vec![Node::from(BinOp::new( AssignOp::Add, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a -= b", vec![Node::from(BinOp::new( AssignOp::Sub, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a *= b", vec![Node::from(BinOp::new( AssignOp::Mul, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a **= b", vec![Node::from(BinOp::new( AssignOp::Exp, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a /= b", vec![Node::from(BinOp::new( AssignOp::Div, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a %= b", vec![Node::from(BinOp::new( AssignOp::Mod, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a &= b", vec![Node::from(BinOp::new( AssignOp::And, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a |= b", vec![Node::from(BinOp::new( AssignOp::Or, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a ^= b", vec![Node::from(BinOp::new( AssignOp::Xor, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a <<= b", vec![Node::from(BinOp::new( AssignOp::Shl, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a >>= b", vec![Node::from(BinOp::new( AssignOp::Shr, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a %= 10 / 2", vec![Node::from(BinOp::new( AssignOp::Mod, - Node::local("a"), + Local::from("a"), Node::from(BinOp::new( NumOp::Div, Node::const_node(10), @@ -330,40 +330,40 @@ fn check_relational_operations() { "a < b", vec![Node::from(BinOp::new( CompOp::LessThan, - Node::Local(String::from("a")), - Node::Local(String::from("b")), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a > b", vec![Node::from(BinOp::new( CompOp::GreaterThan, - Node::Local(String::from("a")), - Node::Local(String::from("b")), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a <= b", vec![Node::from(BinOp::new( CompOp::LessThanOrEqual, - Node::Local(String::from("a")), - Node::Local(String::from("b")), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "a >= b", vec![Node::from(BinOp::new( CompOp::GreaterThanOrEqual, - Node::Local(String::from("a")), - Node::Local(String::from("b")), + Local::from("a"), + Local::from("b"), ))], ); check_parser( "p in o", vec![Node::from(BinOp::new( CompOp::In, - Node::Local(String::from("p")), - Node::Local(String::from("o")), + Local::from("p"), + Local::from("o"), ))], ); } diff --git a/boa/src/syntax/parser/function/mod.rs b/boa/src/syntax/parser/function/mod.rs index dc8ef71f3f2..3402b213e19 100644 --- a/boa/src/syntax/parser/function/mod.rs +++ b/boa/src/syntax/parser/function/mod.rs @@ -186,7 +186,7 @@ impl TokenParser for FormalParameter { let init = Initializer::new(true, self.allow_yield, self.allow_await).try_parse(cursor); - Ok(Self::Output::new(param, init.map(Box::new), false)) + Ok(Self::Output::new(param, init, false)) } } diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs index c6576a19985..5a9dfb22187 100644 --- a/boa/src/syntax/parser/function/tests.rs +++ b/boa/src/syntax/parser/function/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::{ArrowFunctionDecl, BinOp, FormalParameter, Node}, + ast::node::{ArrowFunctionDecl, BinOp, FormalParameter, Local, Node}, ast::op::NumOp, parser::tests::check_parser, }; @@ -12,7 +12,7 @@ fn check_basic() { vec![Node::function_decl( "foo", vec![FormalParameter::new("a", None, false)], - Node::statement_list(vec![Node::return_node(Node::local("a"))]), + Node::statement_list(vec![Node::return_node(Node::from(Local::from("a")))]), )], ); } @@ -25,7 +25,7 @@ fn check_basic_semicolon_insertion() { vec![Node::function_decl( "foo", vec![FormalParameter::new("a", None, false)], - Node::statement_list(vec![Node::return_node(Node::local("a"))]), + Node::statement_list(vec![Node::return_node(Node::from(Local::from("a")))]), )], ); } @@ -114,8 +114,8 @@ fn check_arrow() { ], Node::statement_list(vec![Node::return_node(Node::from(BinOp::new( NumOp::Add, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), )))]), ) .into()], @@ -134,8 +134,8 @@ fn check_arrow_semicolon_insertion() { ], Node::statement_list(vec![Node::return_node(Node::from(BinOp::new( NumOp::Add, - Node::local("a"), - Node::local("b"), + Local::from("a"), + Local::from("b"), )))]), ) .into()], diff --git a/boa/src/syntax/parser/statement/block.rs b/boa/src/syntax/parser/statement/block/mod.rs similarity index 92% rename from boa/src/syntax/parser/statement/block.rs rename to boa/src/syntax/parser/statement/block/mod.rs index bcfa29b54df..e4af925d2e3 100644 --- a/boa/src/syntax/parser/statement/block.rs +++ b/boa/src/syntax/parser/statement/block/mod.rs @@ -7,6 +7,9 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block //! [spec]: https://tc39.es/ecma262/#sec-block +#[cfg(test)] +mod tests; + use super::{declaration::Declaration, Statement}; use crate::syntax::{ ast::{ @@ -65,14 +68,14 @@ impl TokenParser for Block { if let Some(tk) = cursor.peek(0) { if tk.kind == TokenKind::Punctuator(Punctuator::CloseBlock) { cursor.next(); - return Ok(node::Block::new(vec![], vec![])); + return Ok(node::Block::from(vec![])); } } let statement_list = StatementList::new(self.allow_yield, self.allow_await, self.allow_return, true) .parse(cursor) - .map(|(hoistable, statements)| node::Block::new(hoistable, statements))?; + .map(node::Block::from)?; cursor.expect(Punctuator::CloseBlock, "block")?; Ok(statement_list) @@ -118,10 +121,9 @@ impl StatementList { } impl TokenParser for StatementList { - type Output = (Box<[Node]>, Box<[Node]>); + type Output = Box<[Node]>; fn parse(self, cursor: &mut Cursor<'_>) -> Result { - let mut hoistable = Vec::new(); let mut statements = Vec::new(); loop { @@ -147,17 +149,15 @@ impl TokenParser for StatementList { StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor)?; - if item.is_hoistable() { - hoistable.push(item); - } else { - statements.push(item); - } + statements.push(item); // move the cursor forward for any consecutive semicolon. while cursor.next_if(Punctuator::Semicolon).is_some() {} } - Ok((hoistable.into_boxed_slice(), statements.into_boxed_slice())) + statements.sort_by(Node::hoistable_order); + + Ok(statements.into_boxed_slice()) } } diff --git a/boa/src/syntax/parser/statement/block/tests.rs b/boa/src/syntax/parser/statement/block/tests.rs new file mode 100644 index 00000000000..5004700cbd0 --- /dev/null +++ b/boa/src/syntax/parser/statement/block/tests.rs @@ -0,0 +1,95 @@ +//! Block statement parsing tests. + +use crate::syntax::{ + ast::{ + node::{Assign, Block, Local, Node}, + op::UnaryOp, + }, + parser::tests::check_parser, +}; + +/// Helper function to check a block. +// TODO: #[track_caller]: https://github.com/rust-lang/rust/issues/47809 +fn check_block(js: &str, block: Block) { + check_parser(js, vec![Node::from(block)]); +} + +#[test] +fn empty() { + check_block("{}", Block::from(vec![])); +} + +#[test] +fn non_empty() { + check_block( + r"{ + var a = 10; + a++; + }", + Block::from(vec![ + Node::var_decl(vec![("a".into(), Some(Node::const_node(10)))]), + Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), + ]), + ); + + check_block( + r"{ + function hello() { + return 10 + } + + var a = hello(); + a++; + }", + Block::from(vec![ + Node::function_decl( + "hello", + vec![], + Node::statement_list(vec![Node::return_node(Node::const_node(10))]), + ), + Node::var_decl(vec![( + "a".into(), + Some(Node::call(Node::from(Local::from("hello")), vec![])), + )]), + Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), + ]), + ); +} + +#[test] +fn hoisting() { + check_block( + r"{ + var a = hello(); + a++; + + function hello() { return 10 } + }", + Block::from(vec![ + Node::function_decl( + "hello", + vec![], + Node::statement_list(vec![Node::return_node(Node::const_node(10))]), + ), + Node::var_decl(vec![( + "a".into(), + Some(Node::call(Node::from(Local::from("hello")), vec![])), + )]), + Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), + ]), + ); + + check_block( + r"{ + a = 10; + a++; + + var a; + }", + Block::from(vec![ + Node::var_decl(vec![("a".into(), None)]), + Node::from(Assign::new(Local::from("a"), Node::const_node(10))), + Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), + ]), + ); +} diff --git a/boa/src/syntax/parser/statement/break_stm/mod.rs b/boa/src/syntax/parser/statement/break_stm/mod.rs index de3119a3cc0..18ea6b26830 100644 --- a/boa/src/syntax/parser/statement/break_stm/mod.rs +++ b/boa/src/syntax/parser/statement/break_stm/mod.rs @@ -10,9 +10,10 @@ #[cfg(test)] mod tests; +use super::LabelIdentifier; use crate::syntax::{ ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, + parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; /// Break statement parsing @@ -49,7 +50,7 @@ impl TokenParser for BreakStatement { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { cursor.expect(Keyword::Break, "break statement")?; - if let (true, tok) = cursor.peek_semicolon(false) { + let label = if let (true, tok) = cursor.peek_semicolon(false) { match tok { Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => { let _ = cursor.next(); @@ -57,23 +58,14 @@ impl TokenParser for BreakStatement { _ => {} } - return Ok(Node::Break(None)); - } - - let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - // TODO: LabelIdentifier - let node = if let TokenKind::Identifier(name) = &tok.kind { - Node::break_node(name) + None } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - tok.clone(), - "break statement", - )); - }; + let label = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect_semicolon(false, "continue statement")?; - cursor.expect_semicolon(false, "break statement")?; + Some(label) + }; - Ok(node) + Ok(Node::Break(label)) } } diff --git a/boa/src/syntax/parser/statement/break_stm/tests.rs b/boa/src/syntax/parser/statement/break_stm/tests.rs index 801fc811eed..d0a39a15391 100644 --- a/boa/src/syntax/parser/statement/break_stm/tests.rs +++ b/boa/src/syntax/parser/statement/break_stm/tests.rs @@ -4,7 +4,7 @@ use crate::syntax::{ }; #[test] -fn check_inline() { +fn inline() { check_parser( "while (true) break;", vec![Node::while_loop(Node::const_node(true), Node::Break(None))], @@ -12,7 +12,7 @@ fn check_inline() { } #[test] -fn check_new_line() { +fn new_line() { check_parser( "while (true) break;", @@ -21,75 +21,98 @@ fn check_new_line() { } #[test] -fn check_inline_block_semicolon_insertion() { +fn inline_block_semicolon_insertion() { check_parser( "while (true) {break}", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Break(None)])), + Node::from(Block::from(vec![Node::Break(None)])), )], ); } #[test] -fn check_new_line_semicolon_insertion() { +fn new_line_semicolon_insertion() { check_parser( "while (true) { break test }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::break_node("test")])), + Node::from(Block::from(vec![Node::break_node("test")])), )], ); } #[test] -fn check_inline_block() { +fn inline_block() { check_parser( "while (true) {break;}", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Break(None)])), + Node::from(Block::from(vec![Node::Break(None)])), )], ); } #[test] -fn check_new_line_block() { +fn new_line_block() { check_parser( "while (true) { break test; }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::break_node("test")])), + Node::from(Block::from(vec![Node::break_node("test")])), )], ); } #[test] -fn check_new_line_block_empty() { +fn reserved_label() { + check_parser( + "while (true) { + break await; + }", + vec![Node::while_loop( + Node::const_node(true), + Node::from(Block::from(vec![Node::break_node("await")])), + )], + ); + + check_parser( + "while (true) { + break yield; + }", + vec![Node::while_loop( + Node::const_node(true), + Node::from(Block::from(vec![Node::break_node("yield")])), + )], + ); +} + +#[test] +fn new_line_block_empty() { check_parser( "while (true) { break; }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Break(None)])), + Node::from(Block::from(vec![Node::Break(None)])), )], ); } #[test] -fn check_new_line_block_empty_semicolon_insertion() { +fn new_line_block_empty_semicolon_insertion() { check_parser( "while (true) { break }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Break(None)])), + Node::from(Block::from(vec![Node::Break(None)])), )], ); } diff --git a/boa/src/syntax/parser/statement/continue_stm/mod.rs b/boa/src/syntax/parser/statement/continue_stm/mod.rs index d43b40db349..2ca22998c8d 100644 --- a/boa/src/syntax/parser/statement/continue_stm/mod.rs +++ b/boa/src/syntax/parser/statement/continue_stm/mod.rs @@ -10,9 +10,10 @@ #[cfg(test)] mod tests; +use super::LabelIdentifier; use crate::syntax::{ ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, + parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; /// For statement parsing @@ -49,7 +50,7 @@ impl TokenParser for ContinueStatement { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { cursor.expect(Keyword::Continue, "continue statement")?; - if let (true, tok) = cursor.peek_semicolon(false) { + let label = if let (true, tok) = cursor.peek_semicolon(false) { match tok { Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => { let _ = cursor.next(); @@ -57,23 +58,14 @@ impl TokenParser for ContinueStatement { _ => {} } - return Ok(Node::Continue(None)); - } - - let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - // TODO: LabelIdentifier - let node = if let TokenKind::Identifier(name) = &tok.kind { - Node::continue_node(name) + None } else { - return Err(ParseError::Expected( - vec![TokenKind::identifier("identifier")], - tok.clone(), - "continue statement", - )); - }; + let label = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect_semicolon(false, "continue statement")?; - cursor.expect_semicolon(false, "continue statement")?; + Some(label) + }; - Ok(node) + Ok(Node::Continue(label)) } } diff --git a/boa/src/syntax/parser/statement/continue_stm/tests.rs b/boa/src/syntax/parser/statement/continue_stm/tests.rs index dc36040ecf2..80896dfce79 100644 --- a/boa/src/syntax/parser/statement/continue_stm/tests.rs +++ b/boa/src/syntax/parser/statement/continue_stm/tests.rs @@ -4,7 +4,7 @@ use crate::syntax::{ }; #[test] -fn check_inline() { +fn inline() { check_parser( "while (true) continue;", vec![Node::while_loop( @@ -15,7 +15,7 @@ fn check_inline() { } #[test] -fn check_new_line() { +fn new_line() { check_parser( "while (true) continue;", @@ -27,75 +27,98 @@ fn check_new_line() { } #[test] -fn check_inline_block_semicolon_insertion() { +fn inline_block_semicolon_insertion() { check_parser( "while (true) {continue}", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Continue(None)])), + Node::from(Block::from(vec![Node::Continue(None)])), )], ); } #[test] -fn check_new_line_semicolon_insertion() { +fn new_line_semicolon_insertion() { check_parser( "while (true) { continue test }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::continue_node("test")])), + Node::from(Block::from(vec![Node::continue_node("test")])), )], ); } #[test] -fn check_inline_block() { +fn inline_block() { check_parser( "while (true) {continue;}", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Continue(None)])), + Node::from(Block::from(vec![Node::Continue(None)])), )], ); } #[test] -fn check_new_line_block() { +fn new_line_block() { check_parser( "while (true) { continue test; }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::continue_node("test")])), + Node::from(Block::from(vec![Node::continue_node("test")])), )], ); } #[test] -fn check_new_line_block_empty() { +fn reserved_label() { + check_parser( + "while (true) { + continue await; + }", + vec![Node::while_loop( + Node::const_node(true), + Node::from(Block::from(vec![Node::continue_node("await")])), + )], + ); + + check_parser( + "while (true) { + continue yield; + }", + vec![Node::while_loop( + Node::const_node(true), + Node::from(Block::from(vec![Node::continue_node("yield")])), + )], + ); +} + +#[test] +fn new_line_block_empty() { check_parser( "while (true) { continue; }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Continue(None)])), + Node::from(Block::from(vec![Node::Continue(None)])), )], ); } #[test] -fn check_new_line_block_empty_semicolon_insertion() { +fn new_line_block_empty_semicolon_insertion() { check_parser( "while (true) { continue }", vec![Node::while_loop( Node::const_node(true), - Node::from(Block::new(vec![], vec![Node::Continue(None)])), + Node::from(Block::from(vec![Node::Continue(None)])), )], ); } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index 14d2877256b..6d7355e6603 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -181,7 +181,7 @@ impl LexicalBinding { } impl TokenParser for LexicalBinding { - type Output = (String, Option); + type Output = (Box, Option); fn parse(self, cursor: &mut Cursor<'_>) -> Result { let ident = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index 21ec1749f48..1bb4154f6ad 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/boa/src/syntax/parser/statement/declaration/tests.rs @@ -9,7 +9,7 @@ fn var_declaration() { check_parser( "var a = 5;", vec![Node::var_decl(vec![( - String::from("a"), + "a".into(), Some(Node::const_node(5)), )])], ); @@ -21,7 +21,7 @@ fn var_declaration_keywords() { check_parser( "var yield = 5;", vec![Node::var_decl(vec![( - String::from("yield"), + "yield".into(), Some(Node::const_node(5)), )])], ); @@ -29,7 +29,7 @@ fn var_declaration_keywords() { check_parser( "var await = 5;", vec![Node::var_decl(vec![( - String::from("await"), + "await".into(), Some(Node::const_node(5)), )])], ); @@ -41,7 +41,7 @@ fn var_declaration_no_spaces() { check_parser( "var a=5;", vec![Node::var_decl(vec![( - String::from("a"), + "a".into(), Some(Node::const_node(5)), )])], ); @@ -50,10 +50,7 @@ fn var_declaration_no_spaces() { /// Checks empty `var` declaration parsing. #[test] fn empty_var_declaration() { - check_parser( - "var a;", - vec![Node::var_decl(vec![(String::from("a"), None)])], - ); + check_parser("var a;", vec![Node::var_decl(vec![("a".into(), None)])]); } /// Checks multiple `var` declarations. @@ -62,9 +59,9 @@ fn multiple_var_declaration() { check_parser( "var a = 5, b, c = 6;", vec![Node::var_decl(vec![ - (String::from("a"), Some(Node::const_node(5))), - (String::from("b"), None), - (String::from("c"), Some(Node::const_node(6))), + ("a".into(), Some(Node::const_node(5))), + ("b".into(), None), + ("c".into(), Some(Node::const_node(6))), ])], ); } @@ -75,7 +72,7 @@ fn let_declaration() { check_parser( "let a = 5;", vec![Node::let_decl(vec![( - String::from("a"), + "a".into(), Some(Node::const_node(5)), )])], ); @@ -87,7 +84,7 @@ fn let_declaration_keywords() { check_parser( "let yield = 5;", vec![Node::let_decl(vec![( - String::from("yield"), + "yield".into(), Some(Node::const_node(5)), )])], ); @@ -95,7 +92,7 @@ fn let_declaration_keywords() { check_parser( "let await = 5;", vec![Node::let_decl(vec![( - String::from("await"), + "await".into(), Some(Node::const_node(5)), )])], ); @@ -107,7 +104,7 @@ fn let_declaration_no_spaces() { check_parser( "let a=5;", vec![Node::let_decl(vec![( - String::from("a"), + "a".into(), Some(Node::const_node(5)), )])], ); @@ -116,10 +113,7 @@ fn let_declaration_no_spaces() { /// Checks empty `let` declaration parsing. #[test] fn empty_let_declaration() { - check_parser( - "let a;", - vec![Node::let_decl(vec![(String::from("a"), None)])], - ); + check_parser("let a;", vec![Node::let_decl(vec![("a".into(), None)])]); } /// Checks multiple `let` declarations. @@ -128,9 +122,9 @@ fn multiple_let_declaration() { check_parser( "let a = 5, b, c = 6;", vec![Node::let_decl(vec![ - (String::from("a"), Some(Node::const_node(5))), - (String::from("b"), None), - (String::from("c"), Some(Node::const_node(6))), + ("a".into(), Some(Node::const_node(5))), + ("b".into(), None), + ("c".into(), Some(Node::const_node(6))), ])], ); } @@ -140,10 +134,7 @@ fn multiple_let_declaration() { fn const_declaration() { check_parser( "const a = 5;", - vec![Node::const_decl(vec![( - String::from("a"), - Node::const_node(5), - )])], + vec![Node::const_decl(vec![("a".into(), Node::const_node(5))])], ); } @@ -153,7 +144,7 @@ fn const_declaration_keywords() { check_parser( "const yield = 5;", vec![Node::const_decl(vec![( - String::from("yield"), + "yield".into(), Node::const_node(5), )])], ); @@ -161,7 +152,7 @@ fn const_declaration_keywords() { check_parser( "const await = 5;", vec![Node::const_decl(vec![( - String::from("await"), + "await".into(), Node::const_node(5), )])], ); @@ -172,10 +163,7 @@ fn const_declaration_keywords() { fn const_declaration_no_spaces() { check_parser( "const a=5;", - vec![Node::const_decl(vec![( - String::from("a"), - Node::const_node(5), - )])], + vec![Node::const_decl(vec![("a".into(), Node::const_node(5))])], ); } @@ -191,8 +179,8 @@ fn multiple_const_declaration() { check_parser( "const a = 5, c = 6;", vec![Node::const_decl(vec![ - (String::from("a"), Node::const_node(5)), - (String::from("c"), Node::const_node(6)), + ("a".into(), Node::const_node(5)), + ("c".into(), Node::const_node(6)), ])], ); } diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 3446db6edd7..4ee1ae2a529 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -103,6 +103,6 @@ impl TokenParser for ForStatement { let for_loop = Node::for_loop::<_, _, _, Node, Node, Node, _>(init, cond, step, body); // TODO: do not encapsulate the `for` in a block just to have an inner scope. - Ok(Node::from(Block::new(vec![], vec![for_loop]))) + Ok(Node::from(Block::from(vec![for_loop]))) } } diff --git a/boa/src/syntax/parser/statement/iteration/tests.rs b/boa/src/syntax/parser/statement/iteration/tests.rs index 0728c4e4795..27c3de66092 100644 --- a/boa/src/syntax/parser/statement/iteration/tests.rs +++ b/boa/src/syntax/parser/statement/iteration/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::{BinOp, Block, Node}, + ast::node::{BinOp, Block, Local, Node}, ast::op::{AssignOp, CompOp, UnaryOp}, parser::tests::check_parser, }; @@ -12,10 +12,11 @@ fn check_do_while() { a += 1; } while (true)"#, vec![Node::do_while_loop( - Node::from(Block::new( - vec![], - vec![BinOp::new(AssignOp::Add, Node::local("a"), Node::const_node(1)).into()], - )), + Node::from(Block::from(vec![Node::from(BinOp::new( + AssignOp::Add, + Local::from("a"), + Node::const_node(1), + ))])), Node::const_node(true), )], ); @@ -28,23 +29,20 @@ fn check_do_while_semicolon_insertion() { r#"var i = 0; do {console.log("hello");} while(i++ < 10) console.log("end");"#, vec![ - Node::var_decl(vec![(String::from("i"), Some(Node::const_node(0)))]), + Node::var_decl(vec![("i".into(), Some(Node::const_node(0)))]), Node::do_while_loop( - Node::from(Block::new( - vec![], - vec![Node::call( - Node::get_const_field(Node::local("console"), "log"), - vec![Node::const_node("hello")], - )], - )), + Node::from(Block::from(vec![Node::call( + Node::get_const_field(Node::from(Local::from("console")), "log"), + vec![Node::const_node("hello")], + )])), Node::from(BinOp::new( CompOp::LessThan, - Node::unary_op(UnaryOp::IncrementPost, Node::local("i")), + Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("i"))), Node::const_node(10), )), ), Node::call( - Node::get_const_field(Node::local("console"), "log"), + Node::get_const_field(Node::from(Local::from("console")), "log"), vec![Node::const_node("end")], ), ], diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index ab23b8eb8cf..c6ea68d4ea3 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -318,6 +318,16 @@ impl TokenParser for ExpressionStatement { } } +/// Label identifier parsing. +/// +/// This seems to be the same as a `BindingIdentifier`. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-LabelIdentifier +type LabelIdentifier = BindingIdentifier; + /// Binding identifier parsing. /// /// More information: @@ -345,17 +355,17 @@ impl BindingIdentifier { } impl TokenParser for BindingIdentifier { - type Output = String; + type Output = Box; - fn parse(self, cursor: &mut Cursor<'_>) -> Result { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { // TODO: strict mode. let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; match next_token.kind { - TokenKind::Identifier(ref s) => Ok(s.clone()), - TokenKind::Keyword(k @ Keyword::Yield) if !self.allow_yield.0 => Ok(k.to_string()), - TokenKind::Keyword(k @ Keyword::Await) if !self.allow_await.0 => Ok(k.to_string()), + TokenKind::Identifier(ref s) => Ok(s.as_str().into()), + TokenKind::Keyword(k @ Keyword::Yield) if !self.allow_yield.0 => Ok(k.as_str().into()), + TokenKind::Keyword(k @ Keyword::Await) if !self.allow_await.0 => Ok(k.as_str().into()), _ => Err(ParseError::Expected( vec![TokenKind::identifier("identifier")], next_token.clone(), diff --git a/boa/src/syntax/parser/statement/try_stm/catch.rs b/boa/src/syntax/parser/statement/try_stm/catch.rs index 9810b3867d8..717a6539906 100644 --- a/boa/src/syntax/parser/statement/try_stm/catch.rs +++ b/boa/src/syntax/parser/statement/try_stm/catch.rs @@ -1,12 +1,12 @@ use crate::syntax::{ ast::{ keyword::Keyword, - node::{self, Node}, + node::{self, Local}, punc::Punctuator, }, parser::{ statement::{block::Block, BindingIdentifier}, - AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -42,7 +42,7 @@ impl Catch { } impl TokenParser for Catch { - type Output = (Option, node::Block); + type Output = (Option, node::Block); fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Keyword::Catch, "try statement")?; @@ -92,12 +92,12 @@ impl CatchParameter { } impl TokenParser for CatchParameter { - type Output = Node; + type Output = Local; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { // TODO: should accept BindingPattern BindingIdentifier::new(self.allow_yield, self.allow_await) .parse(cursor) - .map(Node::local) + .map(Local::from) } } diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs index 7646b88c99e..8bc7c7ed232 100644 --- a/boa/src/syntax/parser/statement/try_stm/mod.rs +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -69,11 +69,7 @@ impl TokenParser for TryStatement { } let catch = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { - Some( - Catch::new(self.allow_yield, self.allow_await, self.allow_return) - .parse(cursor) - .map(|(param, block)| (param.map(Box::new), block))?, - ) + Some(Catch::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?) } else { None }; diff --git a/boa/src/syntax/parser/statement/try_stm/tests.rs b/boa/src/syntax/parser/statement/try_stm/tests.rs index 384436ccc66..3cf72568beb 100644 --- a/boa/src/syntax/parser/statement/try_stm/tests.rs +++ b/boa/src/syntax/parser/statement/try_stm/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::{Block, Node}, + ast::node::{Block, Local, Node}, parser::tests::{check_invalid, check_parser}, }; @@ -8,8 +8,8 @@ fn check_inline_with_empty_try_catch() { check_parser( "try { } catch(e) {}", vec![Node::try_node( - Block::new(vec![], vec![]), - Some((Some(Box::new(Node::local("e"))), Block::new(vec![], vec![]))), + Block::from(vec![]), + Some((Some(Local::from("e")), Block::from(vec![]))), None, )], ); @@ -20,14 +20,11 @@ fn check_inline_with_var_decl_inside_try() { check_parser( "try { var x = 1; } catch(e) {}", vec![Node::try_node( - Block::new( - vec![], - vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])], - ), - Some((Some(Box::new(Node::local("e"))), Block::new(vec![], vec![]))), + Block::from(vec![Node::var_decl(vec![( + "x".into(), + Some(Node::const_node(1)), + )])]), + Some((Some(Local::from("e")), Block::from(vec![]))), None, )], ); @@ -38,22 +35,16 @@ fn check_inline_with_var_decl_inside_catch() { check_parser( "try { var x = 1; } catch(e) { var x = 1; }", vec![Node::try_node( - Block::new( - vec![], - vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])], - ), + Block::from(vec![Node::var_decl(vec![( + "x".into(), + Some(Node::const_node(1)), + )])]), Some(( - Some(Box::new(Node::local("e"))), - Block::new( - vec![], - vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])], - ), + Some(Local::from("e")), + Block::from(vec![Node::var_decl(vec![( + "x".into(), + Some(Node::const_node(1)), + )])]), )), None, )], @@ -65,9 +56,9 @@ fn check_inline_with_empty_try_catch_finally() { check_parser( "try {} catch(e) {} finally {}", vec![Node::try_node( - Block::new(vec![], vec![]), - Some((Some(Box::new(Node::local("e"))), Block::new(vec![], vec![]))), - Block::new(vec![], vec![]), + Block::from(vec![]), + Some((Some(Local::from("e")), Block::from(vec![]))), + Block::from(vec![]), )], ); } @@ -77,9 +68,9 @@ fn check_inline_with_empty_try_finally() { check_parser( "try {} finally {}", vec![Node::try_node( - Block::new(vec![], vec![]), + Block::from(vec![]), None, - Block::new(vec![], vec![]), + Block::from(vec![]), )], ); } @@ -89,15 +80,12 @@ fn check_inline_with_empty_try_var_decl_in_finally() { check_parser( "try {} finally { var x = 1; }", vec![Node::try_node( - Block::new(vec![], vec![]), + Block::from(vec![]), None, - Block::new( - vec![], - vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])], - ), + Block::from(vec![Node::var_decl(vec![( + "x".into(), + Some(Node::const_node(1)), + )])]), )], ); } @@ -107,16 +95,13 @@ fn check_inline_empty_try_paramless_catch() { check_parser( "try {} catch { var x = 1; }", vec![Node::try_node( - Block::new(vec![], vec![]), + Block::from(vec![]), Some(( None, - Block::new( - vec![], - vec![Node::var_decl(vec![( - String::from("x"), - Some(Node::const_node(1)), - )])], - ), + Block::from(vec![Node::var_decl(vec![( + "x".into(), + Some(Node::const_node(1)), + )])]), )), None, )], diff --git a/boa/src/syntax/parser/statement/variable.rs b/boa/src/syntax/parser/statement/variable.rs index 193d81a4913..bd045a22a50 100644 --- a/boa/src/syntax/parser/statement/variable.rs +++ b/boa/src/syntax/parser/statement/variable.rs @@ -151,7 +151,7 @@ impl VariableDeclaration { } impl TokenParser for VariableDeclaration { - type Output = (String, Option); + type Output = (Box, Option); fn parse(self, cursor: &mut Cursor<'_>) -> Result { // TODO: BindingPattern diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index 94470804d31..f90dfd29313 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -2,12 +2,14 @@ use super::Parser; use crate::syntax::{ - ast::node::{Assign, BinOp, Node}, + ast::node::{Assign, BinOp, Local, Node}, ast::op::NumOp, lexer::Lexer, }; +/// Checks that the given JavaScript string gives the expected expression. #[allow(clippy::result_unwrap_used)] +// TODO: #[track_caller]: https://github.com/rust-lang/rust/issues/47809 pub(super) fn check_parser(js: &str, expr: L) where L: Into>, @@ -23,6 +25,8 @@ where ); } +/// Checks that the given javascript string creates a parse error. +// TODO: #[track_caller]: https://github.com/rust-lang/rust/issues/47809 pub(super) fn check_invalid(js: &str) { let mut lexer = Lexer::new(js); lexer.lex().expect("failed to lex"); @@ -37,7 +41,7 @@ fn check_construct_call_precedence() { "new Date().getTime()", vec![Node::call( Node::get_const_field( - Node::new(Node::call(Node::local("Date"), Vec::new())), + Node::new(Node::call(Node::from(Local::from("Date")), Vec::new())), "getTime", ), Vec::new(), @@ -50,8 +54,8 @@ fn assign_operator_precedence() { check_parser( "a = a + 1", vec![Assign::new( - Node::local("a"), - BinOp::new(NumOp::Add, Node::local("a"), Node::const_node(1)), + Local::from("a"), + BinOp::new(NumOp::Add, Local::from("a"), Node::const_node(1)), ) .into()], );