diff --git a/.vscode/launch.json b/.vscode/launch.json index ac65200653..a86cf58f86 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,18 +10,24 @@ "request": "launch", "name": "Launch", "windows": { - "program": "${workspaceFolder}/target/debug/boa_cli.exe" + "program": "${workspaceFolder}/target/debug/boa.exe" }, - "program": "${workspaceFolder}/target/debug/boa_cli", - "args": ["${workspaceFolder}/tests/js/test.js"], - "sourceLanguages": ["rust"] + "program": "${workspaceFolder}/target/debug/boa", + "args": [ + "${workspaceFolder}/tests/js/test.js" + ], + "sourceLanguages": [ + "rust" + ] }, { "name": "(Windows) Launch", "type": "cppvsdbg", "request": "launch", - "args": ["${workspaceFolder}/tests/js/test.js"], - "program": "${workspaceFolder}/target/debug/boa_cli.exe", + "args": [ + "${workspaceFolder}/tests/js/test.js" + ], + "program": "${workspaceFolder}/target/debug/boa.exe", "cwd": "${workspaceFolder}", "sourceFileMap": { "/rustc/5e1a799842ba6ed4a57e91f7ab9435947482f7d8": "${env:USERPROFILE}/.rustup/toolchains/stable-x86_64-pc-windows-msvc/lib/rustlib/src/rust", @@ -43,4 +49,4 @@ "preLaunchTask": "Cargo Test Build", } ] -} +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 26caaa1618..b7abdb0c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" [[package]] name = "cfg-if" @@ -433,7 +433,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", "version_check", ] @@ -445,7 +445,7 @@ checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" dependencies = [ "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", "syn-mid", "version_check", ] @@ -541,9 +541,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.6" +version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" +checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" dependencies = [ "aho-corasick", "memchr", @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" [[package]] name = "same-file" @@ -628,7 +628,7 @@ checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" dependencies = [ "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", ] [[package]] @@ -650,9 +650,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6da2e8d107dfd7b74df5ef4d205c6aebee0706c647f6bc6a2d5789905c00fb" +checksum = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" dependencies = [ "clap", "lazy_static", @@ -661,15 +661,15 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a489c87c08fbaf12e386665109dd13470dcc9c4583ea3e10dd2b4523e5ebd9ac" +checksum = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", ] [[package]] @@ -685,9 +685,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" dependencies = [ "proc-macro2", "quote 1.0.3", @@ -702,7 +702,7 @@ checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", ] [[package]] @@ -826,7 +826,7 @@ dependencies = [ "log", "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", "wasm-bindgen-shared", ] @@ -848,7 +848,7 @@ checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" dependencies = [ "proc-macro2", "quote 1.0.3", - "syn 1.0.17", + "syn 1.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -887,9 +887,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index c250459bb8..5c1ef90c6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,6 @@ members = [ "boa_wasm", ] -# The release profile, used for `cargo build`. -[profile.dev] -# Enables thin local LTO and some optimizations. -opt-level = 1 - # The release profile, used for `cargo build --release`. [profile.release] # Enables "fat" LTO, for faster release builds diff --git a/boa/Cargo.toml b/boa/Cargo.toml index cab2701742..5460128510 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -18,7 +18,7 @@ gc = "0.3.3" gc_derive = "0.3.2" serde_json = "1.0.51" rand = "0.7.3" -regex = "1.3.6" +regex = "1.3.7" # Optional Dependencies serde = { version = "1.0.106", features = ["derive"], optional = true } diff --git a/boa/src/builtins/console/tests.rs b/boa/src/builtins/console/tests.rs index 613cc2e1b8..23085c4b5d 100644 --- a/boa/src/builtins/console/tests.rs +++ b/boa/src/builtins/console/tests.rs @@ -56,6 +56,7 @@ fn formatter_trailing_format_leader_renders() { } #[test] +#[allow(clippy::approx_constant)] fn formatter_float_format_works() { let val = [ Gc::new(ValueData::String("%f".to_string())), diff --git a/boa/src/builtins/math/tests.rs b/boa/src/builtins/math/tests.rs index 405cc82f59..24464c50a1 100644 --- a/boa/src/builtins/math/tests.rs +++ b/boa/src/builtins/math/tests.rs @@ -1,3 +1,5 @@ +#![allow(clippy::float_cmp)] + use crate::{exec::Executor, forward, forward_val, realm::Realm}; use std::f64; @@ -15,8 +17,8 @@ fn abs() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(2.0)); - assert_eq!(b.to_num(), f64::from(6.655_559_999_999_999_5)); + assert_eq!(a.to_num(), 2.0); + assert_eq!(b.to_num(), 6.655_559_999_999_999_5); } #[test] @@ -37,9 +39,9 @@ fn acos() { let c = forward_val(&mut engine, "c").unwrap(); let d = forward(&mut engine, "d"); - assert_eq!(a.to_num(), f64::from(0.643_501_108_793_284_3)); + assert_eq!(a.to_num(), 0.643_501_108_793_284_3); assert_eq!(b, String::from("NaN")); - assert_eq!(c.to_num(), f64::from(0)); + assert_eq!(c.to_num(), 0_f64); assert_eq!(d, String::from("NaN")); } @@ -59,7 +61,7 @@ fn acosh() { let b = forward(&mut engine, "b"); let c = forward(&mut engine, "c"); - assert_eq!(a.to_num(), f64::from(1.316_957_896_924_816_6)); + assert_eq!(a.to_num(), 1.316_957_896_924_816_6); assert_eq!(b, String::from("NaN")); assert_eq!(c, String::from("NaN")); } @@ -78,7 +80,7 @@ fn asin() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward(&mut engine, "b"); - assert_eq!(a.to_num(), f64::from(0.643_501_108_793_284_4)); + assert_eq!(a.to_num(), 0.643_501_108_793_284_4); assert_eq!(b, String::from("NaN")); } @@ -96,8 +98,8 @@ fn asinh() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(0.881_373_587_019_542_9)); - assert_eq!(b.to_num(), f64::from(0)); + assert_eq!(a.to_num(), 0.881_373_587_019_542_9); + assert_eq!(b.to_num(), 0_f64); } #[test] @@ -116,8 +118,8 @@ fn atan() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(0.785_398_163_397_448_3)); - assert_eq!(b.to_num(), f64::from(0)); + assert_eq!(a.to_num(), f64::consts::FRAC_PI_4); + assert_eq!(b.to_num(), 0_f64); assert_eq!(c.to_num(), f64::from(-0)); } @@ -135,8 +137,8 @@ fn atan2() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(1.405_647_649_380_269_9)); - assert_eq!(b.to_num(), f64::from(0.165_148_677_414_626_83)); + assert_eq!(a.to_num(), 1.405_647_649_380_269_9); + assert_eq!(b.to_num(), 0.165_148_677_414_626_83); } #[test] @@ -155,9 +157,9 @@ fn cbrt() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(4)); - assert_eq!(b.to_num(), f64::from(-1)); - assert_eq!(c.to_num(), f64::from(1)); + assert_eq!(a.to_num(), 4_f64); + assert_eq!(b.to_num(), -1_f64); + assert_eq!(c.to_num(), 1_f64); } #[test] @@ -176,9 +178,9 @@ fn ceil() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(2)); - assert_eq!(b.to_num(), f64::from(4)); - assert_eq!(c.to_num(), f64::from(-7)); + assert_eq!(a.to_num(), 2_f64); + assert_eq!(b.to_num(), 4_f64); + assert_eq!(c.to_num(), -7_f64); } #[test] @@ -195,8 +197,8 @@ fn cos() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(1)); - assert_eq!(b.to_num(), f64::from(0.540_302_305_868_139_8)); + assert_eq!(a.to_num(), 1_f64); + assert_eq!(b.to_num(), 0.540_302_305_868_139_8); } #[test] @@ -215,9 +217,9 @@ fn cosh() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(1)); - assert_eq!(b.to_num(), f64::from(1.543_080_634_815_243_7)); - assert_eq!(c.to_num(), f64::from(1.543_080_634_815_243_7)); + assert_eq!(a.to_num(), 1_f64); + assert_eq!(b.to_num(), 1.543_080_634_815_243_7); + assert_eq!(c.to_num(), 1.543_080_634_815_243_7); } #[test] @@ -236,9 +238,9 @@ fn exp() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(1)); - assert_eq!(b.to_num(), f64::from(0.367_879_441_171_442_33)); - assert_eq!(c.to_num(), f64::from(7.389_056_098_930_65)); + assert_eq!(a.to_num(), 1_f64); + assert_eq!(b.to_num(), 0.367_879_441_171_442_33); + assert_eq!(c.to_num(), 7.389_056_098_930_65); } #[test] @@ -257,9 +259,9 @@ fn floor() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(1)); - assert_eq!(b.to_num(), f64::from(-4)); - assert_eq!(c.to_num(), f64::from(3)); + assert_eq!(a.to_num(), 1_f64); + assert_eq!(b.to_num(), -4_f64); + assert_eq!(c.to_num(), 3_f64); } #[test] @@ -278,8 +280,8 @@ fn log() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward(&mut engine, "c"); - assert_eq!(a.to_num(), f64::from(0)); - assert_eq!(b.to_num(), f64::from(2.302_585_092_994_046)); + assert_eq!(a.to_num(), 0_f64); + assert_eq!(b.to_num(), f64::consts::LN_10); assert_eq!(c, String::from("NaN")); } @@ -299,8 +301,8 @@ fn log10() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward(&mut engine, "c"); - assert_eq!(a.to_num(), f64::from(0.301_029_995_663_981_2)); - assert_eq!(b.to_num(), f64::from(0)); + assert_eq!(a.to_num(), f64::consts::LOG10_2); + assert_eq!(b.to_num(), 0_f64); assert_eq!(c, String::from("NaN")); } @@ -320,8 +322,8 @@ fn log2() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward(&mut engine, "c"); - assert_eq!(a.to_num(), f64::from(1.584_962_500_721_156)); - assert_eq!(b.to_num(), f64::from(0)); + assert_eq!(a.to_num(), 1.584_962_500_721_156); + assert_eq!(b.to_num(), 0_f64); assert_eq!(c, String::from("NaN")); } @@ -341,9 +343,9 @@ fn max() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(20)); - assert_eq!(b.to_num(), f64::from(-10)); - assert_eq!(c.to_num(), f64::from(20)); + assert_eq!(a.to_num(), 20_f64); + assert_eq!(b.to_num(), -10_f64); + assert_eq!(c.to_num(), 20_f64); } #[test] @@ -362,9 +364,9 @@ fn min() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(10)); - assert_eq!(b.to_num(), f64::from(-20)); - assert_eq!(c.to_num(), f64::from(-10)); + assert_eq!(a.to_num(), 10_f64); + assert_eq!(b.to_num(), -20_f64); + assert_eq!(c.to_num(), -10_f64); } #[test] @@ -385,10 +387,10 @@ fn pow() { let c = forward_val(&mut engine, "c").unwrap(); let d = forward_val(&mut engine, "d").unwrap(); - assert_eq!(a.to_num(), f64::from(1024)); - assert_eq!(b.to_num(), f64::from(49)); - assert_eq!(c.to_num(), f64::from(2.0)); - assert_eq!(d.to_num(), f64::from(0.020_408_163_265_306_12)); + assert_eq!(a.to_num(), 1_024_f64); + assert_eq!(b.to_num(), 49_f64); + assert_eq!(c.to_num(), 2.0); + assert_eq!(d.to_num(), 0.020_408_163_265_306_12); } #[test] @@ -405,8 +407,8 @@ fn round() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(21.0)); - assert_eq!(b.to_num(), f64::from(-20.0)); + assert_eq!(a.to_num(), 21.0); + assert_eq!(b.to_num(), -20.0); } #[test] @@ -425,9 +427,9 @@ fn sign() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(1)); - assert_eq!(b.to_num(), f64::from(-1)); - assert_eq!(c.to_num(), f64::from(0)); + assert_eq!(a.to_num(), 1_f64); + assert_eq!(b.to_num(), -1_f64); + assert_eq!(c.to_num(), 0_f64); } #[test] @@ -444,8 +446,8 @@ fn sin() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(0)); - assert_eq!(b.to_num(), f64::from(0.841_470_984_807_896_5)); + assert_eq!(a.to_num(), 0_f64); + assert_eq!(b.to_num(), 0.841_470_984_807_896_5); } #[test] @@ -462,8 +464,8 @@ fn sinh() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(0)); - assert_eq!(b.to_num(), f64::from(1.175_201_193_643_801_4)); + assert_eq!(a.to_num(), 0_f64); + assert_eq!(b.to_num(), 1.175_201_193_643_801_4); } #[test] @@ -482,9 +484,9 @@ fn sqrt() { let b = forward_val(&mut engine, "b").unwrap(); let c = forward_val(&mut engine, "c").unwrap(); - assert_eq!(a.to_num(), f64::from(0)); - assert_eq!(b.to_num(), f64::from(1.414_213_562_373_095_1)); - assert_eq!(c.to_num(), f64::from(3)); + assert_eq!(a.to_num(), 0_f64); + assert_eq!(b.to_num(), f64::consts::SQRT_2); + assert_eq!(c.to_num(), 3_f64); } // TODO: Precision is always off between ci and local. We proably need a better way to compare floats anyways @@ -518,8 +520,8 @@ fn tanh() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(0.761_594_155_955_764_9)); - assert_eq!(b.to_num(), f64::from(0)); + assert_eq!(a.to_num(), 0.761_594_155_955_764_9); + assert_eq!(b.to_num(), 0_f64); } #[test] @@ -536,6 +538,6 @@ fn trunc() { let a = forward_val(&mut engine, "a").unwrap(); let b = forward_val(&mut engine, "b").unwrap(); - assert_eq!(a.to_num(), f64::from(13)); - assert_eq!(b.to_num(), f64::from(0)); + assert_eq!(a.to_num(), 13_f64); + assert_eq!(b.to_num(), 0_f64); } diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index a63b736c29..4dcdb65f96 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -1,6 +1,7 @@ +#![allow(clippy::float_cmp)] + use super::*; use crate::{builtins::value::ValueData, exec::Executor, forward, forward_val, realm::Realm}; -use std::f64; #[test] fn check_number_constructor_is_function() { @@ -34,14 +35,14 @@ fn call_number() { let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap(); let from_exp = forward_val(&mut engine, "from_exp").unwrap(); - assert_eq!(default_zero.to_num(), f64::from(0)); - assert_eq!(int_one.to_num(), f64::from(1)); - assert_eq!(float_two.to_num(), f64::from(2.1)); - assert_eq!(str_three.to_num(), f64::from(3.2)); - assert_eq!(bool_one.to_num(), f64::from(1)); + assert_eq!(default_zero.to_num(), 0_f64); + assert_eq!(int_one.to_num(), 1_f64); + assert_eq!(float_two.to_num(), 2.1); + assert_eq!(str_three.to_num(), 3.2); + assert_eq!(bool_one.to_num(), 1_f64); assert!(invalid_nan.to_num().is_nan()); - assert_eq!(bool_zero.to_num(), f64::from(0)); - assert_eq!(from_exp.to_num(), f64::from(234)); + assert_eq!(bool_zero.to_num(), 0_f64); + assert_eq!(from_exp.to_num(), 234_f64); } #[test] @@ -205,9 +206,9 @@ fn value_of() { let exp_val = forward_val(&mut engine, "exp_val").unwrap(); let neg_val = forward_val(&mut engine, "neg_val").unwrap(); - assert_eq!(default_val.to_num(), f64::from(0)); - assert_eq!(int_val.to_num(), f64::from(123)); - assert_eq!(float_val.to_num(), f64::from(1.234)); - assert_eq!(exp_val.to_num(), f64::from(12000)); - assert_eq!(neg_val.to_num(), f64::from(-12000)); + assert_eq!(default_val.to_num(), 0_f64); + assert_eq!(int_val.to_num(), 123_f64); + assert_eq!(float_val.to_num(), 1.234); + assert_eq!(exp_val.to_num(), 12_000_f64); + assert_eq!(neg_val.to_num(), -12_000_f64); } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index a422e8d52c..850c20fa02 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -312,7 +312,7 @@ impl ObjectInternalMethods for Object { impl Object { /// Return a new ObjectData struct, with `kind` set to Ordinary pub fn default() -> Self { - let mut object = Object { + let mut object = Self { kind: ObjectKind::Ordinary, internal_slots: Box::new(HashMap::new()), properties: Box::new(HashMap::new()), @@ -331,8 +331,8 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-objectcreate // TODO: proto should be a &Value here - pub fn create(proto: Value) -> Object { - let mut obj = Object::default(); + pub fn create(proto: Value) -> Self { + let mut obj = Self::default(); obj.internal_slots .insert(INSTANCE_PROTOTYPE.to_string(), proto); obj.internal_slots @@ -355,7 +355,7 @@ impl Object { /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. fn from_boolean(argument: &Value) -> Self { - let mut obj = Object { + let mut obj = Self { kind: ObjectKind::Boolean, internal_slots: Box::new(HashMap::new()), properties: Box::new(HashMap::new()), @@ -370,7 +370,7 @@ impl Object { /// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument. fn from_number(argument: &Value) -> Self { - let mut obj = Object { + let mut obj = Self { kind: ObjectKind::Number, internal_slots: Box::new(HashMap::new()), properties: Box::new(HashMap::new()), @@ -385,7 +385,7 @@ impl Object { /// Return a new `String` object whose `[[StringData]]` internal slot is set to argument. fn from_string(argument: &Value) -> Self { - let mut obj = Object { + let mut obj = Self { kind: ObjectKind::String, internal_slots: Box::new(HashMap::new()), properties: Box::new(HashMap::new()), diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index ef307e5fc5..9bd2a55e4b 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -66,10 +66,10 @@ impl ValueData { let obj_proto = glob.get_field_slice("Object").get_field_slice(PROTOTYPE); let obj = Object::create(obj_proto); - Gc::new(ValueData::Object(GcCell::new(obj))) + Gc::new(Self::Object(GcCell::new(obj))) } else { let obj = Object::default(); - Gc::new(ValueData::Object(GcCell::new(obj))) + Gc::new(Self::Object(GcCell::new(obj))) } } @@ -82,7 +82,7 @@ impl ValueData { obj.internal_slots .insert(INSTANCE_PROTOTYPE.to_string(), proto); - Gc::new(ValueData::Object(GcCell::new(obj))) + Gc::new(Self::Object(GcCell::new(obj))) } /// This will tell us if we can exten an object or not, not properly implemented yet @@ -101,7 +101,7 @@ impl ValueData { /// Returns true if the value is an object pub fn is_object(&self) -> bool { match *self { - ValueData::Object(_) => true, + Self::Object(_) => true, _ => false, } } @@ -109,7 +109,7 @@ impl ValueData { /// Returns true if the value is a symbol pub fn is_symbol(&self) -> bool { match *self { - ValueData::Symbol(_) => true, + Self::Symbol(_) => true, _ => false, } } @@ -117,8 +117,8 @@ impl ValueData { /// Returns true if the value is a function pub fn is_function(&self) -> bool { match *self { - ValueData::Function(_) => true, - ValueData::Object(ref o) => o.deref().borrow().get_internal_slot("call").is_function(), + Self::Function(_) => true, + Self::Object(ref o) => o.deref().borrow().get_internal_slot("call").is_function(), _ => false, } } @@ -126,7 +126,7 @@ impl ValueData { /// Returns true if the value is undefined pub fn is_undefined(&self) -> bool { match *self { - ValueData::Undefined => true, + Self::Undefined => true, _ => false, } } @@ -134,7 +134,7 @@ impl ValueData { /// Returns true if the value is null pub fn is_null(&self) -> bool { match *self { - ValueData::Null => true, + Self::Null => true, _ => false, } } @@ -142,7 +142,7 @@ impl ValueData { /// Returns true if the value is null or undefined pub fn is_null_or_undefined(&self) -> bool { match *self { - ValueData::Null | ValueData::Undefined => true, + Self::Null | Self::Undefined => true, _ => false, } } @@ -150,7 +150,7 @@ impl ValueData { /// Returns true if the value is a 64-bit floating-point number pub fn is_double(&self) -> bool { match *self { - ValueData::Number(_) => true, + Self::Number(_) => true, _ => false, } } @@ -163,7 +163,7 @@ impl ValueData { /// Returns true if the value is a string pub fn is_string(&self) -> bool { match *self { - ValueData::String(_) => true, + Self::String(_) => true, _ => false, } } @@ -171,7 +171,7 @@ impl ValueData { /// Returns true if the value is a boolean pub fn is_boolean(&self) -> bool { match *self { - ValueData::Boolean(_) => true, + Self::Boolean(_) => true, _ => false, } } @@ -181,11 +181,11 @@ impl ValueData { /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) pub fn is_true(&self) -> bool { match *self { - ValueData::Object(_) => true, - ValueData::String(ref s) if !s.is_empty() => true, - ValueData::Number(n) if n != 0.0 && !n.is_nan() => true, - ValueData::Integer(n) if n != 0 => true, - ValueData::Boolean(v) => v, + Self::Object(_) => true, + Self::String(ref s) if !s.is_empty() => true, + Self::Number(n) if n != 0.0 && !n.is_nan() => true, + Self::Integer(n) if n != 0 => true, + Self::Boolean(v) => v, _ => false, } } @@ -193,37 +193,34 @@ impl ValueData { /// Converts the value into a 64-bit floating point number pub fn to_num(&self) -> f64 { match *self { - ValueData::Object(_) - | ValueData::Symbol(_) - | ValueData::Undefined - | ValueData::Function(_) => NAN, - ValueData::String(ref str) => match FromStr::from_str(str) { + Self::Object(_) | Self::Symbol(_) | Self::Undefined | Self::Function(_) => NAN, + Self::String(ref str) => match FromStr::from_str(str) { Ok(num) => num, Err(_) => NAN, }, - ValueData::Number(num) => num, - ValueData::Boolean(true) => 1.0, - ValueData::Boolean(false) | ValueData::Null => 0.0, - ValueData::Integer(num) => f64::from(num), + Self::Number(num) => num, + Self::Boolean(true) => 1.0, + Self::Boolean(false) | Self::Null => 0.0, + Self::Integer(num) => f64::from(num), } } /// Converts the value into a 32-bit integer pub fn to_int(&self) -> i32 { match *self { - ValueData::Object(_) - | ValueData::Undefined - | ValueData::Symbol(_) - | ValueData::Null - | ValueData::Boolean(false) - | ValueData::Function(_) => 0, - ValueData::String(ref str) => match FromStr::from_str(str) { + Self::Object(_) + | Self::Undefined + | Self::Symbol(_) + | Self::Null + | Self::Boolean(false) + | Self::Function(_) => 0, + Self::String(ref str) => match FromStr::from_str(str) { Ok(num) => num, Err(_) => 0, }, - ValueData::Number(num) => num as i32, - ValueData::Boolean(true) => 1, - ValueData::Integer(num) => num, + Self::Number(num) => num as i32, + Self::Boolean(true) => 1, + Self::Integer(num) => num, } } @@ -232,9 +229,9 @@ impl ValueData { /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned pub fn remove_prop(&self, field: &str) { match *self { - ValueData::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), + Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * - ValueData::Function(ref func) => match func.borrow_mut().deref_mut() { + Self::Function(ref func) => match func.borrow_mut().deref_mut() { Function::NativeFunc(ref mut func) => func.object.properties.remove(field), Function::RegularFunc(ref mut func) => func.object.properties.remove(field), }, @@ -249,24 +246,24 @@ impl ValueData { // Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 // This is only for primitive strings, String() objects have their lengths calculated in string.rs if self.is_string() && field == "length" { - if let ValueData::String(ref s) = *self { + if let Self::String(ref s) = *self { return Some(Property::default().value(to_value(s.len() as i32))); } } let obj: Object = match *self { - ValueData::Object(ref obj) => { + Self::Object(ref obj) => { let hash = obj.clone(); // TODO: This will break, we should return a GcCellRefMut instead // into_inner will consume the wrapped value and remove it from the hashmap hash.into_inner() } // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * - ValueData::Function(ref func) => match func.clone().into_inner() { + Self::Function(ref func) => match func.clone().into_inner() { Function::NativeFunc(ref func) => func.object.clone(), Function::RegularFunc(ref func) => func.object.clone(), }, - ValueData::Symbol(ref obj) => { + Self::Symbol(ref obj) => { let hash = obj.clone(); hash.into_inner() } @@ -294,9 +291,9 @@ impl ValueData { configurable: Option, ) { let obj: Option = match self { - ValueData::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), + Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * - ValueData::Function(ref func) => match func.borrow_mut().deref_mut() { + Self::Function(ref func) => match func.borrow_mut().deref_mut() { Function::NativeFunc(ref mut func) => Some(func.object.clone()), Function::RegularFunc(ref mut func) => Some(func.object.clone()), }, @@ -319,20 +316,20 @@ impl ValueData { /// Returns a copy of the Property. pub fn get_internal_slot(&self, field: &str) -> Value { let obj: Object = match *self { - ValueData::Object(ref obj) => { + Self::Object(ref obj) => { let hash = obj.clone(); hash.into_inner() } - ValueData::Symbol(ref obj) => { + Self::Symbol(ref obj) => { let hash = obj.clone(); hash.into_inner() } - _ => return Gc::new(ValueData::Undefined), + _ => return Gc::new(Self::Undefined), }; match obj.internal_slots.get(field) { Some(val) => val.clone(), - None => Gc::new(ValueData::Undefined), + None => Gc::new(Self::Undefined), } } @@ -342,7 +339,7 @@ impl ValueData { pub fn get_field(&self, field: Value) -> Value { match *field { // Our field will either be a String or a Symbol - ValueData::String(ref s) => { + Self::String(ref s) => { match self.get_prop(s) { Some(prop) => { // If the Property has [[Get]] set to a function, we should run that and return the Value @@ -362,17 +359,17 @@ impl ValueData { val.clone() } } - None => Gc::new(ValueData::Undefined), + None => Gc::new(Self::Undefined), } } - ValueData::Symbol(_) => unimplemented!(), - _ => Gc::new(ValueData::Undefined), + Self::Symbol(_) => unimplemented!(), + _ => Gc::new(Self::Undefined), } } /// Check whether an object has an internal state set. pub fn has_internal_state(&self) -> bool { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { obj.borrow().state.is_some() } else { false @@ -381,7 +378,7 @@ impl ValueData { /// Get the internal state of an object. pub fn get_internal_state(&self) -> Option { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { obj.borrow() .state .as_ref() @@ -401,7 +398,7 @@ impl ValueData { &self, f: F, ) -> R { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { let o = obj.borrow(); let state = o .state @@ -425,7 +422,7 @@ impl ValueData { &self, f: F, ) -> R { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { let mut o = obj.borrow_mut(); let state = o .state @@ -448,7 +445,7 @@ impl ValueData { pub fn get_field_slice(&self, field: &str) -> Value { // get_field used to accept strings, but now Symbols accept it needs to accept a value // So this function will now need to Box strings back into values (at least for now) - let f = Gc::new(ValueData::String(field.to_string())); + let f = Gc::new(Self::String(field.to_string())); self.get_field(f) } @@ -456,7 +453,7 @@ impl ValueData { /// Field could be a Symbol, so we need to accept a Value (not a string) pub fn set_field(&self, field: Value, val: Value) -> Value { match *self { - ValueData::Object(ref obj) => { + Self::Object(ref obj) => { if obj.borrow().kind == ObjectKind::Array { if let Ok(num) = field.to_string().parse::() { if num > 0 { @@ -477,7 +474,7 @@ impl ValueData { .set(to_value(field.to_string()), val.clone()); } } - ValueData::Function(ref func) => { + Self::Function(ref func) => { match *func.borrow_mut().deref_mut() { Function::NativeFunc(ref mut f) => f .object @@ -498,13 +495,13 @@ impl ValueData { pub fn set_field_slice<'a>(&self, field: &'a str, val: Value) -> Value { // set_field used to accept strings, but now Symbols accept it needs to accept a value // So this function will now need to Box strings back into values (at least for now) - let f = Gc::new(ValueData::String(field.to_string())); + let f = Gc::new(Self::String(field.to_string())); self.set_field(f, val) } /// Set the private field in the value pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { obj.borrow_mut() .internal_slots .insert(field.to_string(), val.clone()); @@ -514,7 +511,7 @@ impl ValueData { /// Set the kind of an object pub fn set_kind(&self, kind: ObjectKind) -> ObjectKind { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { obj.borrow_mut().kind = kind.clone(); } kind @@ -523,10 +520,10 @@ impl ValueData { /// Set the property in the value pub fn set_prop(&self, field: String, prop: Property) -> Property { match *self { - ValueData::Object(ref obj) => { + Self::Object(ref obj) => { obj.borrow_mut().properties.insert(field, prop.clone()); } - ValueData::Function(ref func) => { + Self::Function(ref func) => { match *func.borrow_mut().deref_mut() { Function::NativeFunc(ref mut f) => { f.object.properties.insert(field, prop.clone()) @@ -548,7 +545,7 @@ impl ValueData { /// Set internal state of an Object. Discards the previous state if it was set. pub fn set_internal_state(&self, state: T) { - if let ValueData::Object(ref obj) = *self { + if let Self::Object(ref obj) = *self { obj.borrow_mut() .state .replace(Box::new(InternalStateCell::new(state))); @@ -559,10 +556,10 @@ impl ValueData { pub fn from_json(json: JSONValue) -> Self { match json { JSONValue::Number(v) => { - ValueData::Number(v.as_f64().expect("Could not convert value to f64")) + Self::Number(v.as_f64().expect("Could not convert value to f64")) } - JSONValue::String(v) => ValueData::String(v), - JSONValue::Bool(v) => ValueData::Boolean(v), + JSONValue::String(v) => Self::String(v), + JSONValue::Bool(v) => Self::Boolean(v), JSONValue::Array(vs) => { let mut new_obj = Object::default(); for (idx, json) in vs.iter().enumerate() { @@ -575,7 +572,7 @@ impl ValueData { "length".to_string(), Property::default().value(to_value(vs.len() as i32)), ); - ValueData::Object(GcCell::new(new_obj)) + Self::Object(GcCell::new(new_obj)) } JSONValue::Object(obj) => { let mut new_obj = Object::default(); @@ -586,9 +583,9 @@ impl ValueData { ); } - ValueData::Object(GcCell::new(new_obj)) + Self::Object(GcCell::new(new_obj)) } - JSONValue::Null => ValueData::Null, + JSONValue::Null => Self::Null, } } @@ -609,11 +606,11 @@ impl ValueData { .collect::>(); JSONValue::Object(new_obj) } - ValueData::String(ref str) => JSONValue::String(str.clone()), - ValueData::Number(num) => JSONValue::Number( + Self::String(ref str) => JSONValue::String(str.clone()), + Self::Number(num) => JSONValue::Number( JSONNumber::from_f64(num).expect("Could not convert to JSONNumber"), ), - ValueData::Integer(val) => JSONValue::Number(JSONNumber::from(val)), + Self::Integer(val) => JSONValue::Number(JSONNumber::from(val)), } } @@ -622,14 +619,14 @@ impl ValueData { /// https://tc39.es/ecma262/#sec-typeof-operator pub fn get_type(&self) -> &'static str { match *self { - ValueData::Number(_) | ValueData::Integer(_) => "number", - ValueData::String(_) => "string", - ValueData::Boolean(_) => "boolean", - ValueData::Symbol(_) => "symbol", - ValueData::Null => "null", - ValueData::Undefined => "undefined", - ValueData::Function(_) => "function", - ValueData::Object(ref o) => { + Self::Number(_) | Self::Integer(_) => "number", + Self::String(_) => "string", + Self::Boolean(_) => "boolean", + Self::Symbol(_) => "symbol", + Self::Null => "null", + Self::Undefined => "undefined", + Self::Function(_) => "function", + Self::Object(ref o) => { if o.deref().borrow().get_internal_slot("call").is_null() { "object" } else { @@ -639,14 +636,14 @@ impl ValueData { } } - pub fn as_num_to_power(&self, other: ValueData) -> ValueData { - ValueData::Number(self.to_num().powf(other.to_num())) + pub fn as_num_to_power(&self, other: Self) -> Self { + Self::Number(self.to_num().powf(other.to_num())) } } impl Default for ValueData { fn default() -> Self { - ValueData::Undefined + Self::Undefined } } @@ -841,16 +838,16 @@ pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String { impl Display for ValueData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ValueData::Null => write!(f, "null"), - ValueData::Undefined => write!(f, "undefined"), - ValueData::Boolean(v) => write!(f, "{}", v), - ValueData::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { + Self::Null => write!(f, "null"), + Self::Undefined => write!(f, "undefined"), + Self::Boolean(v) => write!(f, "{}", v), + Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { // If a description exists use it - ValueData::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), + Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), _ => write!(f, "Symbol()"), }, - ValueData::String(ref v) => write!(f, "{}", v), - ValueData::Number(v) => write!( + Self::String(ref v) => write!(f, "{}", v), + Self::Number(v) => write!( f, "{}", match v { @@ -860,9 +857,9 @@ impl Display for ValueData { _ => v.to_string(), } ), - ValueData::Object(_) => write!(f, "{}", log_string_from(self, true)), - ValueData::Integer(v) => write!(f, "{}", v), - ValueData::Function(ref v) => match *v.borrow() { + Self::Object(_) => write!(f, "{}", log_string_from(self, true)), + Self::Integer(v) => write!(f, "{}", v), + Self::Function(ref v) => match *v.borrow() { Function::NativeFunc(_) => write!(f, "function() {{ [native code] }}"), Function::RegularFunc(ref rf) => { write!(f, "function{}(", if rf.args.is_empty() { "" } else { " " })?; @@ -885,18 +882,12 @@ impl PartialEq for ValueData { // TODO: fix this // _ if self.ptr.to_inner() == &other.ptr.to_inner() => true, _ if self.is_null_or_undefined() && other.is_null_or_undefined() => true, - (ValueData::String(_), _) | (_, ValueData::String(_)) => { - self.to_string() == other.to_string() - } - (ValueData::Boolean(a), ValueData::Boolean(b)) if a == b => true, - (ValueData::Number(a), ValueData::Number(b)) - if a == b && !a.is_nan() && !b.is_nan() => - { - true - } - (ValueData::Number(a), _) if a == other.to_num() => true, - (_, ValueData::Number(a)) if a == self.to_num() => true, - (ValueData::Integer(a), ValueData::Integer(b)) if a == b => true, + (Self::String(_), _) | (_, Self::String(_)) => self.to_string() == other.to_string(), + (Self::Boolean(a), Self::Boolean(b)) if a == b => true, + (Self::Number(a), Self::Number(b)) if a == b && !a.is_nan() && !b.is_nan() => true, + (Self::Number(a), _) if a == other.to_num() => true, + (_, Self::Number(a)) if a == self.to_num() => true, + (Self::Integer(a), Self::Integer(b)) if a == b => true, _ => false, } } @@ -906,74 +897,72 @@ impl Add for ValueData { type Output = Self; fn add(self, other: Self) -> Self { match (self, other) { - (ValueData::String(ref s), ref o) => { - ValueData::String(format!("{}{}", s.clone(), &o.to_string())) - } - (ref s, ValueData::String(ref o)) => { - ValueData::String(format!("{}{}", s.to_string(), o)) + (Self::String(ref s), ref o) => { + Self::String(format!("{}{}", s.clone(), &o.to_string())) } - (ref s, ref o) => ValueData::Number(s.to_num() + o.to_num()), + (ref s, Self::String(ref o)) => Self::String(format!("{}{}", s.to_string(), o)), + (ref s, ref o) => Self::Number(s.to_num() + o.to_num()), } } } impl Sub for ValueData { type Output = Self; fn sub(self, other: Self) -> Self { - ValueData::Number(self.to_num() - other.to_num()) + Self::Number(self.to_num() - other.to_num()) } } impl Mul for ValueData { type Output = Self; fn mul(self, other: Self) -> Self { - ValueData::Number(self.to_num() * other.to_num()) + Self::Number(self.to_num() * other.to_num()) } } impl Div for ValueData { type Output = Self; fn div(self, other: Self) -> Self { - ValueData::Number(self.to_num() / other.to_num()) + Self::Number(self.to_num() / other.to_num()) } } impl Rem for ValueData { type Output = Self; fn rem(self, other: Self) -> Self { - ValueData::Number(self.to_num() % other.to_num()) + Self::Number(self.to_num() % other.to_num()) } } impl BitAnd for ValueData { type Output = Self; fn bitand(self, other: Self) -> Self { - ValueData::Integer(self.to_int() & other.to_int()) + Self::Integer(self.to_int() & other.to_int()) } } impl BitOr for ValueData { type Output = Self; fn bitor(self, other: Self) -> Self { - ValueData::Integer(self.to_int() | other.to_int()) + Self::Integer(self.to_int() | other.to_int()) } } impl BitXor for ValueData { type Output = Self; fn bitxor(self, other: Self) -> Self { - ValueData::Integer(self.to_int() ^ other.to_int()) + Self::Integer(self.to_int() ^ other.to_int()) } } impl Shl for ValueData { type Output = Self; fn shl(self, other: Self) -> Self { - ValueData::Integer(self.to_int() << other.to_int()) + Self::Integer(self.to_int() << other.to_int()) } } impl Shr for ValueData { type Output = Self; fn shr(self, other: Self) -> Self { - ValueData::Integer(self.to_int() >> other.to_int()) + Self::Integer(self.to_int() >> other.to_int()) } } impl Not for ValueData { type Output = Self; fn not(self) -> Self { - ValueData::Boolean(!self.is_true()) + Self::Boolean(!self.is_true()) } } @@ -1065,7 +1054,7 @@ impl ToValue for usize { } impl FromValue for usize { fn from_value(v: Value) -> Result { - Ok(v.to_int() as usize) + Ok(v.to_int() as Self) } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 9ee6d8b317..9abc9a9181 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -63,7 +63,7 @@ fn exec_assign_op(op: &AssignOp, v_a: ValueData, v_b: ValueData) -> Value { impl Executor for Interpreter { fn new(realm: Realm) -> Self { - Interpreter { + Self { realm, is_return: false, } @@ -311,7 +311,7 @@ impl Executor for Interpreter { !(num_v_a as i32) }) } - _ => unreachable!(), + _ => unimplemented!(), }) } Node::BinOp(BinOp::Bit(ref op), ref a, ref b) => { diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 96cd08179f..0932e79500 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -172,6 +172,7 @@ fn test_early_return() { early_return() "#; assert_eq!(exec(early_return), String::from("true")); + let early_return = r#" function nested_fnct() { return "nested"; @@ -292,3 +293,61 @@ fn test_do_while_loop() { "#; assert_eq!(exec(body_is_executed_at_least_once), String::from("1")); } + +#[test] +#[ignore] +fn test_do_while_post_inc() { + let with_post_incrementors = r#" + var i = 0; + do {} while(i++ < 10) i; + "#; + assert_eq!(exec(with_post_incrementors), String::from("11")); +} + +#[test] +#[ignore] +fn test_unary_pre() { + let unary_inc = r#" + let a = 5; + ++a; + a; + "#; + assert_eq!(exec(unary_inc), String::from("6")); + + let unary_dec = r#" + let a = 5; + --a; + a; + "#; + assert_eq!(exec(unary_dec), String::from("6")); + + let execs_before = r#" + let a = 5; + ++a === 6; + "#; + assert_eq!(exec(execs_before), String::from("true")); +} + +#[test] +#[ignore] +fn test_unary_post() { + let unary_inc = r#" + let a = 5; + a++; + a; + "#; + assert_eq!(exec(unary_inc), String::from("6")); + + let unary_dec = r#" + let a = 5; + a--; + a; + "#; + assert_eq!(exec(unary_dec), String::from("6")); + + let execs_after = r#" + let a = 5; + a++ === 5; + "#; + assert_eq!(exec(execs_after), String::from("true")); +} diff --git a/boa/src/realm.rs b/boa/src/realm.rs index e3931ac621..edc6a6bb2d 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -30,14 +30,14 @@ pub struct Realm { } impl Realm { - pub fn create() -> Realm { + pub fn create() -> Self { // Create brand new global object // Global has no prototype to pass None to new_obj let global = ValueData::new_obj(None); // We need to clone the global here because its referenced from separate places (only pointer is cloned) let global_env = new_global_environment(global.clone(), global.clone()); - let new_realm = Realm { + let new_realm = Self { global_obj: global.clone(), global_env, environment: LexicalEnvironment::new(global), diff --git a/boa/src/syntax/ast/constant.rs b/boa/src/syntax/ast/constant.rs index f253c63ab5..9bb09e76e2 100644 --- a/boa/src/syntax/ast/constant.rs +++ b/boa/src/syntax/ast/constant.rs @@ -101,15 +101,51 @@ pub enum Const { Undefined, } +impl From<&str> for Const { + fn from(s: &str) -> Self { + Const::String(s.into()) + } +} + +impl From<&String> for Const { + fn from(s: &String) -> Self { + Const::String(s.clone()) + } +} + +impl From for Const { + fn from(s: String) -> Self { + Const::String(s) + } +} + +impl From for Const { + fn from(num: f64) -> Self { + Self::Num(num) + } +} + +impl From for Const { + fn from(i: i32) -> Self { + Self::Int(i) + } +} + +impl From for Const { + fn from(b: bool) -> Self { + Self::Bool(b) + } +} + impl Display for Const { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match *self { - Const::String(ref st) => write!(f, "\"{}\"", st), - Const::Num(num) => write!(f, "{}", num), - Const::Int(num) => write!(f, "{}", num), - Const::Bool(v) => write!(f, "{}", v), - Const::Null => write!(f, "null"), - Const::Undefined => write!(f, "undefined"), + Self::String(ref st) => write!(f, "\"{}\"", st), + Self::Num(num) => write!(f, "{}", num), + Self::Int(num) => write!(f, "{}", num), + Self::Bool(v) => write!(f, "{}", v), + Self::Null => write!(f, "null"), + Self::Undefined => write!(f, "undefined"), } } } diff --git a/boa/src/syntax/ast/keyword.rs b/boa/src/syntax/ast/keyword.rs index 20181a5ad8..6fb19a5ab6 100644 --- a/boa/src/syntax/ast/keyword.rs +++ b/boa/src/syntax/ast/keyword.rs @@ -456,42 +456,42 @@ impl FromStr for Keyword { type Err = KeywordError; fn from_str(s: &str) -> Result { match s { - "await" => Ok(Keyword::Await), - "break" => Ok(Keyword::Break), - "case" => Ok(Keyword::Case), - "catch" => Ok(Keyword::Catch), - "class" => Ok(Keyword::Class), - "continue" => Ok(Keyword::Continue), - "const" => Ok(Keyword::Const), - "debugger" => Ok(Keyword::Debugger), - "default" => Ok(Keyword::Default), - "delete" => Ok(Keyword::Delete), - "do" => Ok(Keyword::Do), - "else" => Ok(Keyword::Else), - "enum" => Ok(Keyword::Enum), - "extends" => Ok(Keyword::Extends), - "export" => Ok(Keyword::Export), - "finally" => Ok(Keyword::Finally), - "for" => Ok(Keyword::For), - "function" => Ok(Keyword::Function), - "if" => Ok(Keyword::If), - "in" => Ok(Keyword::In), - "instanceof" => Ok(Keyword::InstanceOf), - "import" => Ok(Keyword::Import), - "let" => Ok(Keyword::Let), - "new" => Ok(Keyword::New), - "return" => Ok(Keyword::Return), - "super" => Ok(Keyword::Super), - "switch" => Ok(Keyword::Switch), - "this" => Ok(Keyword::This), - "throw" => Ok(Keyword::Throw), - "try" => Ok(Keyword::Try), - "typeof" => Ok(Keyword::TypeOf), - "var" => Ok(Keyword::Var), - "void" => Ok(Keyword::Void), - "while" => Ok(Keyword::While), - "with" => Ok(Keyword::With), - "yield" => Ok(Keyword::Yield), + "await" => Ok(Self::Await), + "break" => Ok(Self::Break), + "case" => Ok(Self::Case), + "catch" => Ok(Self::Catch), + "class" => Ok(Self::Class), + "continue" => Ok(Self::Continue), + "const" => Ok(Self::Const), + "debugger" => Ok(Self::Debugger), + "default" => Ok(Self::Default), + "delete" => Ok(Self::Delete), + "do" => Ok(Self::Do), + "else" => Ok(Self::Else), + "enum" => Ok(Self::Enum), + "extends" => Ok(Self::Extends), + "export" => Ok(Self::Export), + "finally" => Ok(Self::Finally), + "for" => Ok(Self::For), + "function" => Ok(Self::Function), + "if" => Ok(Self::If), + "in" => Ok(Self::In), + "instanceof" => Ok(Self::InstanceOf), + "import" => Ok(Self::Import), + "let" => Ok(Self::Let), + "new" => Ok(Self::New), + "return" => Ok(Self::Return), + "super" => Ok(Self::Super), + "switch" => Ok(Self::Switch), + "this" => Ok(Self::This), + "throw" => Ok(Self::Throw), + "try" => Ok(Self::Try), + "typeof" => Ok(Self::TypeOf), + "var" => Ok(Self::Var), + "void" => Ok(Self::Void), + "while" => Ok(Self::While), + "with" => Ok(Self::With), + "yield" => Ok(Self::Yield), _ => Err(KeywordError), } } @@ -502,42 +502,42 @@ impl Display for Keyword { f, "{}", match *self { - Keyword::Await => "await", - Keyword::Break => "break", - Keyword::Case => "case", - Keyword::Catch => "catch", - Keyword::Class => "class", - Keyword::Continue => "continue", - Keyword::Const => "const", - Keyword::Debugger => "debugger", - Keyword::Default => "default", - Keyword::Delete => "delete", - Keyword::Do => "do", - Keyword::Else => "else", - Keyword::Enum => "enum", - Keyword::Extends => "extends", - Keyword::Export => "export", - Keyword::Finally => "finally", - Keyword::For => "for", - Keyword::Function => "function", - Keyword::If => "if", - Keyword::In => "in", - Keyword::InstanceOf => "instanceof", - Keyword::Import => "import", - Keyword::Let => "let", - Keyword::New => "new", - Keyword::Return => "return", - Keyword::Super => "super", - Keyword::Switch => "switch", - Keyword::This => "this", - Keyword::Throw => "throw", - Keyword::Try => "try", - Keyword::TypeOf => "typeof", - Keyword::Var => "var", - Keyword::Void => "void", - Keyword::While => "while", - Keyword::With => "with", - Keyword::Yield => "yield", + 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", } ) } diff --git a/boa/src/syntax/ast/node.rs b/boa/src/syntax/ast/node.rs index 0d39358163..efb3b9f8c3 100644 --- a/boa/src/syntax/ast/node.rs +++ b/boa/src/syntax/ast/node.rs @@ -496,27 +496,27 @@ pub enum Node { impl Operator for Node { fn get_assoc(&self) -> bool { match *self { - Node::UnaryOp(_, _) | Node::TypeOf(_) | Node::If(_, _, _) | Node::Assign(_, _) => false, + Self::UnaryOp(_, _) | Self::TypeOf(_) | Self::If(_, _, _) | Self::Assign(_, _) => false, _ => true, } } fn get_precedence(&self) -> u64 { match self { - Node::GetField(_, _) | Node::GetConstField(_, _) => 1, - Node::Call(_, _) => 2, - Node::UnaryOp(UnaryOp::IncrementPost, _) - | Node::UnaryOp(UnaryOp::IncrementPre, _) - | Node::UnaryOp(UnaryOp::DecrementPost, _) - | Node::UnaryOp(UnaryOp::DecrementPre, _) => 3, - Node::UnaryOp(UnaryOp::Not, _) - | Node::UnaryOp(UnaryOp::Tilde, _) - | Node::UnaryOp(UnaryOp::Minus, _) - | Node::TypeOf(_) => 4, - Node::BinOp(op, _, _) => op.get_precedence(), - Node::If(_, _, _) => 15, + Self::GetField(_, _) | Self::GetConstField(_, _) => 1, + Self::Call(_, _) => 2, + Self::UnaryOp(UnaryOp::IncrementPost, _) + | Self::UnaryOp(UnaryOp::IncrementPre, _) + | Self::UnaryOp(UnaryOp::DecrementPost, _) + | Self::UnaryOp(UnaryOp::DecrementPre, _) => 3, + Self::UnaryOp(UnaryOp::Not, _) + | Self::UnaryOp(UnaryOp::Tilde, _) + | Self::UnaryOp(UnaryOp::Minus, _) + | Self::TypeOf(_) => 4, + Self::BinOp(op, _, _) => op.get_precedence(), + Self::If(_, _, _) => 15, // 16 should be yield - Node::Assign(_, _) => 17, + Self::Assign(_, _) => 17, _ => 19, } } @@ -529,6 +529,307 @@ impl fmt::Display for Node { } 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()) + } + + /// Creates a `Break` AST node. + pub fn break_node(label: OL) -> Self + where + L: Into, + OL: Into>, + { + Self::Break(label.into().map(L::into)) + } + + /// Creates a `Call` AST node. + pub fn call(function: F, params: P) -> Self + where + F: Into>, + P: Into>, + { + Self::Call(function.into(), params.into()) + } + + /// Creates a `ConditionalOp` AST node. + pub fn conditional_op(condition: C, if_true: T, if_false: F) -> Self + where + C: Into>, + T: Into>, + F: Into>, + { + Self::ConditionalOp(condition.into(), if_true.into(), if_false.into()) + } + + /// Creates a `Const` AST node. + pub fn const_node(node: C) -> Self + where + C: Into, + { + Self::Const(node.into()) + } + + /// Creates a `ConstDecl` AST node. + pub fn const_decl(decl: D) -> Self + where + D: Into>, + { + Self::ConstDecl(decl.into()) + } + + /// Creates a `Continue` AST node. + pub fn continue_node(label: OL) -> Self + where + L: Into, + OL: Into>, + { + Self::Continue(label.into().map(L::into)) + } + + /// Creates a `DoWhileLoop` AST node. + pub fn do_while_loop(body: B, condition: C) -> Self + where + B: Into>, + C: Into>, + { + Self::DoWhileLoop(body.into(), condition.into()) + } + + /// Creates a `FunctionDecl` AST node. + pub fn function_decl(name: ON, params: P, body: B) -> Self + where + N: Into, + ON: Into>, + P: Into>, + B: Into>, + { + Self::FunctionDecl(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 + V: Into>, + L: Into, + { + Self::GetConstField(value.into(), label.into()) + } + + /// Creates a `GetField` AST node. + pub fn get_field(value: V, field: F) -> Self + where + V: Into>, + F: Into>, + { + Self::GetField(value.into(), field.into()) + } + + /// Creates a `ForLoop` AST node. + pub fn for_loop(init: OI, condition: OC, step: OS, body: B) -> Self + where + OI: Into>, + OC: Into>, + OS: Into>, + I: Into>, + C: Into>, + S: Into>, + B: Into>, + { + Self::ForLoop( + init.into().map(I::into), + condition.into().map(C::into), + step.into().map(S::into), + body.into(), + ) + } + + /// Creates an `If` AST node. + pub fn if_node(condition: C, body: B, else_node: OE) -> Self + where + C: Into>, + B: Into>, + E: Into>, + OE: Into>, + { + Self::If(condition.into(), body.into(), else_node.into().map(E::into)) + } + + /// Creates a `LetDecl` AST node. + pub fn let_decl(init: I) -> Self + where + I: Into)>>, + { + 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 + N: Into>, + { + Self::New(node.into()) + } + + /// Creates an `Object` AST node. + pub fn object(def: D) -> Self + where + D: Into>, + { + Self::Object(def.into()) + } + + /// Creates a `Return` AST node. + pub fn return_node(expr: OE) -> Self + where + E: Into>, + OE: Into>, + { + Self::Return(expr.into().map(E::into)) + } + + /// Creates a `Switch` AST node. + pub fn switch(val: V, cases: C, default: OD) -> Self + where + V: Into>, + C: Into)>>, + OD: Into>, + D: Into>, + { + Self::Switch(val.into(), cases.into(), default.into().map(D::into)) + } + + /// Creates a `Spread` AST node. + pub fn spread(val: V) -> Self + where + V: Into>, + { + 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 + V: Into>, + { + Self::Throw(val.into()) + } + + /// Creates a `TypeOf` AST node. + pub fn type_of(expr: E) -> Self + where + E: Into>, + { + Self::TypeOf(expr.into()) + } + + /// Creates a `Try` AST node. + pub fn try_node(try_node: T, catch: OC, param: OP, finally: OF) -> Self + where + T: Into>, + OC: Into>, + OP: Into>, + OF: Into>, + C: Into>, + P: Into>, + F: Into>, + { + let catch = catch.into().map(C::into); + let finally = finally.into().map(F::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) + } + + /// Creates a `This` AST node. + pub fn this() -> Self { + Self::This + } + + /// Creates a `UnaryOp` AST node. + pub fn unary_op(op: UnaryOp, val: V) -> Self + where + V: Into>, + { + Self::UnaryOp(op, val.into()) + } + + /// Creates a `VarDecl` AST node. + pub fn var_decl(init: I) -> Self + where + I: Into)>>, + { + Self::VarDecl(init.into()) + } + + /// Creates a `WhileLoop` AST node. + pub fn while_loop(condition: C, body: B) -> Self + where + C: Into>, + B: Into>, + { + Self::WhileLoop(condition.into(), body.into()) + } + /// Implements the display formatting with indentation. fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { let indent = " ".repeat(indentation); @@ -539,12 +840,30 @@ impl Node { match *self { Self::Const(ref c) => write!(f, "{}", c), - Self::ConditionalOp(_, _, _) => write!(f, "Conditional op"), // TODO - Self::ForLoop(_, _, _, _) => write!(f, "for loop"), // TODO - Self::This => write!(f, "this"), // TODO - Self::Try(_, _, _, _) => write!(f, "try/catch/finally"), // TODO - Self::Break(_) => write!(f, "break"), // TODO: add potential value - Self::Continue(_) => write!(f, "continue"), // TODO: add potential value + Self::ConditionalOp(ref cond, ref if_true, ref if_false) => { + write!(f, "{} ? {} : {}", cond, if_true, if_false) + } + Self::ForLoop(_, _, _, _) => write!(f, "for loop"), // TODO + Self::This => write!(f, "this"), + Self::Try(_, _, _, _) => write!(f, "try/catch/finally"), // TODO + Self::Break(ref l) => write!( + f, + "break{}", + if let Some(label) = l { + format!(" {}", label) + } else { + String::new() + } + ), + Self::Continue(ref l) => write!( + f, + "continue{}", + if let Some(label) = l { + format!(" {}", label) + } else { + String::new() + } + ), Self::Spread(ref node) => write!(f, "...{}", node), Self::Block(ref block) => { writeln!(f, "{{")?; @@ -564,7 +883,7 @@ impl Node { } write!(f, "{}}}", indent) } - Node::StatementList(ref list) => { + Self::StatementList(ref list) => { for node in list.iter() { node.display(f, indentation + 1)?; @@ -591,7 +910,7 @@ impl Node { } Self::New(ref call) => { let (func, args) = match call.as_ref() { - Node::Call(func, args) => (func, args), + Self::Call(func, args) => (func, args), _ => unreachable!("Node::New(ref call): 'call' must only be Node::Call type."), }; @@ -754,19 +1073,13 @@ pub struct FormalParameter { pub is_rest_param: bool, } -/// A sequence of `FormalParameter`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-FormalParameters -pub type FormalParameters = Vec; - impl FormalParameter { - pub fn new(name: String, init: Option>, is_rest_param: bool) -> FormalParameter { - FormalParameter { - name, + pub fn new(name: N, init: Option>, is_rest_param: bool) -> Self + where + N: Into, + { + Self { + name: name.into(), init, is_rest_param, } @@ -833,6 +1146,42 @@ pub enum PropertyDefinition { SpreadObject(Node), } +impl PropertyDefinition { + /// Creates an `IdentifierReference` property definition. + pub fn identifier_reference(ident: I) -> Self + where + I: Into, + { + Self::IdentifierReference(ident.into()) + } + + /// Creates a `Property` definition. + pub fn property(name: N, value: V) -> Self + where + N: Into, + V: Into, + { + Self::Property(name.into(), value.into()) + } + + /// Creates a `MethodDefinition`. + pub fn method_definition(kind: MethodDefinitionKind, name: N, body: B) -> Self + where + N: Into, + B: Into, + { + Self::MethodDefinition(kind, name.into(), body.into()) + } + + /// Creates a `SpreadObject`. + pub fn spread_object(obj: O) -> Self + where + O: Into, + { + Self::SpreadObject(obj.into()) + } +} + /// Method definition kinds. /// /// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced. diff --git a/boa/src/syntax/ast/op.rs b/boa/src/syntax/ast/op.rs index 0881cdbd92..2b9aea32ba 100644 --- a/boa/src/syntax/ast/op.rs +++ b/boa/src/syntax/ast/op.rs @@ -113,12 +113,12 @@ impl Display for NumOp { f, "{}", match *self { - NumOp::Add => "+", - NumOp::Sub => "-", - NumOp::Div => "/", - NumOp::Mul => "*", - NumOp::Exp => "**", - NumOp::Mod => "%", + Self::Add => "+", + Self::Sub => "-", + Self::Div => "/", + Self::Mul => "*", + Self::Exp => "**", + Self::Mod => "%", } ) } @@ -322,15 +322,15 @@ impl Display for UnaryOp { f, "{}", match *self { - UnaryOp::IncrementPost | UnaryOp::IncrementPre => "++", - UnaryOp::DecrementPost | UnaryOp::DecrementPre => "--", - UnaryOp::Plus => "+", - UnaryOp::Minus => "-", - UnaryOp::Not => "!", - UnaryOp::Tilde => "~", - UnaryOp::Delete => "delete", - UnaryOp::TypeOf => "typeof", - UnaryOp::Void => "void", + Self::IncrementPost | Self::IncrementPre => "++", + Self::DecrementPost | Self::DecrementPre => "--", + Self::Plus => "+", + Self::Minus => "-", + Self::Not => "!", + Self::Tilde => "~", + Self::Delete => "delete", + Self::TypeOf => "typeof", + Self::Void => "void", } ) } @@ -436,12 +436,12 @@ impl Display for BitOp { f, "{}", match *self { - BitOp::And => "&", - BitOp::Or => "|", - BitOp::Xor => "^", - BitOp::Shl => "<<", - BitOp::Shr => ">>", - BitOp::UShr => ">>>", + Self::And => "&", + Self::Or => "|", + Self::Xor => "^", + Self::Shl => "<<", + Self::Shr => ">>", + Self::UShr => ">>>", } ) } @@ -587,14 +587,14 @@ impl Display for CompOp { f, "{}", match *self { - CompOp::Equal => "==", - CompOp::NotEqual => "!=", - CompOp::StrictEqual => "===", - CompOp::StrictNotEqual => "!==", - CompOp::GreaterThan => ">", - CompOp::GreaterThanOrEqual => ">=", - CompOp::LessThan => "<", - CompOp::LessThanOrEqual => "<=", + Self::Equal => "==", + Self::NotEqual => "!=", + Self::StrictEqual => "===", + Self::StrictNotEqual => "!==", + Self::GreaterThan => ">", + Self::GreaterThanOrEqual => ">=", + Self::LessThan => "<", + Self::LessThanOrEqual => "<=", } ) } @@ -647,8 +647,8 @@ impl Display for LogOp { f, "{}", match *self { - LogOp::And => "&&", - LogOp::Or => "||", + Self::And => "&&", + Self::Or => "||", } ) } @@ -684,30 +684,60 @@ pub enum BinOp { Assign(AssignOp), } +impl From for BinOp { + fn from(op: NumOp) -> Self { + Self::Num(op) + } +} + +impl From for BinOp { + fn from(op: BitOp) -> Self { + Self::Bit(op) + } +} + +impl From for BinOp { + fn from(op: CompOp) -> Self { + Self::Comp(op) + } +} + +impl From for BinOp { + fn from(op: LogOp) -> Self { + Self::Log(op) + } +} + +impl From for BinOp { + fn from(op: AssignOp) -> Self { + Self::Assign(op) + } +} + impl Operator for BinOp { fn get_assoc(&self) -> bool { true } fn get_precedence(&self) -> u64 { match *self { - BinOp::Num(NumOp::Exp) => 4, - BinOp::Num(NumOp::Mul) | BinOp::Num(NumOp::Div) | BinOp::Num(NumOp::Mod) => 5, - BinOp::Num(NumOp::Add) | BinOp::Num(NumOp::Sub) => 6, - BinOp::Bit(BitOp::Shl) | BinOp::Bit(BitOp::Shr) | BinOp::Bit(BitOp::UShr) => 7, - BinOp::Comp(CompOp::LessThan) - | BinOp::Comp(CompOp::LessThanOrEqual) - | BinOp::Comp(CompOp::GreaterThan) - | BinOp::Comp(CompOp::GreaterThanOrEqual) => 8, - BinOp::Comp(CompOp::Equal) - | BinOp::Comp(CompOp::NotEqual) - | BinOp::Comp(CompOp::StrictEqual) - | BinOp::Comp(CompOp::StrictNotEqual) => 9, - BinOp::Bit(BitOp::And) => 10, - BinOp::Bit(BitOp::Xor) => 11, - BinOp::Bit(BitOp::Or) => 12, - BinOp::Log(LogOp::And) => 13, - BinOp::Log(LogOp::Or) => 14, - BinOp::Assign(_) => 15, + Self::Num(NumOp::Exp) => 4, + Self::Num(NumOp::Mul) | Self::Num(NumOp::Div) | Self::Num(NumOp::Mod) => 5, + Self::Num(NumOp::Add) | Self::Num(NumOp::Sub) => 6, + Self::Bit(BitOp::Shl) | Self::Bit(BitOp::Shr) | Self::Bit(BitOp::UShr) => 7, + Self::Comp(CompOp::LessThan) + | Self::Comp(CompOp::LessThanOrEqual) + | Self::Comp(CompOp::GreaterThan) + | Self::Comp(CompOp::GreaterThanOrEqual) => 8, + Self::Comp(CompOp::Equal) + | Self::Comp(CompOp::NotEqual) + | Self::Comp(CompOp::StrictEqual) + | Self::Comp(CompOp::StrictNotEqual) => 9, + Self::Bit(BitOp::And) => 10, + Self::Bit(BitOp::Xor) => 11, + Self::Bit(BitOp::Or) => 12, + Self::Log(LogOp::And) => 13, + Self::Log(LogOp::Or) => 14, + Self::Assign(_) => 15, } } } @@ -718,11 +748,11 @@ impl Display for BinOp { f, "{}", match *self { - BinOp::Num(ref op) => op.to_string(), - BinOp::Bit(ref op) => op.to_string(), - BinOp::Comp(ref op) => op.to_string(), - BinOp::Log(ref op) => op.to_string(), - BinOp::Assign(ref op) => op.to_string(), + Self::Num(ref op) => op.to_string(), + Self::Bit(ref op) => op.to_string(), + Self::Comp(ref op) => op.to_string(), + Self::Log(ref op) => op.to_string(), + Self::Assign(ref op) => op.to_string(), } ) } @@ -889,17 +919,17 @@ impl Display for AssignOp { f, "{}", match *self { - AssignOp::Add => "+=", - AssignOp::Sub => "-=", - AssignOp::Mul => "*=", - AssignOp::Exp => "**=", - AssignOp::Div => "/=", - AssignOp::Mod => "%=", - AssignOp::And => "&=", - AssignOp::Or => "|=", - AssignOp::Xor => "^=", - AssignOp::Shl => "<<=", - AssignOp::Shr => ">>=", + Self::Add => "+=", + Self::Sub => "-=", + Self::Mul => "*=", + Self::Exp => "**=", + Self::Div => "/=", + Self::Mod => "%=", + Self::And => "&=", + Self::Or => "|=", + Self::Xor => "^=", + Self::Shl => "<<=", + Self::Shr => ">>=", } ) } diff --git a/boa/src/syntax/ast/punc.rs b/boa/src/syntax/ast/punc.rs index 6cb23a7c9a..bf3a843011 100644 --- a/boa/src/syntax/ast/punc.rs +++ b/boa/src/syntax/ast/punc.rs @@ -5,7 +5,7 @@ //! //! [spec]: https://tc39.es/ecma262/#prod-Punctuator -use crate::syntax::ast::op::{BinOp, BitOp, CompOp, LogOp, NumOp}; +use crate::syntax::ast::op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp}; use std::fmt::{Display, Error, Formatter}; #[cfg(feature = "serde-ast")] @@ -132,27 +132,38 @@ impl Punctuator { /// If there is no match, `None` will be returned. pub fn as_binop(self) -> Option { match self { - Punctuator::Add => Some(BinOp::Num(NumOp::Add)), - Punctuator::Sub => Some(BinOp::Num(NumOp::Sub)), - Punctuator::Mul => Some(BinOp::Num(NumOp::Mul)), - Punctuator::Div => Some(BinOp::Num(NumOp::Div)), - Punctuator::Mod => Some(BinOp::Num(NumOp::Mod)), - Punctuator::And => Some(BinOp::Bit(BitOp::And)), - Punctuator::Or => Some(BinOp::Bit(BitOp::Or)), - Punctuator::Xor => Some(BinOp::Bit(BitOp::Xor)), - Punctuator::BoolAnd => Some(BinOp::Log(LogOp::And)), - Punctuator::BoolOr => Some(BinOp::Log(LogOp::Or)), - Punctuator::Eq => Some(BinOp::Comp(CompOp::Equal)), - Punctuator::NotEq => Some(BinOp::Comp(CompOp::NotEqual)), - Punctuator::StrictEq => Some(BinOp::Comp(CompOp::StrictEqual)), - Punctuator::StrictNotEq => Some(BinOp::Comp(CompOp::StrictNotEqual)), - Punctuator::LessThan => Some(BinOp::Comp(CompOp::LessThan)), - Punctuator::GreaterThan => Some(BinOp::Comp(CompOp::GreaterThan)), - Punctuator::GreaterThanOrEq => Some(BinOp::Comp(CompOp::GreaterThanOrEqual)), - Punctuator::LessThanOrEq => Some(BinOp::Comp(CompOp::LessThanOrEqual)), - Punctuator::LeftSh => Some(BinOp::Bit(BitOp::Shl)), - Punctuator::RightSh => Some(BinOp::Bit(BitOp::Shr)), - Punctuator::URightSh => Some(BinOp::Bit(BitOp::UShr)), + Self::AssignAdd => Some(BinOp::Assign(AssignOp::Add)), + Self::AssignAnd => Some(BinOp::Assign(AssignOp::And)), + Self::AssignDiv => Some(BinOp::Assign(AssignOp::Div)), + Self::AssignLeftSh => Some(BinOp::Assign(AssignOp::Shl)), + Self::AssignMod => Some(BinOp::Assign(AssignOp::Mod)), + Self::AssignMul => Some(BinOp::Assign(AssignOp::Mul)), + Self::AssignOr => Some(BinOp::Assign(AssignOp::Or)), + Self::AssignPow => Some(BinOp::Assign(AssignOp::Exp)), + Self::AssignRightSh => Some(BinOp::Assign(AssignOp::Shr)), + Self::AssignSub => Some(BinOp::Assign(AssignOp::Sub)), + Self::AssignXor => Some(BinOp::Assign(AssignOp::Xor)), + Self::Add => Some(BinOp::Num(NumOp::Add)), + Self::Sub => Some(BinOp::Num(NumOp::Sub)), + Self::Mul => Some(BinOp::Num(NumOp::Mul)), + Self::Div => Some(BinOp::Num(NumOp::Div)), + Self::Mod => Some(BinOp::Num(NumOp::Mod)), + Self::And => Some(BinOp::Bit(BitOp::And)), + Self::Or => Some(BinOp::Bit(BitOp::Or)), + Self::Xor => Some(BinOp::Bit(BitOp::Xor)), + Self::BoolAnd => Some(BinOp::Log(LogOp::And)), + Self::BoolOr => Some(BinOp::Log(LogOp::Or)), + Self::Eq => Some(BinOp::Comp(CompOp::Equal)), + Self::NotEq => Some(BinOp::Comp(CompOp::NotEqual)), + Self::StrictEq => Some(BinOp::Comp(CompOp::StrictEqual)), + Self::StrictNotEq => Some(BinOp::Comp(CompOp::StrictNotEqual)), + Self::LessThan => Some(BinOp::Comp(CompOp::LessThan)), + Self::GreaterThan => Some(BinOp::Comp(CompOp::GreaterThan)), + Self::GreaterThanOrEq => Some(BinOp::Comp(CompOp::GreaterThanOrEqual)), + Self::LessThanOrEq => Some(BinOp::Comp(CompOp::LessThanOrEqual)), + Self::LeftSh => Some(BinOp::Bit(BitOp::Shl)), + Self::RightSh => Some(BinOp::Bit(BitOp::Shr)), + Self::URightSh => Some(BinOp::Bit(BitOp::UShr)), _ => None, } } @@ -164,58 +175,58 @@ impl Display for Punctuator { f, "{}", match self { - Punctuator::Add => "+", - Punctuator::And => "&", - Punctuator::Arrow => "=>", - Punctuator::Assign => "=", - Punctuator::AssignAdd => "+=", - Punctuator::AssignAnd => "&=", - Punctuator::AssignDiv => "/=", - Punctuator::AssignLeftSh => "<<=", - Punctuator::AssignMod => "%=", - Punctuator::AssignMul => "*=", - Punctuator::AssignOr => "|=", - Punctuator::AssignPow => "**=", - Punctuator::AssignRightSh => ">>=", - Punctuator::AssignSub => "-=", - Punctuator::AssignURightSh => ">>>=", - Punctuator::AssignXor => "^=", - Punctuator::BoolAnd => "&&", - Punctuator::BoolOr => "||", - Punctuator::CloseBlock => "}", - Punctuator::CloseBracket => "]", - Punctuator::CloseParen => ")", - Punctuator::Colon => ":", - Punctuator::Comma => ",", - Punctuator::Dec => "--", - Punctuator::Div => "/", - Punctuator::Dot => ".", - Punctuator::Eq => "==", - Punctuator::GreaterThan => ">", - Punctuator::GreaterThanOrEq => ">=", - Punctuator::Inc => "++", - Punctuator::LeftSh => "<<", - Punctuator::LessThan => "<", - Punctuator::LessThanOrEq => "<=", - Punctuator::Mod => "%", - Punctuator::Mul => "*", - Punctuator::Neg => "~", - Punctuator::Not => "!", - Punctuator::NotEq => "!=", - Punctuator::OpenBlock => "{", - Punctuator::OpenBracket => "[", - Punctuator::OpenParen => "(", - Punctuator::Or => "|", - Punctuator::Exp => "**", - Punctuator::Question => "?", - Punctuator::RightSh => ">>", - Punctuator::Semicolon => ";", - Punctuator::Spread => "...", - Punctuator::StrictEq => "===", - Punctuator::StrictNotEq => "!==", - Punctuator::Sub => "-", - Punctuator::URightSh => ">>>", - Punctuator::Xor => "^", + Self::Add => "+", + Self::And => "&", + Self::Arrow => "=>", + Self::Assign => "=", + Self::AssignAdd => "+=", + Self::AssignAnd => "&=", + Self::AssignDiv => "/=", + Self::AssignLeftSh => "<<=", + Self::AssignMod => "%=", + Self::AssignMul => "*=", + Self::AssignOr => "|=", + Self::AssignPow => "**=", + Self::AssignRightSh => ">>=", + Self::AssignSub => "-=", + Self::AssignURightSh => ">>>=", + Self::AssignXor => "^=", + Self::BoolAnd => "&&", + Self::BoolOr => "||", + Self::CloseBlock => "}", + Self::CloseBracket => "]", + Self::CloseParen => ")", + Self::Colon => ":", + Self::Comma => ",", + Self::Dec => "--", + Self::Div => "/", + Self::Dot => ".", + Self::Eq => "==", + Self::GreaterThan => ">", + Self::GreaterThanOrEq => ">=", + Self::Inc => "++", + Self::LeftSh => "<<", + Self::LessThan => "<", + Self::LessThanOrEq => "<=", + Self::Mod => "%", + Self::Mul => "*", + Self::Neg => "~", + Self::Not => "!", + Self::NotEq => "!=", + Self::OpenBlock => "{", + Self::OpenBracket => "[", + Self::OpenParen => "(", + Self::Or => "|", + Self::Exp => "**", + Self::Question => "?", + Self::RightSh => ">>", + Self::Semicolon => ";", + Self::Spread => "...", + Self::StrictEq => "===", + Self::StrictNotEq => "!==", + Self::Sub => "-", + Self::URightSh => ">>>", + Self::Xor => "^", } ) } diff --git a/boa/src/syntax/ast/token.rs b/boa/src/syntax/ast/token.rs index 6584ccfeea..b6cc26bea9 100644 --- a/boa/src/syntax/ast/token.rs +++ b/boa/src/syntax/ast/token.rs @@ -95,21 +95,94 @@ pub enum TokenKind { LineTerminator, } +impl From for TokenKind { + fn from(oth: bool) -> Self { + Self::BooleanLiteral(oth) + } +} + +impl From for TokenKind { + fn from(kw: Keyword) -> Self { + Self::Keyword(kw) + } +} + +impl From for TokenKind { + fn from(punc: Punctuator) -> Self { + Self::Punctuator(punc) + } +} + +impl TokenKind { + /// Creates a `BooleanLiteral` token kind. + pub fn boolean_literal(lit: bool) -> Self { + Self::BooleanLiteral(lit) + } + + /// Creates an `EOF` token kind. + pub fn eof() -> Self { + Self::EOF + } + + /// Creates an `Identifier` token type. + pub fn identifier(ident: I) -> Self + where + I: Into, + { + Self::Identifier(ident.into()) + } + + /// Creates a `Keyword` token kind. + pub fn keyword(keyword: Keyword) -> Self { + Self::Keyword(keyword) + } + + /// Creates a `NumericLiteral` token kind. + pub fn numeric_literal(lit: f64) -> Self { + Self::NumericLiteral(lit) + } + + /// Creates a `Punctuator` token type. + pub fn punctuator(punc: Punctuator) -> Self { + Self::Punctuator(punc) + } + + /// Creates a `StringLiteral` token type. + pub fn string_literal(lit: S) -> Self + where + S: Into, + { + Self::StringLiteral(lit.into()) + } + + /// Creates a `RegularExpressionLiteral` token kind. + pub fn regular_expression_literal(body: B, flags: F) -> Self + where + B: Into, + F: Into, + { + Self::RegularExpressionLiteral(body.into(), flags.into()) + } + + /// Creates a `LineTerminator` token kind. + pub fn line_terminator() -> Self { + Self::LineTerminator + } +} + impl Display for TokenKind { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match *self { - TokenKind::BooleanLiteral(ref val) => write!(f, "{}", val), - TokenKind::EOF => write!(f, "end of file"), - TokenKind::Identifier(ref ident) => write!(f, "{}", ident), - TokenKind::Keyword(ref word) => write!(f, "{}", word), - TokenKind::NullLiteral => write!(f, "null"), - TokenKind::NumericLiteral(ref num) => write!(f, "{}", num), - TokenKind::Punctuator(ref punc) => write!(f, "{}", punc), - TokenKind::StringLiteral(ref lit) => write!(f, "{}", lit), - TokenKind::RegularExpressionLiteral(ref body, ref flags) => { - write!(f, "/{}/{}", body, flags) - } - TokenKind::LineTerminator => write!(f, "line terminator"), + Self::BooleanLiteral(ref val) => write!(f, "{}", val), + Self::EOF => write!(f, "end of file"), + Self::Identifier(ref ident) => write!(f, "{}", ident), + Self::Keyword(ref word) => write!(f, "{}", word), + Self::NullLiteral => write!(f, "null"), + Self::NumericLiteral(ref num) => write!(f, "{}", num), + Self::Punctuator(ref punc) => write!(f, "{}", punc), + Self::StringLiteral(ref lit) => write!(f, "{}", lit), + Self::RegularExpressionLiteral(ref body, ref flags) => write!(f, "/{}/{}", body, flags), + Self::LineTerminator => write!(f, "line terminator"), } } } diff --git a/boa/src/syntax/lexer/mod.rs b/boa/src/syntax/lexer/mod.rs index 42d99a3251..16e277b6af 100644 --- a/boa/src/syntax/lexer/mod.rs +++ b/boa/src/syntax/lexer/mod.rs @@ -503,9 +503,8 @@ impl<'a> Lexer<'a> { break; } } - // Match won't compare &String to &str so i need to convert it :( - let buf_compare: &str = &buf; - self.push_token(match buf_compare { + + self.push_token(match buf.as_str() { "true" => TokenKind::BooleanLiteral(true), "false" => TokenKind::BooleanLiteral(false), "null" => TokenKind::NullLiteral, @@ -513,12 +512,12 @@ impl<'a> Lexer<'a> { if let Ok(keyword) = FromStr::from_str(slice) { TokenKind::Keyword(keyword) } else { - TokenKind::Identifier(buf.clone()) + TokenKind::identifier(slice) } } }); // Move position forward the length of keyword - self.column_number += (buf_compare.len().wrapping_sub(1)) as u64; + self.column_number += (buf.len().wrapping_sub(1)) as u64; } ';' => self.push_punc(Punctuator::Semicolon), ':' => self.push_punc(Punctuator::Colon), diff --git a/boa/src/syntax/lexer/tests.rs b/boa/src/syntax/lexer/tests.rs index 98859fe43c..42146b593a 100644 --- a/boa/src/syntax/lexer/tests.rs +++ b/boa/src/syntax/lexer/tests.rs @@ -20,7 +20,7 @@ fn check_multi_line_comment() { 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".to_string())); + assert_eq!(lexer.tokens[1].kind, TokenKind::identifier("x")); } #[test] @@ -28,15 +28,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::StringLiteral("aaa".to_string()) - ); + assert_eq!(lexer.tokens[0].kind, TokenKind::string_literal("aaa")); - assert_eq!( - lexer.tokens[1].kind, - TokenKind::StringLiteral("bbb".to_string()) - ); + assert_eq!(lexer.tokens[1].kind, TokenKind::string_literal("bbb")); } #[test] @@ -290,15 +284,12 @@ fn check_variable_definition_tokens() { 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".to_string())); + assert_eq!(lexer.tokens[1].kind, TokenKind::identifier("a")); assert_eq!( lexer.tokens[2].kind, TokenKind::Punctuator(Punctuator::Assign) ); - assert_eq!( - lexer.tokens[3].kind, - TokenKind::StringLiteral("hello".to_string()) - ); + assert_eq!(lexer.tokens[3].kind, TokenKind::string_literal("hello")); } #[test] @@ -336,7 +327,7 @@ fn test_two_divisions_in_expression() { let s = " return a !== 0 || 1 / a === 1 / b;"; let mut lexer = Lexer::new(s); lexer.lex().expect("failed to lex"); - dbg!(&lexer.tokens); + // dbg!(&lexer.tokens); assert_eq!(lexer.tokens[11].pos.column_number, 37); assert_eq!(lexer.tokens[11].pos.line_number, 1); @@ -425,7 +416,7 @@ fn test_regex_literal() { lexer.lex().expect("failed to lex"); assert_eq!( lexer.tokens[0].kind, - TokenKind::RegularExpressionLiteral("(?:)".to_string(), "".to_string()) + TokenKind::regular_expression_literal("(?:)", "") ); } @@ -435,7 +426,7 @@ fn test_regex_literal_flags() { lexer.lex().expect("failed to lex"); assert_eq!( lexer.tokens[0].kind, - TokenKind::RegularExpressionLiteral("\\/[^\\/]*\\/*".to_string(), "gmi".to_string()) + TokenKind::regular_expression_literal("\\/[^\\/]*\\/*", "gmi") ); } diff --git a/boa/src/syntax/parser/cursor.rs b/boa/src/syntax/parser/cursor.rs index 4609c6aa99..ffc61f3df0 100644 --- a/boa/src/syntax/parser/cursor.rs +++ b/boa/src/syntax/parser/cursor.rs @@ -1,6 +1,10 @@ //! Cursor implementation for the parser. -use crate::syntax::ast::token::Token; +use super::ParseError; +use crate::syntax::ast::{ + punc::Punctuator, + token::{Token, TokenKind}, +}; /// Token cursor. /// @@ -28,67 +32,68 @@ impl<'a> Cursor<'a> { } /// Moves the cursor to the given position. - /// This is intended to be used *always* with `Cursor::pos()`. /// + /// This is intended to be used *always* with `Cursor::pos()`. pub(super) fn seek(&mut self, pos: usize) { self.pos = pos } /// Moves the cursor to the next token and returns the token. pub(super) fn next(&mut self) -> Option<&'a Token> { - let token = self.tokens.get(self.pos); - - if self.pos != self.tokens.len() { - self.pos += 1; - } - - token - } - - /// Moves the cursor to the next token after skipping tokens based on the predicate. - pub(super) fn next_skip

(&mut self, mut skip: P) -> Option<&'a Token> - where - P: FnMut(&Token) -> bool, - { - while let Some(token) = self.tokens.get(self.pos) { - self.pos += 1; - - if !skip(token) { - return Some(token); + loop { + let token = self.tokens.get(self.pos); + if let Some(tk) = token { + self.pos += 1; + + if tk.kind != TokenKind::LineTerminator { + break Some(tk); + } + } else { + break None; } } - None } /// Peeks the next token without moving the cursor. pub(super) fn peek(&self, skip: usize) -> Option<&'a Token> { - self.tokens.get(self.pos + skip) - } - - /// Peeks the next token after skipping tokens based on the predicate. - pub(super) fn peek_skip

(&self, mut skip: P) -> Option<&'a Token> - where - P: FnMut(&Token) -> bool, - { - let mut current = self.pos; - while let Some(token) = self.tokens.get(current) { - if !skip(token) { - return Some(token); + let mut count = 0; + let mut skipped = 0; + loop { + let token = self.tokens.get(self.pos + count); + count += 1; + + if let Some(tk) = token { + if tk.kind != TokenKind::LineTerminator { + if skipped == skip { + break Some(tk); + } + + skipped += 1; + } + } else { + break None; } - current += 1; } - - None } /// Moves the cursor to the previous token and returns the token. pub(super) fn back(&mut self) { - assert!( + debug_assert!( self.pos > 0, "cannot go back in a cursor that is at the beginning of the list of tokens" ); self.pos -= 1; + while self + .tokens + .get(self.pos - 1) + .expect("token disappeared") + .kind + == TokenKind::LineTerminator + && self.pos > 0 + { + self.pos -= 1; + } } /// Peeks the previous token without moving the cursor. @@ -96,7 +101,146 @@ impl<'a> Cursor<'a> { if self.pos == 0 { None } else { - self.tokens.get(self.pos - 1) + let mut back = 1; + let mut tok = self.tokens.get(self.pos - back).expect("token disappeared"); + while self.pos >= back && tok.kind == TokenKind::LineTerminator { + back += 1; + tok = self.tokens.get(self.pos - back).expect("token disappeared"); + } + + if back == self.pos { + None + } else { + Some(tok) + } + } + } + + /// Returns an error if the next token is not of kind `kind`. + /// + /// Note: it will consume the next token. + pub(super) fn expect(&mut self, kind: K, routine: &'static str) -> Result<(), ParseError> + where + K: Into, + { + let next_token = self.next().ok_or(ParseError::AbruptEnd)?; + let kind = kind.into(); + + if next_token.kind == kind { + Ok(()) + } else { + Err(ParseError::Expected( + vec![kind], + next_token.clone(), + routine, + )) + } + } + + /// It will peek for the next token, to see if it's a semicolon. + /// + /// It will automatically insert a semicolon if needed, as specified in the [spec][spec]. + /// + /// [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 { + TokenKind::Punctuator(Punctuator::Semicolon) => (true, Some(tk)), + TokenKind::LineTerminator | TokenKind::Punctuator(Punctuator::CloseBlock) => { + (true, Some(tk)) + } + _ => { + if do_while { + debug_assert!( + self.pos != 0, + "cannot be finishing a do-while if we are at the beginning" + ); + + let tok = self + .tokens + .get(self.pos - 1) + .expect("could not find previous token"); + if tok.kind == TokenKind::Punctuator(Punctuator::CloseParen) { + return (true, Some(tk)); + } + } + + (false, Some(tk)) + } + }, + None => (true, None), + } + } + + /// It will check if the next token is a semicolon. + /// + /// It will automatically insert a semicolon if needed, as specified in the [spec][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-automatic-semicolon-insertion + pub(super) fn expect_semicolon( + &mut self, + do_while: bool, + routine: &'static str, + ) -> Result<(), ParseError> { + match self.peek_semicolon(do_while) { + (true, Some(tk)) => match tk.kind { + TokenKind::Punctuator(Punctuator::Semicolon) | TokenKind::LineTerminator => { + self.pos += 1; + Ok(()) + } + _ => Ok(()), + }, + (true, None) => Ok(()), + (false, Some(tk)) => Err(ParseError::Expected( + vec![TokenKind::Punctuator(Punctuator::Semicolon)], + tk.clone(), + routine, + )), + (false, None) => unreachable!(), + } + } + + /// It will make sure that the next token is not a line terminator. + /// + /// It expects that the token stream does not end here. + pub(super) fn peek_expect_no_lineterminator( + &mut self, + skip: usize, + routine: &'static str, + ) -> Result<(), ParseError> { + let mut count = 0; + let mut skipped = 0; + loop { + let token = self.tokens.get(self.pos + count); + count += 1; + if let Some(tk) = token { + if skipped == skip && tk.kind == TokenKind::LineTerminator { + break Err(ParseError::Unexpected(tk.clone(), Some(routine))); + } else if skipped == skip && tk.kind != TokenKind::LineTerminator { + break Ok(()); + } else if tk.kind != TokenKind::LineTerminator { + skipped += 1; + } + } else { + break Err(ParseError::AbruptEnd); + } + } + } + + /// Advance the cursor to the next token and retrieve it, only if it's of `kind` type. + /// + /// When the next token is a `kind` token, get the token, otherwise return `None`. This + /// function skips line terminators. + pub(super) fn next_if(&mut self, kind: K) -> Option<&'a Token> + where + K: Into, + { + let next_token = self.peek(0)?; + + 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 new file mode 100644 index 0000000000..1d83c95357 --- /dev/null +++ b/boa/src/syntax/parser/error.rs @@ -0,0 +1,106 @@ +//! Error and result implementation for the parser. +use crate::syntax::ast::{ + keyword::Keyword, + node::Node, + pos::Position, + token::{Token, TokenKind}, +}; +use std::fmt; + +/// Result of a parsing operation. +pub type ParseResult = Result; + +/// `ParseError` is an enum which represents errors encounted during parsing an expression +#[derive(Debug, Clone)] +pub enum ParseError { + /// When it expected a certain kind of token, but got another as part of something + Expected(Vec, Token, &'static str), + /// When it expected a certain expression, but got another + ExpectedExpr(&'static str, Node, Position), + /// When it didn't expect this keyword + UnexpectedKeyword(Keyword, Position), + /// When a token is unexpected + Unexpected(Token, Option<&'static str>), + /// When there is an abrupt end to the parsing + AbruptEnd, + /// Out of range error, attempting to set a position where there is no token + RangeError, + /// Catch all General Error + General(&'static str, Option), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Expected(expected, actual, routine) => write!( + f, + "Expected {}, got '{}' in {} at line {}, col {}", + if expected.len() == 1 { + format!( + "token '{}'", + expected.first().map(TokenKind::to_string).unwrap() + ) + } else { + format!( + "one of {}", + expected + .iter() + .enumerate() + .map(|(i, t)| { + format!( + "{}'{}'", + if i == 0 { + "" + } else if i == expected.len() - 1 { + " or " + } else { + ", " + }, + t + ) + }) + .collect::() + ) + }, + actual, + routine, + actual.pos.line_number, + actual.pos.column_number + ), + Self::ExpectedExpr(expected, actual, pos) => write!( + f, + "Expected expression '{}', got '{}' at line {}, col {}", + 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 + ), + Self::Unexpected(tok, msg) => write!( + f, + "Unexpected Token '{}'{} at line {}, col {}", + tok, + if let Some(m) = msg { + format!(", {}", m) + } else { + String::new() + }, + tok.pos.line_number, + tok.pos.column_number + ), + Self::AbruptEnd => write!(f, "Abrupt End"), + Self::General(msg, pos) => write!( + f, + "{}{}", + msg, + if let Some(pos) = pos { + format!(" at line {}, col {}", pos.line_number, pos.column_number) + } else { + String::new() + } + ), + Self::RangeError => write!(f, "RangeError!"), + } + } +} diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs new file mode 100644 index 0000000000..6776ecaad5 --- /dev/null +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -0,0 +1,161 @@ +//! Arrow function parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions +//! [spec]: https://tc39.es/ecma262/#sec-arrow-function-definitions + +use super::AssignmentExpression; +use crate::syntax::{ + ast::{ + node::{FormalParameter, Node}, + punc::Punctuator, + token::TokenKind, + }, + parser::{ + function::{FormalParameters, FunctionBody}, + AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// Arrow function parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions +/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct ArrowFunction { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ArrowFunction { + /// Creates a new `ArrowFunction` parser. + pub(in crate::syntax::parser) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + ) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ArrowFunction { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; + let params = match &next_token.kind { + TokenKind::Punctuator(Punctuator::OpenParen) => { + let params = + FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "arrow function")?; + params + } + TokenKind::Identifier(param_name) => vec![FormalParameter { + init: None, + name: param_name.clone(), + is_rest_param: false, + }], + _ => { + return Err(ParseError::Expected( + vec![ + TokenKind::Punctuator(Punctuator::OpenParen), + TokenKind::identifier("identifier"), + ], + next_token.clone(), + "arrow function", + )) + } + }; + cursor.peek_expect_no_lineterminator(0, "arrow function")?; + + cursor.expect(Punctuator::Arrow, "arrow function")?; + + let body = ConciseBody::new(self.allow_in).parse(cursor)?; + + Ok(Node::arrow_function_decl(params, body)) + } +} + +/// +#[derive(Debug, Clone, Copy)] +struct ConciseBody { + allow_in: AllowIn, +} + +impl ConciseBody { + /// Creates a new `ConcideBody` parser. + fn new(allow_in: I) -> Self + where + I: Into, + { + Self { + allow_in: allow_in.into(), + } + } +} + +impl TokenParser for ConciseBody { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + 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::StatementList)?; + cursor.expect(Punctuator::CloseBlock, "arrow function")?; + Ok(body) + } + _ => Ok(Node::return_node( + ExpressionBody::new(self.allow_in, false).parse(cursor)?, + )), + } + } +} + +/// +#[derive(Debug, Clone, Copy)] +struct ExpressionBody { + allow_in: AllowIn, + allow_await: AllowAwait, +} + +impl ExpressionBody { + /// Creates a new `ExpressionBody` parser. + fn new(allow_in: I, allow_await: A) -> Self + where + I: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ExpressionBody { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + AssignmentExpression::new(self.allow_in, false, self.allow_await).parse(cursor) + } +} diff --git a/boa/src/syntax/parser/expression/assignment/conditional.rs b/boa/src/syntax/parser/expression/assignment/conditional.rs new file mode 100644 index 0000000000..874aa950c9 --- /dev/null +++ b/boa/src/syntax/parser/expression/assignment/conditional.rs @@ -0,0 +1,79 @@ +//! Conditional operator parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator +//! [spec]: https://tc39.es/ecma262/#sec-conditional-operator + +use crate::syntax::{ + ast::{node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::{AssignmentExpression, LogicalORExpression}, + AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser, + }, +}; + +/// Conditional expression parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator +/// [spec]: https://tc39.es/ecma262/#prod-ConditionalExpression +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::expression) struct ConditionalExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ConditionalExpression { + /// Creates a new `ConditionalExpression` parser. + pub(in crate::syntax::parser::expression) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + ) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ConditionalExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // TODO: coalesce expression + let lhs = LogicalORExpression::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + + if let Some(tok) = cursor.next() { + if tok.kind == TokenKind::Punctuator(Punctuator::Question) { + let then_clause = + AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + cursor.expect(Punctuator::Colon, "conditional expression")?; + + let else_clause = + AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?; + return Ok(Node::conditional_op(lhs, then_clause, else_clause)); + } else { + cursor.back(); + } + } + + Ok(lhs) + } +} diff --git a/boa/src/syntax/parser/expression/assignment/exponentiation.rs b/boa/src/syntax/parser/expression/assignment/exponentiation.rs new file mode 100644 index 0000000000..5da2b31032 --- /dev/null +++ b/boa/src/syntax/parser/expression/assignment/exponentiation.rs @@ -0,0 +1,94 @@ +//! Exponentiation operator parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation +//! [spec]: https://tc39.es/ecma262/#sec-exp-operator + +use crate::syntax::{ + ast::{ + keyword::Keyword, + node::Node, + op::{BinOp, NumOp}, + punc::Punctuator, + token::TokenKind, + }, + parser::{ + expression::{unary::UnaryExpression, update::UpdateExpression}, + AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, + }, +}; + +/// Parses an exponentiation expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation +/// [spec]: https://tc39.es/ecma262/#prod-ExponentiationExpression +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::expression) struct ExponentiationExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ExponentiationExpression { + /// Creates a new `ExponentiationExpression` parser. + pub(in crate::syntax::parser::expression) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +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 { + TokenKind::Keyword(Keyword::Delete) + | TokenKind::Keyword(Keyword::Void) + | TokenKind::Keyword(Keyword::TypeOf) + | TokenKind::Punctuator(Punctuator::Add) + | TokenKind::Punctuator(Punctuator::Sub) + | TokenKind::Punctuator(Punctuator::Not) + | TokenKind::Punctuator(Punctuator::Neg) => true, + _ => false, + } + } else { + false + } + } +} + +impl TokenParser for ExponentiationExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + if Self::is_unary_expression(cursor) { + return UnaryExpression::new(self.allow_yield, self.allow_await).parse(cursor); + } + + 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)?, + )); + } else { + cursor.back(); + } + } + Ok(lhs) + } +} diff --git a/boa/src/syntax/parser/expression/assignment/mod.rs b/boa/src/syntax/parser/expression/assignment/mod.rs new file mode 100644 index 0000000000..63a040a4d6 --- /dev/null +++ b/boa/src/syntax/parser/expression/assignment/mod.rs @@ -0,0 +1,124 @@ +//! Assignment operator parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Assignment +//! [spec]: https://tc39.es/ecma262/#sec-assignment-operators + +mod arrow_function; +mod conditional; +mod exponentiation; + +use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression}; +use crate::syntax::{ + ast::{node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; +pub(super) use exponentiation::ExponentiationExpression; + +/// Assignment expression parsing. +/// +/// This can be one of the following: +/// +/// - [`ConditionalExpression`](../conditional_operator/struct.ConditionalExpression.html) +/// - `YieldExpression` +/// - [`ArrowFunction`](../../function/arrow_function/struct.ArrowFunction.html) +/// - `AsyncArrowFunction` +/// - [`LeftHandSideExpression`][lhs] `=` `AssignmentExpression` +/// - [`LeftHandSideExpression`][lhs] `AssignmentOperator` `AssignmentExpression` +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Assignment +/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression +/// [lhs]: ../lhs_expression/struct.LeftHandSideExpression.html +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct AssignmentExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl AssignmentExpression { + /// Creates a new `AssignmentExpression` parser. + pub(in crate::syntax::parser) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + ) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for AssignmentExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // Arrow function + let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; + match next_token.kind { + // a=>{} + TokenKind::Identifier(_) + if cursor + .peek_expect_no_lineterminator(1, "arrow function") + .is_ok() => + { + if let Some(tok) = cursor.peek(1) { + if tok.kind == TokenKind::Punctuator(Punctuator::Arrow) { + return ArrowFunction::new( + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor); + } + } + } + // (a,b)=>{} + TokenKind::Punctuator(Punctuator::OpenParen) => { + if let Some(node) = + ArrowFunction::new(self.allow_in, self.allow_yield, self.allow_await) + .try_parse(cursor) + { + return Ok(node); + } + } + _ => {} + } + + 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)?) + } + 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); + } + _ => { + cursor.back(); + } + } + } + + Ok(lhs) + } +} diff --git a/boa/src/syntax/parser/expression/left_hand_side/arguments.rs b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs new file mode 100644 index 0000000000..177e9d3ad1 --- /dev/null +++ b/boa/src/syntax/parser/expression/left_hand_side/arguments.rs @@ -0,0 +1,94 @@ +//! Argument parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Argument +//! [spec]: https://tc39.es/ecma262/#prod-Arguments + +use crate::syntax::{ + ast::{node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser, + }, +}; + +/// Parses a list of arguments. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Argument +/// [spec]: https://tc39.es/ecma262/#prod-Arguments +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::expression) struct Arguments { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl Arguments { + /// Creates a new `Arguments` parser. + pub(in crate::syntax::parser::expression) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for Arguments { + type Output = Vec; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result, ParseError> { + cursor.expect(Punctuator::OpenParen, "arguments")?; + let mut args = Vec::new(); + loop { + let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; + match next_token.kind { + TokenKind::Punctuator(Punctuator::CloseParen) => break, + TokenKind::Punctuator(Punctuator::Comma) => { + if args.is_empty() { + return Err(ParseError::Unexpected(next_token.clone(), None)); + } + + if cursor.next_if(Punctuator::CloseParen).is_some() { + break; + } + } + _ => { + if !args.is_empty() { + return Err(ParseError::Expected( + vec![ + TokenKind::Punctuator(Punctuator::Comma), + TokenKind::Punctuator(Punctuator::CloseParen), + ], + next_token.clone(), + "argument list", + )); + } else { + cursor.back(); + } + } + } + + if cursor.next_if(Punctuator::Spread).is_some() { + args.push(Node::spread( + AssignmentExpression::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?, + )); + } else { + args.push( + AssignmentExpression::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?, + ); + } + } + Ok(args) + } +} diff --git a/boa/src/syntax/parser/expression/left_hand_side/call.rs b/boa/src/syntax/parser/expression/left_hand_side/call.rs new file mode 100644 index 0000000000..ce7fd8e47f --- /dev/null +++ b/boa/src/syntax/parser/expression/left_hand_side/call.rs @@ -0,0 +1,102 @@ +//! Call expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions +//! [spec]: https://tc39.es/ecma262/#prod-CallExpression + +use super::arguments::Arguments; +use crate::syntax::{ + ast::{node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::Expression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, + TokenParser, + }, +}; + +/// Parses a call expression. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-CallExpression +#[derive(Debug)] +pub(super) struct CallExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, + first_member_expr: Node, +} + +impl CallExpression { + /// Creates a new `CallExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A, first_member_expr: Node) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + first_member_expr, + } + } +} + +impl TokenParser for CallExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let mut lhs = match cursor.peek(0) { + 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) + } + _ => { + let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; + return Err(ParseError::Expected( + vec![TokenKind::Punctuator(Punctuator::OpenParen)], + next_token.clone(), + "call expression", + )); + } + }; + + while let Some(tok) = cursor.peek(0) { + 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 { + TokenKind::Identifier(name) => { + lhs = Node::get_const_field(lhs, name); + } + TokenKind::Keyword(kw) => { + lhs = Node::get_const_field(lhs, kw.to_string()); + } + _ => { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + tok.clone(), + "call expression", + )); + } + } + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor. + let idx = + Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseBracket, "call expression")?; + lhs = Node::get_field(lhs, idx); + } + _ => break, + } + } + Ok(lhs) + } +} diff --git a/boa/src/syntax/parser/expression/left_hand_side/member.rs b/boa/src/syntax/parser/expression/left_hand_side/member.rs new file mode 100644 index 0000000000..932387730f --- /dev/null +++ b/boa/src/syntax/parser/expression/left_hand_side/member.rs @@ -0,0 +1,88 @@ +//! Member expression parsing. +//! +//! More information: +//! - [ECMAScript specification][spec] +//! +//! [spec]: https://tc39.es/ecma262/#prod-MemberExpression + +use super::arguments::Arguments; +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::{primary::PrimaryExpression, Expression}, + AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// Parses a member expression. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-MemberExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct MemberExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl MemberExpression { + /// Creates a new `MemberExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +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 _ = cursor.next().expect("keyword disappeared"); + let lhs = self.parse(cursor)?; + let args = Arguments::new(self.allow_yield, self.allow_await).parse(cursor)?; + let call_node = Node::call(lhs, args); + + Node::new(call_node) + } else { + PrimaryExpression::new(self.allow_yield, self.allow_await).parse(cursor)? + }; + while let Some(tok) = cursor.peek(0) { + 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 { + TokenKind::Identifier(name) => lhs = Node::get_const_field(lhs, name), + TokenKind::Keyword(kw) => lhs = Node::get_const_field(lhs, kw.to_string()), + _ => { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + tok.clone(), + "member expression", + )); + } + } + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor forward. + let idx = + Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseBracket, "member expression")?; + lhs = Node::get_field(lhs, idx); + } + _ => break, + } + } + + Ok(lhs) + } +} diff --git a/boa/src/syntax/parser/expression/left_hand_side/mod.rs b/boa/src/syntax/parser/expression/left_hand_side/mod.rs new file mode 100644 index 0000000000..470184276a --- /dev/null +++ b/boa/src/syntax/parser/expression/left_hand_side/mod.rs @@ -0,0 +1,61 @@ +//! Left hand side expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Left-hand-side_expressions +//! [spec]: https://tc39.es/ecma262/#sec-left-hand-side-expressions + +mod arguments; +mod call; +mod member; + +use self::{call::CallExpression, member::MemberExpression}; +use crate::syntax::{ + ast::{node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +}; + +/// Parses a left hand side expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Left-hand-side_expressions +/// [spec]: https://tc39.es/ecma262/#prod-LeftHandSideExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct LeftHandSideExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl LeftHandSideExpression { + /// Creates a new `LeftHandSideExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for LeftHandSideExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // 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) => { + 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 new file mode 100644 index 0000000000..d225fe9d45 --- /dev/null +++ b/boa/src/syntax/parser/expression/mod.rs @@ -0,0 +1,479 @@ +//! Expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators +//! [spec]: https://tc39.es/ecma262/#sec-ecmascript-language-expressions + +mod assignment; +mod left_hand_side; +mod primary; +#[cfg(test)] +mod tests; +mod unary; +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::{node::Node, punc::Punctuator, token::TokenKind}; + +/// Generates an expression parser. +/// +/// This macro has 2 mandatory identifiers: +/// - The `$name` identifier will contain the name of the parsing structure. +/// - The `$lower` identifier will contain the parser for lower level expressions. +/// +/// Those exressions are divided by the punctuators passed as the third parameter. +macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $low_param:ident ),*] ) => { + impl TokenParser for $name { + type Output = Node; + + 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 )||* => { + let _ = cursor.next().expect("token disappeared"); + lhs = Node::bin_op( + op.as_binop().expect("could not get binary operation"), + lhs, + $lower::new($( self.$low_param ),*).parse(cursor)? + ) + } + _ => break + } + } + Ok(lhs) + } + } +} } + +/// Expression parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators +/// [spec]: https://tc39.es/ecma262/#prod-Expression +#[derive(Debug, Clone, Copy)] +pub(super) struct Expression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl Expression { + /// Creates a new `Expression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + Expression, + AssignmentExpression, + [Punctuator::Comma], + [allow_in, allow_yield, allow_await] +); + +/// Parses a logical `OR` expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR_2 +/// [spec]: https://tc39.es/ecma262/#prod-LogicalORExpression +#[derive(Debug, Clone, Copy)] +struct LogicalORExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl LogicalORExpression { + /// Creates a new `LogicalORExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + LogicalORExpression, + LogicalANDExpression, + [Punctuator::BoolOr], + [allow_in, allow_yield, allow_await] +); + +/// Parses a logical `AND` expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_AND_2 +/// [spec]: https://tc39.es/ecma262/#prod-LogicalANDExpression +#[derive(Debug, Clone, Copy)] +struct LogicalANDExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl LogicalANDExpression { + /// Creates a new `LogicalANDExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + LogicalANDExpression, + BitwiseORExpression, + [Punctuator::BoolAnd], + [allow_in, allow_yield, allow_await] +); + +/// Parses a bitwise `OR` expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_OR +/// [spec]: https://tc39.es/ecma262/#prod-BitwiseORExpression +#[derive(Debug, Clone, Copy)] +struct BitwiseORExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl BitwiseORExpression { + /// Creates a new `BitwiseORExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + BitwiseORExpression, + BitwiseXORExpression, + [Punctuator::Or], + [allow_in, allow_yield, allow_await] +); + +/// Parses a bitwise `XOR` expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_XOR +/// [spec]: https://tc39.es/ecma262/#prod-BitwiseXORExpression +#[derive(Debug, Clone, Copy)] +struct BitwiseXORExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl BitwiseXORExpression { + /// Creates a new `BitwiseXORExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + BitwiseXORExpression, + BitwiseANDExpression, + [Punctuator::Xor], + [allow_in, allow_yield, allow_await] +); + +/// Parses a bitwise `AND` expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_AND +/// [spec]: https://tc39.es/ecma262/#prod-BitwiseANDExpression +#[derive(Debug, Clone, Copy)] +struct BitwiseANDExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl BitwiseANDExpression { + /// Creates a new `BitwiseANDExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + BitwiseANDExpression, + EqualityExpression, + [Punctuator::And], + [allow_in, allow_yield, allow_await] +); + +/// Parses an equality expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Equality_operators +/// [spec]: https://tc39.es/ecma262/#sec-equality-operators +#[derive(Debug, Clone, Copy)] +struct EqualityExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl EqualityExpression { + /// Creates a new `EqualityExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + EqualityExpression, + RelationalExpression, + [ + Punctuator::Eq, + Punctuator::NotEq, + Punctuator::StrictEq, + Punctuator::StrictNotEq + ], + [allow_in, allow_yield, allow_await] +); + +/// Parses a relational expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Relational_operators +/// [spec]: https://tc39.es/ecma262/#sec-relational-operators +#[derive(Debug, Clone, Copy)] +struct RelationalExpression { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl RelationalExpression { + /// Creates a new `RelationalExpression` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + RelationalExpression, + ShiftExpression, + [ + Punctuator::LessThan, + Punctuator::GreaterThan, + Punctuator::LessThanOrEq, + Punctuator::GreaterThanOrEq + ], + [allow_yield, allow_await] +); + +/// Parses a bitwise shift expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_shift_operators +/// [spec]: https://tc39.es/ecma262/#sec-bitwise-shift-operators +#[derive(Debug, Clone, Copy)] +struct ShiftExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ShiftExpression { + /// Creates a new `ShiftExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + ShiftExpression, + AdditiveExpression, + [ + Punctuator::LeftSh, + Punctuator::RightSh, + Punctuator::URightSh + ], + [allow_yield, allow_await] +); + +/// Parses an additive expression. +/// +/// This can be either an addition or a subtraction. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators +/// [spec]: https://tc39.es/ecma262/#sec-additive-operators +#[derive(Debug, Clone, Copy)] +struct AdditiveExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl AdditiveExpression { + /// Creates a new `AdditiveExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + AdditiveExpression, + MultiplicativeExpression, + [Punctuator::Add, Punctuator::Sub], + [allow_yield, allow_await] +); + +/// Parses a multiplicative expression. +/// +/// This can be either a multiplication, division or a modulo (remainder) expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Division +/// [spec]: https://tc39.es/ecma262/#sec-multiplicative-operators +#[derive(Debug, Clone, Copy)] +struct MultiplicativeExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl MultiplicativeExpression { + /// Creates a new `MultiplicativeExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +expression!( + MultiplicativeExpression, + ExponentiationExpression, + [Punctuator::Mul, Punctuator::Div, Punctuator::Mod], + [allow_yield, allow_await] +); diff --git a/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs new file mode 100644 index 0000000000..c5ec0db5ef --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/array_initializer/mod.rs @@ -0,0 +1,82 @@ +//! Array initializer parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +//! [spec]: https://tc39.es/ecma262/#sec-array-initializer + +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{constant::Const, node::Node, punc::Punctuator}, + parser::{ + expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, + TokenParser, + }, +}; + +/// Parses an array literal. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral +#[derive(Debug, Clone, Copy)] +pub(super) struct ArrayLiteral { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ArrayLiteral { + /// Creates a new `ArrayLiteral` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ArrayLiteral { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let mut elements = Vec::new(); + + loop { + // TODO: Support all features. + while cursor.next_if(Punctuator::Comma).is_some() { + elements.push(Node::Const(Const::Undefined)); + } + + if cursor.next_if(Punctuator::CloseBracket).is_some() { + break; + } + + let _ = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; // Check that there are more tokens to read. + + if cursor.next_if(Punctuator::Spread).is_some() { + let node = AssignmentExpression::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?; + elements.push(Node::spread(node)); + } else { + elements.push( + AssignmentExpression::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?, + ); + } + cursor.next_if(Punctuator::Comma); + } + + Ok(Node::ArrayDecl(elements)) + } +} diff --git a/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs new file mode 100644 index 0000000000..5345616459 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/array_initializer/tests.rs @@ -0,0 +1,102 @@ +// ! Tests for array initializer parsing. + +use crate::syntax::{ + ast::{constant::Const, node::Node}, + parser::tests::check_parser, +}; + +/// Checks an empty array. +#[test] +fn check_empty() { + check_parser("[]", &[Node::ArrayDecl(Vec::new())]); +} + +/// Checks an array with empty slot. +#[test] +fn check_empty_slot() { + check_parser( + "[,]", + &[Node::ArrayDecl(vec![Node::Const(Const::Undefined)])], + ); +} + +/// Checks a numeric array. +#[test] +fn check_numeric_array() { + check_parser( + "[1, 2, 3]", + &[Node::ArrayDecl(vec![ + Node::const_node(1.0), + Node::const_node(2.0), + Node::const_node(3.0), + ])], + ); +} + +// Checks a numeric array with trailing comma +#[test] +fn check_numeric_array_trailing() { + check_parser( + "[1, 2, 3,]", + &[Node::ArrayDecl(vec![ + Node::const_node(1.0), + Node::const_node(2.0), + Node::const_node(3.0), + ])], + ); +} + +/// Checks a numeric array with an elision. +#[test] +fn check_numeric_array_elision() { + check_parser( + "[1, 2, , 3]", + &[Node::ArrayDecl(vec![ + Node::const_node(1.0), + Node::const_node(2.0), + Node::Const(Const::Undefined), + Node::const_node(3.0), + ])], + ); +} + +/// Checks a numeric array with repeated elisions. +#[test] +fn check_numeric_array_repeated_elision() { + check_parser( + "[1, 2, ,, 3]", + &[Node::ArrayDecl(vec![ + Node::const_node(1.0), + Node::const_node(2.0), + Node::Const(Const::Undefined), + Node::Const(Const::Undefined), + Node::const_node(3.0), + ])], + ); +} + +/// Checks a combined array. +#[test] +fn check_combined() { + check_parser( + "[1, \"a\", 2]", + &[Node::ArrayDecl(vec![ + Node::const_node(1.0), + Node::const_node("a"), + Node::const_node(2.0), + ])], + ); +} + +/// Checks a combined array with an empty string +#[test] +fn check_combined_empty_str() { + check_parser( + "[1, \"\", 2]", + &[Node::ArrayDecl(vec![ + Node::const_node(1.0), + Node::const_node(""), + Node::const_node(2.0), + ])], + ); +} diff --git a/boa/src/syntax/parser/expression/primary/function_expression.rs b/boa/src/syntax/parser/expression/primary/function_expression.rs new file mode 100644 index 0000000000..e821e21b40 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/function_expression.rs @@ -0,0 +1,60 @@ +//! Function expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function +//! [spec]: https://tc39.es/ecma262/#prod-FunctionExpression + +use crate::syntax::{ + ast::{node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + function::{FormalParameters, FunctionBody}, + Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// Function expression parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function +/// [spec]: https://tc39.es/ecma262/#prod-FunctionExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct FunctionExpression; + +impl TokenParser for FunctionExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let name = if let TokenKind::Identifier(name) = + &cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind + { + Some(name) + } else { + None + }; + if name.is_some() { + // We move the cursor forward. + let _ = cursor.next().expect("nex token disappeared"); + } + + cursor.expect(Punctuator::OpenParen, "function expression")?; + + let params = FormalParameters::new(false, false).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "function expression")?; + cursor.expect(Punctuator::OpenBlock, "function expression")?; + + let body = FunctionBody::new(false, false) + .parse(cursor) + .map(Node::StatementList)?; + + cursor.expect(Punctuator::CloseBlock, "function expression")?; + + Ok(Node::function_decl::<_, &String, _, _>(name, params, body)) + } +} diff --git a/boa/src/syntax/parser/expression/primary/mod.rs b/boa/src/syntax/parser/expression/primary/mod.rs new file mode 100644 index 0000000000..4b2fcc0cca --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/mod.rs @@ -0,0 +1,94 @@ +//! Primary expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#Primary_expressions +//! [spec]: https://tc39.es/ecma262/#prod-PrimaryExpression + +mod array_initializer; +mod function_expression; +mod object_initializer; +#[cfg(test)] +mod tests; + +use self::{ + array_initializer::ArrayLiteral, function_expression::FunctionExpression, + object_initializer::ObjectLiteral, +}; +use super::Expression; +use crate::syntax::{ + ast::{constant::Const, keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; +pub(in crate::syntax::parser) use object_initializer::Initializer; + +/// Parses a primary expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Primary_expressions +/// [spec]: https://tc39.es/ecma262/#prod-PrimaryExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct PrimaryExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl PrimaryExpression { + /// Creates a new `PrimaryExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for PrimaryExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + + 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::Punctuator(Punctuator::OpenParen) => { + let expr = + Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "primary expression")?; + Ok(expr) + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + ArrayLiteral::new(self.allow_yield, self.allow_await).parse(cursor) + } + TokenKind::Punctuator(Punctuator::OpenBlock) => { + ObjectLiteral::new(self.allow_yield, self.allow_await).parse(cursor) + } + TokenKind::BooleanLiteral(boolean) => Ok(Node::const_node(*boolean)), + // 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)), + TokenKind::StringLiteral(s) => Ok(Node::const_node(s)), + TokenKind::NumericLiteral(num) => Ok(Node::const_node(*num)), + TokenKind::RegularExpressionLiteral(body, flags) => Ok(Node::new(Node::call( + Node::local("RegExp"), + vec![Node::const_node(body), Node::const_node(flags)], + ))), + _ => Err(ParseError::Unexpected( + tok.clone(), + Some("primary expression"), + )), + } + } +} diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs new file mode 100644 index 0000000000..876c312d2e --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -0,0 +1,290 @@ +//! Object initializer parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer +//! [spec]: https://tc39.es/ecma262/#sec-object-initializer + +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{ + node::{self, MethodDefinitionKind, Node}, + punc::Punctuator, + token::{Token, TokenKind}, + }, + parser::{ + expression::AssignmentExpression, + function::{FormalParameters, FunctionBody}, + AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// Parses an object literal. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer +/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral +#[derive(Debug, Clone, Copy)] +pub(super) struct ObjectLiteral { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ObjectLiteral { + /// Creates a new `ObjectLiteral` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ObjectLiteral { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let mut elements = Vec::new(); + + loop { + if cursor.next_if(Punctuator::CloseBlock).is_some() { + break; + } + + elements + .push(PropertyDefinition::new(self.allow_yield, self.allow_await).parse(cursor)?); + + if cursor.next_if(Punctuator::CloseBlock).is_some() { + break; + } + + if cursor.next_if(Punctuator::Comma).is_none() { + let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?; + return Err(ParseError::Expected( + vec![ + TokenKind::Punctuator(Punctuator::Comma), + TokenKind::Punctuator(Punctuator::CloseBlock), + ], + next_token.clone(), + "object literal", + )); + } + } + + Ok(Node::Object(elements)) + } +} + +/// Parses a property definition. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition +#[derive(Debug, Clone, Copy)] +struct PropertyDefinition { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl PropertyDefinition { + /// Creates a new `PropertyDefinition` parser. + fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for PropertyDefinition { + type Output = node::PropertyDefinition; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + if cursor.next_if(Punctuator::Spread).is_some() { + let node = AssignmentExpression::new(true, self.allow_yield, self.allow_await) + .parse(cursor)?; + return Ok(node::PropertyDefinition::SpreadObject(node)); + } + + let prop_name = cursor + .next() + .map(Token::to_string) + .ok_or(ParseError::AbruptEnd)?; + 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)); + } + + if cursor + .next_if(TokenKind::Punctuator(Punctuator::OpenParen)) + .is_some() + || ["get", "set"].contains(&prop_name.as_str()) + { + return MethodDefinition::new(self.allow_yield, self.allow_await, prop_name) + .parse(cursor); + } + + let pos = cursor + .peek(0) + .map(|tok| tok.pos) + .ok_or(ParseError::AbruptEnd)?; + Err(ParseError::General( + "expected property definition", + Some(pos), + )) + } +} + +/// Parses a method definition. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition +#[derive(Debug, Clone)] +struct MethodDefinition { + allow_yield: AllowYield, + allow_await: AllowAwait, + identifier: String, +} + +impl MethodDefinition { + /// Creates a new `MethodDefinition` parser. + fn new(allow_yield: Y, allow_await: A, identifier: I) -> Self + where + Y: Into, + A: Into, + I: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + identifier: identifier.into(), + } + } +} + +impl TokenParser for MethodDefinition { + type Output = node::PropertyDefinition; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let (methodkind, prop_name, params) = match self.identifier.as_str() { + idn @ "get" | idn @ "set" => { + let prop_name = cursor + .next() + .map(Token::to_string) + .ok_or(ParseError::AbruptEnd)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "property method definition", + )?; + let first_param = cursor.peek(0).expect("current token disappeared").clone(); + let params = FormalParameters::new(false, false).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "method definition")?; + if idn == "get" { + if !params.is_empty() { + return Err(ParseError::Unexpected( + first_param, + Some("getter functions must have no arguments"), + )); + } + (MethodDefinitionKind::Get, prop_name, params) + } else { + if params.len() != 1 { + return Err(ParseError::Unexpected( + first_param, + Some("setter functions must have one argument"), + )); + } + (MethodDefinitionKind::Set, prop_name, params) + } + } + prop_name => { + let params = FormalParameters::new(false, false).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "method definition")?; + ( + MethodDefinitionKind::Ordinary, + prop_name.to_string(), + params, + ) + } + }; + + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenBlock), + "property method definition", + )?; + let body = FunctionBody::new(false, false) + .parse(cursor) + .map(Node::StatementList)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseBlock), + "property method definition", + )?; + + Ok(node::PropertyDefinition::MethodDefinition( + methodkind, + prop_name, + Node::FunctionDecl(None, params, Box::new(body)), + )) + } +} + +/// Initializer parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-Initializer +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct Initializer { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl Initializer { + /// Creates a new `Initializer` parser. + pub(in crate::syntax::parser) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + ) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for Initializer { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(TokenKind::Punctuator(Punctuator::Assign), "initializer")?; + AssignmentExpression::new(self.allow_in, self.allow_yield, self.allow_await).parse(cursor) + } +} diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs new file mode 100644 index 0000000000..7e2f6415c9 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -0,0 +1,131 @@ +use crate::syntax::{ + ast::node::{FormalParameter, MethodDefinitionKind, Node, PropertyDefinition}, + parser::tests::check_parser, +}; + +/// Checks object literal parsing. +#[test] +fn check_object_literal() { + let object_properties = vec![ + PropertyDefinition::property("a", Node::const_node(true)), + PropertyDefinition::property("b", Node::const_node(false)), + ]; + + check_parser( + "const x = { + a: true, + b: false, + }; + ", + &[Node::const_decl(vec![( + String::from("x"), + Node::Object(object_properties), + )])], + ); +} + +/// Tests short function syntax. +#[test] +fn check_object_short_function() { + let object_properties = vec![ + PropertyDefinition::property("a", Node::const_node(true)), + PropertyDefinition::method_definition( + MethodDefinitionKind::Ordinary, + "b", + Node::function_decl::<_, String, _, _>(None, Vec::new(), Node::StatementList(vec![])), + ), + ]; + + check_parser( + "const x = { + a: true, + b() {}, + }; + ", + &[Node::ConstDecl(vec![( + String::from("x"), + Node::Object(object_properties), + )])], + ); +} + +/// Testing short function syntax with arguments. +#[test] +fn check_object_short_function_arguments() { + let object_properties = vec![ + PropertyDefinition::property("a", Node::const_node(true)), + PropertyDefinition::method_definition( + MethodDefinitionKind::Ordinary, + "b", + Node::function_decl::<_, String, _, _>( + None, + vec![FormalParameter::new("test", None, false)], + Node::StatementList(Vec::new()), + ), + ), + ]; + + check_parser( + "const x = { + a: true, + b(test) {} + }; + ", + &[Node::ConstDecl(vec![( + String::from("x"), + Node::Object(object_properties), + )])], + ); +} + +#[test] +fn check_object_getter() { + let object_properties = vec![ + PropertyDefinition::property("a", Node::const_node(true)), + PropertyDefinition::method_definition( + MethodDefinitionKind::Get, + "b", + Node::FunctionDecl(None, Vec::new(), Box::new(Node::StatementList(Vec::new()))), + ), + ]; + + check_parser( + "const x = { + a: true, + get b() {} + }; + ", + &[Node::ConstDecl(vec![( + String::from("x"), + Node::Object(object_properties), + )])], + ); +} + +#[test] +fn check_object_setter() { + let object_properties = vec![ + PropertyDefinition::property("a", Node::const_node(true)), + PropertyDefinition::method_definition( + MethodDefinitionKind::Set, + "b", + Node::FunctionDecl( + None, + vec![FormalParameter::new("test", None, false)], + Box::new(Node::StatementList(Vec::new())), + ), + ), + ]; + + check_parser( + "const x = { + a: true, + set b(test) {} + }; + ", + &[Node::ConstDecl(vec![( + String::from("x"), + Node::Object(object_properties), + )])], + ); +} diff --git a/boa/src/syntax/parser/expression/primary/tests.rs b/boa/src/syntax/parser/expression/primary/tests.rs new file mode 100644 index 0000000000..a1c346a2c8 --- /dev/null +++ b/boa/src/syntax/parser/expression/primary/tests.rs @@ -0,0 +1,10 @@ +use crate::syntax::{ast::node::Node, parser::tests::check_parser}; + +#[test] +fn check_string() { + // Check empty string + check_parser("\"\"", &[Node::const_node("")]); + + // Check non-empty string + check_parser("\"hello\"", &[Node::const_node("hello")]); +} diff --git a/boa/src/syntax/parser/expression/tests.rs b/boa/src/syntax/parser/expression/tests.rs new file mode 100644 index 0000000000..5e6819cce4 --- /dev/null +++ b/boa/src/syntax/parser/expression/tests.rs @@ -0,0 +1,293 @@ +use crate::syntax::{ + ast::node::Node, + ast::op::{AssignOp, BinOp, BitOp, NumOp}, + parser::tests::check_parser, +}; + +/// Checks numeric operations +#[test] +fn check_numeric_operations() { + check_parser( + "a + b", + &[Node::bin_op(NumOp::Add, Node::local("a"), Node::local("b"))], + ); + check_parser( + "a+1", + &[Node::bin_op( + NumOp::Add, + Node::local("a"), + Node::const_node(1.0), + )], + ); + check_parser( + "a - b", + &[Node::bin_op(NumOp::Sub, Node::local("a"), Node::local("b"))], + ); + check_parser( + "a-1", + &[Node::bin_op( + NumOp::Sub, + Node::local("a"), + Node::const_node(1.0), + )], + ); + check_parser( + "a / b", + &[Node::bin_op(NumOp::Div, Node::local("a"), Node::local("b"))], + ); + check_parser( + "a/2", + &[Node::bin_op( + NumOp::Div, + Node::local("a"), + Node::const_node(2.0), + )], + ); + check_parser( + "a * b", + &[Node::bin_op(NumOp::Mul, Node::local("a"), Node::local("b"))], + ); + check_parser( + "a*2", + &[Node::bin_op( + NumOp::Mul, + Node::local("a"), + Node::const_node(2.0), + )], + ); + check_parser( + "a ** b", + &[Node::bin_op(NumOp::Exp, Node::local("a"), Node::local("b"))], + ); + check_parser( + "a**2", + &[Node::bin_op( + NumOp::Exp, + Node::local("a"), + Node::const_node(2.0), + )], + ); + check_parser( + "a % b", + &[Node::bin_op(NumOp::Mod, Node::local("a"), Node::local("b"))], + ); + check_parser( + "a%2", + &[Node::bin_op( + NumOp::Mod, + Node::local("a"), + Node::const_node(2.0), + )], + ); +} + +// Checks complex numeric operations. +#[test] +fn check_complex_numeric_operations() { + check_parser( + "a + d*(b-3)+1", + &[Node::bin_op( + NumOp::Add, + Node::bin_op( + NumOp::Add, + Node::local("a"), + Node::bin_op( + NumOp::Mul, + Node::local("d"), + Node::bin_op(NumOp::Sub, Node::local("b"), Node::const_node(3.0)), + ), + ), + Node::const_node(1.0), + )], + ); +} + +/// Checks bitwise operations. +#[test] +fn check_bitwise_operations() { + check_parser( + "a & b", + &[Node::bin_op( + BinOp::Bit(BitOp::And), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a&b", + &[Node::bin_op( + BinOp::Bit(BitOp::And), + Node::local("a"), + Node::local("b"), + )], + ); + + check_parser( + "a | b", + &[Node::bin_op( + BinOp::Bit(BitOp::Or), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a|b", + &[Node::bin_op( + BinOp::Bit(BitOp::Or), + Node::local("a"), + Node::local("b"), + )], + ); + + check_parser( + "a ^ b", + &[Node::bin_op( + BinOp::Bit(BitOp::Xor), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a^b", + &[Node::bin_op( + BinOp::Bit(BitOp::Xor), + Node::local("a"), + Node::local("b"), + )], + ); + + check_parser( + "a << b", + &[Node::bin_op( + BinOp::Bit(BitOp::Shl), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a<> b", + &[Node::bin_op( + BinOp::Bit(BitOp::Shr), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a>>b", + &[Node::bin_op( + BinOp::Bit(BitOp::Shr), + Node::local("a"), + Node::local("b"), + )], + ); +} + +/// Checks assignment operations. +#[test] +fn check_assign_operations() { + check_parser( + "a += b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Add), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a -= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Sub), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a *= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Mul), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a **= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Exp), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a /= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Div), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a %= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Mod), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a &= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::And), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a |= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Or), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a ^= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Xor), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a <<= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Shl), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a >>= b", + &[Node::bin_op( + BinOp::Assign(AssignOp::Shr), + Node::local("a"), + Node::local("b"), + )], + ); + check_parser( + "a %= 10 / 2", + &[Node::bin_op( + BinOp::Assign(AssignOp::Mod), + Node::local("a"), + Node::bin_op(NumOp::Div, Node::const_node(10.0), Node::const_node(2.0)), + )], + ); +} diff --git a/boa/src/syntax/parser/expression/unary.rs b/boa/src/syntax/parser/expression/unary.rs new file mode 100644 index 0000000000..14a2c7a29b --- /dev/null +++ b/boa/src/syntax/parser/expression/unary.rs @@ -0,0 +1,79 @@ +//! Unary operator parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary +//! [spec]: https://tc39.es/ecma262/#sec-unary-operators + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, op::UnaryOp, punc::Punctuator, token::TokenKind}, + parser::{ + expression::update::UpdateExpression, AllowAwait, AllowYield, Cursor, ParseError, + ParseResult, TokenParser, + }, +}; + +/// Parses a unary expression. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary +/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct UnaryExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl UnaryExpression { + /// Creates a new `UnaryExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for UnaryExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + match tok.kind { + TokenKind::Keyword(Keyword::Delete) => { + Ok(Node::unary_op(UnaryOp::Delete, self.parse(cursor)?)) + } + TokenKind::Keyword(Keyword::Void) => { + Ok(Node::unary_op(UnaryOp::Void, self.parse(cursor)?)) + } + TokenKind::Keyword(Keyword::TypeOf) => { + Ok(Node::unary_op(UnaryOp::TypeOf, self.parse(cursor)?)) + } + TokenKind::Punctuator(Punctuator::Add) => { + Ok(Node::unary_op(UnaryOp::Plus, self.parse(cursor)?)) + } + TokenKind::Punctuator(Punctuator::Sub) => { + Ok(Node::unary_op(UnaryOp::Minus, self.parse(cursor)?)) + } + TokenKind::Punctuator(Punctuator::Neg) => { + Ok(Node::unary_op(UnaryOp::Tilde, self.parse(cursor)?)) + } + TokenKind::Punctuator(Punctuator::Not) => { + Ok(Node::unary_op(UnaryOp::Not, self.parse(cursor)?)) + } + _ => { + cursor.back(); + UpdateExpression::new(self.allow_yield, self.allow_await).parse(cursor) + } + } + } +} diff --git a/boa/src/syntax/parser/expression/update.rs b/boa/src/syntax/parser/expression/update.rs new file mode 100644 index 0000000000..5ba3d839e9 --- /dev/null +++ b/boa/src/syntax/parser/expression/update.rs @@ -0,0 +1,82 @@ +//! Update expression parsing. +//! +//! More information: +//! - [ECMAScript specification][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-update-expressions + +use super::left_hand_side::LeftHandSideExpression; +use crate::syntax::{ + ast::{node::Node, op::UnaryOp, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; + +/// Parses an update expression. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-UpdateExpression +#[derive(Debug, Clone, Copy)] +pub(super) struct UpdateExpression { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl UpdateExpression { + /// Creates a new `UpdateExpression` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for UpdateExpression { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; + match tok.kind { + TokenKind::Punctuator(Punctuator::Inc) => { + cursor.next().expect("token disappeared"); + return Ok(Node::unary_op( + UnaryOp::IncrementPre, + LeftHandSideExpression::new(self.allow_yield, self.allow_await) + .parse(cursor)?, + )); + } + TokenKind::Punctuator(Punctuator::Dec) => { + cursor.next().expect("token disappeared"); + return Ok(Node::unary_op( + UnaryOp::DecrementPre, + LeftHandSideExpression::new(self.allow_yield, self.allow_await) + .parse(cursor)?, + )); + } + _ => {} + } + + let lhs = LeftHandSideExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; + if let Some(tok) = cursor.peek(0) { + match tok.kind { + TokenKind::Punctuator(Punctuator::Inc) => { + cursor.next().expect("token disappeared"); + return Ok(Node::unary_op(UnaryOp::IncrementPost, lhs)); + } + TokenKind::Punctuator(Punctuator::Dec) => { + cursor.next().expect("token disappeared"); + return Ok(Node::unary_op(UnaryOp::DecrementPost, lhs)); + } + _ => {} + } + } + + Ok(lhs) + } +} diff --git a/boa/src/syntax/parser/function/mod.rs b/boa/src/syntax/parser/function/mod.rs new file mode 100644 index 0000000000..0f3c4a991a --- /dev/null +++ b/boa/src/syntax/parser/function/mod.rs @@ -0,0 +1,238 @@ +//! Function definition parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function +//! [spec]: https://tc39.es/ecma262/#sec-function-definitions + +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{ + node::{self, Node}, + punc::Punctuator, + token::TokenKind, + }, + parser::{statement::StatementList, AllowAwait, AllowYield, Cursor, ParseError, TokenParser}, +}; + +/// Formal parameters parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Parameter +/// [spec]: https://tc39.es/ecma262/#prod-FormalParameters +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct FormalParameters { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl FormalParameters { + /// Creates a new `FormalParameters` parser. + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for FormalParameters { + type Output = Vec; + + 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) + { + return Ok(params); + } + + loop { + let mut rest_param = false; + + params.push(if cursor.next_if(Punctuator::Spread).is_some() { + rest_param = true; + FunctionRestParameter::new(self.allow_yield, self.allow_await).parse(cursor)? + } else { + FormalParameter::new(self.allow_yield, self.allow_await).parse(cursor)? + }); + + if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind + == TokenKind::Punctuator(Punctuator::CloseParen) + { + break; + } + + if rest_param { + return Err(ParseError::Unexpected( + cursor + .peek_prev() + .expect("current token disappeared") + .clone(), + Some("rest parameter must be the last formal parameter"), + )); + } + + cursor.expect(Punctuator::Comma, "parameter list")?; + } + + Ok(params) + } +} + +/// Rest parameter parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters +/// [spec]: https://tc39.es/ecma262/#prod-FunctionRestParameter +#[derive(Debug, Clone, Copy)] +struct FunctionRestParameter { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl FunctionRestParameter { + /// Creates a new `FunctionRestParameter` parser. + fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for FunctionRestParameter { + type Output = node::FormalParameter; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let token = cursor.next().ok_or(ParseError::AbruptEnd)?; + Ok(Self::Output::new( + if let TokenKind::Identifier(name) = &token.kind { + name + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + token.clone(), + "rest parameter", + )); + }, + None, + true, + )) + } +} + +/// Formal parameter parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Parameter +/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter +#[derive(Debug, Clone, Copy)] +struct FormalParameter { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl FormalParameter { + /// Creates a new `FormalParameter` parser. + fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for FormalParameter { + type Output = node::FormalParameter; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let token = cursor.next().ok_or(ParseError::AbruptEnd)?; + let name = if let TokenKind::Identifier(name) = &token.kind { + name + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + token.clone(), + "formal parameter", + )); + }; + + // TODO: Implement initializer. + Ok(Self::Output::new(name, None, false)) + } +} + +/// A `FunctionBody` is equivalent to a `FunctionStatementList`. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FunctionBody +pub(in crate::syntax::parser) type FunctionBody = FunctionStatementList; + +/// A function statement list +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FunctionStatementList +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct FunctionStatementList { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl FunctionStatementList { + /// Creates a new `FunctionStatementList` parser. + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for FunctionStatementList { + type Output = Vec; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + if let Some(tk) = cursor.peek(0) { + if tk.kind == Punctuator::CloseBlock.into() { + return Ok(Vec::new()); + } + } + + StatementList::new(self.allow_yield, self.allow_await, true, true).parse(cursor) + } +} diff --git a/boa/src/syntax/parser/function/tests.rs b/boa/src/syntax/parser/function/tests.rs new file mode 100644 index 0000000000..9d334168f4 --- /dev/null +++ b/boa/src/syntax/parser/function/tests.rs @@ -0,0 +1,169 @@ +use crate::syntax::{ + ast::node::{FormalParameter, Node}, + ast::op::NumOp, + parser::tests::check_parser, +}; + +/// Checks basic function declaration parsing. +#[test] +fn check_basic() { + check_parser( + "function foo(a) { return a; }", + &[Node::function_decl( + "foo", + vec![FormalParameter::new("a", None, false)], + Node::StatementList(vec![Node::return_node(Node::local("a"))]), + )], + ); +} + +/// Checks basic function declaration parsing with automatic semicolon insertion. +#[test] +fn check_basic_semicolon_insertion() { + check_parser( + "function foo(a) { return a }", + &[Node::function_decl( + "foo", + vec![FormalParameter::new("a", None, false)], + Node::StatementList(vec![Node::return_node(Node::local("a"))]), + )], + ); +} + +/// Checks functions with empty returns. +#[test] +fn check_empty_return() { + check_parser( + "function foo(a) { return; }", + &[Node::function_decl( + "foo", + vec![FormalParameter::new("a", None, false)], + Node::StatementList(vec![Node::Return(None)]), + )], + ); +} + +/// Checks functions with empty returns without semicolon +#[test] +fn check_empty_return_semicolon_insertion() { + check_parser( + "function foo(a) { return }", + &[Node::function_decl( + "foo", + vec![FormalParameter::new("a", None, false)], + Node::StatementList(vec![Node::Return(None)]), + )], + ); +} + +/// Checks rest operator parsing. +#[test] +fn check_rest_operator() { + check_parser( + "function foo(a, ...b) {}", + &[Node::function_decl( + "foo", + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("b", None, true), + ], + Node::StatementList(Vec::new()), + )], + ); +} + +/// Checks an arrow function with only a rest parameter. +#[test] +fn check_arrow_only_rest() { + check_parser( + "(...a) => {}", + &[Node::arrow_function_decl( + vec![FormalParameter::new("a", None, true)], + Node::StatementList(Vec::new()), + )], + ); +} + +/// Checks an arrow function with a rest parameter. +#[test] +fn check_arrow_rest() { + check_parser( + "(a, b, ...c) => {}", + &[Node::arrow_function_decl( + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("b", None, false), + FormalParameter::new("c", None, true), + ], + Node::StatementList(Vec::new()), + )], + ); +} + +/// Checks an arrow function with expression return. +#[test] +fn check_arrow() { + check_parser( + "(a, b) => { return a + b; }", + &[Node::arrow_function_decl( + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("b", None, false), + ], + Node::StatementList(vec![Node::return_node(Node::bin_op( + NumOp::Add, + Node::local("a"), + Node::local("b"), + ))]), + )], + ); +} + +/// Checks an arrow function with expression return and automatic semicolon insertion +#[test] +fn check_arrow_semicolon_insertion() { + check_parser( + "(a, b) => { return a + b }", + &[Node::arrow_function_decl( + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("b", None, false), + ], + Node::StatementList(vec![Node::return_node(Node::bin_op( + NumOp::Add, + Node::local("a"), + Node::local("b"), + ))]), + )], + ); +} + +/// Checks arrow function with empty return +#[test] +fn check_arrow_epty_return() { + check_parser( + "(a, b) => { return; }", + &[Node::arrow_function_decl( + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("b", None, false), + ], + Node::StatementList(vec![Node::Return(None)]), + )], + ); +} + +/// Checks an arrow function with empty return, with automatic semicolon insertion. +#[test] +fn check_arrow_empty_return_semicolon_insertion() { + check_parser( + "(a, b) => { return }", + &[Node::arrow_function_decl( + vec![ + FormalParameter::new("a", None, false), + FormalParameter::new("b", None, false), + ], + Node::StatementList(vec![Node::Return(None)]), + )], + ); +} diff --git a/boa/src/syntax/parser/mod.rs b/boa/src/syntax/parser/mod.rs index f23a41490e..bab436d94c 100644 --- a/boa/src/syntax/parser/mod.rs +++ b/boa/src/syntax/parser/mod.rs @@ -1,1748 +1,147 @@ //! Boa parser implementation. mod cursor; +pub mod error; +mod expression; +mod function; +mod statement; #[cfg(test)] mod tests; -use crate::syntax::ast::{ - constant::Const, - keyword::Keyword, - node::{FormalParameter, FormalParameters, MethodDefinitionKind, Node, PropertyDefinition}, - op::{AssignOp, BinOp, NumOp, UnaryOp}, - pos::Position, - punc::Punctuator, - token::{Token, TokenKind}, -}; +use self::error::{ParseError, ParseResult}; +use crate::syntax::ast::{node::Node, token::Token}; use cursor::Cursor; -use std::fmt; -/// `ParseError` is an enum which represents errors encounted during parsing an expression -#[derive(Debug, Clone)] -pub enum ParseError { - /// When it expected a certain kind of token, but got another as part of something - Expected(Vec, Token, Option<&'static str>), - /// When it expected a certain expression, but got another - ExpectedExpr(&'static str, Node, Position), - /// When it didn't expect this keyword - UnexpectedKeyword(Keyword, Position), - /// When a token is unexpected - Unexpected(Token, Option<&'static str>), - /// When there is an abrupt end to the parsing - AbruptEnd, - /// Out of range error, attempting to set a position where there is no token - RangeError, - /// Catch all General Error - General(&'static str, Option), -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ParseError::Expected(expected, actual, routine) => write!( - f, - "Expected {}, got '{}'{} at line {}, col {}", - if expected.len() == 1 { - format!( - "token '{}'", - expected.first().map(TokenKind::to_string).unwrap() - ) - } else { - format!( - "one of {}", - expected - .iter() - .enumerate() - .map(|(i, t)| { - format!( - "{}'{}'", - if i == 0 { - "" - } else if i == expected.len() - 1 { - " or " - } else { - ", " - }, - t - ) - }) - .collect::() - ) - }, - actual, - if let Some(routine) = routine { - format!(" in {}", routine) - } else { - String::new() - }, - actual.pos.line_number, - actual.pos.column_number - ), - ParseError::ExpectedExpr(expected, actual, pos) => write!( - f, - "Expected expression '{}', got '{}' at line {}, col {}", - expected, actual, pos.line_number, pos.column_number - ), - ParseError::UnexpectedKeyword(keyword, pos) => write!( - f, - "Unexpected keyword: '{}' at line {}, col {}", - keyword, pos.line_number, pos.column_number - ), - ParseError::Unexpected(tok, msg) => write!( - f, - "Unexpected Token '{}'{} at line {}, col {}", - tok, - if let Some(m) = msg { - format!(", {}", m) - } else { - String::new() - }, - tok.pos.line_number, - tok.pos.column_number - ), - ParseError::AbruptEnd => write!(f, "Abrupt End"), - ParseError::General(msg, pos) => write!( - f, - "{}{}", - msg, - if let Some(pos) = pos { - format!(" at line {}, col {}", pos.line_number, pos.column_number) - } else { - String::new() - } - ), - ParseError::RangeError => write!(f, "RangeError!"), - } - } -} - -pub type ParseResult = Result; - -#[derive(Debug)] -pub struct Parser<'a> { - /// Cursor in the parser, the internal structure used to read tokens. - cursor: Cursor<'a>, -} - -macro_rules! expression { ( $name:ident, $lower:ident, [ $( $op:path ),* ] ) => { - fn $name (&mut self) -> ParseResult { - let mut lhs = self. $lower ()?; - while let Some(tok) = self.peek_skip_lineterminator() { - match tok.kind { - TokenKind::Punctuator(ref op) if $( op == &$op )||* => { - let _ = self.next_skip_lineterminator().expect("token disappeared"); - lhs = Node::BinOp( - op.as_binop().unwrap(), - Box::new(lhs), - Box::new(self. $lower ()?) - ) - } - _ => break - } - } - Ok(lhs) - } -} } - -impl<'a> Parser<'a> { - /// Create a new parser, using `tokens` as input - pub fn new(tokens: &'a [Token]) -> Self { - Self { - cursor: Cursor::new(tokens), - } - } - - /// Parse all expressions in the token array - pub fn parse_all(&mut self) -> ParseResult { - self.read_statement_list() - } - - /// consume the next token and increment position - fn get_next_token(&mut self) -> Result<&Token, ParseError> { - self.cursor.next().ok_or(ParseError::AbruptEnd) - } - - /// Peek the next token skipping line terminators. - pub fn peek_skip_lineterminator(&mut self) -> Option<&'a Token> { - self.cursor - .peek_skip(|tk| tk.kind == TokenKind::LineTerminator) - } - - /// Consume the next token skipping line terminators. - pub fn next_skip_lineterminator(&mut self) -> Option<&'a Token> { - self.cursor - .next_skip(|tk| tk.kind == TokenKind::LineTerminator) - } +/// Trait implemented by parsers. +/// +/// This makes it possible to abstract over the underlying implementation of a parser. +trait TokenParser: Sized { + /// Output type for the parser. + type Output; // = Node; waiting for https://github.com/rust-lang/rust/issues/29661 - /// Advance the cursor to the next token and retrieve it, only if it's of `kind` type. + /// Parses the token stream using the current parser. /// - /// When the next token is a `kind` token, get the token, otherwise return `None`. - fn next_if(&mut self, kind: TokenKind) -> Option<&'a Token> { - let next_token = self.cursor.peek(0)?; - - if next_token.kind == kind { - self.cursor.next() - } else { - None - } - } + /// This method needs to be provided by the implementor type. + fn parse(self, cursor: &mut Cursor<'_>) -> Result; - /// Advance the cursor to the next token and retrieve it, only if it's of `kind` type. + /// Tries to parse the following tokens with this parser. /// - /// When the next token is a `kind` token, get the token, otherwise return `None`. This - /// function skips line terminators. - fn next_if_skip_lineterminator(&mut self, kind: TokenKind) -> Option<&'a Token> { - let next_token = self.peek_skip_lineterminator()?; - - if next_token.kind == kind { - self.next_skip_lineterminator() - } else { - None - } - } - - /// Returns an error if the next Punctuator is not `tk` - fn expect(&mut self, kind: TokenKind, routine: Option<&'static str>) -> Result<(), ParseError> { - let next_token = self.cursor.next().ok_or(ParseError::AbruptEnd)?; - if next_token.kind == kind { - Ok(()) - } else { - Err(ParseError::Expected( - vec![kind], - next_token.clone(), - routine, - )) - } - } - - /// Returns an error if the next symbol is not the punctuator `p` - /// Consumes the next symbol otherwise - fn expect_punc( - &mut self, - p: Punctuator, - routine: Option<&'static str>, - ) -> Result<(), ParseError> { - self.expect(TokenKind::Punctuator(p), routine) - } - - /// Reads a list of statements as a `Node::StatementList`. - /// - /// It will end at the end of file. - fn read_statement_list(&mut self) -> ParseResult { - self.read_statements(false).map(Node::StatementList) - } - - /// Reads a code block as a `Node::Block`. - /// - /// Note: it will start after the `{` character and stop after reading `}`. - fn read_block_statement(&mut self) -> ParseResult { - self.read_statements(true).map(Node::Block) - } - - /// Read a list of statements and stop after `}` - /// - /// Note: it will start after the `{` character and stop after reading `}`. - fn read_block(&mut self) -> ParseResult { - self.read_statements(true).map(Node::StatementList) - } - - /// Reads a list of statements. - /// - /// If `break_when_closingbrase` is `true`, it will stop as soon as it finds a `}` character. - fn read_statements(&mut self, break_when_closingbrase: bool) -> Result, ParseError> { - let mut items = Vec::new(); - - loop { - if let Some(token) = - self.next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::CloseBlock)) - { - if break_when_closingbrase { - break; - } else { - return Err(ParseError::Unexpected(token.clone(), None)); - } - } - - if self.peek_skip_lineterminator().is_none() { - if break_when_closingbrase { - return Err(ParseError::Expected( - vec![TokenKind::Punctuator(Punctuator::CloseBlock)], - self.get_next_token()?.clone(), - None, - )); - } else { - break; - } - }; - - let item = self.read_statement_list_item()?; - items.push(item); - - // move the cursor forward for any consecutive semicolon. - while self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Semicolon)) - .is_some() - {} - } - - Ok(items) - } - - /// Reads an individual statement list item. - /// - /// A statement list item can either be an statement or a declaration. - /// - /// More information: - /// - ECMAScript reference: . - /// - MDN information page about statements and declarations: - /// . - fn read_statement_list_item(&mut self) -> ParseResult { - if let Some(tok) = self.peek_skip_lineterminator() { - match tok.kind { - TokenKind::Keyword(Keyword::Function) - | TokenKind::Keyword(Keyword::Const) - | TokenKind::Keyword(Keyword::Let) => self.read_declaration(), - _ => self.read_statement(), - } - } else { - Err(ParseError::AbruptEnd) - } - } - - /// Parses a declaration. - /// - /// More information:: - fn read_declaration(&mut self) -> ParseResult { - if let Some(tok) = self.next_skip_lineterminator() { - match tok.kind { - TokenKind::Keyword(Keyword::Function) => self.read_function_declaration(), - TokenKind::Keyword(Keyword::Const) => self.read_binding_list(true), - TokenKind::Keyword(Keyword::Let) => self.read_binding_list(false), - _ => unreachable!(), - } - } else { - Err(ParseError::AbruptEnd) - } - } - - /// Reads a binding list. - /// - /// It will return an error if a `const` declaration is being parsed and there is no - /// initializer. - /// - /// More information: . - fn read_binding_list(&mut self, is_const: bool) -> ParseResult { - // Create vectors to store the variable declarations - // Const and Let signatures are slightly different, Const needs definitions, Lets don't - let mut let_decls = Vec::new(); - let mut const_decls = Vec::new(); - - loop { - let token = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(ref name) = token.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_owned())], - token.clone(), - if is_const { - Some("const declaration") - } else { - Some("let declaration") - }, - )); - }; - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Assign)) - .is_some() - { - let init = Some(self.read_initializer()?); - if is_const { - const_decls.push((name, init.unwrap())); - } else { - let_decls.push((name, init)); - }; - } else if is_const { - return Err(ParseError::Expected( - vec![TokenKind::Punctuator(Punctuator::Assign)], - self.next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)? - .clone(), - Some("const declaration"), - )); - } else { - let_decls.push((name, None)); - } - - if !self.lexical_declaration_continuation()? { - break; - } - } - - if is_const { - Ok(Node::ConstDecl(const_decls)) - } else { - Ok(Node::LetDecl(let_decls)) - } - } - - /// Parses a function declaration. - /// - /// More information: - /// - ECMAScript specification: . - /// - MDN documentation: - /// - fn read_function_declaration(&mut self) -> ParseResult { - let token = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(name) = &token.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::Identifier(String::from("function name"))], - token.clone(), - Some("function declaration"), - )); - }; - - self.expect( - TokenKind::Punctuator(Punctuator::OpenParen), - Some("function declaration"), - )?; - - let params = self.read_formal_parameters()?; - - self.expect( - TokenKind::Punctuator(Punctuator::OpenBlock), - Some("function declaration"), - )?; - - let body = self.read_block()?; - - Ok(Node::FunctionDecl(Some(name), params, Box::new(body))) - } - - /// - fn read_statement(&mut self) -> ParseResult { - let tok = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - - let mut is_expression_statement = false; - let stmt = match tok.kind { - TokenKind::Keyword(Keyword::If) => self.read_if_statement(), - TokenKind::Keyword(Keyword::Var) => self.read_variable_statement(), - TokenKind::Keyword(Keyword::While) => self.read_while_statement(), - TokenKind::Keyword(Keyword::Do) => self.read_do_while_statement(), - TokenKind::Keyword(Keyword::For) => self.read_for_statement(), - TokenKind::Keyword(Keyword::Return) => self.read_return_statement(), - TokenKind::Keyword(Keyword::Break) => self.read_break_statement(), - TokenKind::Keyword(Keyword::Continue) => self.read_continue_statement(), - TokenKind::Keyword(Keyword::Try) => self.read_try_statement(), - TokenKind::Keyword(Keyword::Throw) => self.read_throw_statement(), - TokenKind::Keyword(Keyword::Switch) => self.read_switch_statement(), - TokenKind::Punctuator(Punctuator::OpenBlock) => self.read_block_statement(), - // TODO: https://tc39.es/ecma262/#prod-LabelledStatement - // TokenKind::Punctuator(Punctuator::Semicolon) => { - // return Ok(Node::new(NodeBase::Nope, tok.pos)) - // } - _ => { - self.cursor.back(); - is_expression_statement = true; - self.read_expression_statement() - } - }; - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Semicolon)) - .is_none() - && is_expression_statement - { - if let Some(tok) = self.cursor.peek(0) { - if tok.kind != TokenKind::LineTerminator - && tok.kind != TokenKind::Punctuator(Punctuator::CloseBlock) - { - return Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::Semicolon), - TokenKind::Punctuator(Punctuator::CloseBlock), - TokenKind::LineTerminator, - ], - tok.clone(), - None, - )); - } - } - } - - stmt - } - - /// - fn read_switch_statement(&mut self) -> ParseResult { - // TODO: Reimplement the switch statement in the new parser. - unimplemented!("Switch statement parsing is not implemented"); - } - - /// - fn read_expression_statement(&mut self) -> ParseResult { - self.read_expression() - } - - /// - fn read_break_statement(&mut self) -> ParseResult { - let tok = self.get_next_token()?; - match &tok.kind { - TokenKind::LineTerminator - | TokenKind::Punctuator(Punctuator::Semicolon) - | TokenKind::Punctuator(Punctuator::CloseParen) => { - self.cursor.back(); - Ok(Node::Break(None)) - } - TokenKind::Identifier(name) => Ok(Node::Break(Some(name.clone()))), - _ => Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::Semicolon), - TokenKind::Punctuator(Punctuator::CloseParen), - TokenKind::LineTerminator, - TokenKind::Identifier("identifier".to_owned()), - ], - tok.clone(), - Some("break statement"), - )), - } - } - - /// - fn read_continue_statement(&mut self) -> ParseResult { - let tok = self.get_next_token()?; - match &tok.kind { - TokenKind::LineTerminator - | TokenKind::Punctuator(Punctuator::Semicolon) - | TokenKind::Punctuator(Punctuator::CloseBlock) => { - self.cursor.back(); - Ok(Node::Continue(None)) - } - TokenKind::Identifier(name) => Ok(Node::Continue(Some(name.clone()))), - _ => Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::Semicolon), - TokenKind::LineTerminator, - TokenKind::Punctuator(Punctuator::CloseBlock), - ], - tok.clone(), - Some("continue statement"), - )), - } - } - - /// - fn read_throw_statement(&mut self) -> ParseResult { - if let Some(tok) = self.cursor.peek(0) { - match tok.kind { - TokenKind::LineTerminator // no `LineTerminator` here - | TokenKind::Punctuator(Punctuator::Semicolon) - | TokenKind::Punctuator(Punctuator::CloseBlock) => { - return Err(ParseError::Unexpected(tok.clone(), Some("throw statement"))); - } - _ => {} - } - } - - let expr = self.read_expression()?; - if let Some(tok) = self.cursor.peek(0) { - if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) { - let _ = self.cursor.next(); - } - } - - Ok(Node::Throw(Box::new(expr))) - } - - /// - fn read_return_statement(&mut self) -> ParseResult { - if let Some(tok) = self.cursor.peek(0) { - match tok.kind { - TokenKind::LineTerminator | TokenKind::Punctuator(Punctuator::Semicolon) => { - let _ = self.cursor.next(); - return Ok(Node::Return(None)); - } - TokenKind::Punctuator(Punctuator::CloseBlock) => { - return Ok(Node::Return(None)); - } - _ => {} - } - } - - let expr = self.read_expression()?; - if let Some(tok) = self.cursor.peek(0) { - if tok.kind == TokenKind::Punctuator(Punctuator::CloseBlock) { - let _ = self.cursor.next(); - } - } - - Ok(Node::Return(Some(Box::new(expr)))) - } - - /// - fn read_if_statement(&mut self) -> ParseResult { - self.expect_punc(Punctuator::OpenParen, Some("if statement"))?; - - let cond = self.read_expression()?; - - self.expect_punc(Punctuator::CloseParen, Some("if statement"))?; - - let then_stm = self.read_statement()?; - - if let Some(else_tok) = self.cursor.next() { - if else_tok.kind == TokenKind::Keyword(Keyword::Else) { - let else_stm = self.read_statement()?; - return Ok(Node::If( - Box::new(cond), - Box::new(then_stm), - Some(Box::new(else_stm)), - )); - } else { - self.cursor.back(); - } - } - - Ok(Node::If(Box::new(cond), Box::new(then_stm), None)) - } - - /// - fn read_while_statement(&mut self) -> ParseResult { - self.expect_punc(Punctuator::OpenParen, Some("while statement"))?; - - let cond = self.read_expression()?; - - self.expect_punc(Punctuator::CloseParen, Some("while statement"))?; - - let body = self.read_statement()?; - - Ok(Node::WhileLoop(Box::new(cond), Box::new(body))) - } - - /// https://tc39.es/ecma262/#sec-do-while-statement - fn read_do_while_statement(&mut self) -> ParseResult { - let body = self.read_statement()?; - - let next_token = self - .peek_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - - if next_token.kind != TokenKind::Keyword(Keyword::While) { - return Err(ParseError::Expected( - vec![TokenKind::Keyword(Keyword::While)], - next_token.clone(), - Some("do while statement"), - )); - } - - let _ = self.next_skip_lineterminator(); // skip while token - - self.expect_punc(Punctuator::OpenParen, Some("do while statement"))?; - - let cond = self.read_expression()?; - - self.expect_punc(Punctuator::CloseParen, Some("do while statement"))?; - - Ok(Node::DoWhileLoop(Box::new(body), Box::new(cond))) - } - - /// - fn read_try_statement(&mut self) -> ParseResult { - // TRY - self.expect_punc(Punctuator::OpenBlock, Some("try statement"))?; - let try_clause = self.read_block_statement()?; - - let next_token = self - .peek_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - - if next_token.kind != TokenKind::Keyword(Keyword::Catch) - && next_token.kind != TokenKind::Keyword(Keyword::Finally) - { - return Err(ParseError::Expected( - vec![ - TokenKind::Keyword(Keyword::Catch), - TokenKind::Keyword(Keyword::Finally), - ], - next_token.clone(), - Some("try statement"), - )); - } - - // CATCH - let (catch, param) = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { - let _ = self.next_skip_lineterminator(); // Advance the cursor - - // Catch binding - self.expect_punc(Punctuator::OpenParen, Some("catch in try statement"))?; - // TODO: should accept BindingPattern - let tok = self.get_next_token()?; - let catch_param = if let TokenKind::Identifier(s) = &tok.kind { - Node::Local(s.clone()) - } else { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_owned())], - tok.clone(), - Some("catch in try statement"), - )); - }; - self.expect_punc(Punctuator::CloseParen, Some("catch in try statement"))?; - - // Catch block - self.expect_punc(Punctuator::OpenBlock, Some("catch in try statement"))?; - ( - Some(Box::new(self.read_block()?)), - Some(Box::new(catch_param)), - ) + /// It will return the cursor to the initial position if an error occurs during parsing. + fn try_parse(self, cursor: &mut Cursor<'_>) -> Option { + let initial_pos = cursor.pos(); + if let Ok(node) = self.parse(cursor) { + Some(node) } else { - (None, None) - }; - - // FINALLY - let finally_block = if self - .next_if_skip_lineterminator(TokenKind::Keyword(Keyword::Finally)) - .is_some() - { - self.expect_punc(Punctuator::OpenBlock, Some("finally in try statement"))?; - Some(Box::new(self.read_block_statement()?)) - } else { - None - }; - - Ok(Node::Try(Box::new(try_clause), catch, param, finally_block)) - } - - /// - fn read_for_statement(&mut self) -> ParseResult { - self.expect_punc(Punctuator::OpenParen, Some("for statement"))?; - - let init = match self.cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind { - TokenKind::Keyword(Keyword::Var) => { - Some(Box::new(self.read_variable_declaration_list()?)) - } - TokenKind::Keyword(Keyword::Let) | TokenKind::Keyword(Keyword::Const) => { - Some(Box::new(self.read_declaration()?)) - } - TokenKind::Punctuator(Punctuator::Semicolon) => None, - _ => Some(Box::new(self.read_expression()?)), - }; - - self.expect_punc(Punctuator::Semicolon, Some("for statement"))?; - - let cond = if self - .next_if(TokenKind::Punctuator(Punctuator::Semicolon)) - .is_some() - { - Some(Box::new(Node::Const(Const::Bool(true)))) - } else { - let step = Some(Box::new(self.read_expression()?)); - self.expect_punc(Punctuator::Semicolon, Some("for statement"))?; - step - }; - - let step = if self - .next_if(TokenKind::Punctuator(Punctuator::CloseParen)) - .is_some() - { + cursor.seek(initial_pos); None - } else { - let step = self.read_expression()?; - self.expect( - TokenKind::Punctuator(Punctuator::CloseParen), - Some("for statement"), - )?; - Some(Box::new(step)) - }; - - let body = Box::new(self.read_statement()?); - - let for_node = Node::ForLoop(init, cond, step, body); - - Ok(Node::Block(vec![for_node])) - } - /// - fn read_variable_statement(&mut self) -> ParseResult { - self.read_variable_declaration_list() - } - - /// - fn read_variable_declaration_list(&mut self) -> ParseResult { - let mut list = Vec::new(); - - loop { - list.push(self.read_variable_declaration()?); - if !self.lexical_declaration_continuation()? { - break; - } - } - - Ok(Node::VarDecl(list)) - } - - /// Checks if the lexical declaration continues with more bindings. - /// - /// If it does, it will advance the internal cursor to the next identifier token. - /// A Lexical Declaration continues its binding list if we find a `,` character. A New line - /// indicates the same as a `;`. - /// - /// More information: . - fn lexical_declaration_continuation(&mut self) -> Result { - if let Some(tok) = self.cursor.peek(0) { - match tok.kind { - TokenKind::LineTerminator => { - let _ = self.cursor.next().ok_or(ParseError::AbruptEnd)?; - Ok(false) - } - TokenKind::Punctuator(Punctuator::Semicolon) => Ok(false), - TokenKind::Punctuator(Punctuator::Comma) => { - let _ = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - Ok(true) - } - _ => Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::Semicolon), - TokenKind::LineTerminator, - ], - self.cursor.next().ok_or(ParseError::AbruptEnd)?.clone(), - Some("lexical declaration"), - )), - } - } else { - Ok(false) - } - } - - /// - fn read_variable_declaration(&mut self) -> Result<(String, Option), ParseError> { - let tok = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(name) = &tok.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_string())], - tok.clone(), - Some("variable declaration"), - )); - }; - - if self - .next_if(TokenKind::Punctuator(Punctuator::Assign)) - .is_some() - { - Ok((name, Some(self.read_initializer()?))) - } else { - Ok((name, None)) } } +} - /// Reads an initializer of variables. - /// - /// Note: it will expect the `=` token to have been already read. - /// - /// More information: - fn read_initializer(&mut self) -> ParseResult { - self.read_assignment_expression() - } - - // https://tc39.es/ecma262/#prod-Expression - expression!( - read_expression, - read_assignment_expression, - [Punctuator::Comma] - ); - - /// - fn read_assignment_expression(&mut self) -> ParseResult { - // Arrow function - let next_token = self.cursor.peek(0).ok_or(ParseError::AbruptEnd)?; - match next_token.kind { - // a=>{} - TokenKind::Identifier(_) => { - if let Some(tok) = self.cursor.peek(1) { - if tok.kind == TokenKind::Punctuator(Punctuator::Arrow) { - return self.read_arrow_function(); - } - } - } - // (a,b)=>{} - TokenKind::Punctuator(Punctuator::OpenParen) => { - // TODO: breakpoints in the cursor. - let save_pos = self.cursor.pos(); - let f = self.read_arrow_function(); // We try to read an arrow function. - if f.is_err() { - self.cursor.seek(save_pos); - } else { - return f; - } - } - _ => {} - } - - let mut lhs = self.read_conditional_expression()?; - // let mut lhs = self.read_block()?; - - if let Ok(tok) = self.get_next_token() { - match tok.kind { - TokenKind::Punctuator(Punctuator::Assign) => { - lhs = Node::Assign(Box::new(lhs), Box::new(self.read_assignment_expression()?)) - } - TokenKind::Punctuator(Punctuator::AssignAdd) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Add), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignSub) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Sub), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignMul) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Mul), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignDiv) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Div), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignAnd) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::And), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignOr) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Or), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignXor) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Xor), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignRightSh) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Shr), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignLeftSh) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Shl), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignMod) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Mod), Box::new(lhs), Box::new(expr)); - } - TokenKind::Punctuator(Punctuator::AssignPow) => { - let expr = self.read_assignment_expression()?; - lhs = Node::BinOp(BinOp::Assign(AssignOp::Exp), Box::new(lhs), Box::new(expr)); - } - _ => { - self.cursor.back(); - } - } - } - - Ok(lhs) - } - - /// - fn read_conditional_expression(&mut self) -> ParseResult { - let lhs = self.read_logical_or_expression()?; - - if let Ok(tok) = self.get_next_token() { - if tok.kind == TokenKind::Punctuator(Punctuator::Question) { - let then_ = self.read_assignment_expression()?; - self.expect_punc(Punctuator::Colon, Some("conditional expression"))?; - let else_ = self.read_assignment_expression()?; - return Ok(Node::ConditionalOp( - Box::new(lhs), - Box::new(then_), - Box::new(else_), - )); - } else { - self.cursor.back(); - } - } - - Ok(lhs) - } - - /// - fn read_arrow_function(&mut self) -> ParseResult { - let next_token = self.get_next_token()?; - let params = match &next_token.kind { - TokenKind::Punctuator(Punctuator::OpenParen) => self.read_formal_parameters()?, - TokenKind::Identifier(param_name) => vec![FormalParameter { - init: None, - name: param_name.clone(), - is_rest_param: false, - }], - _ => { - return Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::OpenParen), - TokenKind::Identifier("identifier".to_owned()), - ], - next_token.clone(), - Some("arrow function"), - )) - } - }; - - self.expect_punc(Punctuator::Arrow, Some("arrow function"))?; - - let body = if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::OpenBlock)) - .is_some() - { - self.read_block()? - } else { - Node::Return(Some(Box::new(self.read_assignment_expression()?))) - }; - - Ok(Node::ArrowFunctionDecl(params, Box::new(body))) - } - - /// Collect parameters from functions or arrow functions - fn read_formal_parameters(&mut self) -> Result { - let mut params = Vec::new(); - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::CloseParen)) - .is_some() - { - return Ok(params); - } - - loop { - let mut rest_param = false; - - params.push( - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Spread)) - .is_some() - { - rest_param = true; - self.read_function_rest_parameter()? - } else { - self.read_formal_parameter()? - }, - ); - - if self - .next_if(TokenKind::Punctuator(Punctuator::CloseParen)) - .is_some() - { - break; - } - - if rest_param { - return Err(ParseError::Unexpected( - self.cursor - .peek_prev() - .expect("current token disappeared") - .clone(), - Some("rest parameter must be the last formal parameter"), - )); - } - - self.expect_punc(Punctuator::Comma, Some("parameter list"))?; - } - - Ok(params) - } - - /// - fn read_function_rest_parameter(&mut self) -> Result { - let token = self.get_next_token()?; - Ok(FormalParameter::new( - if let TokenKind::Identifier(name) = &token.kind { - name.clone() - } else { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_owned())], - token.clone(), - Some("rest parameter"), - )); - }, - None, - true, - )) - } - - /// - fn read_formal_parameter(&mut self) -> Result { - let token = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - let name = if let TokenKind::Identifier(name) = &token.kind { - name - } else { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_owned())], - token.clone(), - Some("formal parameter"), - )); - }; - - // TODO: Implement initializer. - Ok(FormalParameter::new(name.clone(), None, false)) - } - - // - expression!( - read_logical_or_expression, - read_logical_and_expression, - [Punctuator::BoolOr] - ); - - // - expression!( - read_logical_and_expression, - read_bitwise_or_expression, - [Punctuator::BoolAnd] - ); - - // https://tc39.es/ecma262/#prod-BitwiseORExpression - expression!( - read_bitwise_or_expression, - read_bitwise_xor_expression, - [Punctuator::Or] - ); - - // https://tc39.es/ecma262/#prod-BitwiseXORExpression - expression!( - read_bitwise_xor_expression, - read_bitwise_and_expression, - [Punctuator::Xor] - ); - - // - expression!( - read_bitwise_and_expression, - read_equality_expression, - [Punctuator::And] - ); - - // - expression!( - read_equality_expression, - read_relational_expression, - [ - Punctuator::Eq, - Punctuator::NotEq, - Punctuator::StrictEq, - Punctuator::StrictNotEq - ] - ); - - // - expression!( - read_relational_expression, - read_shift_expression, - [ - Punctuator::LessThan, - Punctuator::GreaterThan, - Punctuator::LessThanOrEq, - Punctuator::GreaterThanOrEq - ] - ); - - // - expression!( - read_shift_expression, - read_additive_expression, - [ - Punctuator::LeftSh, - Punctuator::RightSh, - Punctuator::URightSh - ] - ); - - // - expression!( - read_additive_expression, - read_multiplicate_expression, - [Punctuator::Add, Punctuator::Sub] - ); - - // - expression!( - read_multiplicate_expression, - read_exponentiation_expression, - [Punctuator::Mul, Punctuator::Div, Punctuator::Mod] - ); - - /// - fn read_exponentiation_expression(&mut self) -> ParseResult { - if self.is_unary_expression() { - return self.read_unary_expression(); - } +/// Boolean representing if the parser should allow a `yield` keyword. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct AllowYield(bool); - let lhs = self.read_update_expression()?; - if let Ok(tok) = self.get_next_token() { - if let TokenKind::Punctuator(Punctuator::Exp) = tok.kind { - return Ok(Node::BinOp( - BinOp::Num(NumOp::Exp), - Box::new(lhs), - Box::new(self.read_exponentiation_expression()?), - )); - } else { - self.cursor.back(); - } - } - Ok(lhs) +impl From for AllowYield { + fn from(allow: bool) -> Self { + Self(allow) } +} - // Checks by looking at the next token to see whether its a Unary operator or not. - fn is_unary_expression(&mut self) -> bool { - if let Some(tok) = self.cursor.peek(0) { - match tok.kind { - TokenKind::Keyword(Keyword::Delete) - | TokenKind::Keyword(Keyword::Void) - | TokenKind::Keyword(Keyword::TypeOf) - | TokenKind::Punctuator(Punctuator::Add) - | TokenKind::Punctuator(Punctuator::Sub) - | TokenKind::Punctuator(Punctuator::Not) - | TokenKind::Punctuator(Punctuator::Neg) => true, - _ => false, - } - } else { - false - } - } +/// Boolean representing if the parser should allow a `await` keyword. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct AllowAwait(bool); - /// - fn read_unary_expression(&mut self) -> ParseResult { - let tok = self.get_next_token()?; - match tok.kind { - TokenKind::Keyword(Keyword::Delete) => Ok(Node::UnaryOp( - UnaryOp::Delete, - Box::new(self.read_unary_expression()?), - )), - TokenKind::Keyword(Keyword::Void) => Ok(Node::UnaryOp( - UnaryOp::Void, - Box::new(self.read_unary_expression()?), - )), - TokenKind::Keyword(Keyword::TypeOf) => Ok(Node::UnaryOp( - UnaryOp::TypeOf, - Box::new(self.read_unary_expression()?), - )), - TokenKind::Punctuator(Punctuator::Add) => Ok(Node::UnaryOp( - UnaryOp::Plus, - Box::new(self.read_unary_expression()?), - )), - TokenKind::Punctuator(Punctuator::Sub) => Ok(Node::UnaryOp( - UnaryOp::Minus, - Box::new(self.read_unary_expression()?), - )), - TokenKind::Punctuator(Punctuator::Neg) => Ok(Node::UnaryOp( - UnaryOp::Tilde, - Box::new(self.read_unary_expression()?), - )), - TokenKind::Punctuator(Punctuator::Not) => Ok(Node::UnaryOp( - UnaryOp::Not, - Box::new(self.read_unary_expression()?), - )), - _ => { - self.cursor.back(); - self.read_update_expression() - } - } +impl From for AllowAwait { + fn from(allow: bool) -> Self { + Self(allow) } +} - /// - fn read_update_expression(&mut self) -> ParseResult { - let tok = self - .peek_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - match tok.kind { - TokenKind::Punctuator(Punctuator::Inc) => { - self.next_skip_lineterminator().unwrap(); - return Ok(Node::UnaryOp( - UnaryOp::IncrementPre, - Box::new(self.read_left_hand_side_expression()?), - )); - } - TokenKind::Punctuator(Punctuator::Dec) => { - self.next_skip_lineterminator().unwrap(); - return Ok(Node::UnaryOp( - UnaryOp::DecrementPre, - Box::new(self.read_left_hand_side_expression()?), - )); - } - _ => {} - } - - let lhs = self.read_left_hand_side_expression()?; - if let Some(tok) = self.cursor.peek(0) { - match tok.kind { - TokenKind::Punctuator(Punctuator::Inc) => { - self.get_next_token().unwrap(); - return Ok(Node::UnaryOp(UnaryOp::IncrementPost, Box::new(lhs))); - } - TokenKind::Punctuator(Punctuator::Dec) => { - self.get_next_token().unwrap(); - return Ok(Node::UnaryOp(UnaryOp::DecrementPost, Box::new(lhs))); - } - _ => {} - } - } +/// Boolean representing if the parser should allow a `in` keyword. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct AllowIn(bool); - Ok(lhs) +impl From for AllowIn { + fn from(allow: bool) -> Self { + Self(allow) } +} - /// - fn read_left_hand_side_expression(&mut self) -> ParseResult { - // TODO: Implement NewExpression: new MemberExpression - let lhs = self.read_member_expression()?; - match self.peek_skip_lineterminator() { - Some(ref tok) if tok.kind == TokenKind::Punctuator(Punctuator::OpenParen) => { - self.read_call_expression(lhs) - } - _ => self.read_new_expression(lhs), - } - } +/// Boolean representing if the parser should allow a `return` keyword. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct AllowReturn(bool); - /// - fn read_new_expression(&mut self, first_member_expr: Node) -> ParseResult { - Ok(first_member_expr) +impl From for AllowReturn { + fn from(allow: bool) -> Self { + Self(allow) } +} - /// - fn read_member_expression(&mut self) -> ParseResult { - let mut lhs = if self - .peek_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)? - .kind - == TokenKind::Keyword(Keyword::New) - { - let _ = self - .next_skip_lineterminator() - .expect("keyword disappeared"); - let lhs = self.read_member_expression()?; - self.expect_punc(Punctuator::OpenParen, Some("member expression"))?; - let args = self.read_arguments()?; - let call_node = Node::Call(Box::new(lhs), args); +/// Boolean representing if the parser should allow a `default` keyword. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct AllowDefault(bool); - Node::New(Box::new(call_node)) - } else { - self.read_primary_expression()? - }; - while let Some(tok) = self.peek_skip_lineterminator() { - match &tok.kind { - TokenKind::Punctuator(Punctuator::Dot) => { - let _ = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; // We move the cursor forward. - match &self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)? - .kind - { - TokenKind::Identifier(name) => { - lhs = Node::GetConstField(Box::new(lhs), name.clone()) - } - TokenKind::Keyword(kw) => { - lhs = Node::GetConstField(Box::new(lhs), kw.to_string()) - } - _ => { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_owned())], - tok.clone(), - Some("member expression"), - )); - } - } - } - TokenKind::Punctuator(Punctuator::OpenBracket) => { - let _ = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; // We move the cursor forward. - let idx = self.read_expression()?; - self.expect_punc(Punctuator::CloseBracket, Some("member expression"))?; - lhs = Node::GetField(Box::new(lhs), Box::new(idx)); - } - _ => break, - } - } - - Ok(lhs) +impl From for AllowDefault { + fn from(allow: bool) -> Self { + Self(allow) } +} - /// - fn read_call_expression(&mut self, first_member_expr: Node) -> ParseResult { - let mut lhs = first_member_expr; - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::OpenParen)) - .is_some() - { - let args = self.read_arguments()?; - lhs = Node::Call(Box::new(lhs), args); - } else { - let next_token = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - return Err(ParseError::Expected( - vec![TokenKind::Punctuator(Punctuator::OpenParen)], - next_token.clone(), - Some("call expression"), - )); - } +#[derive(Debug)] +pub struct Parser<'a> { + /// Cursor in the parser, the internal structure used to read tokens. + cursor: Cursor<'a>, +} - while let Some(tok) = self.peek_skip_lineterminator() { - match tok.kind { - TokenKind::Punctuator(Punctuator::OpenParen) => { - let _ = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; // We move the cursor. - let args = self.read_arguments()?; - lhs = Node::Call(Box::new(lhs), args); - } - TokenKind::Punctuator(Punctuator::Dot) => { - let _ = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; // We move the cursor. - match &self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)? - .kind - { - TokenKind::Identifier(name) => { - lhs = Node::GetConstField(Box::new(lhs), name.clone()); - } - TokenKind::Keyword(kw) => { - lhs = Node::GetConstField(Box::new(lhs), kw.to_string()); - } - _ => { - return Err(ParseError::Expected( - vec![TokenKind::Identifier("identifier".to_owned())], - tok.clone(), - Some("call expression"), - )); - } - } - } - TokenKind::Punctuator(Punctuator::OpenBracket) => { - let _ = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; // We move the cursor. - let idx = self.read_expression()?; - self.expect_punc(Punctuator::CloseBracket, Some("call expression"))?; - lhs = Node::GetField(Box::new(lhs), Box::new(idx)); - } - _ => break, - } +impl<'a> Parser<'a> { + /// Create a new parser, using `tokens` as input + pub fn new(tokens: &'a [Token]) -> Self { + Self { + cursor: Cursor::new(tokens), } - - Ok(lhs) } - /// - fn read_arguments(&mut self) -> Result, ParseError> { - let mut args = Vec::new(); - loop { - let next_token = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - match next_token.kind { - TokenKind::Punctuator(Punctuator::CloseParen) => break, - TokenKind::Punctuator(Punctuator::Comma) => { - if args.is_empty() { - return Err(ParseError::Unexpected(next_token.clone(), None)); - } - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::CloseParen)) - .is_some() - { - break; - } - } - _ => { - if !args.is_empty() { - return Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::Comma), - TokenKind::Punctuator(Punctuator::CloseParen), - ], - next_token.clone(), - Some("argument list"), - )); - } else { - self.cursor.back(); - } - } - } - - if self - .next_if(TokenKind::Punctuator(Punctuator::Spread)) - .is_some() - { - args.push(Node::Spread(Box::new(self.read_assignment_expression()?))); - } else { - args.push(self.read_assignment_expression()?); - } - } - - Ok(args) + /// Parse all expressions in the token array + pub fn parse_all(&mut self) -> ParseResult { + Script.parse(&mut self.cursor).map(Node::StatementList) } +} - /// - fn read_primary_expression(&mut self) -> ParseResult { - let tok = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; +/// Parses a full script. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-Script +#[derive(Debug, Clone, Copy)] +pub struct Script; - 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) => self.read_function_expression(), - TokenKind::Punctuator(Punctuator::OpenParen) => { - let expr = self.read_expression(); - self.expect_punc(Punctuator::CloseParen, Some("primary expression"))?; - expr - } - TokenKind::Punctuator(Punctuator::OpenBracket) => self.read_array_literal(), - TokenKind::Punctuator(Punctuator::OpenBlock) => self.read_object_literal(), - TokenKind::BooleanLiteral(boolean) => Ok(Node::Const(Const::Bool(*boolean))), - // 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.clone())), - TokenKind::StringLiteral(s) => Ok(Node::Const(Const::String(s.clone()))), - TokenKind::NumericLiteral(num) => Ok(Node::Const(Const::Num(*num))), - TokenKind::RegularExpressionLiteral(body, flags) => { - Ok(Node::New(Box::new(Node::Call( - Box::new(Node::Local("RegExp".to_string())), - vec![ - Node::Const(Const::String(body.clone())), - Node::Const(Const::String(flags.clone())), - ], - )))) - } - _ => Err(ParseError::Unexpected( - tok.clone(), - Some("primary expression"), - )), - } - } +impl TokenParser for Script { + type Output = Vec; - /// - fn read_function_expression(&mut self) -> ParseResult { - let name = if let TokenKind::Identifier(name) = - &self.cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind - { - Some(name.clone()) + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + if cursor.peek(0).is_some() { + ScriptBody.parse(cursor) } else { - None - }; - if name.is_some() { - // We move the cursor forward. - let _ = self.get_next_token()?; - } - - self.expect_punc(Punctuator::OpenParen, Some("function expression"))?; - - let params = self.read_formal_parameters()?; - - self.expect_punc(Punctuator::OpenBlock, Some("function expression"))?; - - let body = self.read_block()?; - - Ok(Node::FunctionDecl(name, params, Box::new(body))) - } - - /// - fn read_array_literal(&mut self) -> ParseResult { - let mut elements = Vec::new(); - - loop { - // TODO: Support all features. - while self - .next_if(TokenKind::Punctuator(Punctuator::Comma)) - .is_some() - { - elements.push(Node::Const(Const::Undefined)); - } - - if self - .next_if(TokenKind::Punctuator(Punctuator::CloseBracket)) - .is_some() - { - break; - } - - let _ = self.cursor.peek(0).ok_or(ParseError::AbruptEnd)?; // Check that there are more tokens to read. - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Spread)) - .is_some() - { - let node = self.read_assignment_expression()?; - elements.push(Node::Spread(Box::new(node))); - } else { - elements.push(self.read_assignment_expression()?); - } - self.next_if(TokenKind::Punctuator(Punctuator::Comma)); + Ok(Vec::new()) } - - Ok(Node::ArrayDecl(elements)) - } - - /// - fn read_object_literal(&mut self) -> ParseResult { - let mut elements = Vec::new(); - - loop { - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::CloseBlock)) - .is_some() - { - break; - } - - elements.push(self.read_property_definition()?); - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::CloseBlock)) - .is_some() - { - break; - } - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Comma)) - .is_none() - { - let next_token = self - .next_skip_lineterminator() - .ok_or(ParseError::AbruptEnd)?; - return Err(ParseError::Expected( - vec![ - TokenKind::Punctuator(Punctuator::Comma), - TokenKind::Punctuator(Punctuator::CloseBlock), - ], - next_token.clone(), - Some("object literal"), - )); - } - } - - Ok(Node::Object(elements)) - } - - fn get_object_property_name(&mut self) -> Result { - let to_string = |token: &Token| match &token.kind { - TokenKind::Identifier(name) => name.clone(), - TokenKind::NumericLiteral(n) => format!("{}", n), - TokenKind::StringLiteral(s) => s.clone(), - _ => unimplemented!("{:?}", token.kind), - }; - self.next_skip_lineterminator() - .map(to_string) - .ok_or(ParseError::AbruptEnd) - } - - /// - fn read_property_definition(&mut self) -> Result { - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Spread)) - .is_some() - { - let node = self.read_assignment_expression()?; - return Ok(PropertyDefinition::SpreadObject(node)); - } - - let prop_name = self.get_object_property_name()?; - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::Colon)) - .is_some() - { - let val = self.read_assignment_expression()?; - return Ok(PropertyDefinition::Property(prop_name, val)); - } - - if self - .next_if_skip_lineterminator(TokenKind::Punctuator(Punctuator::OpenParen)) - .is_some() - || ["get", "set"].contains(&prop_name.as_str()) - { - let method_identifer = prop_name; - return self.read_property_method_definition(method_identifer.as_str()); - } - - let pos = self - .cursor - .peek(0) - .map(|tok| tok.pos) - .ok_or(ParseError::AbruptEnd)?; - Err(ParseError::General( - "expected property definition", - Some(pos), - )) } +} - /// - fn read_property_method_definition( - &mut self, - identifier: &str, - ) -> Result { - let (methodkind, prop_name, params) = match identifier { - "get" | "set" => { - let prop_name = self.get_object_property_name()?; - self.expect( - TokenKind::Punctuator(Punctuator::OpenParen), - Some("property method definition"), - )?; - let first_param = self - .peek_skip_lineterminator() - .expect("current token disappeared") - .clone(); - let params = self.read_formal_parameters()?; - if identifier == "get" { - if !params.is_empty() { - return Err(ParseError::Unexpected( - first_param, - Some("getter functions must have no arguments"), - )); - } - (MethodDefinitionKind::Get, prop_name, params) - } else { - if params.len() != 1 { - return Err(ParseError::Unexpected( - first_param, - Some("setter functions must have one argument"), - )); - } - (MethodDefinitionKind::Set, prop_name, params) - } - } - prop_name => { - let params = self.read_formal_parameters()?; - ( - MethodDefinitionKind::Ordinary, - prop_name.to_string(), - params, - ) - } - }; +/// Parses a script body. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ScriptBody +#[derive(Debug, Clone, Copy)] +pub struct ScriptBody; - self.expect( - TokenKind::Punctuator(Punctuator::OpenBlock), - Some("property method definition"), - )?; - let body = self.read_block()?; +impl TokenParser for ScriptBody { + type Output = Vec; - Ok(PropertyDefinition::MethodDefinition( - methodkind, - prop_name, - Node::FunctionDecl(None, params, Box::new(body)), - )) + 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.rs b/boa/src/syntax/parser/statement/block.rs new file mode 100644 index 0000000000..7ee6812d6e --- /dev/null +++ b/boa/src/syntax/parser/statement/block.rs @@ -0,0 +1,203 @@ +//! Block statement parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block +//! [spec]: https://tc39.es/ecma262/#sec-block + +use super::{declaration::Declaration, Statement}; +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; + +/// A `BlockStatement` is equivalent to a `Block`. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement +pub(super) type BlockStatement = Block; + +/// Variable declaration list parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block +/// [spec]: https://tc39.es/ecma262/#prod-Block +#[derive(Debug, Clone, Copy)] +pub(super) struct Block { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl Block { + /// Creates a new `Block` parser. + pub(super) 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 Block { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + 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(Vec::new())); + } + } + + let statement_list = + StatementList::new(self.allow_yield, self.allow_await, self.allow_return, true) + .parse(cursor) + .map(Node::Block)?; + cursor.expect(Punctuator::CloseBlock, "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 = Vec; + + 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) => { + 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)?; + items.push(item); + + // move the cursor forward for any consecutive semicolon. + while cursor.next_if(Punctuator::Semicolon).is_some() {} + } + + Ok(items) + } +} + +/// 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/break_stm/mod.rs b/boa/src/syntax/parser/statement/break_stm/mod.rs new file mode 100644 index 0000000000..0edace6bb2 --- /dev/null +++ b/boa/src/syntax/parser/statement/break_stm/mod.rs @@ -0,0 +1,78 @@ +//! Break expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break +//! [spec]: https://tc39.es/ecma262/#sec-break-statement + +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; + +/// Break statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break +/// [spec]: https://tc39.es/ecma262/#prod-BreakStatement +#[derive(Debug, Clone, Copy)] +pub(super) struct BreakStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl BreakStatement { + /// Creates a new `BreakStatement` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for BreakStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Break, "break statement")?; + + if let (true, tok) = cursor.peek_semicolon(false) { + match tok { + Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => { + let _ = cursor.next(); + } + _ => {} + } + + return Ok(Node::Break(None)); + } + + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + let node = if let TokenKind::Identifier(name) = &tok.kind { + Node::break_node(name) + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + tok.clone(), + "break statement", + )); + }; + + cursor.expect_semicolon(false, "break statement")?; + + Ok(node) + } +} diff --git a/boa/src/syntax/parser/statement/break_stm/tests.rs b/boa/src/syntax/parser/statement/break_stm/tests.rs new file mode 100644 index 0000000000..2506908d4e --- /dev/null +++ b/boa/src/syntax/parser/statement/break_stm/tests.rs @@ -0,0 +1,92 @@ +use crate::syntax::{ast::node::Node, parser::tests::check_parser}; + +#[test] +fn check_inline() { + check_parser( + "while (true) break;", + &[Node::while_loop(Node::const_node(true), Node::Break(None))], + ); +} + +#[test] +fn check_new_line() { + check_parser( + "while (true) + break;", + &[Node::while_loop(Node::const_node(true), Node::Break(None))], + ); +} + +#[test] +fn check_inline_block_semicolon_insertion() { + check_parser( + "while (true) {break}", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Break(None)]), + )], + ); +} + +#[test] +fn check_new_line_semicolon_insertion() { + check_parser( + "while (true) { + break test + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::break_node("test")]), + )], + ); +} + +#[test] +fn check_inline_block() { + check_parser( + "while (true) {break;}", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Break(None)]), + )], + ); +} + +#[test] +fn check_new_line_block() { + check_parser( + "while (true) { + break test; + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::break_node("test")]), + )], + ); +} + +#[test] +fn check_new_line_block_empty() { + check_parser( + "while (true) { + break; + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Break(None)]), + )], + ); +} + +#[test] +fn check_new_line_block_empty_semicolon_insertion() { + check_parser( + "while (true) { + break + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(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 new file mode 100644 index 0000000000..ca794da07c --- /dev/null +++ b/boa/src/syntax/parser/statement/continue_stm/mod.rs @@ -0,0 +1,78 @@ +//! Continue expression parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue +//! [spec]: https://tc39.es/ecma262/#sec-continue-statement + +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; + +/// For statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue +/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement +#[derive(Debug, Clone, Copy)] +pub(super) struct ContinueStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ContinueStatement { + /// Creates a new `ContinueStatement` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ContinueStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Continue, "continue statement")?; + + if let (true, tok) = cursor.peek_semicolon(false) { + match tok { + Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => { + let _ = cursor.next(); + } + _ => {} + } + + return Ok(Node::Continue(None)); + } + + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + let node = if let TokenKind::Identifier(name) = &tok.kind { + Node::continue_node(name) + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + tok.clone(), + "continue statement", + )); + }; + + cursor.expect_semicolon(false, "continue statement")?; + + Ok(node) + } +} diff --git a/boa/src/syntax/parser/statement/continue_stm/tests.rs b/boa/src/syntax/parser/statement/continue_stm/tests.rs new file mode 100644 index 0000000000..8eea05b31e --- /dev/null +++ b/boa/src/syntax/parser/statement/continue_stm/tests.rs @@ -0,0 +1,98 @@ +use crate::syntax::{ast::node::Node, parser::tests::check_parser}; + +#[test] +fn check_inline() { + check_parser( + "while (true) continue;", + &[Node::while_loop( + Node::const_node(true), + Node::Continue(None), + )], + ); +} + +#[test] +fn check_new_line() { + check_parser( + "while (true) + continue;", + &[Node::while_loop( + Node::const_node(true), + Node::Continue(None), + )], + ); +} + +#[test] +fn check_inline_block_semicolon_insertion() { + check_parser( + "while (true) {continue}", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Continue(None)]), + )], + ); +} + +#[test] +fn check_new_line_semicolon_insertion() { + check_parser( + "while (true) { + continue test + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::continue_node("test")]), + )], + ); +} + +#[test] +fn check_inline_block() { + check_parser( + "while (true) {continue;}", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Continue(None)]), + )], + ); +} + +#[test] +fn check_new_line_block() { + check_parser( + "while (true) { + continue test; + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::continue_node("test")]), + )], + ); +} + +#[test] +fn check_new_line_block_empty() { + check_parser( + "while (true) { + continue; + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Continue(None)]), + )], + ); +} + +#[test] +fn check_new_line_block_empty_semicolon_insertion() { + check_parser( + "while (true) { + continue + }", + &[Node::while_loop( + Node::const_node(true), + Node::Block(vec![Node::Continue(None)]), + )], + ); +} diff --git a/boa/src/syntax/parser/statement/declaration/hoistable.rs b/boa/src/syntax/parser/statement/declaration/hoistable.rs new file mode 100644 index 0000000000..0a712e113b --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/hoistable.rs @@ -0,0 +1,118 @@ +//! Hoistable declaration parsing. +//! +//! More information: +//! - [ECMAScript specification][spec] +//! +//! [spec]: https://tc39.es/ecma262/#prod-HoistableDeclaration + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + function::FormalParameters, function::FunctionBody, AllowAwait, AllowDefault, AllowYield, + Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// Hoistable declaration parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration +#[derive(Debug, Clone, Copy)] +pub(super) struct HoistableDeclaration { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_default: AllowDefault, +} + +impl HoistableDeclaration { + /// Creates a new `HoistableDeclaration` parser. + pub(super) fn new(allow_yield: Y, allow_await: A, allow_default: D) -> Self + where + Y: Into, + A: Into, + D: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + allow_default: allow_default.into(), + } + } +} + +impl TokenParser for HoistableDeclaration { + type Output = Node; + + 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) + .parse(cursor) + } +} + +/// Function declaration parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function +/// [spec]: https://tc39.es/ecma262/#prod-FunctionDeclaration +#[derive(Debug, Clone, Copy)] +struct FunctionDeclaration { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_default: AllowDefault, +} + +impl FunctionDeclaration { + /// Creates a new `FunctionDeclaration` parser. + fn new(allow_yield: Y, allow_await: A, allow_default: D) -> Self + where + Y: Into, + A: Into, + D: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + allow_default: allow_default.into(), + } + } +} + +impl TokenParser for FunctionDeclaration { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Function, "function declaration")?; + + let token = cursor.next().ok_or(ParseError::AbruptEnd)?; + let name = if let TokenKind::Identifier(name) = &token.kind { + name.clone() + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("function name")], + token.clone(), + "function declaration", + )); + }; + + cursor.expect(Punctuator::OpenParen, "function declaration")?; + + let params = FormalParameters::new(false, false).parse(cursor)?; + + 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::StatementList)?; + + cursor.expect(Punctuator::CloseBlock, "function declaration")?; + + Ok(Node::function_decl(name, params, body)) + } +} diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs new file mode 100644 index 0000000000..5b8d6e0b80 --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -0,0 +1,172 @@ +//! Lexical declaration parsing. +//! +//! This parses `let` and `const` declarations. +//! +//! More information: +//! - [ECMAScript specification][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::Initializer, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, + TokenParser, + }, +}; + +/// Parses a lexical declaration. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-LexicalDeclaration +#[derive(Debug, Clone, Copy)] +pub(super) struct LexicalDeclaration { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl LexicalDeclaration { + /// Creates a new `LexicalDeclaration` parser. + pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for LexicalDeclaration { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + + match tok.kind { + TokenKind::Keyword(Keyword::Const) => { + BindingList::new(self.allow_in, self.allow_yield, self.allow_await, true) + .parse(cursor) + } + TokenKind::Keyword(Keyword::Let) => { + BindingList::new(self.allow_in, self.allow_yield, self.allow_await, false) + .parse(cursor) + } + _ => unreachable!("unknown token found"), + } + } +} + +/// Parses a binding list. +/// +/// It will return an error if a `const` declaration is being parsed and there is no +/// initializer. +/// +/// More information: . +#[derive(Debug, Clone, Copy)] +struct BindingList { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, + is_const: bool, +} + +impl BindingList { + /// Creates a new `BindingList` parser. + fn new(allow_in: I, allow_yield: Y, allow_await: A, is_const: bool) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + is_const, + } + } +} + +impl TokenParser for BindingList { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // Create vectors to store the variable declarations + // Const and Let signatures are slightly different, Const needs definitions, Lets don't + let mut let_decls = Vec::new(); + let mut const_decls = Vec::new(); + + loop { + let token = cursor.next().ok_or(ParseError::AbruptEnd)?; + let name = if let TokenKind::Identifier(ref name) = token.kind { + name.clone() + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + token.clone(), + if self.is_const { + "const declaration" + } else { + "let declaration" + }, + )); + }; + + match cursor.peek(0) { + Some(token) if token.kind == TokenKind::Punctuator(Punctuator::Assign) => { + let init = Some( + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ); + if self.is_const { + const_decls.push((name, init.unwrap())); + } else { + let_decls.push((name, init)); + }; + } + _ => { + if self.is_const { + return Err(ParseError::Expected( + vec![TokenKind::Punctuator(Punctuator::Assign)], + cursor.next().ok_or(ParseError::AbruptEnd)?.clone(), + "const declaration", + )); + } else { + let_decls.push((name, None)); + } + } + } + + match cursor.peek_semicolon(false) { + (true, _) => break, + (false, Some(tk)) if tk.kind == TokenKind::Punctuator(Punctuator::Comma) => { + let _ = cursor.next(); + } + _ => { + return Err(ParseError::Expected( + vec![ + TokenKind::Punctuator(Punctuator::Semicolon), + TokenKind::LineTerminator, + ], + cursor.next().ok_or(ParseError::AbruptEnd)?.clone(), + "lexical declaration", + )) + } + } + } + + if self.is_const { + Ok(Node::ConstDecl(const_decls)) + } else { + Ok(Node::LetDecl(let_decls)) + } + } +} diff --git a/boa/src/syntax/parser/statement/declaration/mod.rs b/boa/src/syntax/parser/statement/declaration/mod.rs new file mode 100644 index 0000000000..11355289c3 --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/mod.rs @@ -0,0 +1,62 @@ +//! Declaration parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#Declarations +//! [spec]:https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement + +mod hoistable; +mod lexical; +#[cfg(test)] +mod tests; + +use self::{hoistable::HoistableDeclaration, lexical::LexicalDeclaration}; +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, token::TokenKind}, + parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; + +/// Parses a declaration. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-Declaration +#[derive(Debug, Clone, Copy)] +pub(super) struct Declaration { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl Declaration { + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for Declaration { + 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) => { + HoistableDeclaration::new(self.allow_yield, self.allow_await, false).parse(cursor) + } + TokenKind::Keyword(Keyword::Const) | TokenKind::Keyword(Keyword::Let) => { + LexicalDeclaration::new(true, self.allow_yield, self.allow_await).parse(cursor) + } + _ => unreachable!("unknown token found"), + } + } +} diff --git a/boa/src/syntax/parser/statement/declaration/tests.rs b/boa/src/syntax/parser/statement/declaration/tests.rs new file mode 100644 index 0000000000..b4433d6b93 --- /dev/null +++ b/boa/src/syntax/parser/statement/declaration/tests.rs @@ -0,0 +1,132 @@ +use crate::syntax::{ + ast::node::Node, + parser::tests::{check_invalid, check_parser}, +}; + +/// Checks `var` declaration parsing. +#[test] +fn check_var_declaration() { + check_parser( + "var a = 5;", + &[Node::VarDecl(vec![( + String::from("a"), + Some(Node::const_node(5.0)), + )])], + ); +} + +/// Checks `var` declaration parsing with no spaces. +#[test] +fn check_var_declaration_no_spaces() { + check_parser( + "var a=5;", + &[Node::VarDecl(vec![( + String::from("a"), + Some(Node::const_node(5.0)), + )])], + ); +} + +/// Checks empty `var` declaration parsing. +#[test] +fn check_empty_var_declaration() { + check_parser("var a;", &[Node::VarDecl(vec![(String::from("a"), None)])]); +} + +/// Checks multiple `var` declarations. +#[test] +fn check_multiple_var_declaration() { + check_parser( + "var a = 5, b, c = 6;", + &[Node::VarDecl(vec![ + (String::from("a"), Some(Node::const_node(5.0))), + (String::from("b"), None), + (String::from("c"), Some(Node::const_node(6.0))), + ])], + ); +} + +/// Checks `let` declaration parsing. +#[test] +fn check_let_declaration() { + check_parser( + "let a = 5;", + &[Node::LetDecl(vec![( + String::from("a"), + Some(Node::const_node(5.0)), + )])], + ); +} + +/// Checks `let` declaration parsing with no spaces. +#[test] +fn check_let_declaration_no_spaces() { + check_parser( + "let a=5;", + &[Node::LetDecl(vec![( + String::from("a"), + Some(Node::const_node(5.0)), + )])], + ); +} + +/// Checks empty `let` declaration parsing. +#[test] +fn check_empty_let_declaration() { + check_parser("let a;", &[Node::LetDecl(vec![(String::from("a"), None)])]); +} + +/// Checks multiple `let` declarations. +#[test] +fn check_multiple_let_declaration() { + check_parser( + "let a = 5, b, c = 6;", + &[Node::LetDecl(vec![ + (String::from("a"), Some(Node::const_node(5.0))), + (String::from("b"), None), + (String::from("c"), Some(Node::const_node(6.0))), + ])], + ); +} + +/// Checks `const` declaration parsing. +#[test] +fn check_const_declaration() { + check_parser( + "const a = 5;", + &[Node::ConstDecl(vec![( + String::from("a"), + Node::const_node(5.0), + )])], + ); +} + +/// Checks `const` declaration parsing with no spaces. +#[test] +fn check_const_declaration_no_spaces() { + check_parser( + "const a=5;", + &[Node::ConstDecl(vec![( + String::from("a"), + Node::const_node(5.0), + )])], + ); +} + +/// Checks empty `const` declaration parsing. +#[test] +fn check_empty_const_declaration() { + check_invalid("const a;"); +} + +/// Checks multiple `const` declarations. +#[test] +fn check_multiple_const_declaration() { + check_parser( + "const a = 5, c = 6;", + &[Node::ConstDecl(vec![ + (String::from("a"), Node::const_node(5.0)), + (String::from("c"), Node::const_node(6.0)), + ])], + ); +} diff --git a/boa/src/syntax/parser/statement/if_stm/mod.rs b/boa/src/syntax/parser/statement/if_stm/mod.rs new file mode 100644 index 0000000000..8caeab0101 --- /dev/null +++ b/boa/src/syntax/parser/statement/if_stm/mod.rs @@ -0,0 +1,73 @@ +#[cfg(test)] +mod tests; + +use super::Statement; +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseResult, + TokenParser, + }, +}; + +/// If statement parsing. +/// +/// An _If_ statement will have a condition, a block statemet, and an optional _else_ statement. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else +/// [spec]: https://tc39.es/ecma262/#prod-IfStatement +#[derive(Debug, Clone, Copy)] +pub(super) struct IfStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl IfStatement { + /// Creates a new `IfStatement` parser. + pub(super) 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 IfStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::If, "if statement")?; + cursor.expect(Punctuator::OpenParen, "if statement")?; + + let cond = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "if statement")?; + + let then_stm = + 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( + Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor)?, + ), + _ => { + cursor.back(); + None + } + }; + + Ok(Node::if_node::<_, _, Node, _>(cond, then_stm, else_stm)) + } +} diff --git a/boa/src/syntax/parser/statement/if_stm/tests.rs b/boa/src/syntax/parser/statement/if_stm/tests.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/boa/src/syntax/parser/statement/if_stm/tests.rs @@ -0,0 +1 @@ + diff --git a/boa/src/syntax/parser/statement/iteration/do_while_statement.rs b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs new file mode 100644 index 0000000000..d7ac6b8831 --- /dev/null +++ b/boa/src/syntax/parser/statement/iteration/do_while_statement.rs @@ -0,0 +1,82 @@ +//! Do-while statement parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while +//! [spec]: https://tc39.es/ecma262/#sec-do-while-statement + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, + ParseError, ParseResult, TokenParser, + }, +}; + +/// Do...while statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while +/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::statement) struct DoWhileStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl DoWhileStatement { + /// Creates a new `DoWhileStatement` parser. + pub(in crate::syntax::parser::statement) 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 DoWhileStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Do, "do while statement")?; + + let body = + Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + + let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; + + if next_token.kind != TokenKind::Keyword(Keyword::While) { + return Err(ParseError::Expected( + vec![TokenKind::Keyword(Keyword::While)], + next_token.clone(), + "do while statement", + )); + } + + cursor.expect(Keyword::While, "do while statement")?; + cursor.expect(Punctuator::OpenParen, "do while statement")?; + + let cond = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "do while statement")?; + cursor.expect_semicolon(true, "do while statement")?; + + Ok(Node::do_while_loop(body, cond)) + } +} diff --git a/boa/src/syntax/parser/statement/iteration/for_statement.rs b/boa/src/syntax/parser/statement/iteration/for_statement.rs new file mode 100644 index 0000000000..6fa232455e --- /dev/null +++ b/boa/src/syntax/parser/statement/iteration/for_statement.rs @@ -0,0 +1,102 @@ +//! For statement parsing. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript specification][spec] +//! +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for +//! [spec]: https://tc39.es/ecma262/#sec-for-statement + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::Expression, + statement::declaration::Declaration, + statement::{variable::VariableDeclarationList, Statement}, + AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, + }, +}; + +/// For statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for +/// [spec]: https://tc39.es/ecma262/#sec-for-statement +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::statement) struct ForStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl ForStatement { + /// Creates a new `ForStatement` parser. + pub(in crate::syntax::parser::statement) 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 ForStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::For, "for statement")?; + cursor.expect(Punctuator::OpenParen, "for statement")?; + + 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)?, + ), + TokenKind::Keyword(Keyword::Let) | TokenKind::Keyword(Keyword::Const) => { + Some(Declaration::new(self.allow_yield, self.allow_await).parse(cursor)?) + } + TokenKind::Punctuator(Punctuator::Semicolon) => None, + _ => Some(Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?), + }; + + cursor.expect(Punctuator::Semicolon, "for statement")?; + + let cond = if cursor.next_if(Punctuator::Semicolon).is_some() { + Node::const_node(true) + } else { + let step = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect(Punctuator::Semicolon, "for statement")?; + step + }; + + let step = if cursor.next_if(Punctuator::CloseParen).is_some() { + None + } else { + let step = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + cursor.expect( + TokenKind::Punctuator(Punctuator::CloseParen), + "for statement", + )?; + Some(step) + }; + + 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); + + Ok(Node::Block(vec![for_node])) + } +} diff --git a/boa/src/syntax/parser/statement/iteration/mod.rs b/boa/src/syntax/parser/statement/iteration/mod.rs new file mode 100644 index 0000000000..6338c225d7 --- /dev/null +++ b/boa/src/syntax/parser/statement/iteration/mod.rs @@ -0,0 +1,10 @@ +mod do_while_statement; +mod for_statement; +#[cfg(test)] +mod tests; +mod while_statement; + +pub(super) use self::{ + do_while_statement::DoWhileStatement, for_statement::ForStatement, + while_statement::WhileStatement, +}; diff --git a/boa/src/syntax/parser/statement/iteration/tests.rs b/boa/src/syntax/parser/statement/iteration/tests.rs new file mode 100644 index 0000000000..e71dab5d6c --- /dev/null +++ b/boa/src/syntax/parser/statement/iteration/tests.rs @@ -0,0 +1,50 @@ +use crate::syntax::{ + ast::node::Node, + ast::op::{AssignOp, BinOp, CompOp, UnaryOp}, + parser::tests::check_parser, +}; + +/// Checks do-while statement parsing. +#[test] +fn check_do_while() { + check_parser( + r#"do { + a += 1; + } while (true)"#, + &[Node::do_while_loop( + Node::Block(vec![Node::bin_op( + BinOp::Assign(AssignOp::Add), + Node::local("a"), + Node::const_node(1.0), + )]), + Node::const_node(true), + )], + ); +} + +// Checks automatic semicolon insertion after do-while. +#[test] +fn check_do_while_semicolon_insertion() { + check_parser( + r#"var i = 0; + do {console.log("hello");} while(i++ < 10) console.log("end");"#, + &[ + Node::VarDecl(vec![(String::from("i"), Some(Node::const_node(0.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::unary_op(UnaryOp::IncrementPost, Node::local("i")), + Node::const_node(10.0), + ), + ), + Node::call( + Node::get_const_field(Node::local("console"), "log"), + vec![Node::const_node("end")], + ), + ], + ); +} diff --git a/boa/src/syntax/parser/statement/iteration/while_statement.rs b/boa/src/syntax/parser/statement/iteration/while_statement.rs new file mode 100644 index 0000000000..2b9f765ec0 --- /dev/null +++ b/boa/src/syntax/parser/statement/iteration/while_statement.rs @@ -0,0 +1,60 @@ +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator}, + parser::{ + expression::Expression, statement::Statement, AllowAwait, AllowReturn, AllowYield, Cursor, + ParseResult, TokenParser, + }, +}; + +/// While statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while +/// [spec]: https://tc39.es/ecma262/#sec-while-statement +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::statement) struct WhileStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl WhileStatement { + /// Creates a new `WhileStatement` parser. + pub(in crate::syntax::parser::statement) 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 WhileStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::While, "while statement")?; + cursor.expect(Punctuator::OpenParen, "while statement")?; + + let cond = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "while statement")?; + + let body = + Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + + Ok(Node::while_loop(cond, body)) + } +} diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs new file mode 100644 index 0000000000..4f95f57716 --- /dev/null +++ b/boa/src/syntax/parser/statement/mod.rs @@ -0,0 +1,318 @@ +//! Statement and declaration parsing. +//! +//! 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/#sec-ecmascript-language-statements-and-declarations + +mod block; +mod break_stm; +mod continue_stm; +mod declaration; +mod if_stm; +mod iteration; +mod return_stm; +mod switch; +mod throw; +mod try_stm; +mod variable; + +use self::{ + block::BlockStatement, + break_stm::BreakStatement, + continue_stm::ContinueStatement, + declaration::Declaration, + if_stm::IfStatement, + iteration::{DoWhileStatement, ForStatement, WhileStatement}, + return_stm::ReturnStatement, + switch::SwitchStatement, + throw::ThrowStatement, + try_stm::TryStatement, + variable::VariableStatement, +}; +use super::{ + expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, + TokenParser, +}; +use crate::syntax::ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}; + +/// Statement parsing. +/// +/// This can be one of the following: +/// +/// - `BlockStatement` +/// - `VariableStatement` +/// - `EmptyStatement` +/// - `ExpressionStatement` +/// - `IfStatement` +/// - `BreakableStatement` +/// - `ContinueStatement` +/// - `BreakStatement` +/// - `ReturnStatement` +/// - `WithStatement` +/// - `LabelledStatement` +/// - `ThrowStatement` +/// - `TryStatement` +/// - `DebuggerStatement` +/// +/// 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-Statement +#[derive(Debug, Clone, Copy)] +pub(super) struct Statement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl Statement { + /// Creates a new `Statement` parser. + pub(super) 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 Statement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // 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 { + 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) + } + TokenKind::Keyword(Keyword::While) => { + WhileStatement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + } + TokenKind::Keyword(Keyword::Do) => { + DoWhileStatement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + } + TokenKind::Keyword(Keyword::For) => { + ForStatement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + } + TokenKind::Keyword(Keyword::Return) => { + if self.allow_return.0 { + ReturnStatement::new(self.allow_yield, self.allow_await).parse(cursor) + } else { + Err(ParseError::Unexpected(tok.clone(), Some("statement"))) + } + } + TokenKind::Keyword(Keyword::Break) => { + BreakStatement::new(self.allow_yield, self.allow_await).parse(cursor) + } + TokenKind::Keyword(Keyword::Continue) => { + ContinueStatement::new(self.allow_yield, self.allow_await).parse(cursor) + } + TokenKind::Keyword(Keyword::Try) => { + TryStatement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + } + TokenKind::Keyword(Keyword::Throw) => { + ThrowStatement::new(self.allow_yield, self.allow_await).parse(cursor) + } + TokenKind::Keyword(Keyword::Switch) => { + SwitchStatement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + } + TokenKind::Punctuator(Punctuator::OpenBlock) => { + BlockStatement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor) + } + // TODO: https://tc39.es/ecma262/#prod-LabelledStatement + // TokenKind::Punctuator(Punctuator::Semicolon) => { + // return Ok(Node::new(NodeBase::Nope, tok.pos)) + // } + _ => ExpressionStatement::new(self.allow_yield, self.allow_await).parse(cursor), + } + } +} + +/// 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(super) struct StatementList { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, + break_when_closingbrase: bool, +} + +impl StatementList { + /// Creates a new `StatementList` parser. + pub(super) 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 = Vec; + + fn parse(self, cursor: &mut Cursor<'_>) -> Result, ParseError> { + let mut items = 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)?; + items.push(item); + + // move the cursor forward for any consecutive semicolon. + while cursor.next_if(Punctuator::Semicolon).is_some() {} + } + + Ok(items) + } +} + +/// 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) + } + } + } +} + +/// Expression statement parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ExpressionStatement +#[derive(Debug, Clone, Copy)] +struct ExpressionStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ExpressionStatement { + /// Creates a new `ExpressionStatement` parser. + fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ExpressionStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // TODO: lookahead + let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect_semicolon(false, "expression statement")?; + + Ok(expr) + } +} diff --git a/boa/src/syntax/parser/statement/return_stm/mod.rs b/boa/src/syntax/parser/statement/return_stm/mod.rs new file mode 100644 index 0000000000..54a7bf5cce --- /dev/null +++ b/boa/src/syntax/parser/statement/return_stm/mod.rs @@ -0,0 +1,63 @@ +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +}; + +/// Return statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return +/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement +#[derive(Debug, Clone, Copy)] +pub(super) struct ReturnStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ReturnStatement { + /// Creates a new `ReturnStatement` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ReturnStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Return, "return statement")?; + + if let (true, tok) = cursor.peek_semicolon(false) { + match tok { + Some(tok) + if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) + || tok.kind == TokenKind::LineTerminator => + { + let _ = cursor.next(); + } + _ => {} + } + + return Ok(Node::Return(None)); + } + + let expr = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect_semicolon(false, "return statement")?; + + Ok(Node::return_node(expr)) + } +} diff --git a/boa/src/syntax/parser/statement/return_stm/tests.rs b/boa/src/syntax/parser/statement/return_stm/tests.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/boa/src/syntax/parser/statement/return_stm/tests.rs @@ -0,0 +1 @@ + diff --git a/boa/src/syntax/parser/statement/switch/mod.rs b/boa/src/syntax/parser/statement/switch/mod.rs new file mode 100644 index 0000000000..4a3e6f7ebd --- /dev/null +++ b/boa/src/syntax/parser/statement/switch/mod.rs @@ -0,0 +1,101 @@ +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator}, + parser::{ + expression::Expression, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, + ParseResult, TokenParser, + }, +}; + +/// Switch statement parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch +/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement +#[derive(Debug, Clone, Copy)] +pub(super) struct SwitchStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl SwitchStatement { + /// Creates a new `SwitchStatement` parser. + pub(super) 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 SwitchStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Switch, "switch statement")?; + cursor.expect(Punctuator::OpenParen, "switch statement")?; + + let condition = Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect(Punctuator::CloseParen, "switch statement")?; + + let (cases, default) = + CaseBlock::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + + Ok(Node::switch::<_, _, _, Node>(condition, cases, default)) + } +} + +/// Switch case block parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-CaseBlock +#[derive(Debug, Clone, Copy)] +struct CaseBlock { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl CaseBlock { + /// Creates a new `CaseBlock` 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 CaseBlock { + type Output = (Vec<(Node, Vec)>, Option); + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + cursor.expect(Punctuator::OpenBlock, "switch case block")?; + + // CaseClauses[?Yield, ?Await, ?Return]opt + // CaseClauses[?Yield, ?Await, ?Return]optDefaultClause[?Yield, ?Await, ?Return]CaseClauses[?Yield, ?Await, ?Return]opt + + unimplemented!("switch case block parsing") + } +} diff --git a/boa/src/syntax/parser/statement/switch/tests.rs b/boa/src/syntax/parser/statement/switch/tests.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/boa/src/syntax/parser/statement/switch/tests.rs @@ -0,0 +1 @@ + diff --git a/boa/src/syntax/parser/statement/throw/mod.rs b/boa/src/syntax/parser/statement/throw/mod.rs new file mode 100644 index 0000000000..bbfbfba9db --- /dev/null +++ b/boa/src/syntax/parser/statement/throw/mod.rs @@ -0,0 +1,54 @@ +#[cfg(test)] +mod tests; + +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{expression::Expression, AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, +}; + +/// For statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw +/// [spec]: https://tc39.es/ecma262/#prod-ThrowStatement +#[derive(Debug, Clone, Copy)] +pub(super) struct ThrowStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl ThrowStatement { + /// Creates a new `ThrowStatement` parser. + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for ThrowStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Throw, "throw statement")?; + + cursor.peek_expect_no_lineterminator(0, "throw statement")?; + + 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) { + let _ = cursor.next(); + } + } + + Ok(Node::throw(expr)) + } +} diff --git a/boa/src/syntax/parser/statement/throw/tests.rs b/boa/src/syntax/parser/statement/throw/tests.rs new file mode 100644 index 0000000000..2c32877f46 --- /dev/null +++ b/boa/src/syntax/parser/statement/throw/tests.rs @@ -0,0 +1,6 @@ +use crate::syntax::{ast::node::Node, parser::tests::check_parser}; + +#[test] +fn check_throw_parsing() { + check_parser("throw 'error';", &[Node::throw(Node::const_node("error"))]); +} diff --git a/boa/src/syntax/parser/statement/try_stm/mod.rs b/boa/src/syntax/parser/statement/try_stm/mod.rs new file mode 100644 index 0000000000..0760698f1f --- /dev/null +++ b/boa/src/syntax/parser/statement/try_stm/mod.rs @@ -0,0 +1,109 @@ +#[cfg(test)] +mod tests; + +use super::block::Block; +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, +}; + +/// Try...catch statement parsing +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch +/// [spec]: https://tc39.es/ecma262/#sec-try-statement +#[derive(Debug, Clone, Copy)] +pub(super) struct TryStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, + allow_return: AllowReturn, +} + +impl TryStatement { + /// Creates a new `TryStatement` parser. + pub(super) 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 TryStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + // TRY + cursor.expect(Keyword::Try, "try statement")?; + + let try_clause = + Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?; + + 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) + { + return Err(ParseError::Expected( + vec![ + TokenKind::Keyword(Keyword::Catch), + TokenKind::Keyword(Keyword::Finally), + ], + next_token.clone(), + "try statement", + )); + } + + // CATCH + let (catch, param) = if next_token.kind == TokenKind::Keyword(Keyword::Catch) { + // Catch binding + cursor.expect(Punctuator::OpenParen, "catch in try statement")?; + // TODO: should accept BindingPattern + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + let catch_param = if let TokenKind::Identifier(s) = &tok.kind { + Node::local(s) + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + tok.clone(), + "catch in try statement", + )); + }; + cursor.expect(Punctuator::CloseParen, "catch in try statement")?; + + // Catch block + ( + Some( + Block::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor)?, + ), + Some(catch_param), + ) + } else { + (None, None) + }; + + // FINALLY + let finally_block = if cursor.next_if(Keyword::Finally).is_some() { + Some(Block::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor)?) + } else { + None + }; + + Ok(Node::try_node::<_, _, _, _, Node, Node, Node>( + try_clause, + catch, + param, + finally_block, + )) + } +} diff --git a/boa/src/syntax/parser/statement/try_stm/tests.rs b/boa/src/syntax/parser/statement/try_stm/tests.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/boa/src/syntax/parser/statement/try_stm/tests.rs @@ -0,0 +1 @@ + diff --git a/boa/src/syntax/parser/statement/variable.rs b/boa/src/syntax/parser/statement/variable.rs new file mode 100644 index 0000000000..6f86598cd8 --- /dev/null +++ b/boa/src/syntax/parser/statement/variable.rs @@ -0,0 +1,179 @@ +// use super::lexical_declaration_continuation; +use crate::syntax::{ + ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, + parser::{ + expression::Initializer, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, + TokenParser, + }, +}; + +/// Variable statement parsing. +/// +/// A varible statement contains the `var` keyword. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var +/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::statement) struct VariableStatement { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl VariableStatement { + /// Creates a new `VariableStatement` parser. + pub(in crate::syntax::parser::statement) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for VariableStatement { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + cursor.expect(Keyword::Var, "variable statement")?; + + let decl_list = + VariableDeclarationList::new(true, self.allow_yield, self.allow_await).parse(cursor)?; + + cursor.expect_semicolon(false, "variable statement")?; + + Ok(decl_list) + } +} + +/// Variable declaration list parsing. +/// +/// More information: +/// - [MDN documentation][mdn] +/// - [ECMAScript specification][spec] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var +/// [spec]: https://tc39.es/ecma262/#prod-VariableDeclarationList +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser::statement) struct VariableDeclarationList { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl VariableDeclarationList { + /// Creates a new `VariableDeclarationList` parser. + pub(in crate::syntax::parser::statement) fn new( + allow_in: I, + allow_yield: Y, + allow_await: A, + ) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for VariableDeclarationList { + type Output = Node; + + fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { + let mut list = Vec::new(); + + loop { + list.push( + VariableDeclaration::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ); + + match cursor.peek_semicolon(false) { + (true, _) => break, + (false, Some(tk)) if tk.kind == TokenKind::Punctuator(Punctuator::Comma) => { + let _ = cursor.next(); + } + _ => { + return Err(ParseError::Expected( + vec![ + TokenKind::Punctuator(Punctuator::Semicolon), + TokenKind::LineTerminator, + ], + cursor.next().ok_or(ParseError::AbruptEnd)?.clone(), + "lexical declaration", + )) + } + } + } + + Ok(Node::VarDecl(list)) + } +} + +/// Reads an individual variable declaration. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-VariableDeclaration +#[derive(Debug, Clone, Copy)] +struct VariableDeclaration { + allow_in: AllowIn, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl VariableDeclaration { + /// Creates a new `VariableDeclaration` parser. + fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + where + I: Into, + Y: Into, + A: Into, + { + Self { + allow_in: allow_in.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for VariableDeclaration { + type Output = (String, Option); + + fn parse(self, cursor: &mut Cursor<'_>) -> Result { + let tok = cursor.next().ok_or(ParseError::AbruptEnd)?; + let name = if let TokenKind::Identifier(name) = &tok.kind { + name.clone() + } else { + return Err(ParseError::Expected( + vec![TokenKind::identifier("identifier")], + tok.clone(), + "variable declaration", + )); + }; + + match cursor.peek(0) { + Some(tk) if tk.kind == TokenKind::Punctuator(Punctuator::Assign) => Ok(( + name, + Some( + Initializer::new(self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor)?, + ), + )), + _ => Ok((name, None)), + } + } +} diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs index 0b2f85de99..7b14566bdd 100644 --- a/boa/src/syntax/parser/tests.rs +++ b/boa/src/syntax/parser/tests.rs @@ -1,800 +1,39 @@ //! Tests for the parser. -use super::*; -use crate::syntax::ast::{constant::Const, op::BinOp, op::BitOp}; -use crate::syntax::{ - ast::node::{FormalParameter, Node}, - lexer::Lexer, -}; - -fn create_bin_op(op: BinOp, exp1: Node, exp2: Node) -> Node { - Node::BinOp(op, Box::new(exp1), Box::new(exp2)) -} +use super::Parser; +use crate::syntax::{ast::node::Node, ast::op::NumOp, lexer::Lexer}; #[allow(clippy::result_unwrap_used)] -fn check_parser(js: &str, expr: &[Node]) { +pub(super) fn check_parser(js: &str, expr: &[Node]) { let mut lexer = Lexer::new(js); lexer.lex().expect("failed to lex"); assert_eq!( - Parser::new(&lexer.tokens).parse_all().unwrap(), - Node::StatementList(expr.into()) + Parser::new(&lexer.tokens) + .parse_all() + .expect("failed to parse"), + Node::statement_list(expr) ); } -fn check_invalid(js: &str) { +pub(super) fn check_invalid(js: &str) { let mut lexer = Lexer::new(js); lexer.lex().expect("failed to lex"); assert!(Parser::new(&lexer.tokens).parse_all().is_err()); } -#[test] -fn check_string() { - use crate::syntax::ast::constant::Const; - - // Check empty string - check_parser("\"\"", &[Node::Const(Const::String(String::new()))]); - - // Check non-empty string - check_parser( - "\"hello\"", - &[Node::Const(Const::String(String::from("hello")))], - ); -} - -#[test] -fn check_object_literal() { - let object_properties = vec![ - PropertyDefinition::Property(String::from("a"), Node::Const(Const::Bool(true))), - PropertyDefinition::Property(String::from("b"), Node::Const(Const::Bool(false))), - ]; - - check_parser( - "const x = { - a: true, - b: false, - }; - ", - &[Node::ConstDecl(vec![( - String::from("x"), - Node::Object(object_properties), - )])], - ); -} - -#[test] -fn check_object_short_function() { - // Testing short function syntax - let object_properties = vec![ - PropertyDefinition::Property(String::from("a"), Node::Const(Const::Bool(true))), - PropertyDefinition::MethodDefinition( - MethodDefinitionKind::Ordinary, - String::from("b"), - Node::FunctionDecl(None, Vec::new(), Box::new(Node::StatementList(Vec::new()))), - ), - ]; - - check_parser( - "const x = { - a: true, - b() {}, - }; - ", - &[Node::ConstDecl(vec![( - String::from("x"), - Node::Object(object_properties), - )])], - ); -} - -#[test] -fn check_object_short_function_arguments() { - // Testing short function syntax - let object_properties = vec![ - PropertyDefinition::Property(String::from("a"), Node::Const(Const::Bool(true))), - PropertyDefinition::MethodDefinition( - MethodDefinitionKind::Ordinary, - String::from("b"), - Node::FunctionDecl( - None, - vec![FormalParameter::new(String::from("test"), None, false)], - Box::new(Node::StatementList(Vec::new())), - ), - ), - ]; - - check_parser( - "const x = { - a: true, - b(test) {} - }; - ", - &[Node::ConstDecl(vec![( - String::from("x"), - Node::Object(object_properties), - )])], - ); -} - -#[test] -fn check_object_getter() { - let object_properties = vec![ - PropertyDefinition::Property(String::from("a"), Node::Const(Const::Bool(true))), - PropertyDefinition::MethodDefinition( - MethodDefinitionKind::Get, - String::from("b"), - Node::FunctionDecl(None, Vec::new(), Box::new(Node::StatementList(Vec::new()))), - ), - ]; - - check_parser( - "const x = { - a: true, - get b() {} - }; - ", - &[Node::ConstDecl(vec![( - String::from("x"), - Node::Object(object_properties), - )])], - ); -} - -#[test] -fn check_object_setter() { - let object_properties = vec![ - PropertyDefinition::Property(String::from("a"), Node::Const(Const::Bool(true))), - PropertyDefinition::MethodDefinition( - MethodDefinitionKind::Set, - String::from("b"), - Node::FunctionDecl( - None, - vec![FormalParameter::new(String::from("test"), None, false)], - Box::new(Node::StatementList(Vec::new())), - ), - ), - ]; - - check_parser( - "const x = { - a: true, - set b(test) {} - }; - ", - &[Node::ConstDecl(vec![( - String::from("x"), - Node::Object(object_properties), - )])], - ); -} - -#[test] -fn check_array() { - use crate::syntax::ast::constant::Const; - - // Check empty array - check_parser("[]", &[Node::ArrayDecl(vec![])]); - - // Check array with empty slot - check_parser( - "[,]", - &[Node::ArrayDecl(vec![Node::Const(Const::Undefined)])], - ); - - // Check numeric array - check_parser( - "[1, 2, 3]", - &[Node::ArrayDecl(vec![ - Node::Const(Const::Num(1.0)), - Node::Const(Const::Num(2.0)), - Node::Const(Const::Num(3.0)), - ])], - ); - - // Check numeric array with trailing comma - check_parser( - "[1, 2, 3,]", - &[Node::ArrayDecl(vec![ - Node::Const(Const::Num(1.0)), - Node::Const(Const::Num(2.0)), - Node::Const(Const::Num(3.0)), - ])], - ); - - // Check numeric array with an elision - check_parser( - "[1, 2, , 3]", - &[Node::ArrayDecl(vec![ - Node::Const(Const::Num(1.0)), - Node::Const(Const::Num(2.0)), - Node::Const(Const::Undefined), - Node::Const(Const::Num(3.0)), - ])], - ); - - // Check numeric array with repeated elision - check_parser( - "[1, 2, ,, 3]", - &[Node::ArrayDecl(vec![ - Node::Const(Const::Num(1.0)), - Node::Const(Const::Num(2.0)), - Node::Const(Const::Undefined), - Node::Const(Const::Undefined), - Node::Const(Const::Num(3.0)), - ])], - ); - - // Check combined array - check_parser( - "[1, \"a\", 2]", - &[Node::ArrayDecl(vec![ - Node::Const(Const::Num(1.0)), - Node::Const(Const::String(String::from("a"))), - Node::Const(Const::Num(2.0)), - ])], - ); - - // Check combined array with empty string - check_parser( - "[1, \"\", 2]", - &[Node::ArrayDecl(vec![ - Node::Const(Const::Num(1.0)), - Node::Const(Const::String(String::new())), - Node::Const(Const::Num(2.0)), - ])], - ); -} - -#[test] -fn check_declarations() { - use crate::syntax::ast::constant::Const; - - // Check `var` declaration - check_parser( - "var a = 5;", - &[Node::VarDecl(vec![( - String::from("a"), - Some(Node::Const(Const::Num(5.0))), - )])], - ); - - // Check `var` declaration with no spaces - check_parser( - "var a=5;", - &[Node::VarDecl(vec![( - String::from("a"), - Some(Node::Const(Const::Num(5.0))), - )])], - ); - - // Check empty `var` declaration - check_parser("var a;", &[Node::VarDecl(vec![(String::from("a"), None)])]); - - // Check multiple `var` declaration - check_parser( - "var a = 5, b, c = 6;", - &[Node::VarDecl(vec![ - (String::from("a"), Some(Node::Const(Const::Num(5.0)))), - (String::from("b"), None), - (String::from("c"), Some(Node::Const(Const::Num(6.0)))), - ])], - ); - - // Check `let` declaration - check_parser( - "let a = 5;", - &[Node::LetDecl(vec![( - String::from("a"), - Some(Node::Const(Const::Num(5.0))), - )])], - ); - - // Check `let` declaration with no spaces - check_parser( - "let a=5;", - &[Node::LetDecl(vec![( - String::from("a"), - Some(Node::Const(Const::Num(5.0))), - )])], - ); - - // Check empty `let` declaration - check_parser("let a;", &[Node::LetDecl(vec![(String::from("a"), None)])]); - - // Check multiple `let` declaration - check_parser( - "let a = 5, b, c = 6;", - &[Node::LetDecl(vec![ - (String::from("a"), Some(Node::Const(Const::Num(5.0)))), - (String::from("b"), None), - (String::from("c"), Some(Node::Const(Const::Num(6.0)))), - ])], - ); - - // Check `const` declaration - check_parser( - "const a = 5;", - &[Node::ConstDecl(vec![( - String::from("a"), - Node::Const(Const::Num(5.0)), - )])], - ); - - // Check `const` declaration with no spaces - check_parser( - "const a=5;", - &[Node::ConstDecl(vec![( - String::from("a"), - Node::Const(Const::Num(5.0)), - )])], - ); - - // Check empty `const` declaration - check_invalid("const a;"); - - // Check multiple `const` declaration - check_parser( - "const a = 5, c = 6;", - &[Node::ConstDecl(vec![ - (String::from("a"), Node::Const(Const::Num(5.0))), - (String::from("c"), Node::Const(Const::Num(6.0))), - ])], - ); -} - -#[test] -fn check_operations() { - use crate::syntax::ast::{constant::Const, op::BinOp}; - - fn create_bin_op(op: BinOp, exp1: Node, exp2: Node) -> Node { - Node::BinOp(op, Box::new(exp1), Box::new(exp2)) - } - - // Check numeric operations - check_parser( - "a + b", - &[create_bin_op( - BinOp::Num(NumOp::Add), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a+1", - &[create_bin_op( - BinOp::Num(NumOp::Add), - Node::Local(String::from("a")), - Node::Const(Const::Num(1.0)), - )], - ); - check_parser( - "a - b", - &[create_bin_op( - BinOp::Num(NumOp::Sub), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a-1", - &[create_bin_op( - BinOp::Num(NumOp::Sub), - Node::Local(String::from("a")), - Node::Const(Const::Num(1.0)), - )], - ); - check_parser( - "a / b", - &[create_bin_op( - BinOp::Num(NumOp::Div), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a/2", - &[create_bin_op( - BinOp::Num(NumOp::Div), - Node::Local(String::from("a")), - Node::Const(Const::Num(2.0)), - )], - ); - check_parser( - "a * b", - &[create_bin_op( - BinOp::Num(NumOp::Mul), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a*2", - &[create_bin_op( - BinOp::Num(NumOp::Mul), - Node::Local(String::from("a")), - Node::Const(Const::Num(2.0)), - )], - ); - check_parser( - "a ** b", - &[create_bin_op( - BinOp::Num(NumOp::Exp), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a**2", - &[create_bin_op( - BinOp::Num(NumOp::Exp), - Node::Local(String::from("a")), - Node::Const(Const::Num(2.0)), - )], - ); - check_parser( - "a % b", - &[create_bin_op( - BinOp::Num(NumOp::Mod), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a%2", - &[create_bin_op( - BinOp::Num(NumOp::Mod), - Node::Local(String::from("a")), - Node::Const(Const::Num(2.0)), - )], - ); - - // Check complex numeric operations - check_parser( - "a + d*(b-3)+1", - &[create_bin_op( - BinOp::Num(NumOp::Add), - create_bin_op( - BinOp::Num(NumOp::Add), - Node::Local(String::from("a")), - create_bin_op( - BinOp::Num(NumOp::Mul), - Node::Local(String::from("d")), - create_bin_op( - BinOp::Num(NumOp::Sub), - Node::Local(String::from("b")), - Node::Const(Const::Num(3.0)), - ), - ), - ), - Node::Const(Const::Num(1.0)), - )], - ); - - // Check bitwise operations - check_parser( - "a & b", - &[create_bin_op( - BinOp::Bit(BitOp::And), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a&b", - &[create_bin_op( - BinOp::Bit(BitOp::And), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - - check_parser( - "a | b", - &[create_bin_op( - BinOp::Bit(BitOp::Or), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a|b", - &[create_bin_op( - BinOp::Bit(BitOp::Or), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - - check_parser( - "a ^ b", - &[create_bin_op( - BinOp::Bit(BitOp::Xor), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a^b", - &[create_bin_op( - BinOp::Bit(BitOp::Xor), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - - check_parser( - "a << b", - &[create_bin_op( - BinOp::Bit(BitOp::Shl), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a<> b", - &[create_bin_op( - BinOp::Bit(BitOp::Shr), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a>>b", - &[create_bin_op( - BinOp::Bit(BitOp::Shr), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - - // Check assign ops - check_parser( - "a += b", - &[create_bin_op( - BinOp::Assign(AssignOp::Add), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a -= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Sub), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a *= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Mul), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a **= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Exp), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a /= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Div), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a %= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Mod), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a &= b", - &[create_bin_op( - BinOp::Assign(AssignOp::And), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a |= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Or), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a ^= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Xor), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a <<= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Shl), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a >>= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Shr), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - )], - ); - check_parser( - "a %= 10 / 2", - &[create_bin_op( - BinOp::Assign(AssignOp::Mod), - Node::Local(String::from("a")), - create_bin_op( - BinOp::Num(NumOp::Div), - Node::Const(Const::Num(10.0)), - Node::Const(Const::Num(2.0)), - ), - )], - ); -} - -#[test] -fn check_function_declarations() { - check_parser( - "function foo(a) { return a; }", - &[Node::FunctionDecl( - Some(String::from("foo")), - vec![FormalParameter::new(String::from("a"), None, false)], - Box::new(Node::StatementList(vec![Node::Return(Some(Box::new( - Node::Local(String::from("a")), - )))])), - )], - ); - - check_parser( - "function foo(a) { return; }", - &[Node::FunctionDecl( - Some(String::from("foo")), - vec![FormalParameter::new(String::from("a"), None, false)], - Box::new(Node::StatementList(vec![Node::Return(None)])), - )], - ); - - check_parser( - "function foo(a) { return }", - &[Node::FunctionDecl( - Some(String::from("foo")), - vec![FormalParameter::new(String::from("a"), None, false)], - Box::new(Node::StatementList(vec![Node::Return(None)])), - )], - ); - - check_parser( - "function foo(a, ...b) {}", - &[Node::FunctionDecl( - Some(String::from("foo")), - vec![ - FormalParameter::new(String::from("a"), None, false), - FormalParameter::new(String::from("b"), None, true), - ], - Box::new(Node::StatementList(Vec::new())), - )], - ); - - check_parser( - "(...a) => {}", - &[Node::ArrowFunctionDecl( - vec![FormalParameter::new(String::from("a"), None, true)], - Box::new(Node::StatementList(Vec::new())), - )], - ); - - check_parser( - "(a, b, ...c) => {}", - &[Node::ArrowFunctionDecl( - vec![ - FormalParameter::new(String::from("a"), None, false), - FormalParameter::new(String::from("b"), None, false), - FormalParameter::new(String::from("c"), None, true), - ], - Box::new(Node::StatementList(Vec::new())), - )], - ); - - check_parser( - "(a, b) => { return a + b; }", - &[Node::ArrowFunctionDecl( - vec![ - FormalParameter::new(String::from("a"), None, false), - FormalParameter::new(String::from("b"), None, false), - ], - Box::new(Node::StatementList(vec![Node::Return(Some(Box::new( - create_bin_op( - BinOp::Num(NumOp::Add), - Node::Local(String::from("a")), - Node::Local(String::from("b")), - ), - )))])), - )], - ); - - check_parser( - "(a, b) => { return; }", - &[Node::ArrowFunctionDecl( - vec![ - FormalParameter::new(String::from("a"), None, false), - FormalParameter::new(String::from("b"), None, false), - ], - Box::new(Node::StatementList(vec![Node::Return(None)])), - )], - ); - - check_parser( - "(a, b) => { return }", - &[Node::ArrowFunctionDecl( - vec![ - FormalParameter::new(String::from("a"), None, false), - FormalParameter::new(String::from("b"), None, false), - ], - Box::new(Node::StatementList(vec![Node::Return(None)])), - )], - ); -} - -// Should be parsed as `new Class().method()` instead of `new (Class().method())` -#[test] -fn check_do_while() { - check_parser( - r#"do { - a += 1; - } while (true)"#, - &[Node::DoWhileLoop( - Box::new(Node::Block(vec![create_bin_op( - BinOp::Assign(AssignOp::Add), - Node::Local(String::from("a")), - Node::Const(Const::Num(1.0)), - )])), - Box::new(Node::Const(Const::Bool(true))), - )], - ); -} - /// Should be parsed as `new Class().method()` instead of `new (Class().method())` #[test] fn check_construct_call_precedence() { check_parser( "new Date().getTime()", - &[Node::Call( - Box::new(Node::GetConstField( - Box::new(Node::New(Box::new(Node::Call( - Box::new(Node::Local(String::from("Date"))), - vec![], - )))), - String::from("getTime"), - )), - vec![], + &[Node::call( + Node::get_const_field( + Node::new(Node::call(Node::local("Date"), Vec::new())), + "getTime", + ), + Vec::new(), )], ); } @@ -803,13 +42,9 @@ fn check_construct_call_precedence() { fn assing_operator_precedence() { check_parser( "a = a + 1", - &[Node::Assign( - Box::new(Node::Local(String::from("a"))), - Box::new(create_bin_op( - BinOp::Num(NumOp::Add), - Node::Local(String::from("a")), - Node::Const(Const::Num(1.0)), - )), + &[Node::assign( + Node::local("a"), + Node::bin_op(NumOp::Add, Node::local("a"), Node::const_node(1.0)), )], ); }