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/4] 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/4] 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()], ); From 948532be97c7cba539d74f20f96507f92f1146dc Mon Sep 17 00:00:00 2001 From: Iban Eguia Moraza Date: Tue, 12 May 2020 22:01:34 +0200 Subject: [PATCH 3/4] New refactoring in progress --- boa/src/exec/tests.rs | 35 ++++- boa/src/syntax/ast/node/block.rs | 36 +---- boa/src/syntax/ast/node/mod.rs | 132 ++-------------- boa/src/syntax/ast/node/statement_list.rs | 150 +++++++++++++++++++ boa/src/syntax/parser/mod.rs | 18 ++- boa/src/syntax/parser/statement/block/mod.rs | 142 +----------------- boa/src/syntax/parser/statement/mod.rs | 4 +- boa/src/syntax/parser/tests.rs | 38 ++++- 8 files changed, 250 insertions(+), 305 deletions(-) create mode 100644 boa/src/syntax/ast/node/statement_list.rs diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 9ae7de6d21f..dcf209e7345 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -611,20 +611,45 @@ 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; + x = hello(); + function hello() {return 5} var x; - var y = hello(); + x; + "#; + assert_eq!(&exec(scenario), "5"); - function hello() {return 5} + let scenario = r#" + hello = function() { return 5 } + x = hello(); x; "#; - assert_eq!(&exec(scenario), "undefined"); + assert_eq!(&exec(scenario), "5"); + + let scenario = r#" + let x = b(); + + function a() {return 5} + function b() {return a()} + + x; + "#; + assert_eq!(&exec(scenario), "5"); + + let scenario = r#" + let x = b(); + + function b() {return a()} + function a() {return 5} + + x; + "#; + assert_eq!(&exec(scenario), "5"); } diff --git a/boa/src/syntax/ast/node/block.rs b/boa/src/syntax/ast/node/block.rs index 1360094b34a..2fef1629b4e 100644 --- a/boa/src/syntax/ast/node/block.rs +++ b/boa/src/syntax/ast/node/block.rs @@ -1,6 +1,6 @@ //! Block AST node. -use super::Node; +use super::{Node, StatementList}; use gc::{Finalize, Trace}; use std::fmt; @@ -25,27 +25,14 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct Block { - statements: Box<[Node]>, + statements: StatementList, } impl Block { /// 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.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)?; - } + self.statements.display(f, indentation + 1)?; write!(f, "{}}}", " ".repeat(indentation)) } } @@ -56,23 +43,6 @@ 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/mod.rs b/boa/src/syntax/ast/node/mod.rs index e841c71e7d2..d5bd69b2589 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -5,6 +5,7 @@ pub mod arrow_function; pub mod block; pub mod local; pub mod operator; +pub mod statement_list; pub use self::{ array::ArrayDecl, @@ -12,16 +13,14 @@ pub use self::{ block::Block, local::Local, operator::{Assign, BinOp}, + statement_list::{StatementList, VarDecl}, }; use crate::syntax::ast::{ constant::Const, op::{Operator, UnaryOp}, }; use gc::{Finalize, Trace}; -use std::{ - cmp::Ordering, - fmt::{self, Display}, -}; +use std::fmt::{self, Display}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -154,25 +153,6 @@ pub enum Node { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while DoWhileLoop(Box, Box), - /// The `function` declaration (function statement) defines a function with the specified - /// parameters. - /// - /// A function created with a function declaration is a `Function` object and has all the - /// properties, methods and behavior of `Function`. - /// - /// A function can also be created using an expression (see function expression). - /// - /// By default, functions return `undefined`. To return any other value, the function must have - /// a return statement that specifies the value to return. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function - FunctionDecl(Box, Box<[FormalParameter]>, Box), - /// The `function` expression defines a function with the specified parameters. /// /// A function created with a function expression is a `Function` object and has all the @@ -390,14 +370,6 @@ pub enum Node { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax Spread(Box), - /// Similar to `Node::Block` but without the braces - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#prod-StatementList - StatementList(Box<[Node]>), - /// The `throw` statement throws a user-defined exception. /// /// Syntax: `throw expression;` @@ -467,26 +439,6 @@ pub enum Node { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators UnaryOp(UnaryOp, Box), - /// The `var` statement declares a variable, optionally initializing it to a value. - /// - /// var declarations, wherever they occur, are processed before any code is executed. This is - /// called hoisting, and is discussed further below. - /// - /// The scope of a variable declared with var is its current execution context, which is either - /// the enclosing function or, for variables declared outside any function, global. If you - /// re-declare a JavaScript variable, it will not lose its value. - /// - /// Assigning a value to an undeclared variable implicitly creates it as a global variable (it - /// becomes a property of the global object) when the assignment is executed. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-VariableStatement - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var - VarDecl(Box<[(Box, Option)]>), - /// The `while` statement creates a loop that executes a specified statement as long as the /// test condition evaluates to `true`. /// @@ -537,21 +489,6 @@ impl Display for Node { } impl Node { - /// 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 @@ -614,16 +551,6 @@ impl Node { Self::DoWhileLoop(body.into(), condition.into()) } - /// Creates a `FunctionDecl` AST node. - pub fn function_decl(name: N, params: P, body: B) -> Self - where - N: Into>, - P: Into>, - B: Into>, - { - Self::FunctionDecl(name.into(), params.into(), body.into()) - } - /// Creates a `FunctionDecl` AST node. pub fn function_expr(name: ON, params: P, body: B) -> Self where @@ -735,14 +662,6 @@ impl Node { Self::Spread(val.into()) } - /// Creates a `StatementList` AST node. - pub fn statement_list(list: L) -> Self - where - L: Into>, - { - Self::StatementList(list.into()) - } - /// Creates a `Throw` AST node. pub fn throw(val: V) -> Self where @@ -789,13 +708,13 @@ impl Node { Self::UnaryOp(op, val.into()) } - /// Creates a `VarDecl` AST node. - pub fn var_decl(init: I) -> Self - where - I: Into, Option)]>>, - { - Self::VarDecl(init.into()) - } + // /// Creates a `VarDecl` AST node. + // pub fn var_decl(init: I) -> Self + // where + // I: Into, Option)]>>, + // { + // Self::VarDecl(init.into()) + // } /// Creates a `WhileLoop` AST node. pub fn while_loop(condition: C, body: B) -> Self @@ -842,23 +761,6 @@ impl Node { ), Self::Spread(ref node) => write!(f, "...{}", node), Self::Block(ref block) => block.display(f, indentation), - Self::StatementList(ref list) => { - for node in list.iter() { - node.display(f, indentation + 1)?; - - match node { - Self::Block(_) - | Self::If(_, _, _) - | Self::Switch(_, _, _) - | Self::FunctionDecl(_, _, _) - | Self::WhileLoop(_, _) - | Self::StatementList(_) => {} - _ => write!(f, ";")?, - } - writeln!(f)?; - } - Ok(()) - } 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), @@ -944,12 +846,6 @@ impl Node { 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 - f.write_str("} ")?; - node.display(f, indentation + 1) - } Self::FunctionExpr(ref name, ref args, ref node) => { write!(f, "function ")?; if let Some(func_name) = name { @@ -967,12 +863,8 @@ impl Node { Self::Return(None) => write!(f, "return"), Self::Throw(ref ex) => write!(f, "throw {}", ex), 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 ")?; - } else { - f.write_str("let ")?; - } + Self::LetDecl(ref vars) => { + f.write_str("let ")?; for (key, val) in vars.iter() { match val { Some(x) => write!(f, "{} = {}", key, x)?, diff --git a/boa/src/syntax/ast/node/statement_list.rs b/boa/src/syntax/ast/node/statement_list.rs new file mode 100644 index 00000000000..cc2d818c8f1 --- /dev/null +++ b/boa/src/syntax/ast/node/statement_list.rs @@ -0,0 +1,150 @@ +//! Statement list node. + +use super::{join_nodes, FormalParameter, Node}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// List of statements. +/// +/// Similar to `Node::Block` but without the braces. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-StatementList +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct StatementList { + functions: Box<[FunctionDecl]>, + statements: Box<[Node]>, +} + +impl StatementList { + /// Creates a new statement list. + pub(crate) fn new(functions: F, statements: S) -> Self + where + F: Into>, + S: Into>, + { + Self { + functions: functions.into(), + statements: statements.into(), + } + } + + /// Implements the display formatting with indentation. + pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + // Print the functions first. + for function in self.functions.iter() { + function.display(f, indentation)?; + writeln!(f); + } + + // Print statements + for node in self.statements.iter() { + node.display(f, indentation + 1)?; + + match node { + Node::Block(_) + | Node::If(_, _, _) + | Node::Switch(_, _, _) + | Node::WhileLoop(_, _) => {} + _ => write!(f, ";")?, + } + writeln!(f)?; + } + Ok(()) + } +} + +impl fmt::Display for StatementList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +/// The `var` statement declares a variable, optionally initializing it to a value. +/// +/// var declarations, wherever they occur, are processed before any code is executed. This is +/// called hoisting, and is discussed further below. +/// +/// The scope of a variable declared with var is its current execution context, which is either +/// the enclosing function or, for variables declared outside any function, global. If you +/// re-declare a JavaScript variable, it will not lose its value. +/// +/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it +/// becomes a property of the global object) when the assignment is executed. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct VarDecl { + vars: Box<[Box]>, +} + +impl From for VarDecl +where + T: Into]>>, +{ + fn from(list: T) -> Self { + Self { vars: list.into() } + } +} + +impl fmt::Display for VarDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.vars.is_empty() { + write!(f, "var ")?; + join_nodes(f, &self.vars) + } else { + Ok(()) + } + } +} + +/// The `function` declaration (function statement) defines a function with the specified +/// parameters. +/// +/// A function created with a function declaration is a `Function` object and has all the +/// properties, methods and behavior of `Function`. +/// +/// A function can also be created using an expression (see [function expression][func_expr]). +/// +/// By default, functions return `undefined`. To return any other value, the function must have +/// a return statement that specifies the value to return. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function +/// [func_expr]: ../enum.Node.html#variant.FunctionExpr +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct FunctionDecl { + name: Box, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl FunctionDecl { + /// Implements the display formatting with indentation. + fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + write!(f, "function {} (", self.name)?; + join_nodes(f, &self.parameters)?; + f.write_str(") {{")?; + + self.body.display(f, indentation + 1); + + writeln!(f, "}}") + } +} diff --git a/boa/src/syntax/parser/mod.rs b/boa/src/syntax/parser/mod.rs index d02130a9368..bfbb6798cb2 100644 --- a/boa/src/syntax/parser/mod.rs +++ b/boa/src/syntax/parser/mod.rs @@ -9,7 +9,10 @@ mod statement; mod tests; use self::error::{ParseError, ParseResult}; -use crate::syntax::ast::{node::Node, token::Token}; +use crate::syntax::ast::{ + node::{StatementList, VarDecl}, + token::Token, +}; use cursor::Cursor; /// Trait implemented by parsers. @@ -103,8 +106,8 @@ impl<'a> Parser<'a> { } /// Parse all expressions in the token array - pub fn parse_all(&mut self) -> ParseResult { - Script.parse(&mut self.cursor).map(Node::statement_list) + pub fn parse_all(&mut self) -> (VarDecl, StatementList) { + Script.parse(&mut self.cursor) } } @@ -118,13 +121,16 @@ impl<'a> Parser<'a> { pub struct Script; impl TokenParser for Script { - type Output = Vec; + type Output = (VarDecl, StatementList); fn parse(self, cursor: &mut Cursor<'_>) -> Result { if cursor.peek(0).is_some() { ScriptBody.parse(cursor) } else { - Ok(Vec::new()) + Ok(( + Vec::new().into(), + StatementList::new(Vec::new(), Vec::new()), + )) } } } @@ -139,7 +145,7 @@ impl TokenParser for Script { pub struct ScriptBody; impl TokenParser for ScriptBody { - type Output = Vec; + type Output = (VarDecl, StatementList); fn parse(self, cursor: &mut Cursor<'_>) -> Result { self::statement::StatementList::new(false, false, false, false).parse(cursor) diff --git a/boa/src/syntax/parser/statement/block/mod.rs b/boa/src/syntax/parser/statement/block/mod.rs index e4af925d2e3..bde56d93efc 100644 --- a/boa/src/syntax/parser/statement/block/mod.rs +++ b/boa/src/syntax/parser/statement/block/mod.rs @@ -10,15 +10,10 @@ #[cfg(test)] mod tests; -use super::{declaration::Declaration, Statement}; +use super::StatementList; use crate::syntax::{ - ast::{ - keyword::Keyword, - node::{self, Node}, - punc::Punctuator, - token::TokenKind, - }, - parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, + ast::{node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, }; /// A `BlockStatement` is equivalent to a `Block`. @@ -81,134 +76,3 @@ impl TokenParser for Block { Ok(statement_list) } } - -/// Reads a list of statements. -/// -/// If `break_when_closingbrase` is `true`, it will stop as soon as it finds a `}` character. -/// -/// More information: -/// - [ECMAScript specification][spec] -/// -/// [spec]: https://tc39.es/ecma262/#prod-StatementList -#[derive(Debug, Clone, Copy)] -pub(in crate::syntax::parser) struct StatementList { - allow_yield: AllowYield, - allow_await: AllowAwait, - allow_return: AllowReturn, - break_when_closingbrase: bool, -} - -impl StatementList { - /// Creates a new `StatementList` parser. - pub(in crate::syntax::parser) fn new( - allow_yield: Y, - allow_await: A, - allow_return: R, - break_when_closingbrase: bool, - ) -> Self - where - Y: Into, - A: Into, - R: Into, - { - Self { - allow_yield: allow_yield.into(), - allow_await: allow_await.into(), - allow_return: allow_return.into(), - break_when_closingbrase, - } - } -} - -impl TokenParser for StatementList { - type Output = Box<[Node]>; - - fn parse(self, cursor: &mut Cursor<'_>) -> Result { - let mut statements = Vec::new(); - - loop { - match cursor.peek(0) { - Some(token) if token.kind == TokenKind::Punctuator(Punctuator::CloseBlock) => { - if self.break_when_closingbrase { - break; - } else { - return Err(ParseError::Unexpected(token.clone(), None)); - } - } - None => { - if self.break_when_closingbrase { - return Err(ParseError::AbruptEnd); - } else { - break; - } - } - _ => {} - } - - let item = - StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return) - .parse(cursor)?; - - statements.push(item); - - // move the cursor forward for any consecutive semicolon. - while cursor.next_if(Punctuator::Semicolon).is_some() {} - } - - statements.sort_by(Node::hoistable_order); - - Ok(statements.into_boxed_slice()) - } -} - -/// Statement list item parsing -/// -/// A statement list item can either be an statement or a declaration. -/// -/// More information: -/// - [MDN documentation][mdn] -/// - [ECMAScript specification][spec] -/// -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements -/// [spec]: https://tc39.es/ecma262/#prod-StatementListItem -#[derive(Debug, Clone, Copy)] -struct StatementListItem { - allow_yield: AllowYield, - allow_await: AllowAwait, - allow_return: AllowReturn, -} - -impl StatementListItem { - /// Creates a new `StatementListItem` parser. - fn new(allow_yield: Y, allow_await: A, allow_return: R) -> Self - where - Y: Into, - A: Into, - R: Into, - { - Self { - allow_yield: allow_yield.into(), - allow_await: allow_await.into(), - allow_return: allow_return.into(), - } - } -} - -impl TokenParser for StatementListItem { - type Output = Node; - - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { - let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - - match tok.kind { - TokenKind::Keyword(Keyword::Function) - | TokenKind::Keyword(Keyword::Const) - | TokenKind::Keyword(Keyword::Let) => { - Declaration::new(self.allow_yield, self.allow_await).parse(cursor) - } - _ => { - Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor) - } - } - } -} diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index c6ea68d4ea3..be222241c74 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -190,7 +190,7 @@ impl StatementList { } impl TokenParser for StatementList { - type Output = Vec; + type Output = (VarDecl, StatementList); fn parse(self, cursor: &mut Cursor<'_>) -> Result { let mut items = Vec::new(); @@ -223,6 +223,8 @@ impl TokenParser for StatementList { while cursor.next_if(Punctuator::Semicolon).is_some() {} } + items.sort_by(Node::hoistable_order); + Ok(items) } } diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index f90dfd29313..d34dd0e2d73 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -3,7 +3,7 @@ use super::Parser; use crate::syntax::{ ast::node::{Assign, BinOp, Local, Node}, - ast::op::NumOp, + ast::op::{NumOp, UnaryOp}, lexer::Lexer, }; @@ -60,3 +60,39 @@ fn assign_operator_precedence() { .into()], ); } + +#[test] +fn hoisting() { + check_parser( + r" + var a = hello(); + a++; + + function hello() { return 10 }", + 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_parser( + r" + a = 10; + a++; + + var a;", + 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"))), + ], + ); +} From fe83bf33350b51235e7d317c1ba4811bc26e1ae9 Mon Sep 17 00:00:00 2001 From: Iban Eguia Moraza Date: Wed, 13 May 2020 18:55:19 +0200 Subject: [PATCH 4/4] More changes --- Cargo.lock | 23 +- boa/Cargo.toml | 3 +- boa/src/builtins/function/mod.rs | 12 +- boa/src/exec/array.rs | 4 +- boa/src/exec/arrow_function.rs | 6 +- boa/src/exec/block.rs | 6 +- boa/src/exec/declaration.rs | 114 ++++ boa/src/exec/mod.rs | 134 +---- boa/src/exec/operator.rs | 34 +- boa/src/exec/statement_list.rs | 37 ++ boa/src/lib.rs | 21 +- boa/src/syntax/ast/mod.rs | 12 +- boa/src/syntax/ast/node/array.rs | 2 +- boa/src/syntax/ast/node/arrow_function.rs | 61 -- boa/src/syntax/ast/node/block.rs | 26 + boa/src/syntax/ast/node/declaration.rs | 334 +++++++++++ boa/src/syntax/ast/node/mod.rs | 109 ++-- boa/src/syntax/ast/node/statement_list.rs | 130 ++--- boa/src/syntax/ast/pos.rs | 36 -- boa/src/syntax/ast/position.rs | 277 ++++++++++ boa/src/syntax/ast/{punc.rs => punctuator.rs} | 0 boa/src/syntax/ast/token.rs | 25 +- boa/src/syntax/lexer/mod.rs | 178 +++--- boa/src/syntax/lexer/tests.rs | 523 ++++++++++-------- boa/src/syntax/parser/cursor.rs | 28 +- boa/src/syntax/parser/error.rs | 28 +- .../expression/assignment/arrow_function.rs | 17 +- .../expression/assignment/conditional.rs | 4 +- .../expression/assignment/exponentiation.rs | 8 +- .../parser/expression/assignment/mod.rs | 10 +- .../expression/left_hand_side/arguments.rs | 4 +- .../parser/expression/left_hand_side/call.rs | 8 +- .../expression/left_hand_side/member.rs | 10 +- .../parser/expression/left_hand_side/mod.rs | 4 +- boa/src/syntax/parser/expression/mod.rs | 10 +- .../primary/array_initializer/mod.rs | 2 +- .../expression/primary/function_expression.rs | 14 +- .../syntax/parser/expression/primary/mod.rs | 10 +- .../primary/object_initializer/mod.rs | 12 +- .../primary/object_initializer/tests.rs | 20 +- .../syntax/parser/expression/primary/tests.rs | 2 +- boa/src/syntax/parser/expression/unary.rs | 4 +- boa/src/syntax/parser/expression/update.rs | 6 +- boa/src/syntax/parser/function/mod.rs | 25 +- boa/src/syntax/parser/function/tests.rs | 67 +-- boa/src/syntax/parser/mod.rs | 16 +- boa/src/syntax/parser/statement/block/mod.rs | 4 +- .../syntax/parser/statement/block/tests.rs | 40 +- .../syntax/parser/statement/break_stm/mod.rs | 4 +- .../parser/statement/continue_stm/mod.rs | 4 +- .../parser/statement/declaration/hoistable.rs | 30 +- .../parser/statement/declaration/lexical.rs | 6 +- .../parser/statement/declaration/mod.rs | 4 +- .../parser/statement/declaration/tests.rs | 56 +- boa/src/syntax/parser/statement/if_stm/mod.rs | 4 +- .../statement/iteration/do_while_statement.rs | 4 +- .../statement/iteration/for_statement.rs | 9 +- .../parser/statement/iteration/tests.rs | 8 +- .../statement/iteration/while_statement.rs | 2 +- boa/src/syntax/parser/statement/mod.rs | 18 +- .../syntax/parser/statement/return_stm/mod.rs | 6 +- boa/src/syntax/parser/statement/switch/mod.rs | 2 +- boa/src/syntax/parser/statement/throw/mod.rs | 4 +- .../syntax/parser/statement/throw/tests.rs | 2 +- .../syntax/parser/statement/try_stm/catch.rs | 3 +- .../parser/statement/try_stm/finally.rs | 2 +- .../syntax/parser/statement/try_stm/mod.rs | 10 +- .../syntax/parser/statement/try_stm/tests.rs | 37 +- boa/src/syntax/parser/statement/variable.rs | 23 +- boa/src/syntax/parser/tests.rs | 22 +- 70 files changed, 1667 insertions(+), 1053 deletions(-) create mode 100644 boa/src/exec/declaration.rs create mode 100644 boa/src/exec/statement_list.rs create mode 100644 boa/src/syntax/ast/node/declaration.rs delete mode 100644 boa/src/syntax/ast/pos.rs create mode 100644 boa/src/syntax/ast/position.rs rename boa/src/syntax/ast/{punc.rs => punctuator.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index acc1be69e29..e257f940986 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ dependencies = [ "gc", "jemallocator", "num-traits", + "once_cell", "rand", "regex", "rustc-hash", @@ -253,7 +254,7 @@ checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" [[package]] name = "gc" version = "0.3.4" -source = "git+https://github.com/Razican/rust-gc.git?branch=box_str#fadf8eb29b55c27ef973ecc3395bd3c18de849bb" +source = "git+https://github.com/Manishearth/rust-gc.git#332ac522e13bc4d8ea2e46639bfe262e8d3a66a5" dependencies = [ "gc_derive", ] @@ -261,7 +262,7 @@ dependencies = [ [[package]] name = "gc_derive" version = "0.3.4" -source = "git+https://github.com/Razican/rust-gc.git?branch=box_str#fadf8eb29b55c27ef973ecc3395bd3c18de849bb" +source = "git+https://github.com/Manishearth/rust-gc.git#332ac522e13bc4d8ea2e46639bfe262e8d3a66a5" dependencies = [ "proc-macro2", "quote", @@ -351,9 +352,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" [[package]] name = "log" @@ -404,6 +405,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" + [[package]] name = "oorandom" version = "11.1.1" @@ -465,9 +472,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7" +checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" dependencies = [ "proc-macro2", ] @@ -678,9 +685,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1b5e337360b1fae433c59fcafa0c6b77c605e92540afa5221a7b81a9eca91d" +checksum = "4696caa4048ac7ce2bcd2e484b3cef88c1004e41b8e945a277e2c25dc0b72060" dependencies = [ "proc-macro2", "quote", diff --git a/boa/Cargo.toml b/boa/Cargo.toml index 1b4608e7112..0624828de1c 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"], git = "https://github.com/Razican/rust-gc.git", branch = "box_str" } +gc = { version = "0.3.4", features = ["derive"], git = "https://github.com/Manishearth/rust-gc.git" } serde_json = "1.0.53" rand = "0.7.3" num-traits = "0.2.11" @@ -20,6 +20,7 @@ rustc-hash = "1.1.0" # Optional Dependencies serde = { version = "1.0.110", features = ["derive"], optional = true } +once_cell = "1.3.1" [dev-dependencies] criterion = "0.3.2" diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index b1dae529dfd..065d9616e2a 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -19,8 +19,8 @@ use crate::{ value::{ResultValue, Value}, }, environment::lexical_environment::{new_function_environment, Environment}, - exec::Interpreter, - syntax::ast::node::{FormalParameter, Node}, + exec::{Executable, Interpreter}, + syntax::ast::node::{FormalParameter, StatementList}, }; use gc::{unsafe_empty_trace, Finalize, Trace}; use std::fmt::{self, Debug}; @@ -48,14 +48,14 @@ pub enum ThisMode { #[derive(Clone, Finalize)] pub enum FunctionBody { BuiltIn(NativeFunctionData), - Ordinary(Node), + Ordinary(StatementList), } impl Debug for FunctionBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::BuiltIn(_) => write!(f, "native code"), - Self::Ordinary(node) => write!(f, "{}", node), + Self::Ordinary(statements) => write!(f, "{:?}", statements), } } } @@ -191,7 +191,7 @@ impl Function { // Call body should be set before reaching here let result = match &self.body { - FunctionBody::Ordinary(ref body) => interpreter.exec(body), + FunctionBody::Ordinary(ref body) => body.run(interpreter), _ => panic!("Ordinary function should not have BuiltIn Function body"), }; @@ -250,7 +250,7 @@ impl Function { // Call body should be set before reaching here let result = match &self.body { - FunctionBody::Ordinary(ref body) => interpreter.exec(body), + FunctionBody::Ordinary(ref body) => body.run(interpreter), _ => panic!("Ordinary function should not have BuiltIn Function body"), }; diff --git a/boa/src/exec/array.rs b/boa/src/exec/array.rs index d29038bd58b..2aa92a955dd 100644 --- a/boa/src/exec/array.rs +++ b/boa/src/exec/array.rs @@ -15,12 +15,12 @@ impl Executable for ArrayDecl { let mut elements = Vec::new(); for elem in self.as_ref() { if let Node::Spread(ref x) = elem { - let val = interpreter.exec(x)?; + let val = x.run(interpreter)?; 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)?); + elements.push(elem.run(interpreter)?); } add_to_array_object(&array, &elements)?; diff --git a/boa/src/exec/arrow_function.rs b/boa/src/exec/arrow_function.rs index 4fb9520f1d4..fccf2dec7e5 100644 --- a/boa/src/exec/arrow_function.rs +++ b/boa/src/exec/arrow_function.rs @@ -22,20 +22,20 @@ impl Executable for ArrowFunctionDecl { // .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 + self.params().to_vec(), // 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()), + FunctionBody::Ordinary(self.body().to_vec().into()), 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())); + 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 index 521e922024e..c592a05ab92 100644 --- a/boa/src/exec/block.rs +++ b/boa/src/exec/block.rs @@ -1,3 +1,5 @@ +//! Block statement execution. + use super::{Executable, Interpreter}; use crate::{ builtins::value::{ResultValue, Value}, @@ -15,8 +17,8 @@ impl Executable for Block { } let mut obj = Value::null(); - for statement in self.as_ref() { - obj = interpreter.exec(statement)?; + for statement in self.statements() { + obj = statement.run(interpreter)?; // early return if interpreter.is_return { diff --git a/boa/src/exec/declaration.rs b/boa/src/exec/declaration.rs new file mode 100644 index 00000000000..9e9154228d8 --- /dev/null +++ b/boa/src/exec/declaration.rs @@ -0,0 +1,114 @@ +//! Declaration execution. + +use super::{Executable, Interpreter}; +use crate::{ + builtins::{ + function::{Function as FunctionObject, FunctionBody, ThisMode}, + object::Object, + value::{ResultValue, Value}, + }, + environment::lexical_environment::VariableScope, + syntax::ast::node::{FunctionDecl, FunctionExpr, VarDeclList}, +}; + +impl Executable for FunctionDecl { + 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.parameters().to_vec(), + interpreter + .realm_mut() + .environment + .get_current_environment() + .clone(), + FunctionBody::Ordinary(self.body().to_vec().into()), + ThisMode::NonLexical, + ); + + 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.parameters().len())); + + // Set the name and assign it in the current environment + if let Some(name) = self.name() { + val.set_field_slice("name", Value::from(self.name())); + interpreter.realm_mut().environment.create_mutable_binding( + name.to_owned(), + false, + VariableScope::Function, + ); + + interpreter + .realm_mut() + .environment + .initialize_binding(name, val.clone()); + } + + Ok(val) + } +} + +impl Executable for FunctionExpr { + 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.parameters().to_vec(), + interpreter + .realm_mut() + .environment + .get_current_environment() + .clone(), + FunctionBody::Ordinary(self.body().to_vec().into()), + ThisMode::NonLexical, + ); + + 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.parameters().len())); + + if let Some(name) = self.name() { + val.set_field_slice("name", Value::string(name)); + } + + Ok(val) + } +} + +impl Executable for VarDeclList { + fn run(&self, interpreter: &mut Interpreter) -> ResultValue { + for var in self.as_ref() { + let val = match var.init() { + Some(v) => v.run(interpreter)?, + None => Value::undefined(), + }; + interpreter.realm_mut().environment.create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Function, + ); + interpreter + .realm_mut() + .environment + .initialize_binding(var.name(), val); + } + Ok(Value::undefined()) + } +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 3a722bd8327..0eabd5c2605 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -3,21 +3,22 @@ mod array; mod arrow_function; mod block; +mod declaration; mod operator; +mod statement_list; #[cfg(test)] mod tests; use crate::{ builtins::{ - function::{Function as FunctionObject, FunctionBody, ThisMode}, object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, + internal_methods_trait::ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, }, property::Property, value::{ResultValue, Value, ValueData}, }, - environment::lexical_environment::{new_declarative_environment, VariableScope}, + environment::lexical_environment::VariableScope, realm::Realm, syntax::ast::{ constant::Const, @@ -479,7 +480,7 @@ impl Executable for Node { PropertyDefinition::MethodDefinition(kind, name, func) => { if let MethodDefinitionKind::Ordinary = kind { obj.borrow() - .set_field_slice(&name.clone(), interpreter.exec(func)?); + .set_field_slice(&name.clone(), func.run(interpreter)?); } else { // TODO: Implement other types of MethodDefinitionKinds. unimplemented!("other types of property method definitions."); @@ -493,80 +494,9 @@ impl Executable for Node { } 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 - // 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 - interpreter - .realm_mut() - .environment - .get_current_environment() - .clone(), - FunctionBody::Ordinary(*expr.clone()), - ThisMode::NonLexical, - ); - - 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())); - - // Set the name and assign it in the current environment - val.set_field_slice("name", Value::from(name.as_ref())); - interpreter.realm_mut().environment.create_mutable_binding( - name.as_ref().to_owned(), - false, - VariableScope::Function, - ); - - interpreter - .realm_mut() - .environment - .initialize_binding(name, val.clone()); - - Ok(val) - } + Node::FunctionDecl(ref decl) => decl.run(interpreter), // - Node::FunctionExpr(ref name, 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 - interpreter - .realm_mut() - .environment - .get_current_environment() - .clone(), - FunctionBody::Ordinary(*expr.clone()), - ThisMode::NonLexical, - ); - - 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())); - - if let Some(name) = name { - val.set_field_slice("name", Value::string(name.as_ref())); - } - - Ok(val) - } + Node::FunctionExpr(ref expr) => expr.run(interpreter), Node::ArrowFunctionDecl(ref decl) => decl.run(interpreter), Node::BinOp(ref op) => op.run(interpreter), Node::UnaryOp(ref op, ref a) => { @@ -614,8 +544,8 @@ impl Executable for Node { Node::ArrayDecl(_) | Node::Block(_) | Node::Const(_) - | Node::FunctionDecl(_, _, _) - | Node::FunctionExpr(_, _, _) + | Node::FunctionDecl(_) + | Node::FunctionExpr(_) | Node::New(_) | Node::Object(_) | Node::TypeOf(_) @@ -663,25 +593,7 @@ impl Executable for Node { } 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) => interpreter.exec(&v)?, - None => Value::undefined(), - }; - interpreter.realm_mut().environment.create_mutable_binding( - name.as_ref().to_owned(), - false, - VariableScope::Function, - ); - interpreter - .realm_mut() - .environment - .initialize_binding(&name, val); - } - Ok(Value::undefined()) - } + Node::VarDeclList(ref decl) => decl.run(interpreter), Node::LetDecl(ref vars) => { for var in vars.iter() { let (name, value) = var.clone(); @@ -737,32 +649,6 @@ impl Executable for Node { } })) } - Node::StatementList(ref list) => { - { - 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 (i, item) in list.iter().enumerate() { - let val = interpreter.exec(item)?; - // early return - if interpreter.is_return { - obj = val; - break; - } - if i + 1 == list.len() { - obj = val; - } - } - - // pop the block env - 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 interpreter.exec(node) diff --git a/boa/src/exec/operator.rs b/boa/src/exec/operator.rs index e4617bb59b5..8bec69e4c15 100644 --- a/boa/src/exec/operator.rs +++ b/boa/src/exec/operator.rs @@ -12,7 +12,7 @@ use crate::{ impl Executable for Assign { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { - let val = interpreter.exec(self.rhs())?; + let val = self.rhs().run(interpreter)?; match self.lhs() { Node::Local(ref name) => { if interpreter.realm().environment.has_binding(name.as_ref()) { @@ -35,12 +35,12 @@ impl Executable for Assign { } } Node::GetConstField(ref obj, ref field) => { - let val_obj = interpreter.exec(obj)?; + let val_obj = obj.run(interpreter)?; 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)?; + let val_obj = obj.run(interpreter)?; + let val_field = field.run(interpreter)?; val_obj.set_field(val_field, val.clone()); } _ => (), @@ -53,8 +53,8 @@ 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())?; + let v_a = self.lhs().run(interpreter)?; + let v_b = self.rhs().run(interpreter)?; Ok(match op { NumOp::Add => v_a + v_b, NumOp::Sub => v_a - v_b, @@ -65,8 +65,8 @@ impl Executable for BinOp { }) } op::BinOp::Bit(op) => { - let v_a = interpreter.exec(self.lhs())?; - let v_b = interpreter.exec(self.rhs())?; + let v_a = self.lhs().run(interpreter)?; + let v_b = self.rhs().run(interpreter)?; Ok(match op { BitOp::And => v_a & v_b, BitOp::Or => v_a | v_b, @@ -78,8 +78,8 @@ impl Executable for BinOp { }) } op::BinOp::Comp(op) => { - let mut v_a = interpreter.exec(self.lhs())?; - let mut v_b = interpreter.exec(self.rhs())?; + let mut v_a = self.lhs().run(interpreter)?; + let mut v_b = self.rhs().run(interpreter)?; Ok(Value::from(match op { CompOp::Equal if v_a.is_object() => v_a == v_b, CompOp::Equal => v_a == v_b, @@ -107,12 +107,12 @@ impl Executable for BinOp { 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())?), + to_bool(self.lhs().run(interpreter)?) + && to_bool(self.rhs().run(interpreter)?), ), LogOp::Or => Value::from( - to_bool(interpreter.exec(self.lhs())?) - || to_bool(interpreter.exec(self.rhs())?), + to_bool(self.lhs().run(interpreter)?) + || to_bool(self.rhs().run(interpreter)?), ), }) } @@ -122,7 +122,7 @@ impl Executable for BinOp { .realm() .environment .get_binding_value(name.as_ref()); - let v_b = interpreter.exec(self.rhs())?; + let v_b = self.rhs().run(interpreter)?; let value = Self::run_assign(op, v_a, v_b); interpreter.realm.environment.set_mutable_binding( name.as_ref(), @@ -132,9 +132,9 @@ impl Executable for BinOp { Ok(value) } Node::GetConstField(ref obj, ref field) => { - let v_r_a = interpreter.exec(obj)?; + let v_r_a = obj.run(interpreter)?; let v_a = v_r_a.get_field_slice(field); - let v_b = interpreter.exec(self.rhs())?; + let v_b = self.rhs().run(interpreter)?; let value = Self::run_assign(op, v_a, v_b); v_r_a.set_field_slice(&field.clone(), value.clone()); Ok(value) diff --git a/boa/src/exec/statement_list.rs b/boa/src/exec/statement_list.rs new file mode 100644 index 00000000000..e9fbc19e567 --- /dev/null +++ b/boa/src/exec/statement_list.rs @@ -0,0 +1,37 @@ +//! Statement list execution. + +use super::{Executable, Interpreter}; +use crate::{ + builtins::value::{ResultValue, Value}, + environment::lexical_environment::new_declarative_environment, + syntax::ast::node::StatementList, +}; + +impl Executable for StatementList { + 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 (i, item) in self.statements().iter().enumerate() { + let val = interpreter.exec(item)?; + // early return + if interpreter.is_return { + obj = val; + break; + } + if i + 1 == self.statements().len() { + obj = val; + } + } + + // pop the block env + let _ = interpreter.realm_mut().environment.pop(); + + Ok(obj) + } +} diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 62321d4de06..f84c58d8c7f 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -40,12 +40,12 @@ pub mod realm; pub mod syntax; use crate::{ builtins::value::ResultValue, - exec::Interpreter, + exec::{Executable, Interpreter}, realm::Realm, - syntax::{ast::node::Node, lexer::Lexer, parser::Parser}, + syntax::{ast::node::StatementList, lexer::Lexer, parser::Parser}, }; -fn parser_expr(src: &str) -> Result { +fn parser_expr(src: &str) -> Result { let mut lexer = Lexer::new(src); lexer.lex().map_err(|e| format!("SyntaxError: {}", e))?; let tokens = lexer.tokens; @@ -59,16 +59,11 @@ fn parser_expr(src: &str) -> Result { pub fn forward(engine: &mut Interpreter, src: &str) -> String { // Setup executor let expr = match parser_expr(src) { - Ok(v) => v, - Err(error_string) => { - return error_string; - } + Ok(res) => res, + Err(e) => return e, }; - let result = engine.exec(&expr); - match result { - Ok(v) => v.to_string(), - Err(v) => format!("{}: {}", "Error", v.to_string()), - } + expr.run(engine) + .map_or_else(|v| v.to_string(), |e| format!("Error: {}", e)) } /// Execute the code using an existing Interpreter. @@ -78,7 +73,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.exec(&expr), + Ok(expr) => expr.run(engine), Err(e) => { eprintln!("{}", e); std::process::exit(1); diff --git a/boa/src/syntax/ast/mod.rs b/boa/src/syntax/ast/mod.rs index 7d1d1c7fd17..1657b783720 100644 --- a/boa/src/syntax/ast/mod.rs +++ b/boa/src/syntax/ast/mod.rs @@ -4,6 +4,14 @@ pub mod constant; pub mod keyword; pub mod node; pub mod op; -pub mod pos; -pub mod punc; +pub mod position; +pub mod punctuator; pub mod token; + +pub use self::{ + keyword::Keyword, + node::Node, + position::{Position, Span}, + punctuator::Punctuator, + token::{Token, TokenKind}, +}; diff --git a/boa/src/syntax/ast/node/array.rs b/boa/src/syntax/ast/node/array.rs index 55763df7c01..c1f2479e59e 100644 --- a/boa/src/syntax/ast/node/array.rs +++ b/boa/src/syntax/ast/node/array.rs @@ -24,9 +24,9 @@ use serde::{Deserialize, Serialize}; /// [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 { + #[cfg_attr(feature = "serde", serde(flatten))] arr: Box<[Node]>, } diff --git a/boa/src/syntax/ast/node/arrow_function.rs b/boa/src/syntax/ast/node/arrow_function.rs index 926448a8010..8b137891791 100644 --- a/boa/src/syntax/ast/node/arrow_function.rs +++ b/boa/src/syntax/ast/node/arrow_function.rs @@ -1,62 +1 @@ -//! 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 index 2fef1629b4e..a71e4ddf2b8 100644 --- a/boa/src/syntax/ast/node/block.rs +++ b/boa/src/syntax/ast/node/block.rs @@ -23,12 +23,27 @@ use serde::{Deserialize, Serialize}; /// [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))] +#[cfg_attr(feature = "serde", serde(transparent))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct Block { + #[cfg_attr(feature = "serde", serde(flatten))] statements: StatementList, } impl Block { + /// Gets the list of statements in this block. + pub(crate) fn statements(&self) -> &[Node] { + self.statements.statements() + } + + /// Gets the lexically declared names. + /// + /// More information: + /// + pub(crate) fn lexically_declared_names(&self) -> &[Box] { + self.statements.lexically_declared_names() + } + /// Implements the display formatting with indentation. pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { writeln!(f, "{{")?; @@ -37,6 +52,17 @@ impl Block { } } +impl From for Block +where + T: Into, +{ + fn from(list: T) -> Self { + Self { + statements: list.into(), + } + } +} + impl fmt::Display for Block { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display(f, 0) diff --git a/boa/src/syntax/ast/node/declaration.rs b/boa/src/syntax/ast/node/declaration.rs new file mode 100644 index 00000000000..579ce9b771c --- /dev/null +++ b/boa/src/syntax/ast/node/declaration.rs @@ -0,0 +1,334 @@ +//! Declaration nodes. + +use super::{join_nodes, FormalParameter, Node, StatementList}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +/// The `var` statement declares a variable, optionally initializing it to a value. +/// +/// var declarations, wherever they occur, are processed before any code is executed. This is +/// called hoisting, and is discussed further below. +/// +/// The scope of a variable declared with var is its current execution context, which is either +/// the enclosing function or, for variables declared outside any function, global. If you +/// re-declare a JavaScript variable, it will not lose its value. +/// +/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it +/// becomes a property of the global object) when the assignment is executed. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct VarDeclList { + #[cfg_attr(feature = "serde", serde(flatten))] + vars: Box<[VarDecl]>, +} + +impl From for VarDeclList +where + T: Into>, +{ + fn from(list: T) -> Self { + Self { vars: list.into() } + } +} + +impl From for VarDeclList { + fn from(decl: VarDecl) -> Self { + Self { + vars: Box::new([decl]), + } + } +} + +impl AsRef<[VarDecl]> for VarDeclList { + fn as_ref(&self) -> &[VarDecl] { + &self.vars + } +} + +impl fmt::Display for VarDeclList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.vars.is_empty() { + write!(f, "var ")?; + join_nodes(f, &self.vars) + } else { + Ok(()) + } + } +} + +impl From for Node +where + T: Into, +{ + fn from(list: T) -> Self { + Self::VarDeclList(list.into()) + } +} + +/// Individual variable declaration. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct VarDecl { + name: Box, + init: Option, +} + +impl fmt::Display for VarDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.name, f)?; + if let Some(ref init) = self.init { + write!(f, " = {}", init); + } + Ok(()) + } +} + +impl VarDecl { + /// Creates a new variable declaration. + pub(crate) fn new(name: N, init: I) -> Self + where + N: Into>, + I: Into>, + { + Self { + name: name.into(), + init: init.into(), + } + } + + /// Gets the name of the variable. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the initialization node for the variable, if any. + pub fn init(&self) -> Option<&Node> { + self.init.as_ref() + } +} + +/// The `function` expression defines a function with the specified parameters. +/// +/// A function created with a function expression is a `Function` object and has all the +/// properties, methods and behavior of `Function`. +/// +/// A function can also be created using a declaration (see function expression). +/// +/// By default, functions return `undefined`. To return any other value, the function must have +/// a return statement that specifies the value to return. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct FunctionExpr { + #[cfg_attr(feature = "serde", serde(flatten))] + inner: FunctionDecl, +} + +impl FunctionExpr { + /// Creates a new function expression + pub(crate) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>>, + P: Into>, + B: Into, + { + Self { + inner: FunctionDecl::new(name, parameters, body), + } + } + + /// Gets the name of the function declaration. + pub fn name(&self) -> Option<&str> { + self.inner.name() + } + + /// Gets the list of parameters of the function declaration. + pub fn parameters(&self) -> &[FormalParameter] { + self.inner.parameters() + } + + /// Gets the body of the function declaration. + pub fn body(&self) -> &[Node] { + self.inner.body() + } + + /// Implements the display formatting with indentation. + pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + self.inner.display(f, indentation) + } +} + +impl fmt::Display for FunctionExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(expr: FunctionExpr) -> Self { + Self::FunctionExpr(expr) + } +} + +/// The `function` declaration (function statement) defines a function with the specified +/// parameters. +/// +/// A function created with a function declaration is a `Function` object and has all the +/// properties, methods and behavior of `Function`. +/// +/// A function can also be created using an expression (see [function expression][func_expr]). +/// +/// By default, functions return `undefined`. To return any other value, the function must have +/// a return statement that specifies the value to return. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function +/// [func_expr]: ../enum.Node.html#variant.FunctionExpr +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct FunctionDecl { + name: Option>, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl FunctionDecl { + /// Creates a new function declaration. + pub(crate) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the function declaration. + pub fn name(&self) -> Option<&str> { + self.name.as_ref().map(Box::as_ref) + } + + /// Gets the list of parameters of the function declaration. + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the function declaration. + pub fn body(&self) -> &[Node] { + self.body.statements() + } + + /// Implements the display formatting with indentation. + pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + f.write_str("function")?; + if let Some(ref name) = self.name { + write!(f, " {}", name); + } + f.write_str("(")?; + join_nodes(f, &self.parameters)?; + f.write_str(") {{")?; + + self.body.display(f, indentation + 1); + + writeln!(f, "}}") + } +} + +impl From for Node { + fn from(decl: FunctionDecl) -> Self { + Self::FunctionDecl(decl) + } +} + +impl fmt::Display for FunctionDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +/// 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 { + params: Box<[FormalParameter]>, + body: StatementList, +} + +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(), + } + } + + /// Gets the list of parameters of the arrow function. + pub(crate) fn params(&self) -> &[FormalParameter] { + &self.params + } + + /// Gets the body of the arrow function. + pub(crate) fn body(&self) -> &[Node] { + &self.body.statements() + } + + /// 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/mod.rs b/boa/src/syntax/ast/node/mod.rs index d5bd69b2589..dfd2652e100 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -1,26 +1,30 @@ //! This module implements the `Node` structure, which composes the AST. pub mod array; -pub mod arrow_function; pub mod block; +pub mod declaration; pub mod local; pub mod operator; pub mod statement_list; pub use self::{ array::ArrayDecl, - arrow_function::ArrowFunctionDecl, block::Block, + declaration::{ArrowFunctionDecl, FunctionDecl, FunctionExpr, VarDecl, VarDeclList}, local::Local, operator::{Assign, BinOp}, - statement_list::{StatementList, VarDecl}, + statement_list::StatementList, }; use crate::syntax::ast::{ constant::Const, op::{Operator, UnaryOp}, }; use gc::{Finalize, Trace}; -use std::fmt::{self, Display}; +use once_cell::sync::OnceCell; +use std::{ + cmp::Ordering, + fmt::{self, Display}, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -153,23 +157,11 @@ pub enum Node { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while DoWhileLoop(Box, Box), - /// The `function` expression defines a function with the specified parameters. - /// - /// A function created with a function expression is a `Function` object and has all the - /// properties, methods and behavior of `Function`. - /// - /// A function can also be created using a declaration (see function expression). - /// - /// By default, functions return `undefined`. To return any other value, the function must have - /// a return statement that specifies the value to return. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [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), + /// A function declaration node. [More information](./declaration/struct.FunctionDecl.html). + FunctionDecl(FunctionDecl), + + /// A function expressino node. [More information](./declaration/struct.FunctionExpr.html) + FunctionExpr(FunctionExpr), /// This property accessor provides access to an object's properties by using the /// [dot notation][mdn]. @@ -439,6 +431,9 @@ pub enum Node { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators UnaryOp(UnaryOp, Box), + /// Array declaration node. [More information](./declaration/struct.VarDeclList.html). + VarDeclList(VarDeclList), + /// The `while` statement creates a loop that executes a specified statement as long as the /// test condition evaluates to `true`. /// @@ -488,7 +483,28 @@ impl Display for Node { } } +impl AsRef for Node { + fn as_ref(&self) -> &Self { + &self + } +} + impl Node { + /// 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::VarDeclList(_), Node::VarDeclList(_)) => Ordering::Equal, + (_, Node::VarDeclList(_)) => Ordering::Greater, + (Node::VarDeclList(_), _) => Ordering::Less, + + (_, _) => Ordering::Equal, + } + } + /// Creates a `Break` AST node. pub fn break_node(label: OL) -> Self where @@ -551,17 +567,6 @@ impl Node { Self::DoWhileLoop(body.into(), condition.into()) } - /// Creates a `FunctionDecl` AST node. - pub fn function_expr(name: ON, params: P, body: B) -> Self - where - N: Into>, - ON: Into>, - P: Into>, - B: Into>, - { - Self::FunctionExpr(name.into().map(N::into), params.into(), body.into()) - } - /// Creates a `GetConstField` AST node. pub fn get_const_field(value: V, label: L) -> Self where @@ -708,14 +713,6 @@ impl Node { Self::UnaryOp(op, val.into()) } - // /// Creates a `VarDecl` AST node. - // pub fn var_decl(init: I) -> Self - // where - // I: Into, Option)]>>, - // { - // Self::VarDecl(init.into()) - // } - /// Creates a `WhileLoop` AST node. pub fn while_loop(condition: C, body: B) -> Self where @@ -725,6 +722,18 @@ impl Node { Self::WhileLoop(condition.into(), body.into()) } + /// Gets the lexically declared names. + /// + /// More information: + /// + pub(crate) fn lexically_declared_names(&self) -> &[Box] { + static LIST: OnceCell]>> = OnceCell::new(); + + LIST.get_or_init(|| match *self { + _ => unimplemented!(), + }) + } + /// Implements the display formatting with indentation. fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { let indent = " ".repeat(indentation); @@ -846,16 +855,9 @@ impl Node { f.write_str("}") } Self::ArrayDecl(ref arr) => Display::fmt(arr, f), - Self::FunctionExpr(ref name, ref args, ref node) => { - write!(f, "function ")?; - if let Some(func_name) = name { - write!(f, "{}", func_name)?; - } - write!(f, "{{")?; - join_nodes(f, args)?; - f.write_str("} ")?; - node.display(f, indentation + 1) - } + Self::VarDeclList(ref list) => Display::fmt(list, f), + Self::FunctionDecl(ref decl) => decl.display(f, indentation), + Self::FunctionExpr(ref expr) => expr.display(f, indentation), 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), @@ -1010,7 +1012,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, Box, Node), + MethodDefinition(MethodDefinitionKind, Box, FunctionExpr), /// 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. @@ -1045,12 +1047,11 @@ impl PropertyDefinition { } /// Creates a `MethodDefinition`. - pub fn method_definition(kind: MethodDefinitionKind, name: N, body: B) -> Self + pub fn method_definition(kind: MethodDefinitionKind, name: N, body: FunctionExpr) -> Self where N: Into>, - B: Into, { - Self::MethodDefinition(kind, name.into(), body.into()) + Self::MethodDefinition(kind, name.into(), body) } /// Creates a `SpreadObject`. diff --git a/boa/src/syntax/ast/node/statement_list.rs b/boa/src/syntax/ast/node/statement_list.rs index cc2d818c8f1..b302d6cba88 100644 --- a/boa/src/syntax/ast/node/statement_list.rs +++ b/boa/src/syntax/ast/node/statement_list.rs @@ -1,7 +1,8 @@ //! Statement list node. -use super::{join_nodes, FormalParameter, Node}; +use super::Node; use gc::{Finalize, Trace}; +use once_cell::sync::OnceCell; use std::fmt; #[cfg(feature = "serde")] @@ -18,31 +19,36 @@ use serde::{Deserialize, Serialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct StatementList { - functions: Box<[FunctionDecl]>, + #[cfg_attr(feature = "serde", serde(flatten))] statements: Box<[Node]>, } impl StatementList { - /// Creates a new statement list. - pub(crate) fn new(functions: F, statements: S) -> Self - where - F: Into>, - S: Into>, - { - Self { - functions: functions.into(), - statements: statements.into(), - } + /// Gets the list of statements. + pub fn statements(&self) -> &[Node] { + &self.statements + } + + /// Gets the lexically declared names. + /// + /// More information: + /// + pub(crate) fn lexically_declared_names(&self) -> &[Box] { + static LIST: OnceCell]>> = OnceCell::new(); + + LIST.get_or_init(|| { + self.statements + .iter() + .map(|node| node.lexically_declared_names()) + .flatten() + .cloned() + .collect::>() + .into_boxed_slice() + }) } /// Implements the display formatting with indentation. pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - // Print the functions first. - for function in self.functions.iter() { - function.display(f, indentation)?; - writeln!(f); - } - // Print statements for node in self.statements.iter() { node.display(f, indentation + 1)?; @@ -60,91 +66,19 @@ impl StatementList { } } -impl fmt::Display for StatementList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -/// The `var` statement declares a variable, optionally initializing it to a value. -/// -/// var declarations, wherever they occur, are processed before any code is executed. This is -/// called hoisting, and is discussed further below. -/// -/// The scope of a variable declared with var is its current execution context, which is either -/// the enclosing function or, for variables declared outside any function, global. If you -/// re-declare a JavaScript variable, it will not lose its value. -/// -/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it -/// becomes a property of the global object) when the assignment is executed. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct VarDecl { - vars: Box<[Box]>, -} - -impl From for VarDecl +impl From for StatementList where - T: Into]>>, + T: Into>, { - fn from(list: T) -> Self { - Self { vars: list.into() } - } -} - -impl fmt::Display for VarDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.vars.is_empty() { - write!(f, "var ")?; - join_nodes(f, &self.vars) - } else { - Ok(()) + fn from(stm: T) -> Self { + Self { + statements: stm.into(), } } } -/// The `function` declaration (function statement) defines a function with the specified -/// parameters. -/// -/// A function created with a function declaration is a `Function` object and has all the -/// properties, methods and behavior of `Function`. -/// -/// A function can also be created using an expression (see [function expression][func_expr]). -/// -/// By default, functions return `undefined`. To return any other value, the function must have -/// a return statement that specifies the value to return. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function -/// [func_expr]: ../enum.Node.html#variant.FunctionExpr -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct FunctionDecl { - name: Box, - parameters: Box<[FormalParameter]>, - body: StatementList, -} - -impl FunctionDecl { - /// Implements the display formatting with indentation. - fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "function {} (", self.name)?; - join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1); - - writeln!(f, "}}") +impl fmt::Display for StatementList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) } } diff --git a/boa/src/syntax/ast/pos.rs b/boa/src/syntax/ast/pos.rs deleted file mode 100644 index a77d50e8e8c..00000000000 --- a/boa/src/syntax/ast/pos.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This module implements the `Pos` structure, which represents a position in the source code. - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// A position in the Javascript source code. -/// -/// Stores both the column number and the line number -/// -/// ## Similar Implementations -/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216) -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct Position { - // Column number - pub column_number: u64, - // Line number - pub line_number: u64, -} - -impl Position { - /// Creates a new `Position`. - /// - /// Positions are usually created by a [`Token`](struct.token/Token.html). - /// - /// # Arguments - /// - /// * `line_number` - The line number the token starts at - /// * `column_number` - The column number the token starts at - pub fn new(line_number: u64, column_number: u64) -> Self { - Self { - line_number, - column_number, - } - } -} diff --git a/boa/src/syntax/ast/position.rs b/boa/src/syntax/ast/position.rs new file mode 100644 index 00000000000..329485af4be --- /dev/null +++ b/boa/src/syntax/ast/position.rs @@ -0,0 +1,277 @@ +//! This module implements the `Pos` structure, which represents a position in the source code. + +use std::{cmp::Ordering, fmt}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// A position in the JavaScript source code. +/// +/// Stores both the column number and the line number +/// +/// ## Similar Implementations +/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216) +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Position { + /// Line number. + line_number: u64, + /// Column number. + column_number: u64, +} + +impl Position { + /// Creates a new `Position`. + #[inline] + pub fn new(line_number: u64, column_number: u64) -> Self { + Self { + line_number, + column_number, + } + } + + /// Gets the line number of the position. + #[inline] + pub fn line_number(self) -> u64 { + self.line_number + } + + /// Gets the column number of the position. + #[inline] + pub fn column_number(self) -> u64 { + self.column_number + } +} + +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.line_number, self.column_number) + } +} + +/// A span in the JavaScript source code. +/// +/// Stores a start position and an end position. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span { + start: Position, + end: Position, +} + +impl Span { + /// Creates a new `Span`. + #[inline] + pub fn new(start: Position, end: Position) -> Self { + assert!(start <= end, "a span cannot start after its end"); + + Self { start, end } + } + + /// Gets the starting position of the span. + #[inline] + pub fn start(self) -> Position { + self.start + } + + /// Gets the final position of the span. + #[inline] + pub fn end(self) -> Position { + self.end + } + + /// Checks if this span inclusively contains another span or position. + #[inline] + pub fn contains(self, other: S) -> bool + where + S: Into, + { + let other = other.into(); + self.start <= other.start && self.end >= other.end + } +} + +impl From for Span { + fn from(pos: Position) -> Self { + Self { + start: pos, + end: pos, + } + } +} + +impl PartialOrd for Span { + fn partial_cmp(&self, other: &Self) -> Option { + if self == other { + Some(Ordering::Equal) + } else if self.end < other.start { + Some(Ordering::Less) + } else if self.start > other.end { + Some(Ordering::Greater) + } else { + None + } + } +} + +impl fmt::Display for Span { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{}..{}]", self.start, self.end) + } +} + +#[cfg(test)] +mod tests { + use super::{Position, Span}; + + /// Checks that the `PartialEq` implementation of `Position` is consistent. + #[test] + fn position_equality() { + assert_eq!(Position::new(10, 50), Position::new(10, 50)); + assert_ne!(Position::new(10, 50), Position::new(10, 51)); + assert_ne!(Position::new(10, 50), Position::new(11, 50)); + assert_ne!(Position::new(10, 50), Position::new(11, 51)); + } + + /// Checks that the `PartialOrd` implementation of `Position` is consistent. + #[test] + fn position_order() { + assert!(Position::new(10, 50) < Position::new(10, 51)); + assert!(Position::new(9, 50) < Position::new(10, 50)); + assert!(Position::new(10, 50) < Position::new(11, 51)); + assert!(Position::new(10, 50) < Position::new(11, 49)); + + assert!(Position::new(10, 51) > Position::new(10, 50)); + assert!(Position::new(10, 50) > Position::new(9, 50)); + assert!(Position::new(11, 51) > Position::new(10, 50)); + assert!(Position::new(11, 49) > Position::new(10, 50)); + } + + /// Checks that the position getters actually retreive correct values. + #[test] + fn position_getters() { + let pos = Position::new(10, 50); + assert_eq!(pos.line_number(), 10); + assert_eq!(pos.column_number(), 50); + } + + /// Checks that the string representation of a position is correct. + #[test] + fn position_to_string() { + let pos = Position::new(10, 50); + + assert_eq!("10:50", pos.to_string()); + assert_eq!("10:50", format!("{}", pos)); + } + + /// Checks that we cannot create an invalid span. + #[test] + #[should_panic] + fn invalid_span() { + let a = Position::new(10, 30); + let b = Position::new(10, 50); + Span::new(b, a); + } + + /// Checks that we can create valid spans. + #[test] + fn span_creation() { + let a = Position::new(10, 30); + let b = Position::new(10, 50); + + let _ = Span::new(a, b); + let _ = Span::new(a, a); + let _ = Span::from(a); + } + + /// Checks that the `PartialEq` implementation of `Span` is consistent. + #[test] + fn span_equality() { + let a = Position::new(10, 50); + let b = Position::new(10, 52); + let c = Position::new(11, 20); + + let span_ab = Span::new(a, b); + let span_ab_2 = Span::new(a, b); + let span_ac = Span::new(a, c); + let span_bc = Span::new(b, c); + + assert_eq!(span_ab, span_ab_2); + assert_ne!(span_ab, span_ac); + assert_ne!(span_ab, span_bc); + assert_ne!(span_bc, span_ac); + + let span_a = Span::from(a); + let span_aa = Span::new(a, a); + + assert_eq!(span_a, span_aa); + } + + /// Checks that the getters retrieve the correct value. + #[test] + fn span_getters() { + let a = Position::new(10, 50); + let b = Position::new(10, 52); + + let span = Span::new(a, b); + + assert_eq!(span.start(), a); + assert_eq!(span.end(), b); + } + + /// Checks that the `Span::contains()` method works properly. + #[test] + fn span_contains() { + let a = Position::new(10, 50); + let b = Position::new(10, 52); + let c = Position::new(11, 20); + let d = Position::new(12, 5); + + let span_ac = Span::new(a, c); + assert!(span_ac.contains(b)); + + let span_ab = Span::new(a, b); + let span_dc = Span::new(d, c); + + assert!(!span_ab.contains(span_dc)); + assert!(span_ab.contains(b)); + + let span_ad = Span::new(a, d); + let span_bc = Span::new(b, c); + + assert!(span_ad.contains(span_bc)); + assert!(!span_bc.contains(span_ad)); + + let span_ac = Span::new(a, c); + let span_bd = Span::new(b, d); + + assert!(!span_ac.contains(span_bd)); + assert!(!span_bd.contains(span_ac)); + } + + /// Checks that the string representation of a span is correct. + #[test] + fn span_to_string() { + let a = Position::new(10, 50); + let b = Position::new(11, 20); + let span = Span::new(a, b); + + assert_eq!("[10:50..11:20]", span.to_string()); + assert_eq!("[10:50..11:20]", format!("{}", span)); + } + + /// Checks that the ordering of spans is correct. + #[test] + fn span_ordering() { + let a = Position::new(10, 50); + let b = Position::new(10, 52); + let c = Position::new(11, 20); + let d = Position::new(12, 5); + + let span_ab = Span::new(a, b); + let span_dc = Span::new(d, c); + + assert!(span_ab < span_dc); + assert!(span_dc > span_ab); + } +} diff --git a/boa/src/syntax/ast/punc.rs b/boa/src/syntax/ast/punctuator.rs similarity index 100% rename from boa/src/syntax/ast/punc.rs rename to boa/src/syntax/ast/punctuator.rs diff --git a/boa/src/syntax/ast/token.rs b/boa/src/syntax/ast/token.rs index 0df76ac7738..037a17d40da 100644 --- a/boa/src/syntax/ast/token.rs +++ b/boa/src/syntax/ast/token.rs @@ -5,7 +5,7 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-tokens -use crate::syntax::ast::{keyword::Keyword, pos::Position, punc::Punctuator}; +use crate::syntax::ast::{Keyword, Punctuator, Span}; use std::fmt::{Debug, Display, Formatter, Result}; #[cfg(feature = "serde")] @@ -21,19 +21,24 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] pub struct Token { /// The token kind, which contains the actual data of the token. - pub kind: TokenKind, - - /// The token position from origina source code. - pub pos: Position, + kind: TokenKind, + /// The token position in the original source code. + span: Span, } impl Token { /// Create a new detailed token from the token data, line number and column number - pub fn new(kind: TokenKind, line_number: u64, column_number: u64) -> Self { - Self { - kind, - pos: Position::new(line_number, column_number), - } + pub fn new(kind: TokenKind, span: Span) -> Self { + Self { kind, span } + } + + pub fn kind(&self) -> &TokenKind { + &self.kind + } + + /// Gets the token span in the original source code. + pub fn span(&self) -> Span { + self.span } } diff --git a/boa/src/syntax/lexer/mod.rs b/boa/src/syntax/lexer/mod.rs index cda968f4e15..ac035284279 100644 --- a/boa/src/syntax/lexer/mod.rs +++ b/boa/src/syntax/lexer/mod.rs @@ -7,8 +7,8 @@ mod tests; use crate::syntax::ast::{ - punc::Punctuator, token::{NumericLiteral, Token, TokenKind}, + Position, Punctuator, Span, }; use std::{ char::{decode_utf16, from_u32}, @@ -26,7 +26,7 @@ macro_rules! vop { match preview { '=' => { $this.next(); - $this.column_number += 1; + $this.next_column(); $assign_op } _ => $op, @@ -37,12 +37,12 @@ macro_rules! vop { match preview { '=' => { $this.next(); - $this.column_number += 1; + $this.next_column(); $assign_op }, $($case => { $this.next(); - $this.column_number += 1; + $this.next_column(); $block })+, _ => $op @@ -53,7 +53,7 @@ macro_rules! vop { match preview { $($case => { $this.next()?; - $this.column_number += 1; + $this.next_column(); $block })+, _ => $op @@ -64,17 +64,19 @@ macro_rules! vop { /// The `op` macro handles binary operations or assignment operations and converts them into tokens. macro_rules! op { ($this:ident, $assign_op:expr, $op:expr) => ({ + let start_pos = $this.position; let punc = vop!($this, $assign_op, $op); - $this.push_punc(punc); + $this.push_punc(punc, start_pos); }); ($this:ident, $assign_op:expr, $op:expr, {$($case:pat => $block:expr),+}) => ({ + let start_pos = $this.position; let punc = vop!($this, $assign_op, $op, {$($case => $block),+}); - $this.push_punc(punc); - }); - ($this:ident, $op:expr, {$($case:pat => $block:expr),+}) => ({ - let punc = vop!($this, $op, {$($case => $block),+}); - $this.push_punc(); + $this.push_punc(punc, start_pos); }); + // ($this:ident, $op:expr, {$($case:pat => $block:expr),+}) => ({ + // let punc = vop!($this, $op, {$($case => $block),+}); + // $this.push_punc(); + // }); } /// An error that occurred during lexing or compiling of the source input. @@ -121,10 +123,8 @@ pub struct Lexer<'a> { /// /// This field is public so you can use them once lexing has finished. pub tokens: Vec, - /// The current line number in the script - line_number: u64, - /// the current column number in the script - column_number: u64, + /// The current position in the source code. + position: Position, /// The full Peekable buffer, an array of [Char]s buffer: Peekable>, } @@ -136,21 +136,55 @@ impl<'a> Lexer<'a> { pub fn new(buffer: &'a str) -> Lexer<'a> { Lexer { tokens: Vec::new(), - line_number: 1, - column_number: 0, + position: Position::new(1, 1), buffer: buffer.chars().peekable(), } } /// Push a token onto the token queue. - fn push_token(&mut self, tk: TokenKind) { + fn push_token(&mut self, tk: TokenKind, start: Position) { self.tokens - .push(Token::new(tk, self.line_number, self.column_number)) + .push(Token::new(tk, Span::new(start, self.position))) } /// Push a punctuation token - fn push_punc(&mut self, punc: Punctuator) { - self.push_token(TokenKind::Punctuator(punc)); + fn push_punc(&mut self, punc: Punctuator, start: Position) { + self.push_token(TokenKind::Punctuator(punc), start); + } + + /// Changes the current position by advancing to the next column. + fn next_column(&mut self) { + let pos = Position::new( + self.position.line_number(), + self.position.column_number() + 1, + ); + self.position = pos; + } + + /// Changes the current position by advancing the given number of columns. + fn move_columns(&mut self, columns: u64) { + let pos = Position::new( + self.position.line_number(), + self.position.column_number() + columns, + ); + self.position = pos; + } + + fn carriage_return(&mut self) { + let pos = Position::new(self.position.line_number(), 0); + self.position = pos; + } + + /// Changes the current position by advancing to the next line. + fn next_line(&mut self) { + let pos = Position::new(self.position.line_number() + 1, 1); + self.position = pos; + } + + /// Changes the current position by advancing the given number of lines. + fn move_lines(&mut self, lines: u64) { + let pos = Position::new(self.position.line_number() + lines, 1); + self.position = pos; } /// next fetches the next token and return it, or a LexerError if there are no more. @@ -266,11 +300,15 @@ impl<'a> Lexer<'a> { let mut buf = ch.to_string(); let mut position_offset = 0; let mut kind = NumericKind::Integer(10); + let start_pos = self.position; if ch == '0' { match self.preview_next() { None => { - self.push_token(TokenKind::NumericLiteral(NumericLiteral::Integer(0))); - self.column_number += 1; + self.push_token( + TokenKind::NumericLiteral(NumericLiteral::Integer(0)), + start_pos, + ); + self.next_column(); return Ok(()); } Some('x') | Some('X') => { @@ -425,8 +463,8 @@ impl<'a> Lexer<'a> { } }; - self.push_token(TokenKind::NumericLiteral(num)); - self.column_number += (buf.len() as u64) + position_offset - 1; + self.push_token(TokenKind::NumericLiteral(num), start_pos); + self.move_columns((buf.len() as u64) + position_offset - 1); Ok(()) } @@ -449,7 +487,8 @@ impl<'a> Lexer<'a> { if self.preview_next().is_none() { return Ok(()); } - self.column_number += 1; + self.next_column(); + let start_pos = self.position; let ch = self.next(); match ch { '"' | '\'' => { @@ -486,7 +525,7 @@ impl<'a> Lexer<'a> { } nums.push(self.next()); } - self.column_number += 2; + self.move_columns(2); let as_num = match u64::from_str_radix(&nums, 16) { Ok(v) => v, Err(_) => 0, @@ -494,8 +533,8 @@ impl<'a> Lexer<'a> { match from_u32(as_num as u32) { Some(v) => v, None => panic!( - "{}:{}: {} is not a valid unicode scalar value", - self.line_number, self.column_number, as_num + "{}: {} is not a valid unicode scalar value", + self.position, as_num ), } } @@ -522,8 +561,7 @@ impl<'a> Lexer<'a> { return Err(LexerError::new("Unterminated String")); } self.next(); // '}' - self.column_number += - (s.len() as u64).wrapping_add(3); + self.move_columns((s.len() as u64).wrapping_add(3)); c } else { let mut codepoints: Vec = vec![]; @@ -540,8 +578,7 @@ impl<'a> Lexer<'a> { }; codepoints.push(as_num); - self.column_number += - (s.len() as u64).wrapping_add(2); + self.move_columns((s.len() as u64).wrapping_add(2)); // Check for another UTF-16 codepoint if self.next_is('\\') && self.next_is('u') { @@ -560,7 +597,7 @@ impl<'a> Lexer<'a> { } '\'' | '"' | '\\' => escape, ch => { - let details = format!("{}:{}: Invalid escape `{}`", self.line_number, self.column_number, ch); + let details = format!("{}: Invalid escape `{}`", self.position, ch); return Err(LexerError { details }); } }; @@ -571,11 +608,11 @@ impl<'a> Lexer<'a> { } } let str_length = buf.len() as u64; - self.push_token(TokenKind::StringLiteral(buf)); // Why +1? Quotation marks are not included, // So technically it would be +2, (for both " ") but we want to be 1 less // to compensate for the incrementing at the top - self.column_number += str_length.wrapping_add(1); + self.move_columns( str_length.wrapping_add(1)); + self.push_token(TokenKind::StringLiteral(buf), start_pos); } _ if ch.is_digit(10) => self.reed_numerical_literal(ch)?, _ if ch.is_alphabetic() || ch == '$' || ch == '_' => { @@ -587,8 +624,7 @@ impl<'a> Lexer<'a> { break; } } - - self.push_token(match buf.as_str() { + let tk = match buf.as_str() { "true" => TokenKind::BooleanLiteral(true), "false" => TokenKind::BooleanLiteral(false), "null" => TokenKind::NullLiteral, @@ -599,33 +635,36 @@ impl<'a> Lexer<'a> { TokenKind::identifier(slice) } } - }); - // Move position forward the length of keyword - self.column_number += (buf.len().wrapping_sub(1)) as u64; + }; + + // Move position forward the length of the token + self.move_columns( (buf.len().wrapping_sub(1)) as u64); + + self.push_token(tk, start_pos); } - ';' => self.push_punc(Punctuator::Semicolon), - ':' => self.push_punc(Punctuator::Colon), + ';' => self.push_punc(Punctuator::Semicolon, start_pos), + ':' => self.push_punc(Punctuator::Colon, start_pos), '.' => { // . or ... if self.next_is('.') { if self.next_is('.') { - self.push_punc(Punctuator::Spread); - self.column_number += 2; + self.push_punc(Punctuator::Spread, start_pos); + self.move_columns( 2); } else { return Err(LexerError::new("Expecting Token .")); } } else { - self.push_punc(Punctuator::Dot); + self.push_punc(Punctuator::Dot, start_pos); }; } - '(' => self.push_punc(Punctuator::OpenParen), - ')' => self.push_punc(Punctuator::CloseParen), - ',' => self.push_punc(Punctuator::Comma), - '{' => self.push_punc(Punctuator::OpenBlock), - '}' => self.push_punc(Punctuator::CloseBlock), - '[' => self.push_punc(Punctuator::OpenBracket), - ']' => self.push_punc(Punctuator::CloseBracket), - '?' => self.push_punc(Punctuator::Question), + '(' => self.push_punc(Punctuator::OpenParen, start_pos), + ')' => self.push_punc(Punctuator::CloseParen, start_pos), + ',' => self.push_punc(Punctuator::Comma, start_pos), + '{' => self.push_punc(Punctuator::OpenBlock, start_pos), + '}' => self.push_punc(Punctuator::CloseBlock, start_pos), + '[' => self.push_punc(Punctuator::OpenBracket, start_pos), + ']' => self.push_punc(Punctuator::CloseBracket, start_pos), + '?' => self.push_punc(Punctuator::Question, start_pos), // Comments '/' => { if let Some(ch) = self.preview_next() { @@ -637,8 +676,7 @@ impl<'a> Lexer<'a> { break; } } - self.line_number += 1; - self.column_number = 0; + self.next_line() } // block comment '*' => { @@ -660,8 +698,7 @@ impl<'a> Lexer<'a> { }, } } - self.line_number += lines; - self.column_number = 0; + self.move_lines(lines); } // division, assigndiv or regex literal _ => { @@ -672,7 +709,7 @@ impl<'a> Lexer<'a> { let mut body = String::new(); let mut regex = false; loop { - self.column_number +=1; + self.next_column(); match self.buffer.next() { // end of body Some('/') => { @@ -682,14 +719,14 @@ impl<'a> Lexer<'a> { // newline/eof not allowed in regex literal n @ Some('\n') | n @ Some('\r') | n @ Some('\u{2028}') | n @ Some('\u{2029}') => { - self.column_number = 0; + self.carriage_return(); if n != Some('\r') { - self.line_number += 1; + self.next_line(); } break }, None => { - self.column_number -= 1; + self.position = Position::new(self.position.line_number(), self.position.column_number()-1); break } // escape sequence @@ -712,7 +749,7 @@ impl<'a> Lexer<'a> { let flags = self.take_char_while(char::is_alphabetic)?; self.push_token(TokenKind::RegularExpressionLiteral( body, flags, - )); + ), start_pos); } else { // failed to parse regex, restore original buffer position and // parse either div or assigndiv @@ -720,9 +757,9 @@ impl<'a> Lexer<'a> { if self.next_is('=') { self.push_token(TokenKind::Punctuator( Punctuator::AssignDiv, - )); + ), start_pos); } else { - self.push_token(TokenKind::Punctuator(Punctuator::Div)); + self.push_token(TokenKind::Punctuator(Punctuator::Div), start_pos); } } } @@ -772,14 +809,13 @@ impl<'a> Lexer<'a> { vop!(self, Punctuator::StrictNotEq, Punctuator::NotEq), Punctuator::Not ), - '~' => self.push_punc(Punctuator::Neg), + '~' => self.push_punc(Punctuator::Neg, start_pos), '\n' | '\u{2028}' | '\u{2029}' => { - self.push_token(TokenKind::LineTerminator); - self.line_number += 1; - self.column_number = 0; + self.next_line(); + self.push_token(TokenKind::LineTerminator, start_pos); } '\r' => { - self.column_number = 0; + self.carriage_return(); } // The rust char::is_whitespace function and the ecma standard use different sets // of characters as whitespaces: @@ -791,7 +827,7 @@ impl<'a> Lexer<'a> { // Unicode Space_Seperator category (minus \u{0020} and \u{00A0} which are allready stated above) '\u{1680}' | '\u{2000}'..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' => (), _ => { - let details = format!("{}:{}: Unexpected '{}'", self.line_number, self.column_number, ch); + let details = format!("{}: Unexpected '{}'", self.position, ch); return Err(LexerError { details }); }, } diff --git a/boa/src/syntax/lexer/tests.rs b/boa/src/syntax/lexer/tests.rs index a50bc3b5b63..855d8b46a30 100644 --- a/boa/src/syntax/lexer/tests.rs +++ b/boa/src/syntax/lexer/tests.rs @@ -2,16 +2,20 @@ #![allow(clippy::indexing_slicing)] use super::*; -use crate::syntax::ast::keyword::Keyword; +use crate::syntax::ast::Keyword; + +fn span(start: (u64, u64), end: (u64, u64)) -> Span { + Span::new(Position::new(start.0, start.1), Position::new(end.0, end.1)) +} #[test] fn check_single_line_comment() { let s1 = "var \n//This is a comment\ntrue"; let mut lexer = Lexer::new(s1); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::Keyword(Keyword::Var)); - assert_eq!(lexer.tokens[1].kind, TokenKind::LineTerminator); - assert_eq!(lexer.tokens[2].kind, TokenKind::BooleanLiteral(true)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::Keyword(Keyword::Var)); + assert_eq!(lexer.tokens[1].kind(), &TokenKind::LineTerminator); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::BooleanLiteral(true)); } #[test] @@ -19,8 +23,8 @@ fn check_multi_line_comment() { let s = "var /* await \n break \n*/ x"; let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::Keyword(Keyword::Var)); - assert_eq!(lexer.tokens[1].kind, TokenKind::identifier("x")); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::Keyword(Keyword::Var)); + assert_eq!(lexer.tokens[1].kind(), &TokenKind::identifier("x")); } #[test] @@ -28,9 +32,9 @@ fn check_string() { let s = "'aaa' \"bbb\""; let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::string_literal("aaa")); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::string_literal("aaa")); - assert_eq!(lexer.tokens[1].kind, TokenKind::string_literal("bbb")); + assert_eq!(lexer.tokens[1].kind(), &TokenKind::string_literal("bbb")); } #[test] @@ -42,191 +46,200 @@ fn check_punctuators() { let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); assert_eq!( - lexer.tokens[0].kind, - TokenKind::Punctuator(Punctuator::OpenBlock) + lexer.tokens[0].kind(), + &TokenKind::Punctuator(Punctuator::OpenBlock) + ); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::OpenParen) + ); + assert_eq!( + lexer.tokens[2].kind(), + &TokenKind::Punctuator(Punctuator::CloseParen) + ); + assert_eq!( + lexer.tokens[3].kind(), + &TokenKind::Punctuator(Punctuator::OpenBracket) ); assert_eq!( - lexer.tokens[1].kind, - TokenKind::Punctuator(Punctuator::OpenParen) + lexer.tokens[4].kind(), + &TokenKind::Punctuator(Punctuator::CloseBracket) ); assert_eq!( - lexer.tokens[2].kind, - TokenKind::Punctuator(Punctuator::CloseParen) + lexer.tokens[5].kind(), + &TokenKind::Punctuator(Punctuator::Dot) ); assert_eq!( - lexer.tokens[3].kind, - TokenKind::Punctuator(Punctuator::OpenBracket) + lexer.tokens[6].kind(), + &TokenKind::Punctuator(Punctuator::Spread) ); assert_eq!( - lexer.tokens[4].kind, - TokenKind::Punctuator(Punctuator::CloseBracket) + lexer.tokens[7].kind(), + &TokenKind::Punctuator(Punctuator::Semicolon) ); - assert_eq!(lexer.tokens[5].kind, TokenKind::Punctuator(Punctuator::Dot)); assert_eq!( - lexer.tokens[6].kind, - TokenKind::Punctuator(Punctuator::Spread) + lexer.tokens[8].kind(), + &TokenKind::Punctuator(Punctuator::Comma) ); assert_eq!( - lexer.tokens[7].kind, - TokenKind::Punctuator(Punctuator::Semicolon) + lexer.tokens[9].kind(), + &TokenKind::Punctuator(Punctuator::LessThan) ); assert_eq!( - lexer.tokens[8].kind, - TokenKind::Punctuator(Punctuator::Comma) + lexer.tokens[10].kind(), + &TokenKind::Punctuator(Punctuator::GreaterThan) ); assert_eq!( - lexer.tokens[9].kind, - TokenKind::Punctuator(Punctuator::LessThan) + lexer.tokens[11].kind(), + &TokenKind::Punctuator(Punctuator::LessThanOrEq) ); assert_eq!( - lexer.tokens[10].kind, - TokenKind::Punctuator(Punctuator::GreaterThan) + lexer.tokens[12].kind(), + &TokenKind::Punctuator(Punctuator::GreaterThanOrEq) ); assert_eq!( - lexer.tokens[11].kind, - TokenKind::Punctuator(Punctuator::LessThanOrEq) + lexer.tokens[13].kind(), + &TokenKind::Punctuator(Punctuator::Eq) ); assert_eq!( - lexer.tokens[12].kind, - TokenKind::Punctuator(Punctuator::GreaterThanOrEq) + lexer.tokens[14].kind(), + &TokenKind::Punctuator(Punctuator::NotEq) ); - assert_eq!(lexer.tokens[13].kind, TokenKind::Punctuator(Punctuator::Eq)); assert_eq!( - lexer.tokens[14].kind, - TokenKind::Punctuator(Punctuator::NotEq) + lexer.tokens[15].kind(), + &TokenKind::Punctuator(Punctuator::StrictEq) ); assert_eq!( - lexer.tokens[15].kind, - TokenKind::Punctuator(Punctuator::StrictEq) + lexer.tokens[16].kind(), + &TokenKind::Punctuator(Punctuator::StrictNotEq) ); assert_eq!( - lexer.tokens[16].kind, - TokenKind::Punctuator(Punctuator::StrictNotEq) + lexer.tokens[17].kind(), + &TokenKind::Punctuator(Punctuator::Add) ); assert_eq!( - lexer.tokens[17].kind, - TokenKind::Punctuator(Punctuator::Add) + lexer.tokens[18].kind(), + &TokenKind::Punctuator(Punctuator::Sub) ); assert_eq!( - lexer.tokens[18].kind, - TokenKind::Punctuator(Punctuator::Sub) + lexer.tokens[19].kind(), + &TokenKind::Punctuator(Punctuator::Mul) ); assert_eq!( - lexer.tokens[19].kind, - TokenKind::Punctuator(Punctuator::Mul) + lexer.tokens[20].kind(), + &TokenKind::Punctuator(Punctuator::Mod) ); assert_eq!( - lexer.tokens[20].kind, - TokenKind::Punctuator(Punctuator::Mod) + lexer.tokens[21].kind(), + &TokenKind::Punctuator(Punctuator::Dec) ); assert_eq!( - lexer.tokens[21].kind, - TokenKind::Punctuator(Punctuator::Dec) + lexer.tokens[22].kind(), + &TokenKind::Punctuator(Punctuator::LeftSh) ); assert_eq!( - lexer.tokens[22].kind, - TokenKind::Punctuator(Punctuator::LeftSh) + lexer.tokens[23].kind(), + &TokenKind::Punctuator(Punctuator::RightSh) ); assert_eq!( - lexer.tokens[23].kind, - TokenKind::Punctuator(Punctuator::RightSh) + lexer.tokens[24].kind(), + &TokenKind::Punctuator(Punctuator::URightSh) ); assert_eq!( - lexer.tokens[24].kind, - TokenKind::Punctuator(Punctuator::URightSh) + lexer.tokens[25].kind(), + &TokenKind::Punctuator(Punctuator::And) ); assert_eq!( - lexer.tokens[25].kind, - TokenKind::Punctuator(Punctuator::And) + lexer.tokens[26].kind(), + &TokenKind::Punctuator(Punctuator::Or) ); - assert_eq!(lexer.tokens[26].kind, TokenKind::Punctuator(Punctuator::Or)); assert_eq!( - lexer.tokens[27].kind, - TokenKind::Punctuator(Punctuator::Xor) + lexer.tokens[27].kind(), + &TokenKind::Punctuator(Punctuator::Xor) ); assert_eq!( - lexer.tokens[28].kind, - TokenKind::Punctuator(Punctuator::Not) + lexer.tokens[28].kind(), + &TokenKind::Punctuator(Punctuator::Not) ); assert_eq!( - lexer.tokens[29].kind, - TokenKind::Punctuator(Punctuator::Neg) + lexer.tokens[29].kind(), + &TokenKind::Punctuator(Punctuator::Neg) ); assert_eq!( - lexer.tokens[30].kind, - TokenKind::Punctuator(Punctuator::BoolAnd) + lexer.tokens[30].kind(), + &TokenKind::Punctuator(Punctuator::BoolAnd) ); assert_eq!( - lexer.tokens[31].kind, - TokenKind::Punctuator(Punctuator::BoolOr) + lexer.tokens[31].kind(), + &TokenKind::Punctuator(Punctuator::BoolOr) ); assert_eq!( - lexer.tokens[32].kind, - TokenKind::Punctuator(Punctuator::Question) + lexer.tokens[32].kind(), + &TokenKind::Punctuator(Punctuator::Question) ); assert_eq!( - lexer.tokens[33].kind, - TokenKind::Punctuator(Punctuator::Colon) + lexer.tokens[33].kind(), + &TokenKind::Punctuator(Punctuator::Colon) ); assert_eq!( - lexer.tokens[34].kind, - TokenKind::Punctuator(Punctuator::Assign) + lexer.tokens[34].kind(), + &TokenKind::Punctuator(Punctuator::Assign) ); assert_eq!( - lexer.tokens[35].kind, - TokenKind::Punctuator(Punctuator::AssignAdd) + lexer.tokens[35].kind(), + &TokenKind::Punctuator(Punctuator::AssignAdd) ); assert_eq!( - lexer.tokens[36].kind, - TokenKind::Punctuator(Punctuator::AssignSub) + lexer.tokens[36].kind(), + &TokenKind::Punctuator(Punctuator::AssignSub) ); assert_eq!( - lexer.tokens[37].kind, - TokenKind::Punctuator(Punctuator::AssignMul) + lexer.tokens[37].kind(), + &TokenKind::Punctuator(Punctuator::AssignMul) ); assert_eq!( - lexer.tokens[38].kind, - TokenKind::Punctuator(Punctuator::AssignAnd) + lexer.tokens[38].kind(), + &TokenKind::Punctuator(Punctuator::AssignAnd) ); assert_eq!( - lexer.tokens[39].kind, - TokenKind::Punctuator(Punctuator::AssignPow) + lexer.tokens[39].kind(), + &TokenKind::Punctuator(Punctuator::AssignPow) ); assert_eq!( - lexer.tokens[40].kind, - TokenKind::Punctuator(Punctuator::Inc) + lexer.tokens[40].kind(), + &TokenKind::Punctuator(Punctuator::Inc) ); assert_eq!( - lexer.tokens[41].kind, - TokenKind::Punctuator(Punctuator::Exp) + lexer.tokens[41].kind(), + &TokenKind::Punctuator(Punctuator::Exp) ); assert_eq!( - lexer.tokens[42].kind, - TokenKind::Punctuator(Punctuator::AssignLeftSh) + lexer.tokens[42].kind(), + &TokenKind::Punctuator(Punctuator::AssignLeftSh) ); assert_eq!( - lexer.tokens[43].kind, - TokenKind::Punctuator(Punctuator::AssignRightSh) + lexer.tokens[43].kind(), + &TokenKind::Punctuator(Punctuator::AssignRightSh) ); assert_eq!( - lexer.tokens[44].kind, - TokenKind::Punctuator(Punctuator::AssignURightSh) + lexer.tokens[44].kind(), + &TokenKind::Punctuator(Punctuator::AssignURightSh) ); assert_eq!( - lexer.tokens[45].kind, - TokenKind::Punctuator(Punctuator::AssignAnd) + lexer.tokens[45].kind(), + &TokenKind::Punctuator(Punctuator::AssignAnd) ); assert_eq!( - lexer.tokens[46].kind, - TokenKind::Punctuator(Punctuator::AssignOr) + lexer.tokens[46].kind(), + &TokenKind::Punctuator(Punctuator::AssignOr) ); assert_eq!( - lexer.tokens[47].kind, - TokenKind::Punctuator(Punctuator::AssignXor) + lexer.tokens[47].kind(), + &TokenKind::Punctuator(Punctuator::AssignXor) ); assert_eq!( - lexer.tokens[48].kind, - TokenKind::Punctuator(Punctuator::Arrow) + lexer.tokens[48].kind(), + &TokenKind::Punctuator(Punctuator::Arrow) ); } @@ -239,43 +252,76 @@ fn check_keywords() { let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::Keyword(Keyword::Await)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Keyword(Keyword::Break)); - assert_eq!(lexer.tokens[2].kind, TokenKind::Keyword(Keyword::Case)); - assert_eq!(lexer.tokens[3].kind, TokenKind::Keyword(Keyword::Catch)); - assert_eq!(lexer.tokens[4].kind, TokenKind::Keyword(Keyword::Class)); - assert_eq!(lexer.tokens[5].kind, TokenKind::Keyword(Keyword::Const)); - assert_eq!(lexer.tokens[6].kind, TokenKind::Keyword(Keyword::Continue)); - assert_eq!(lexer.tokens[7].kind, TokenKind::Keyword(Keyword::Debugger)); - assert_eq!(lexer.tokens[8].kind, TokenKind::Keyword(Keyword::Default)); - assert_eq!(lexer.tokens[9].kind, TokenKind::Keyword(Keyword::Delete)); - assert_eq!(lexer.tokens[10].kind, TokenKind::Keyword(Keyword::Do)); - assert_eq!(lexer.tokens[11].kind, TokenKind::Keyword(Keyword::Else)); - assert_eq!(lexer.tokens[12].kind, TokenKind::Keyword(Keyword::Export)); - assert_eq!(lexer.tokens[13].kind, TokenKind::Keyword(Keyword::Extends)); - assert_eq!(lexer.tokens[14].kind, TokenKind::Keyword(Keyword::Finally)); - assert_eq!(lexer.tokens[15].kind, TokenKind::Keyword(Keyword::For)); - assert_eq!(lexer.tokens[16].kind, TokenKind::Keyword(Keyword::Function)); - assert_eq!(lexer.tokens[17].kind, TokenKind::Keyword(Keyword::If)); - assert_eq!(lexer.tokens[18].kind, TokenKind::Keyword(Keyword::Import)); - assert_eq!(lexer.tokens[19].kind, TokenKind::Keyword(Keyword::In)); - assert_eq!( - lexer.tokens[20].kind, - TokenKind::Keyword(Keyword::InstanceOf) - ); - assert_eq!(lexer.tokens[21].kind, TokenKind::Keyword(Keyword::New)); - assert_eq!(lexer.tokens[22].kind, TokenKind::Keyword(Keyword::Return)); - assert_eq!(lexer.tokens[23].kind, TokenKind::Keyword(Keyword::Super)); - assert_eq!(lexer.tokens[24].kind, TokenKind::Keyword(Keyword::Switch)); - assert_eq!(lexer.tokens[25].kind, TokenKind::Keyword(Keyword::This)); - assert_eq!(lexer.tokens[26].kind, TokenKind::Keyword(Keyword::Throw)); - assert_eq!(lexer.tokens[27].kind, TokenKind::Keyword(Keyword::Try)); - assert_eq!(lexer.tokens[28].kind, TokenKind::Keyword(Keyword::TypeOf)); - assert_eq!(lexer.tokens[29].kind, TokenKind::Keyword(Keyword::Var)); - assert_eq!(lexer.tokens[30].kind, TokenKind::Keyword(Keyword::Void)); - assert_eq!(lexer.tokens[31].kind, TokenKind::Keyword(Keyword::While)); - assert_eq!(lexer.tokens[32].kind, TokenKind::Keyword(Keyword::With)); - assert_eq!(lexer.tokens[33].kind, TokenKind::Keyword(Keyword::Yield)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::Keyword(Keyword::Await)); + assert_eq!(lexer.tokens[1].kind(), &TokenKind::Keyword(Keyword::Break)); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::Keyword(Keyword::Case)); + assert_eq!(lexer.tokens[3].kind(), &TokenKind::Keyword(Keyword::Catch)); + assert_eq!(lexer.tokens[4].kind(), &TokenKind::Keyword(Keyword::Class)); + assert_eq!(lexer.tokens[5].kind(), &TokenKind::Keyword(Keyword::Const)); + assert_eq!( + lexer.tokens[6].kind(), + &TokenKind::Keyword(Keyword::Continue) + ); + assert_eq!( + lexer.tokens[7].kind(), + &TokenKind::Keyword(Keyword::Debugger) + ); + assert_eq!( + lexer.tokens[8].kind(), + &TokenKind::Keyword(Keyword::Default) + ); + assert_eq!(lexer.tokens[9].kind(), &TokenKind::Keyword(Keyword::Delete)); + assert_eq!(lexer.tokens[10].kind(), &TokenKind::Keyword(Keyword::Do)); + assert_eq!(lexer.tokens[11].kind(), &TokenKind::Keyword(Keyword::Else)); + assert_eq!( + lexer.tokens[12].kind(), + &TokenKind::Keyword(Keyword::Export) + ); + assert_eq!( + lexer.tokens[13].kind(), + &TokenKind::Keyword(Keyword::Extends) + ); + assert_eq!( + lexer.tokens[14].kind(), + &TokenKind::Keyword(Keyword::Finally) + ); + assert_eq!(lexer.tokens[15].kind(), &TokenKind::Keyword(Keyword::For)); + assert_eq!( + lexer.tokens[16].kind(), + &TokenKind::Keyword(Keyword::Function) + ); + assert_eq!(lexer.tokens[17].kind(), &TokenKind::Keyword(Keyword::If)); + assert_eq!( + lexer.tokens[18].kind(), + &TokenKind::Keyword(Keyword::Import) + ); + assert_eq!(lexer.tokens[19].kind(), &TokenKind::Keyword(Keyword::In)); + assert_eq!( + lexer.tokens[20].kind(), + &TokenKind::Keyword(Keyword::InstanceOf) + ); + assert_eq!(lexer.tokens[21].kind(), &TokenKind::Keyword(Keyword::New)); + assert_eq!( + lexer.tokens[22].kind(), + &TokenKind::Keyword(Keyword::Return) + ); + assert_eq!(lexer.tokens[23].kind(), &TokenKind::Keyword(Keyword::Super)); + assert_eq!( + lexer.tokens[24].kind(), + &TokenKind::Keyword(Keyword::Switch) + ); + assert_eq!(lexer.tokens[25].kind(), &TokenKind::Keyword(Keyword::This)); + assert_eq!(lexer.tokens[26].kind(), &TokenKind::Keyword(Keyword::Throw)); + assert_eq!(lexer.tokens[27].kind(), &TokenKind::Keyword(Keyword::Try)); + assert_eq!( + lexer.tokens[28].kind(), + &TokenKind::Keyword(Keyword::TypeOf) + ); + assert_eq!(lexer.tokens[29].kind(), &TokenKind::Keyword(Keyword::Var)); + assert_eq!(lexer.tokens[30].kind(), &TokenKind::Keyword(Keyword::Void)); + assert_eq!(lexer.tokens[31].kind(), &TokenKind::Keyword(Keyword::While)); + assert_eq!(lexer.tokens[32].kind(), &TokenKind::Keyword(Keyword::With)); + assert_eq!(lexer.tokens[33].kind(), &TokenKind::Keyword(Keyword::Yield)); } #[test] @@ -283,13 +329,13 @@ fn check_variable_definition_tokens() { let s = "let a = 'hello';"; let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::Keyword(Keyword::Let)); - assert_eq!(lexer.tokens[1].kind, TokenKind::identifier("a")); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::Keyword(Keyword::Let)); + assert_eq!(lexer.tokens[1].kind(), &TokenKind::identifier("a")); assert_eq!( - lexer.tokens[2].kind, - TokenKind::Punctuator(Punctuator::Assign) + lexer.tokens[2].kind(), + &TokenKind::Punctuator(Punctuator::Assign) ); - assert_eq!(lexer.tokens[3].kind, TokenKind::string_literal("hello")); + assert_eq!(lexer.tokens[3].kind(), &TokenKind::string_literal("hello")); } #[test] @@ -299,26 +345,25 @@ fn check_positions() { let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); // The first column is 1 (not zero indexed) - assert_eq!(lexer.tokens[0].pos.column_number, 1); - assert_eq!(lexer.tokens[0].pos.line_number, 1); + assert_eq!(lexer.tokens[0].span(), span((1, 1), (1, 7))); + // Dot Token starts on column 8 - assert_eq!(lexer.tokens[1].pos.column_number, 8); - assert_eq!(lexer.tokens[1].pos.line_number, 1); + assert_eq!(lexer.tokens[1].span(), span((1, 8), (1, 8))); + // Log Token starts on column 9 - assert_eq!(lexer.tokens[2].pos.column_number, 9); - assert_eq!(lexer.tokens[2].pos.line_number, 1); + assert_eq!(lexer.tokens[2].span(), span((1, 9), (1, 11))); + // Open parenthesis token starts on column 12 - assert_eq!(lexer.tokens[3].pos.column_number, 12); - assert_eq!(lexer.tokens[3].pos.line_number, 1); + assert_eq!(lexer.tokens[3].span(), span((1, 12), (1, 12))); + // String token starts on column 13 - assert_eq!(lexer.tokens[4].pos.column_number, 13); - assert_eq!(lexer.tokens[4].pos.line_number, 1); + assert_eq!(lexer.tokens[4].span(), span((1, 13), (1, 25))); + // Close parenthesis token starts on column 26 - assert_eq!(lexer.tokens[5].pos.column_number, 26); - assert_eq!(lexer.tokens[5].pos.line_number, 1); + assert_eq!(lexer.tokens[5].span(), span((1, 26), (1, 26))); + // Semi Colon token starts on column 27 - assert_eq!(lexer.tokens[6].pos.column_number, 27); - assert_eq!(lexer.tokens[6].pos.line_number, 1); + assert_eq!(lexer.tokens[6].span(), span((1, 27), (1, 27))); } #[test] @@ -329,8 +374,7 @@ fn two_divisions_in_expression() { lexer.lex().expect("failed to lex"); // dbg!(&lexer.tokens); - assert_eq!(lexer.tokens[11].pos.column_number, 37); - assert_eq!(lexer.tokens[11].pos.line_number, 1); + assert_eq!(lexer.tokens[11].span(), span((1, 37), (1, 37))); } #[test] @@ -340,17 +384,10 @@ fn check_line_numbers() { let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].pos.column_number, 1); - assert_eq!(lexer.tokens[0].pos.line_number, 1); - - assert_eq!(lexer.tokens[1].pos.column_number, 2); - assert_eq!(lexer.tokens[1].pos.line_number, 1); - - assert_eq!(lexer.tokens[2].pos.column_number, 1); - assert_eq!(lexer.tokens[2].pos.line_number, 2); - - assert_eq!(lexer.tokens[3].pos.column_number, 2); - assert_eq!(lexer.tokens[3].pos.line_number, 2); + assert_eq!(lexer.tokens[0].span(), span((1, 1), (1, 1))); + assert_eq!(lexer.tokens[1].span(), span((1, 2), (1, 3))); + assert_eq!(lexer.tokens[2].span(), span((1, 4), (1, 4))); + assert_eq!(lexer.tokens[3].span(), span((1, 4), (1, 6))); } // Increment/Decrement @@ -360,12 +397,15 @@ fn check_decrement_advances_lexer_2_places() { let s = "let a = b--;"; let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[4].kind, TokenKind::Punctuator(Punctuator::Dec)); + assert_eq!( + lexer.tokens[4].kind(), + &TokenKind::Punctuator(Punctuator::Dec) + ); // Decrementing means adding 2 characters '--', the lexer should consume it as a single token // and move the curser forward by 2, meaning the next token should be a semicolon assert_eq!( - lexer.tokens[5].kind, - TokenKind::Punctuator(Punctuator::Semicolon) + lexer.tokens[5].kind(), + &TokenKind::Punctuator(Punctuator::Semicolon) ); } @@ -376,24 +416,24 @@ fn numbers() { ); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1)); - assert_eq!(lexer.tokens[1].kind, TokenKind::numeric_literal(2)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(52)); - assert_eq!(lexer.tokens[3].kind, TokenKind::numeric_literal(46)); - assert_eq!(lexer.tokens[4].kind, TokenKind::numeric_literal(7.89)); - assert_eq!(lexer.tokens[5].kind, TokenKind::numeric_literal(42.0)); - assert_eq!(lexer.tokens[6].kind, TokenKind::numeric_literal(5000.0)); - assert_eq!(lexer.tokens[7].kind, TokenKind::numeric_literal(5000.0)); - assert_eq!(lexer.tokens[8].kind, TokenKind::numeric_literal(0.005)); - assert_eq!(lexer.tokens[9].kind, TokenKind::numeric_literal(2)); - assert_eq!(lexer.tokens[10].kind, TokenKind::numeric_literal(83)); - assert_eq!(lexer.tokens[11].kind, TokenKind::numeric_literal(999)); - assert_eq!(lexer.tokens[12].kind, TokenKind::numeric_literal(10.0)); - assert_eq!(lexer.tokens[13].kind, TokenKind::numeric_literal(0.1)); - assert_eq!(lexer.tokens[14].kind, TokenKind::numeric_literal(10.0)); - assert_eq!(lexer.tokens[15].kind, TokenKind::numeric_literal(10.0)); - assert_eq!(lexer.tokens[16].kind, TokenKind::numeric_literal(0.0)); - assert_eq!(lexer.tokens[17].kind, TokenKind::numeric_literal(0.12)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1)); + assert_eq!(lexer.tokens[1].kind(), &TokenKind::numeric_literal(2)); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(52)); + assert_eq!(lexer.tokens[3].kind(), &TokenKind::numeric_literal(46)); + assert_eq!(lexer.tokens[4].kind(), &TokenKind::numeric_literal(7.89)); + assert_eq!(lexer.tokens[5].kind(), &TokenKind::numeric_literal(42.0)); + assert_eq!(lexer.tokens[6].kind(), &TokenKind::numeric_literal(5000.0)); + assert_eq!(lexer.tokens[7].kind(), &TokenKind::numeric_literal(5000.0)); + assert_eq!(lexer.tokens[8].kind(), &TokenKind::numeric_literal(0.005)); + assert_eq!(lexer.tokens[9].kind(), &TokenKind::numeric_literal(2)); + assert_eq!(lexer.tokens[10].kind(), &TokenKind::numeric_literal(83)); + assert_eq!(lexer.tokens[11].kind(), &TokenKind::numeric_literal(999)); + assert_eq!(lexer.tokens[12].kind(), &TokenKind::numeric_literal(10.0)); + assert_eq!(lexer.tokens[13].kind(), &TokenKind::numeric_literal(0.1)); + assert_eq!(lexer.tokens[14].kind(), &TokenKind::numeric_literal(10.0)); + assert_eq!(lexer.tokens[15].kind(), &TokenKind::numeric_literal(10.0)); + assert_eq!(lexer.tokens[16].kind(), &TokenKind::numeric_literal(0.0)); + assert_eq!(lexer.tokens[17].kind(), &TokenKind::numeric_literal(0.12)); } #[test] @@ -401,11 +441,14 @@ fn implicit_octal_edge_case() { let mut lexer = Lexer::new("044.5 094.5"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(36)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Dot)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(5)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(36)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Dot) + ); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(5)); - assert_eq!(lexer.tokens[3].kind, TokenKind::numeric_literal(94.5)); + assert_eq!(lexer.tokens[3].kind(), &TokenKind::numeric_literal(94.5)); } #[test] @@ -413,16 +456,19 @@ fn hexadecimal_edge_case() { let mut lexer = Lexer::new("0xffff.ff 0xffffff"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(0xffff)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Dot)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(0xffff)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Dot) + ); assert_eq!( - lexer.tokens[2].kind, - TokenKind::Identifier(String::from("ff")) + lexer.tokens[2].kind(), + &TokenKind::Identifier(String::from("ff")) ); assert_eq!( - lexer.tokens[3].kind, - TokenKind::numeric_literal(0x00ff_ffff) + lexer.tokens[3].kind(), + &TokenKind::numeric_literal(0x00ff_ffff) ); } @@ -436,8 +482,11 @@ fn single_number_without_semicolon() { fn number_followed_by_dot() { let mut lexer = Lexer::new("1.."); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1.0)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Dot)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1.0)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Dot) + ); } #[test] @@ -445,8 +494,8 @@ fn regex_literal() { let mut lexer = Lexer::new("/(?:)/"); lexer.lex().expect("failed to lex"); assert_eq!( - lexer.tokens[0].kind, - TokenKind::regular_expression_literal("(?:)", "") + lexer.tokens[0].kind(), + &TokenKind::regular_expression_literal("(?:)", "") ); } @@ -455,8 +504,8 @@ fn regex_literal_flags() { let mut lexer = Lexer::new(r"/\/[^\/]*\/*/gmi"); lexer.lex().expect("failed to lex"); assert_eq!( - lexer.tokens[0].kind, - TokenKind::regular_expression_literal("\\/[^\\/]*\\/*", "gmi") + lexer.tokens[0].kind(), + &TokenKind::regular_expression_literal("\\/[^\\/]*\\/*", "gmi") ); } @@ -464,55 +513,73 @@ fn regex_literal_flags() { fn addition_no_spaces() { let mut lexer = Lexer::new("1+1"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(1)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Add) + ); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(1)); } #[test] fn addition_no_spaces_left_side() { let mut lexer = Lexer::new("1+ 1"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(1)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Add) + ); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(1)); } #[test] fn addition_no_spaces_right_side() { let mut lexer = Lexer::new("1 +1"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(1)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Add) + ); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(1)); } #[test] fn addition_no_spaces_e_number_left_side() { let mut lexer = Lexer::new("1e2+ 1"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(100.0)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(1)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(100.0)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Add) + ); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(1)); } #[test] fn addition_no_spaces_e_number_right_side() { let mut lexer = Lexer::new("1 +1e3"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].kind, TokenKind::numeric_literal(1000.0)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Add) + ); + assert_eq!(lexer.tokens[2].kind(), &TokenKind::numeric_literal(1000.0)); } #[test] fn addition_no_spaces_e_number() { let mut lexer = Lexer::new("1e3+1e11"); lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(1000.0)); - assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Add)); + assert_eq!(lexer.tokens[0].kind(), &TokenKind::numeric_literal(1000.0)); + assert_eq!( + lexer.tokens[1].kind(), + &TokenKind::Punctuator(Punctuator::Add) + ); assert_eq!( - lexer.tokens[2].kind, - TokenKind::numeric_literal(100_000_000_000.0) + lexer.tokens[2].kind(), + &TokenKind::numeric_literal(100_000_000_000.0) ); } diff --git a/boa/src/syntax/parser/cursor.rs b/boa/src/syntax/parser/cursor.rs index ffc61f3df0f..744b201e86f 100644 --- a/boa/src/syntax/parser/cursor.rs +++ b/boa/src/syntax/parser/cursor.rs @@ -2,8 +2,8 @@ use super::ParseError; use crate::syntax::ast::{ - punc::Punctuator, token::{Token, TokenKind}, + Punctuator, }; /// Token cursor. @@ -45,7 +45,7 @@ impl<'a> Cursor<'a> { if let Some(tk) = token { self.pos += 1; - if tk.kind != TokenKind::LineTerminator { + if tk.kind() != &TokenKind::LineTerminator { break Some(tk); } } else { @@ -63,7 +63,7 @@ impl<'a> Cursor<'a> { count += 1; if let Some(tk) = token { - if tk.kind != TokenKind::LineTerminator { + if tk.kind() != &TokenKind::LineTerminator { if skipped == skip { break Some(tk); } @@ -88,8 +88,8 @@ impl<'a> Cursor<'a> { .tokens .get(self.pos - 1) .expect("token disappeared") - .kind - == TokenKind::LineTerminator + .kind() + == &TokenKind::LineTerminator && self.pos > 0 { self.pos -= 1; @@ -103,7 +103,7 @@ impl<'a> Cursor<'a> { } else { let mut back = 1; let mut tok = self.tokens.get(self.pos - back).expect("token disappeared"); - while self.pos >= back && tok.kind == TokenKind::LineTerminator { + while self.pos >= back && tok.kind() == &TokenKind::LineTerminator { back += 1; tok = self.tokens.get(self.pos - back).expect("token disappeared"); } @@ -126,7 +126,7 @@ impl<'a> Cursor<'a> { let next_token = self.next().ok_or(ParseError::AbruptEnd)?; let kind = kind.into(); - if next_token.kind == kind { + if next_token.kind() == &kind { Ok(()) } else { Err(ParseError::Expected( @@ -144,7 +144,7 @@ impl<'a> Cursor<'a> { /// [spec]: https://tc39.es/ecma262/#sec-automatic-semicolon-insertion pub(super) fn peek_semicolon(&self, do_while: bool) -> (bool, Option<&Token>) { match self.tokens.get(self.pos) { - Some(tk) => match tk.kind { + Some(tk) => match *tk.kind() { TokenKind::Punctuator(Punctuator::Semicolon) => (true, Some(tk)), TokenKind::LineTerminator | TokenKind::Punctuator(Punctuator::CloseBlock) => { (true, Some(tk)) @@ -160,7 +160,7 @@ impl<'a> Cursor<'a> { .tokens .get(self.pos - 1) .expect("could not find previous token"); - if tok.kind == TokenKind::Punctuator(Punctuator::CloseParen) { + if tok.kind() == &TokenKind::Punctuator(Punctuator::CloseParen) { return (true, Some(tk)); } } @@ -183,7 +183,7 @@ impl<'a> Cursor<'a> { routine: &'static str, ) -> Result<(), ParseError> { match self.peek_semicolon(do_while) { - (true, Some(tk)) => match tk.kind { + (true, Some(tk)) => match *tk.kind() { TokenKind::Punctuator(Punctuator::Semicolon) | TokenKind::LineTerminator => { self.pos += 1; Ok(()) @@ -214,11 +214,11 @@ impl<'a> Cursor<'a> { let token = self.tokens.get(self.pos + count); count += 1; if let Some(tk) = token { - if skipped == skip && tk.kind == TokenKind::LineTerminator { + if skipped == skip && tk.kind() == &TokenKind::LineTerminator { break Err(ParseError::Unexpected(tk.clone(), Some(routine))); - } else if skipped == skip && tk.kind != TokenKind::LineTerminator { + } else if skipped == skip && tk.kind() != &TokenKind::LineTerminator { break Ok(()); - } else if tk.kind != TokenKind::LineTerminator { + } else if tk.kind() != &TokenKind::LineTerminator { skipped += 1; } } else { @@ -237,7 +237,7 @@ impl<'a> Cursor<'a> { { let next_token = self.peek(0)?; - if next_token.kind == kind.into() { + if next_token.kind() == &kind.into() { self.next() } else { None diff --git a/boa/src/syntax/parser/error.rs b/boa/src/syntax/parser/error.rs index 1d83c95357a..14adec452fd 100644 --- a/boa/src/syntax/parser/error.rs +++ b/boa/src/syntax/parser/error.rs @@ -1,9 +1,8 @@ //! Error and result implementation for the parser. use crate::syntax::ast::{ - keyword::Keyword, - node::Node, - pos::Position, + position::Position, token::{Token, TokenKind}, + Keyword, Node, }; use std::fmt; @@ -64,18 +63,23 @@ impl fmt::Display for ParseError { }, actual, routine, - actual.pos.line_number, - actual.pos.column_number + actual.span().start().line_number(), + actual.span().start().column_number() ), Self::ExpectedExpr(expected, actual, pos) => write!( f, "Expected expression '{}', got '{}' at line {}, col {}", - expected, actual, pos.line_number, pos.column_number + expected, + actual, + pos.line_number(), + pos.column_number() ), Self::UnexpectedKeyword(keyword, pos) => write!( f, "Unexpected keyword: '{}' at line {}, col {}", - keyword, pos.line_number, pos.column_number + keyword, + pos.line_number(), + pos.column_number() ), Self::Unexpected(tok, msg) => write!( f, @@ -86,8 +90,8 @@ impl fmt::Display for ParseError { } else { String::new() }, - tok.pos.line_number, - tok.pos.column_number + tok.span().start().line_number(), + tok.span().start().column_number() ), Self::AbruptEnd => write!(f, "Abrupt End"), Self::General(msg, pos) => write!( @@ -95,7 +99,11 @@ impl fmt::Display for ParseError { "{}{}", msg, if let Some(pos) = pos { - format!(" at line {}, col {}", pos.line_number, pos.column_number) + format!( + " at line {}, col {}", + pos.line_number(), + pos.column_number() + ) } else { String::new() } diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index 2d44f6cedb2..fff5d52a3c4 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -10,9 +10,8 @@ use super::AssignmentExpression; use crate::syntax::{ ast::{ - node::{ArrowFunctionDecl, FormalParameter, Node}, - punc::Punctuator, - token::TokenKind, + node::{ArrowFunctionDecl, Block, FormalParameter, Node}, + Punctuator, TokenKind, }, parser::{ function::{FormalParameters, FunctionBody}, @@ -61,12 +60,12 @@ impl TokenParser for ArrowFunction { 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 { + let params = if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind() { // CoverParenthesizedExpressionAndArrowParameterList cursor.expect(Punctuator::OpenParen, "arrow function")?; let params = FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect(Punctuator::CloseParen, "arrow function")?; - params.into_boxed_slice() + params } else { let param = BindingIdentifier::new(self.allow_yield, self.allow_await) .parse(cursor) @@ -86,7 +85,7 @@ impl TokenParser for ArrowFunction { let body = ConciseBody::new(self.allow_in).parse(cursor)?; - Ok(ArrowFunctionDecl::new(params, body)) + Ok(ArrowFunctionDecl::new(params, vec![body])) } } @@ -112,12 +111,10 @@ impl TokenParser for ConciseBody { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> Result { - match cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind { + match cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { let _ = cursor.next(); - let body = FunctionBody::new(false, false) - .parse(cursor) - .map(Node::statement_list)?; + let body = Block::from(FunctionBody::new(false, false).parse(cursor)?).into(); cursor.expect(Punctuator::CloseBlock, "arrow function")?; Ok(body) } diff --git a/boa/src/syntax/parser/expression/assignment/conditional.rs b/boa/src/syntax/parser/expression/assignment/conditional.rs index 874aa950c9d..b55d063c273 100644 --- a/boa/src/syntax/parser/expression/assignment/conditional.rs +++ b/boa/src/syntax/parser/expression/assignment/conditional.rs @@ -8,7 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-conditional-operator use crate::syntax::{ - ast::{node::Node, punc::Punctuator, token::TokenKind}, + ast::{Node, Punctuator, TokenKind}, parser::{ expression::{AssignmentExpression, LogicalORExpression}, AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser, @@ -59,7 +59,7 @@ impl TokenParser for ConditionalExpression { .parse(cursor)?; if let Some(tok) = cursor.next() { - if tok.kind == TokenKind::Punctuator(Punctuator::Question) { + if tok.kind() == &TokenKind::Punctuator(Punctuator::Question) { let then_clause = AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await) .parse(cursor)?; diff --git a/boa/src/syntax/parser/expression/assignment/exponentiation.rs b/boa/src/syntax/parser/expression/assignment/exponentiation.rs index 8acc765ebbd..78a5f9a4b8f 100644 --- a/boa/src/syntax/parser/expression/assignment/exponentiation.rs +++ b/boa/src/syntax/parser/expression/assignment/exponentiation.rs @@ -9,11 +9,9 @@ use crate::syntax::{ ast::{ - keyword::Keyword, node::{BinOp, Node}, op::NumOp, - punc::Punctuator, - token::TokenKind, + Keyword, Punctuator, TokenKind, }, parser::{ expression::{unary::UnaryExpression, update::UpdateExpression}, @@ -53,7 +51,7 @@ impl ExponentiationExpression { /// Checks by looking at the next token to see whether it's a unary operator or not. fn is_unary_expression(cursor: &mut Cursor<'_>) -> bool { if let Some(tok) = cursor.peek(0) { - match tok.kind { + match tok.kind() { TokenKind::Keyword(Keyword::Delete) | TokenKind::Keyword(Keyword::Void) | TokenKind::Keyword(Keyword::TypeOf) @@ -79,7 +77,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 { + if let TokenKind::Punctuator(Punctuator::Exp) = tok.kind() { 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 e11e1b82b89..a3d652324f3 100644 --- a/boa/src/syntax/parser/expression/assignment/mod.rs +++ b/boa/src/syntax/parser/expression/assignment/mod.rs @@ -14,10 +14,8 @@ mod exponentiation; use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; use crate::syntax::{ ast::{ - keyword::Keyword, node::{Assign, BinOp, Node}, - punc::Punctuator, - token::TokenKind, + Keyword, Punctuator, TokenKind, }, parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; @@ -74,7 +72,7 @@ impl TokenParser for AssignmentExpression { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { // Arrow function let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - match next_token.kind { + match next_token.kind() { // a=>{} TokenKind::Identifier(_) | TokenKind::Keyword(Keyword::Yield) @@ -84,7 +82,7 @@ impl TokenParser for AssignmentExpression { .is_ok() => { if let Some(tok) = cursor.peek(1) { - if tok.kind == TokenKind::Punctuator(Punctuator::Arrow) { + if tok.kind() == &TokenKind::Punctuator(Punctuator::Arrow) { return ArrowFunction::new( self.allow_in, self.allow_yield, @@ -112,7 +110,7 @@ impl TokenParser for AssignmentExpression { .parse(cursor)?; if let Some(tok) = cursor.next() { - match tok.kind { + match tok.kind() { TokenKind::Punctuator(Punctuator::Assign) => { lhs = Node::from(Assign::new(lhs, self.parse(cursor)?)); } 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 330b804c7dc..1d055e6fad6 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/arguments.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs @@ -8,7 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#prod-Arguments use crate::syntax::{ - ast::{node::Node, punc::Punctuator, token::TokenKind}, + ast::{Node, Punctuator, TokenKind}, parser::{ expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, }, @@ -50,7 +50,7 @@ impl TokenParser for Arguments { let mut args = Vec::new(); loop { let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; - match next_token.kind { + match next_token.kind() { TokenKind::Punctuator(Punctuator::CloseParen) => break, TokenKind::Punctuator(Punctuator::Comma) => { if args.is_empty() { 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 274512c3827..67336829201 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/call.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/call.rs @@ -9,7 +9,7 @@ use super::arguments::Arguments; use crate::syntax::{ - ast::{node::Node, punc::Punctuator, token::TokenKind}, + ast::{Node, Punctuator, TokenKind}, parser::{ expression::Expression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -49,7 +49,7 @@ impl TokenParser for CallExpression { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let mut lhs = match cursor.peek(0) { - Some(tk) if tk.kind == TokenKind::Punctuator(Punctuator::OpenParen) => { + Some(tk) if tk.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => { let args = Arguments::new(self.allow_yield, self.allow_await).parse(cursor)?; Node::call(self.first_member_expr, args) } @@ -64,14 +64,14 @@ impl TokenParser for CallExpression { }; while let Some(tok) = cursor.peek(0) { - match tok.kind { + match tok.kind() { TokenKind::Punctuator(Punctuator::OpenParen) => { let args = Arguments::new(self.allow_yield, self.allow_await).parse(cursor)?; lhs = Node::call(lhs, args); } TokenKind::Punctuator(Punctuator::Dot) => { let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor. - match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind { + match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind() { TokenKind::Identifier(name) => { lhs = Node::get_const_field(lhs, name.clone().into_boxed_str()); } 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 5c3362ffb83..7f9b0abb2e7 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/member.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/member.rs @@ -7,7 +7,7 @@ use super::arguments::Arguments; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{ expression::{primary::PrimaryExpression, Expression}, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -44,8 +44,8 @@ impl TokenParser for MemberExpression { type Output = Node; fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { - let mut lhs = if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind - == TokenKind::Keyword(Keyword::New) + let mut lhs = if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind() + == &TokenKind::Keyword(Keyword::New) { let _ = cursor.next().expect("keyword disappeared"); let lhs = self.parse(cursor)?; @@ -57,10 +57,10 @@ impl TokenParser for MemberExpression { PrimaryExpression::new(self.allow_yield, self.allow_await).parse(cursor)? }; while let Some(tok) = cursor.peek(0) { - match &tok.kind { + match &tok.kind() { TokenKind::Punctuator(Punctuator::Dot) => { let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor forward. - match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind { + match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind() { TokenKind::Identifier(name) => { lhs = Node::get_const_field(lhs, name.clone().into_boxed_str()) } diff --git a/boa/src/syntax/parser/expression/left_hand_side/mod.rs b/boa/src/syntax/parser/expression/left_hand_side/mod.rs index 470184276a1..461ed9b6c13 100644 --- a/boa/src/syntax/parser/expression/left_hand_side/mod.rs +++ b/boa/src/syntax/parser/expression/left_hand_side/mod.rs @@ -13,7 +13,7 @@ mod member; use self::{call::CallExpression, member::MemberExpression}; use crate::syntax::{ - ast::{node::Node, punc::Punctuator, token::TokenKind}, + ast::{Node, Punctuator, TokenKind}, parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; @@ -52,7 +52,7 @@ impl TokenParser for LeftHandSideExpression { // TODO: Implement NewExpression: new MemberExpression let lhs = MemberExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; match cursor.peek(0) { - Some(ref tok) if tok.kind == TokenKind::Punctuator(Punctuator::OpenParen) => { + Some(ref tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => { CallExpression::new(self.allow_yield, self.allow_await, lhs).parse(cursor) } _ => Ok(lhs), // TODO: is this correct? diff --git a/boa/src/syntax/parser/expression/mod.rs b/boa/src/syntax/parser/expression/mod.rs index 6d33ea4a07c..f3a4d777536 100644 --- a/boa/src/syntax/parser/expression/mod.rs +++ b/boa/src/syntax/parser/expression/mod.rs @@ -19,10 +19,8 @@ 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::{BinOp, Node}, - punc::Punctuator, - token::TokenKind, + Keyword, Punctuator, TokenKind, }; // For use in the expression! macro to allow for both Punctuator and Keyword parameters. @@ -55,8 +53,8 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let mut lhs = $lower::new($( self.$low_param ),*).parse(cursor)?; while let Some(tok) = cursor.peek(0) { - match tok.kind { - TokenKind::Punctuator(op) if $( op == $op )||* => { + match tok.kind() { + TokenKind::Punctuator(op) if $( *op == $op )||* => { let _ = cursor.next().expect("token disappeared"); lhs = Node::from(BinOp::new( op.as_binop().expect("Could not get binary operation."), @@ -64,7 +62,7 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo $lower::new($( self.$low_param ),*).parse(cursor)? )); } - TokenKind::Keyword(op) if $( op == $op )||* => { + TokenKind::Keyword(op) if $( *op == $op )||* => { let _ = cursor.next().expect("token disappeared"); lhs = Node::from(BinOp::new( op.as_binop().expect("Could not get binary operation."), 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 e34ba511ac0..7a92f23ffee 100644 --- a/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs @@ -14,7 +14,7 @@ use crate::syntax::{ ast::{ constant::Const, node::{ArrayDecl, Node}, - punc::Punctuator, + Punctuator, }, parser::{ expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, diff --git a/boa/src/syntax/parser/expression/primary/function_expression.rs b/boa/src/syntax/parser/expression/primary/function_expression.rs index dc2a6307c6d..f27bcab26b8 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression.rs @@ -8,11 +8,11 @@ //! [spec]: https://tc39.es/ecma262/#prod-FunctionExpression use crate::syntax::{ - ast::{node::Node, punc::Punctuator}, + ast::{node::FunctionExpr, Punctuator}, parser::{ function::{FormalParameters, FunctionBody}, statement::BindingIdentifier, - Cursor, ParseResult, TokenParser, + Cursor, ParseError, TokenParser, }, }; @@ -28,9 +28,9 @@ use crate::syntax::{ pub(super) struct FunctionExpression; impl TokenParser for FunctionExpression { - type Output = Node; + type Output = FunctionExpr; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { let name = BindingIdentifier::new(false, false).try_parse(cursor); cursor.expect(Punctuator::OpenParen, "function expression")?; @@ -40,12 +40,10 @@ impl TokenParser for FunctionExpression { cursor.expect(Punctuator::CloseParen, "function expression")?; cursor.expect(Punctuator::OpenBlock, "function expression")?; - let body = FunctionBody::new(false, false) - .parse(cursor) - .map(Node::statement_list)?; + let body = FunctionBody::new(false, false).parse(cursor)?; cursor.expect(Punctuator::CloseBlock, "function expression")?; - Ok(Node::function_expr::<_, Box<_>, _, _>(name, params, body)) + Ok(FunctionExpr::new(name, params, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs index 627d5e2ac48..ee77eea0f12 100644 --- a/boa/src/syntax/parser/expression/primary/mod.rs +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -21,11 +21,9 @@ use super::Expression; use crate::syntax::{ ast::{ constant::Const, - keyword::Keyword, node::{Local, Node}, - punc::Punctuator, token::NumericLiteral, - token::TokenKind, + Keyword, Punctuator, TokenKind, }, parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; @@ -65,10 +63,12 @@ impl TokenParser for PrimaryExpression { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - match &tok.kind { + match &tok.kind() { TokenKind::Keyword(Keyword::This) => Ok(Node::This), // TokenKind::Keyword(Keyword::Arguments) => Ok(Node::new(NodeBase::Arguments, tok.pos)), - TokenKind::Keyword(Keyword::Function) => FunctionExpression.parse(cursor), + TokenKind::Keyword(Keyword::Function) => { + FunctionExpression.parse(cursor).map(Node::from) + } TokenKind::Punctuator(Punctuator::OpenParen) => { let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; 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 a4e22f630d1..b562eb4a32b 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -12,9 +12,9 @@ mod tests; use crate::syntax::{ ast::{ - node::{self, MethodDefinitionKind, Node}, - punc::Punctuator, + node::{self, FunctionExpr, MethodDefinitionKind, Node}, token::{Token, TokenKind}, + Punctuator, }, parser::{ expression::AssignmentExpression, @@ -143,7 +143,7 @@ impl TokenParser for PropertyDefinition { let pos = cursor .peek(0) - .map(|tok| tok.pos) + .map(|tok| tok.span().start()) .ok_or(ParseError::AbruptEnd)?; Err(ParseError::General( "expected property definition", @@ -231,9 +231,7 @@ impl TokenParser for MethodDefinition { TokenKind::Punctuator(Punctuator::OpenBlock), "property method definition", )?; - let body = FunctionBody::new(false, false) - .parse(cursor) - .map(Node::statement_list)?; + let body = FunctionBody::new(false, false).parse(cursor)?; cursor.expect( TokenKind::Punctuator(Punctuator::CloseBlock), "property method definition", @@ -242,7 +240,7 @@ impl TokenParser for MethodDefinition { Ok(node::PropertyDefinition::method_definition( methodkind, prop_name, - Node::function_expr::<_, String, _, _>(None, params, body), + FunctionExpr::new(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 acf8a6ef3cf..19149198538 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::{FormalParameter, MethodDefinitionKind, Node, PropertyDefinition}, + ast::node::{FormalParameter, FunctionExpr, MethodDefinitionKind, Node, PropertyDefinition}, parser::tests::check_parser, }; @@ -32,7 +32,7 @@ fn check_object_short_function() { PropertyDefinition::method_definition( MethodDefinitionKind::Ordinary, "b", - Node::function_expr::<_, String, _, _>(None, Vec::new(), Node::statement_list(vec![])), + FunctionExpr::new(None, Vec::new(), Vec::new()), ), ]; @@ -57,10 +57,10 @@ fn check_object_short_function_arguments() { PropertyDefinition::method_definition( MethodDefinitionKind::Ordinary, "b", - Node::FunctionExpr( + FunctionExpr::new( None, - Box::new([FormalParameter::new("test", None, false)]), - Box::new(Node::StatementList(Box::new([]))), + vec![FormalParameter::new("test", None, false)], + Vec::new(), ), ), ]; @@ -85,11 +85,7 @@ fn check_object_getter() { PropertyDefinition::method_definition( MethodDefinitionKind::Get, "b", - Node::FunctionExpr( - None, - Box::new([]), - Box::new(Node::statement_list(Vec::new())), - ), + FunctionExpr::new(None, Vec::new(), Vec::new()), ), ]; @@ -113,10 +109,10 @@ fn check_object_setter() { PropertyDefinition::method_definition( MethodDefinitionKind::Set, "b", - Node::function_expr::<_, String, _, _>( + FunctionExpr::new( None, vec![FormalParameter::new("test", None, false)], - Node::statement_list(Vec::new()), + Vec::new(), ), ), ]; diff --git a/boa/src/syntax/parser/expression/primary/tests.rs b/boa/src/syntax/parser/expression/primary/tests.rs index 19f76af5bea..cb05481cfa8 100644 --- a/boa/src/syntax/parser/expression/primary/tests.rs +++ b/boa/src/syntax/parser/expression/primary/tests.rs @@ -1,4 +1,4 @@ -use crate::syntax::{ast::node::Node, parser::tests::check_parser}; +use crate::syntax::{ast::Node, parser::tests::check_parser}; #[test] fn check_string() { diff --git a/boa/src/syntax/parser/expression/unary.rs b/boa/src/syntax/parser/expression/unary.rs index 14a2c7a29be..ffd24c2fa7e 100644 --- a/boa/src/syntax/parser/expression/unary.rs +++ b/boa/src/syntax/parser/expression/unary.rs @@ -8,7 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-unary-operators use crate::syntax::{ - ast::{keyword::Keyword, node::Node, op::UnaryOp, punc::Punctuator, token::TokenKind}, + ast::{op::UnaryOp, Keyword, Node, Punctuator, TokenKind}, parser::{ expression::update::UpdateExpression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -48,7 +48,7 @@ impl TokenParser for UnaryExpression { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - match tok.kind { + match tok.kind() { TokenKind::Keyword(Keyword::Delete) => { Ok(Node::unary_op(UnaryOp::Delete, self.parse(cursor)?)) } diff --git a/boa/src/syntax/parser/expression/update.rs b/boa/src/syntax/parser/expression/update.rs index 5ba3d839e94..1ed38e62e17 100644 --- a/boa/src/syntax/parser/expression/update.rs +++ b/boa/src/syntax/parser/expression/update.rs @@ -7,7 +7,7 @@ use super::left_hand_side::LeftHandSideExpression; use crate::syntax::{ - ast::{node::Node, op::UnaryOp, punc::Punctuator, token::TokenKind}, + ast::{op::UnaryOp, Node, Punctuator, TokenKind}, parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; @@ -42,7 +42,7 @@ impl TokenParser for UpdateExpression { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - match tok.kind { + match tok.kind() { TokenKind::Punctuator(Punctuator::Inc) => { cursor.next().expect("token disappeared"); return Ok(Node::unary_op( @@ -64,7 +64,7 @@ impl TokenParser for UpdateExpression { let lhs = LeftHandSideExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; if let Some(tok) = cursor.peek(0) { - match tok.kind { + match tok.kind() { TokenKind::Punctuator(Punctuator::Inc) => { cursor.next().expect("token disappeared"); return Ok(Node::unary_op(UnaryOp::IncrementPost, lhs)); diff --git a/boa/src/syntax/parser/function/mod.rs b/boa/src/syntax/parser/function/mod.rs index 3402b213e19..d8058ddee23 100644 --- a/boa/src/syntax/parser/function/mod.rs +++ b/boa/src/syntax/parser/function/mod.rs @@ -12,9 +12,8 @@ mod tests; use crate::syntax::{ ast::{ - node::{self, Node}, - punc::Punctuator, - token::TokenKind, + node::{self}, + Punctuator, TokenKind, }, parser::{ expression::Initializer, @@ -52,15 +51,15 @@ impl FormalParameters { } impl TokenParser for FormalParameters { - type Output = Vec; + type Output = Box<[node::FormalParameter]>; fn parse(self, cursor: &mut Cursor<'_>) -> Result { let mut params = Vec::new(); - if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind - == TokenKind::Punctuator(Punctuator::CloseParen) + if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind() + == &TokenKind::Punctuator(Punctuator::CloseParen) { - return Ok(params); + return Ok(params.into_boxed_slice()); } loop { @@ -73,8 +72,8 @@ impl TokenParser for FormalParameters { FormalParameter::new(self.allow_yield, self.allow_await).parse(cursor)? }); - if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind - == TokenKind::Punctuator(Punctuator::CloseParen) + if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind() + == &TokenKind::Punctuator(Punctuator::CloseParen) { break; } @@ -92,7 +91,7 @@ impl TokenParser for FormalParameters { cursor.expect(Punctuator::Comma, "parameter list")?; } - Ok(params) + Ok(params.into_boxed_slice()) } } @@ -225,12 +224,12 @@ impl FunctionStatementList { } impl TokenParser for FunctionStatementList { - type Output = Vec; + type Output = node::StatementList; fn parse(self, cursor: &mut Cursor<'_>) -> Result { if let Some(tk) = cursor.peek(0) { - if tk.kind == Punctuator::CloseBlock.into() { - return Ok(Vec::new()); + if tk.kind() == &Punctuator::CloseBlock.into() { + return Ok(Vec::new().into()); } } diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs index 5a9dfb22187..06deb570fe1 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, Local, Node}, + ast::node::{ArrowFunctionDecl, BinOp, FormalParameter, FunctionDecl, Local, Node}, ast::op::NumOp, parser::tests::check_parser, }; @@ -9,11 +9,12 @@ use crate::syntax::{ fn check_basic() { check_parser( "function foo(a) { return a; }", - vec![Node::function_decl( - "foo", + vec![FunctionDecl::new( + Box::from("foo"), vec![FormalParameter::new("a", None, false)], - Node::statement_list(vec![Node::return_node(Node::from(Local::from("a")))]), - )], + vec![Node::return_node(Node::from(Local::from("a")))], + ) + .into()], ); } @@ -22,11 +23,12 @@ fn check_basic() { fn check_basic_semicolon_insertion() { check_parser( "function foo(a) { return a }", - vec![Node::function_decl( - "foo", + vec![FunctionDecl::new( + Box::from("foo"), vec![FormalParameter::new("a", None, false)], - Node::statement_list(vec![Node::return_node(Node::from(Local::from("a")))]), - )], + vec![Node::return_node(Node::from(Local::from("a")))], + ) + .into()], ); } @@ -35,11 +37,12 @@ fn check_basic_semicolon_insertion() { fn check_empty_return() { check_parser( "function foo(a) { return; }", - vec![Node::function_decl( - "foo", + vec![FunctionDecl::new( + Box::from("foo"), vec![FormalParameter::new("a", None, false)], - Node::statement_list(vec![Node::Return(None)]), - )], + vec![Node::Return(None)], + ) + .into()], ); } @@ -48,11 +51,12 @@ fn check_empty_return() { fn check_empty_return_semicolon_insertion() { check_parser( "function foo(a) { return }", - vec![Node::function_decl( - "foo", + vec![FunctionDecl::new( + Box::from("foo"), vec![FormalParameter::new("a", None, false)], - Node::statement_list(vec![Node::Return(None)]), - )], + vec![Node::Return(None)], + ) + .into()], ); } @@ -61,14 +65,15 @@ fn check_empty_return_semicolon_insertion() { fn check_rest_operator() { check_parser( "function foo(a, ...b) {}", - vec![Node::function_decl( - "foo", + vec![FunctionDecl::new( + Box::from("foo"), vec![ FormalParameter::new("a", None, false), FormalParameter::new("b", None, true), ], - Node::StatementList(Box::new([])), - )], + vec![], + ) + .into()], ); } @@ -77,11 +82,7 @@ fn check_rest_operator() { fn check_arrow_only_rest() { check_parser( "(...a) => {}", - vec![ArrowFunctionDecl::new( - vec![FormalParameter::new("a", None, true)], - Node::StatementList(Box::new([])), - ) - .into()], + vec![ArrowFunctionDecl::new(vec![FormalParameter::new("a", None, true)], vec![]).into()], ); } @@ -96,7 +97,7 @@ fn check_arrow_rest() { FormalParameter::new("b", None, false), FormalParameter::new("c", None, true), ], - Node::StatementList(Box::new([])), + vec![], ) .into()], ); @@ -112,11 +113,11 @@ fn check_arrow() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - Node::statement_list(vec![Node::return_node(Node::from(BinOp::new( + vec![Node::return_node(Node::from(BinOp::new( NumOp::Add, Local::from("a"), Local::from("b"), - )))]), + )))], ) .into()], ); @@ -132,11 +133,11 @@ fn check_arrow_semicolon_insertion() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - Node::statement_list(vec![Node::return_node(Node::from(BinOp::new( + vec![Node::return_node(Node::from(BinOp::new( NumOp::Add, Local::from("a"), Local::from("b"), - )))]), + )))], ) .into()], ); @@ -152,7 +153,7 @@ fn check_arrow_epty_return() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - Node::statement_list(vec![Node::Return(None)]), + vec![Node::Return(None)], ) .into()], ); @@ -168,7 +169,7 @@ fn check_arrow_empty_return_semicolon_insertion() { FormalParameter::new("a", None, false), FormalParameter::new("b", None, false), ], - Node::statement_list(vec![Node::Return(None)]), + vec![Node::Return(None)], ) .into()], ); diff --git a/boa/src/syntax/parser/mod.rs b/boa/src/syntax/parser/mod.rs index bfbb6798cb2..79968cd7d3a 100644 --- a/boa/src/syntax/parser/mod.rs +++ b/boa/src/syntax/parser/mod.rs @@ -9,10 +9,7 @@ mod statement; mod tests; use self::error::{ParseError, ParseResult}; -use crate::syntax::ast::{ - node::{StatementList, VarDecl}, - token::Token, -}; +use crate::syntax::ast::{node::StatementList, Token}; use cursor::Cursor; /// Trait implemented by parsers. @@ -106,7 +103,7 @@ impl<'a> Parser<'a> { } /// Parse all expressions in the token array - pub fn parse_all(&mut self) -> (VarDecl, StatementList) { + pub fn parse_all(&mut self) -> Result { Script.parse(&mut self.cursor) } } @@ -121,16 +118,13 @@ impl<'a> Parser<'a> { pub struct Script; impl TokenParser for Script { - type Output = (VarDecl, StatementList); + type Output = StatementList; fn parse(self, cursor: &mut Cursor<'_>) -> Result { if cursor.peek(0).is_some() { ScriptBody.parse(cursor) } else { - Ok(( - Vec::new().into(), - StatementList::new(Vec::new(), Vec::new()), - )) + Ok(StatementList::from(Vec::new())) } } } @@ -145,7 +139,7 @@ impl TokenParser for Script { pub struct ScriptBody; impl TokenParser for ScriptBody { - type Output = (VarDecl, StatementList); + type Output = StatementList; fn parse(self, cursor: &mut Cursor<'_>) -> Result { self::statement::StatementList::new(false, false, false, false).parse(cursor) diff --git a/boa/src/syntax/parser/statement/block/mod.rs b/boa/src/syntax/parser/statement/block/mod.rs index bde56d93efc..1717834b8b6 100644 --- a/boa/src/syntax/parser/statement/block/mod.rs +++ b/boa/src/syntax/parser/statement/block/mod.rs @@ -12,7 +12,7 @@ mod tests; use super::StatementList; use crate::syntax::{ - ast::{node, punc::Punctuator, token::TokenKind}, + ast::{node, Punctuator, TokenKind}, parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, }; @@ -61,7 +61,7 @@ impl TokenParser for Block { 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) { + if tk.kind() == &TokenKind::Punctuator(Punctuator::CloseBlock) { cursor.next(); return Ok(node::Block::from(vec![])); } diff --git a/boa/src/syntax/parser/statement/block/tests.rs b/boa/src/syntax/parser/statement/block/tests.rs index 5004700cbd0..9812cf4cad1 100644 --- a/boa/src/syntax/parser/statement/block/tests.rs +++ b/boa/src/syntax/parser/statement/block/tests.rs @@ -2,7 +2,7 @@ use crate::syntax::{ ast::{ - node::{Assign, Block, Local, Node}, + node::{Assign, Block, FunctionDecl, Local, Node, VarDecl, VarDeclList}, op::UnaryOp, }, parser::tests::check_parser, @@ -27,7 +27,7 @@ fn non_empty() { a++; }", Block::from(vec![ - Node::var_decl(vec![("a".into(), Some(Node::const_node(10)))]), + VarDeclList::from(vec![VarDecl::new("a", Some(Node::const_node(10)))]).into(), Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), ]), ); @@ -42,15 +42,17 @@ fn non_empty() { a++; }", Block::from(vec![ - Node::function_decl( - "hello", + FunctionDecl::new( + "hello".to_owned().into_boxed_str(), vec![], - Node::statement_list(vec![Node::return_node(Node::const_node(10))]), - ), - Node::var_decl(vec![( - "a".into(), + vec![Node::return_node(Node::const_node(10))], + ) + .into(), + VarDeclList::from(vec![VarDecl::new( + "a", Some(Node::call(Node::from(Local::from("hello")), vec![])), - )]), + )]) + .into(), Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), ]), ); @@ -66,15 +68,17 @@ fn hoisting() { function hello() { return 10 } }", Block::from(vec![ - Node::function_decl( - "hello", + FunctionDecl::new( + "hello".to_owned().into_boxed_str(), vec![], - Node::statement_list(vec![Node::return_node(Node::const_node(10))]), - ), - Node::var_decl(vec![( - "a".into(), + vec![Node::return_node(Node::const_node(10))], + ) + .into(), + VarDeclList::from(vec![VarDecl::new( + "a", Some(Node::call(Node::from(Local::from("hello")), vec![])), - )]), + )]) + .into(), Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), ]), ); @@ -87,8 +91,8 @@ fn hoisting() { var a; }", Block::from(vec![ - Node::var_decl(vec![("a".into(), None)]), - Node::from(Assign::new(Local::from("a"), Node::const_node(10))), + VarDeclList::from(vec![VarDecl::new("a", None)]).into(), + Assign::new(Local::from("a"), Node::const_node(10)).into(), 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 18ea6b26830..18aa1c8050d 100644 --- a/boa/src/syntax/parser/statement/break_stm/mod.rs +++ b/boa/src/syntax/parser/statement/break_stm/mod.rs @@ -12,7 +12,7 @@ mod tests; use super::LabelIdentifier; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; @@ -52,7 +52,7 @@ impl TokenParser for BreakStatement { let label = if let (true, tok) = cursor.peek_semicolon(false) { match tok { - Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => { + Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Semicolon) => { let _ = cursor.next(); } _ => {} diff --git a/boa/src/syntax/parser/statement/continue_stm/mod.rs b/boa/src/syntax/parser/statement/continue_stm/mod.rs index 2ca22998c8d..89414faa2e9 100644 --- a/boa/src/syntax/parser/statement/continue_stm/mod.rs +++ b/boa/src/syntax/parser/statement/continue_stm/mod.rs @@ -12,7 +12,7 @@ mod tests; use super::LabelIdentifier; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; @@ -52,7 +52,7 @@ impl TokenParser for ContinueStatement { let label = if let (true, tok) = cursor.peek_semicolon(false) { match tok { - Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => { + Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Semicolon) => { let _ = cursor.next(); } _ => {} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable.rs b/boa/src/syntax/parser/statement/declaration/hoistable.rs index 691066844b9..44a942a908e 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable.rs @@ -6,10 +6,10 @@ //! [spec]: https://tc39.es/ecma262/#prod-HoistableDeclaration use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator}, + ast::{node::FunctionDecl, Keyword, Node, Punctuator}, parser::{ function::FormalParameters, function::FunctionBody, statement::BindingIdentifier, - AllowAwait, AllowDefault, AllowYield, Cursor, ParseResult, TokenParser, + AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }; @@ -23,12 +23,12 @@ use crate::syntax::{ pub(super) struct HoistableDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, - allow_default: AllowDefault, + is_default: AllowDefault, } impl HoistableDeclaration { /// Creates a new `HoistableDeclaration` parser. - pub(super) fn new(allow_yield: Y, allow_await: A, allow_default: D) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, @@ -37,7 +37,7 @@ impl HoistableDeclaration { Self { allow_yield: allow_yield.into(), allow_await: allow_await.into(), - allow_default: allow_default.into(), + is_default: is_default.into(), } } } @@ -47,8 +47,9 @@ impl TokenParser for HoistableDeclaration { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { // TODO: check for generators and async functions + generators - FunctionDeclaration::new(self.allow_yield, self.allow_await, self.allow_default) + FunctionDeclaration::new(self.allow_yield, self.allow_await, self.is_default) .parse(cursor) + .map(Node::from) } } @@ -64,12 +65,12 @@ impl TokenParser for HoistableDeclaration { struct FunctionDeclaration { allow_yield: AllowYield, allow_await: AllowAwait, - allow_default: AllowDefault, + is_default: AllowDefault, } impl FunctionDeclaration { /// Creates a new `FunctionDeclaration` parser. - fn new(allow_yield: Y, allow_await: A, allow_default: D) -> Self + fn new(allow_yield: Y, allow_await: A, is_default: D) -> Self where Y: Into, A: Into, @@ -78,17 +79,18 @@ impl FunctionDeclaration { Self { allow_yield: allow_yield.into(), allow_await: allow_await.into(), - allow_default: allow_default.into(), + is_default: is_default.into(), } } } impl TokenParser for FunctionDeclaration { - type Output = Node; + type Output = FunctionDecl; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Keyword::Function, "function declaration")?; + // TODO: If self.is_default, then this can be empty. let name = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect(Punctuator::OpenParen, "function declaration")?; @@ -98,12 +100,10 @@ impl TokenParser for FunctionDeclaration { cursor.expect(Punctuator::CloseParen, "function declaration")?; cursor.expect(Punctuator::OpenBlock, "function declaration")?; - let body = FunctionBody::new(self.allow_yield, self.allow_await) - .parse(cursor) - .map(Node::statement_list)?; + let body = FunctionBody::new(self.allow_yield, self.allow_await).parse(cursor)?; cursor.expect(Punctuator::CloseBlock, "function declaration")?; - Ok(Node::function_decl(name, params, body)) + Ok(FunctionDecl::new(name, params, body)) } } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index 6d7355e6603..27904da5c78 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -8,7 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{ expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -50,7 +50,7 @@ impl TokenParser for LexicalDeclaration { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; - match tok.kind { + match tok.kind() { TokenKind::Keyword(Keyword::Const) => { BindingList::new(self.allow_in, self.allow_yield, self.allow_await, true) .parse(cursor) @@ -128,7 +128,7 @@ impl TokenParser for BindingList { match cursor.peek_semicolon(false) { (true, _) => break, - (false, Some(tk)) if tk.kind == TokenKind::Punctuator(Punctuator::Comma) => { + (false, Some(tk)) if tk.kind() == &TokenKind::Punctuator(Punctuator::Comma) => { let _ = cursor.next(); } _ => { diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs index 11355289c3c..e7e9b463f2b 100644 --- a/boa/src/syntax/parser/statement/declaration/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/mod.rs @@ -14,7 +14,7 @@ mod tests; use self::{hoistable::HoistableDeclaration, lexical::LexicalDeclaration}; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, token::TokenKind}, + ast::{Keyword, Node, TokenKind}, parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; @@ -49,7 +49,7 @@ impl TokenParser for Declaration { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - match tok.kind { + match tok.kind() { TokenKind::Keyword(Keyword::Function) => { HoistableDeclaration::new(self.allow_yield, self.allow_await, false).parse(cursor) } diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs index 1bb4154f6ad..1f95e7a6a04 100644 --- a/boa/src/syntax/parser/statement/declaration/tests.rs +++ b/boa/src/syntax/parser/statement/declaration/tests.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::node::Node, + ast::node::{FunctionDecl, Node, VarDecl, VarDeclList}, parser::tests::{check_invalid, check_parser}, }; @@ -8,10 +8,7 @@ use crate::syntax::{ fn var_declaration() { check_parser( "var a = 5;", - vec![Node::var_decl(vec![( - "a".into(), - Some(Node::const_node(5)), - )])], + vec![VarDeclList::from(vec![VarDecl::new("a", Some(Node::const_node(5)))]).into()], ); } @@ -20,18 +17,12 @@ fn var_declaration() { fn var_declaration_keywords() { check_parser( "var yield = 5;", - vec![Node::var_decl(vec![( - "yield".into(), - Some(Node::const_node(5)), - )])], + vec![VarDeclList::from(vec![VarDecl::new("yield", Some(Node::const_node(5)))]).into()], ); check_parser( "var await = 5;", - vec![Node::var_decl(vec![( - "await".into(), - Some(Node::const_node(5)), - )])], + vec![VarDeclList::from(vec![VarDecl::new("await", Some(Node::const_node(5)))]).into()], ); } @@ -40,17 +31,17 @@ fn var_declaration_keywords() { fn var_declaration_no_spaces() { check_parser( "var a=5;", - vec![Node::var_decl(vec![( - "a".into(), - Some(Node::const_node(5)), - )])], + vec![VarDeclList::from(vec![VarDecl::new("a", Some(Node::const_node(5)))]).into()], ); } /// Checks empty `var` declaration parsing. #[test] fn empty_var_declaration() { - check_parser("var a;", vec![Node::var_decl(vec![("a".into(), None)])]); + check_parser( + "var a;", + vec![VarDeclList::from(vec![VarDecl::new("a", None)]).into()], + ); } /// Checks multiple `var` declarations. @@ -58,11 +49,12 @@ fn empty_var_declaration() { fn multiple_var_declaration() { check_parser( "var a = 5, b, c = 6;", - vec![Node::var_decl(vec![ - ("a".into(), Some(Node::const_node(5))), - ("b".into(), None), - ("c".into(), Some(Node::const_node(6))), - ])], + vec![VarDeclList::from(vec![ + VarDecl::new("a", Some(Node::const_node(5))), + VarDecl::new("b", None), + VarDecl::new("c", Some(Node::const_node(6))), + ]) + .into()], ); } @@ -190,11 +182,7 @@ fn multiple_const_declaration() { fn function_declaration() { check_parser( "function hello() {}", - vec![Node::function_decl( - "hello", - vec![], - Node::statement_list(vec![]), - )], + vec![FunctionDecl::new(Box::from("hello"), vec![], vec![]).into()], ); } @@ -203,19 +191,11 @@ fn function_declaration() { fn function_declaration_keywords() { check_parser( "function yield() {}", - vec![Node::function_decl( - "yield", - vec![], - Node::statement_list(vec![]), - )], + vec![FunctionDecl::new(Box::from("yield"), vec![], vec![]).into()], ); check_parser( "function await() {}", - vec![Node::function_decl( - "await", - vec![], - Node::statement_list(vec![]), - )], + vec![FunctionDecl::new(Box::from("await"), vec![], vec![]).into()], ); } diff --git a/boa/src/syntax/parser/statement/if_stm/mod.rs b/boa/src/syntax/parser/statement/if_stm/mod.rs index 8caeab0101d..93ebcb934eb 100644 --- a/boa/src/syntax/parser/statement/if_stm/mod.rs +++ b/boa/src/syntax/parser/statement/if_stm/mod.rs @@ -3,7 +3,7 @@ mod tests; use super::Statement; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{ expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, TokenParser, @@ -58,7 +58,7 @@ impl TokenParser for IfStatement { Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; let else_stm = match cursor.next() { - Some(else_tok) if else_tok.kind == TokenKind::Keyword(Keyword::Else) => Some( + Some(else_tok) if else_tok.kind() == &TokenKind::Keyword(Keyword::Else) => Some( Statement::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor)?, ), diff --git a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs index d7ac6b88310..78b7e78a607 100644 --- a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs @@ -8,7 +8,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-do-while-statement use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{ expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -61,7 +61,7 @@ impl TokenParser for DoWhileStatement { let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - if next_token.kind != TokenKind::Keyword(Keyword::While) { + if next_token.kind() != &TokenKind::Keyword(Keyword::While) { return Err(ParseError::Expected( vec![TokenKind::Keyword(Keyword::While)], next_token.clone(), diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs index 4ee1ae2a529..1913f415443 100644 --- a/boa/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -9,10 +9,8 @@ use crate::syntax::{ ast::{ - keyword::Keyword, node::{Block, Node}, - punc::Punctuator, - token::TokenKind, + Keyword, Punctuator, TokenKind, }, parser::{ expression::Expression, @@ -64,10 +62,11 @@ impl TokenParser for ForStatement { cursor.expect(Keyword::For, "for statement")?; cursor.expect(Punctuator::OpenParen, "for statement")?; - let init = match cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind { + let init = match cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind() { TokenKind::Keyword(Keyword::Var) => Some( VariableDeclarationList::new(false, self.allow_yield, self.allow_await) - .parse(cursor)?, + .parse(cursor) + .map(Node::from)?, ), TokenKind::Keyword(Keyword::Let) | TokenKind::Keyword(Keyword::Const) => { Some(Declaration::new(self.allow_yield, self.allow_await).parse(cursor)?) diff --git a/boa/src/syntax/parser/statement/iteration/tests.rs b/boa/src/syntax/parser/statement/iteration/tests.rs index 27c3de66092..ad7e1eaa375 100644 --- a/boa/src/syntax/parser/statement/iteration/tests.rs +++ b/boa/src/syntax/parser/statement/iteration/tests.rs @@ -1,6 +1,8 @@ use crate::syntax::{ - ast::node::{BinOp, Block, Local, Node}, - ast::op::{AssignOp, CompOp, UnaryOp}, + ast::{ + node::{BinOp, Block, Local, Node, VarDecl, VarDeclList}, + op::{AssignOp, CompOp, UnaryOp}, + }, parser::tests::check_parser, }; @@ -29,7 +31,7 @@ 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![("i".into(), Some(Node::const_node(0)))]), + VarDeclList::from(vec![VarDecl::new("i", Some(Node::const_node(0)))]).into(), Node::do_while_loop( Node::from(Block::from(vec![Node::call( Node::get_const_field(Node::from(Local::from("console")), "log"), diff --git a/boa/src/syntax/parser/statement/iteration/while_statement.rs b/boa/src/syntax/parser/statement/iteration/while_statement.rs index 2b9f765ec0f..dd8040ae248 100644 --- a/boa/src/syntax/parser/statement/iteration/while_statement.rs +++ b/boa/src/syntax/parser/statement/iteration/while_statement.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator}, + ast::{Keyword, Node, Punctuator}, parser::{ expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, TokenParser, diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index be222241c74..fd9d753a19a 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -36,7 +36,7 @@ use super::{ expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }; -use crate::syntax::ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}; +use crate::syntax::ast::{node, Keyword, Node, Punctuator, TokenKind}; /// Statement parsing. /// @@ -93,13 +93,15 @@ impl TokenParser for Statement { // TODO: add BreakableStatement and divide Whiles, fors and so on to another place. let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - match tok.kind { + match tok.kind() { TokenKind::Keyword(Keyword::If) => { IfStatement::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor) } TokenKind::Keyword(Keyword::Var) => { - VariableStatement::new(self.allow_yield, self.allow_await).parse(cursor) + VariableStatement::new(self.allow_yield, self.allow_await) + .parse(cursor) + .map(Node::from) } TokenKind::Keyword(Keyword::While) => { WhileStatement::new(self.allow_yield, self.allow_await, self.allow_return) @@ -190,14 +192,14 @@ impl StatementList { } impl TokenParser for StatementList { - type Output = (VarDecl, StatementList); + type Output = node::StatementList; fn parse(self, cursor: &mut Cursor<'_>) -> Result { let mut items = Vec::new(); loop { match cursor.peek(0) { - Some(token) if token.kind == TokenKind::Punctuator(Punctuator::CloseBlock) => { + Some(token) if token.kind() == &TokenKind::Punctuator(Punctuator::CloseBlock) => { if self.break_when_closingbrase { break; } else { @@ -225,7 +227,7 @@ impl TokenParser for StatementList { items.sort_by(Node::hoistable_order); - Ok(items) + Ok(items.into()) } } @@ -268,7 +270,7 @@ impl TokenParser for StatementListItem { fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - match tok.kind { + match tok.kind() { TokenKind::Keyword(Keyword::Function) | TokenKind::Keyword(Keyword::Const) | TokenKind::Keyword(Keyword::Let) => { @@ -364,7 +366,7 @@ impl TokenParser for BindingIdentifier { let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; - match next_token.kind { + match next_token.kind() { 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()), diff --git a/boa/src/syntax/parser/statement/return_stm/mod.rs b/boa/src/syntax/parser/statement/return_stm/mod.rs index 54a7bf5cce8..e2167515c37 100644 --- a/boa/src/syntax/parser/statement/return_stm/mod.rs +++ b/boa/src/syntax/parser/statement/return_stm/mod.rs @@ -2,7 +2,7 @@ mod tests; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; @@ -43,8 +43,8 @@ impl TokenParser for ReturnStatement { if let (true, tok) = cursor.peek_semicolon(false) { match tok { Some(tok) - if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) - || tok.kind == TokenKind::LineTerminator => + if tok.kind() == &TokenKind::Punctuator(Punctuator::Semicolon) + || tok.kind() == &TokenKind::LineTerminator => { let _ = cursor.next(); } diff --git a/boa/src/syntax/parser/statement/switch/mod.rs b/boa/src/syntax/parser/statement/switch/mod.rs index 1c187b7f529..c8416149a03 100644 --- a/boa/src/syntax/parser/statement/switch/mod.rs +++ b/boa/src/syntax/parser/statement/switch/mod.rs @@ -2,7 +2,7 @@ mod tests; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator}, + ast::{Keyword, Node, Punctuator}, parser::{ expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, diff --git a/boa/src/syntax/parser/statement/throw/mod.rs b/boa/src/syntax/parser/statement/throw/mod.rs index bbfbfba9db8..711ff56587d 100644 --- a/boa/src/syntax/parser/statement/throw/mod.rs +++ b/boa/src/syntax/parser/statement/throw/mod.rs @@ -2,7 +2,7 @@ mod tests; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{Keyword, Node, Punctuator, TokenKind}, parser::{expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, }; @@ -44,7 +44,7 @@ impl TokenParser for ThrowStatement { let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; if let Some(tok) = cursor.peek(0) { - if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) { + if tok.kind() == &TokenKind::Punctuator(Punctuator::Semicolon) { let _ = cursor.next(); } } diff --git a/boa/src/syntax/parser/statement/throw/tests.rs b/boa/src/syntax/parser/statement/throw/tests.rs index f0919d18701..9557d46fd35 100644 --- a/boa/src/syntax/parser/statement/throw/tests.rs +++ b/boa/src/syntax/parser/statement/throw/tests.rs @@ -1,4 +1,4 @@ -use crate::syntax::{ast::node::Node, parser::tests::check_parser}; +use crate::syntax::{ast::Node, parser::tests::check_parser}; #[test] fn check_throw_parsing() { diff --git a/boa/src/syntax/parser/statement/try_stm/catch.rs b/boa/src/syntax/parser/statement/try_stm/catch.rs index 717a6539906..7fac5a05527 100644 --- a/boa/src/syntax/parser/statement/try_stm/catch.rs +++ b/boa/src/syntax/parser/statement/try_stm/catch.rs @@ -1,8 +1,7 @@ use crate::syntax::{ ast::{ - keyword::Keyword, node::{self, Local}, - punc::Punctuator, + Keyword, Punctuator, }, parser::{ statement::{block::Block, BindingIdentifier}, diff --git a/boa/src/syntax/parser/statement/try_stm/finally.rs b/boa/src/syntax/parser/statement/try_stm/finally.rs index d187c3c1381..ff9e4d65c23 100644 --- a/boa/src/syntax/parser/statement/try_stm/finally.rs +++ b/boa/src/syntax/parser/statement/try_stm/finally.rs @@ -1,5 +1,5 @@ use crate::syntax::{ - ast::{keyword::Keyword, node}, + ast::{node, Keyword}, parser::{ statement::block::Block, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs index 8bc7c7ed232..c80b1c130e8 100644 --- a/boa/src/syntax/parser/statement/try_stm/mod.rs +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -8,7 +8,7 @@ use self::catch::Catch; use self::finally::Finally; use super::block::Block; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, token::TokenKind}, + ast::{Keyword, Node, TokenKind}, parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; @@ -55,8 +55,8 @@ impl TokenParser for TryStatement { let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - if next_token.kind != TokenKind::Keyword(Keyword::Catch) - && next_token.kind != TokenKind::Keyword(Keyword::Finally) + if next_token.kind() != &TokenKind::Keyword(Keyword::Catch) + && next_token.kind() != &TokenKind::Keyword(Keyword::Finally) { return Err(ParseError::Expected( vec![ @@ -68,7 +68,7 @@ impl TokenParser for TryStatement { )); } - let catch = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { + let catch = if next_token.kind() == &TokenKind::Keyword(Keyword::Catch) { Some(Catch::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?) } else { None @@ -76,7 +76,7 @@ impl TokenParser for TryStatement { let next_token = cursor.peek(0); let finally_block = match next_token { - Some(token) => match token.kind { + Some(token) => match token.kind() { TokenKind::Keyword(Keyword::Finally) => Some( Finally::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor)?, diff --git a/boa/src/syntax/parser/statement/try_stm/tests.rs b/boa/src/syntax/parser/statement/try_stm/tests.rs index 3cf72568beb..a95dd241d22 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, Local, Node}, + ast::node::{Block, Local, Node, VarDecl, VarDeclList}, parser::tests::{check_invalid, check_parser}, }; @@ -20,10 +20,11 @@ fn check_inline_with_var_decl_inside_try() { check_parser( "try { var x = 1; } catch(e) {}", vec![Node::try_node( - Block::from(vec![Node::var_decl(vec![( - "x".into(), + Block::from(vec![VarDeclList::from(vec![VarDecl::new( + "x", Some(Node::const_node(1)), - )])]), + )]) + .into()]), Some((Some(Local::from("e")), Block::from(vec![]))), None, )], @@ -35,16 +36,18 @@ fn check_inline_with_var_decl_inside_catch() { check_parser( "try { var x = 1; } catch(e) { var x = 1; }", vec![Node::try_node( - Block::from(vec![Node::var_decl(vec![( - "x".into(), + Block::from(vec![VarDeclList::from(vec![VarDecl::new( + "x", Some(Node::const_node(1)), - )])]), + )]) + .into()]), Some(( Some(Local::from("e")), - Block::from(vec![Node::var_decl(vec![( - "x".into(), + Block::from(vec![VarDeclList::from(vec![VarDecl::new( + "x", Some(Node::const_node(1)), - )])]), + )]) + .into()]), )), None, )], @@ -82,10 +85,11 @@ fn check_inline_with_empty_try_var_decl_in_finally() { vec![Node::try_node( Block::from(vec![]), None, - Block::from(vec![Node::var_decl(vec![( - "x".into(), + Block::from(vec![VarDeclList::from(vec![VarDecl::new( + "x", Some(Node::const_node(1)), - )])]), + )]) + .into()]), )], ); } @@ -98,10 +102,11 @@ fn check_inline_empty_try_paramless_catch() { Block::from(vec![]), Some(( None, - Block::from(vec![Node::var_decl(vec![( - "x".into(), + Block::from(vec![VarDeclList::from(vec![VarDecl::new( + "x", Some(Node::const_node(1)), - )])]), + )]) + .into()]), )), None, )], diff --git a/boa/src/syntax/parser/statement/variable.rs b/boa/src/syntax/parser/statement/variable.rs index bd045a22a50..75be78e53ac 100644 --- a/boa/src/syntax/parser/statement/variable.rs +++ b/boa/src/syntax/parser/statement/variable.rs @@ -1,9 +1,12 @@ // use super::lexical_declaration_continuation; use crate::syntax::{ - ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + ast::{ + node::{VarDecl, VarDeclList}, + Keyword, Punctuator, TokenKind, + }, parser::{ expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, - Cursor, ParseError, ParseResult, TokenParser, + Cursor, ParseError, TokenParser, }, }; @@ -38,9 +41,9 @@ impl VariableStatement { } impl TokenParser for VariableStatement { - type Output = Node; + type Output = VarDeclList; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { cursor.expect(Keyword::Var, "variable statement")?; let decl_list = @@ -88,9 +91,9 @@ impl VariableDeclarationList { } impl TokenParser for VariableDeclarationList { - type Output = Node; + type Output = VarDeclList; - fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + fn parse(self, cursor: &mut Cursor<'_>) -> Result { let mut list = Vec::new(); loop { @@ -101,7 +104,7 @@ impl TokenParser for VariableDeclarationList { match cursor.peek_semicolon(false) { (true, _) => break, - (false, Some(tk)) if tk.kind == TokenKind::Punctuator(Punctuator::Comma) => { + (false, Some(tk)) if tk.kind() == &TokenKind::Punctuator(Punctuator::Comma) => { let _ = cursor.next(); } _ => { @@ -117,7 +120,7 @@ impl TokenParser for VariableDeclarationList { } } - Ok(Node::var_decl(list)) + Ok(VarDeclList::from(list)) } } @@ -151,7 +154,7 @@ impl VariableDeclaration { } impl TokenParser for VariableDeclaration { - type Output = (Box, Option); + type Output = VarDecl; fn parse(self, cursor: &mut Cursor<'_>) -> Result { // TODO: BindingPattern @@ -161,6 +164,6 @@ impl TokenParser for VariableDeclaration { let ident = Initializer::new(self.allow_in, self.allow_yield, self.allow_await).try_parse(cursor); - Ok((name, ident)) + Ok(VarDecl::new(name, ident)) } } diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index d34dd0e2d73..fb7ecd19c68 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -2,7 +2,7 @@ use super::Parser; use crate::syntax::{ - ast::node::{Assign, BinOp, Local, Node}, + ast::node::{Assign, BinOp, FunctionDecl, Local, Node, StatementList, VarDecl, VarDeclList}, ast::op::{NumOp, UnaryOp}, lexer::Lexer, }; @@ -21,7 +21,7 @@ where Parser::new(&lexer.tokens) .parse_all() .expect("failed to parse"), - Node::statement_list(expr) + StatementList::from(expr) ); } @@ -70,15 +70,17 @@ fn hoisting() { function hello() { return 10 }", vec![ - Node::function_decl( - "hello", + FunctionDecl::new( + Box::from("hello"), vec![], - Node::statement_list(vec![Node::return_node(Node::const_node(10))]), - ), - Node::var_decl(vec![( - "a".into(), + vec![Node::return_node(Node::const_node(10))], + ) + .into(), + VarDeclList::from(vec![VarDecl::new( + "a", Some(Node::call(Node::from(Local::from("hello")), vec![])), - )]), + )]) + .into(), Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), ], ); @@ -90,7 +92,7 @@ fn hoisting() { var a;", vec![ - Node::var_decl(vec![("a".into(), None)]), + VarDeclList::from(vec![VarDecl::new("a", None)]).into(), Node::from(Assign::new(Local::from("a"), Node::const_node(10))), Node::unary_op(UnaryOp::IncrementPost, Node::from(Local::from("a"))), ],