Browse Source

Modularized parser (#304)

pull/353/head
Iban Eguia 4 years ago committed by GitHub
parent
commit
bc63b28b6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      .vscode/launch.json
  2. 42
      Cargo.lock
  3. 5
      Cargo.toml
  4. 2
      boa/Cargo.toml
  5. 1
      boa/src/builtins/console/tests.rs
  6. 124
      boa/src/builtins/math/tests.rs
  7. 27
      boa/src/builtins/number/tests.rs
  8. 12
      boa/src/builtins/object/mod.rs
  9. 239
      boa/src/builtins/value/mod.rs
  10. 4
      boa/src/exec/mod.rs
  11. 59
      boa/src/exec/tests.rs
  12. 4
      boa/src/realm.rs
  13. 48
      boa/src/syntax/ast/constant.rs
  14. 144
      boa/src/syntax/ast/keyword.rs
  15. 417
      boa/src/syntax/ast/node.rs
  16. 160
      boa/src/syntax/ast/op.rs
  17. 159
      boa/src/syntax/ast/punc.rs
  18. 97
      boa/src/syntax/ast/token.rs
  19. 9
      boa/src/syntax/lexer/mod.rs
  20. 25
      boa/src/syntax/lexer/tests.rs
  21. 222
      boa/src/syntax/parser/cursor.rs
  22. 106
      boa/src/syntax/parser/error.rs
  23. 161
      boa/src/syntax/parser/expression/assignment/arrow_function.rs
  24. 79
      boa/src/syntax/parser/expression/assignment/conditional.rs
  25. 94
      boa/src/syntax/parser/expression/assignment/exponentiation.rs
  26. 124
      boa/src/syntax/parser/expression/assignment/mod.rs
  27. 94
      boa/src/syntax/parser/expression/left_hand_side/arguments.rs
  28. 102
      boa/src/syntax/parser/expression/left_hand_side/call.rs
  29. 88
      boa/src/syntax/parser/expression/left_hand_side/member.rs
  30. 61
      boa/src/syntax/parser/expression/left_hand_side/mod.rs
  31. 479
      boa/src/syntax/parser/expression/mod.rs
  32. 82
      boa/src/syntax/parser/expression/primary/array_initializer/mod.rs
  33. 102
      boa/src/syntax/parser/expression/primary/array_initializer/tests.rs
  34. 60
      boa/src/syntax/parser/expression/primary/function_expression.rs
  35. 94
      boa/src/syntax/parser/expression/primary/mod.rs
  36. 290
      boa/src/syntax/parser/expression/primary/object_initializer/mod.rs
  37. 131
      boa/src/syntax/parser/expression/primary/object_initializer/tests.rs
  38. 10
      boa/src/syntax/parser/expression/primary/tests.rs
  39. 293
      boa/src/syntax/parser/expression/tests.rs
  40. 79
      boa/src/syntax/parser/expression/unary.rs
  41. 82
      boa/src/syntax/parser/expression/update.rs
  42. 238
      boa/src/syntax/parser/function/mod.rs
  43. 169
      boa/src/syntax/parser/function/tests.rs
  44. 1799
      boa/src/syntax/parser/mod.rs
  45. 203
      boa/src/syntax/parser/statement/block.rs
  46. 78
      boa/src/syntax/parser/statement/break_stm/mod.rs
  47. 92
      boa/src/syntax/parser/statement/break_stm/tests.rs
  48. 78
      boa/src/syntax/parser/statement/continue_stm/mod.rs
  49. 98
      boa/src/syntax/parser/statement/continue_stm/tests.rs
  50. 118
      boa/src/syntax/parser/statement/declaration/hoistable.rs
  51. 172
      boa/src/syntax/parser/statement/declaration/lexical.rs
  52. 62
      boa/src/syntax/parser/statement/declaration/mod.rs
  53. 132
      boa/src/syntax/parser/statement/declaration/tests.rs
  54. 73
      boa/src/syntax/parser/statement/if_stm/mod.rs
  55. 1
      boa/src/syntax/parser/statement/if_stm/tests.rs
  56. 82
      boa/src/syntax/parser/statement/iteration/do_while_statement.rs
  57. 102
      boa/src/syntax/parser/statement/iteration/for_statement.rs
  58. 10
      boa/src/syntax/parser/statement/iteration/mod.rs
  59. 50
      boa/src/syntax/parser/statement/iteration/tests.rs
  60. 60
      boa/src/syntax/parser/statement/iteration/while_statement.rs
  61. 318
      boa/src/syntax/parser/statement/mod.rs
  62. 63
      boa/src/syntax/parser/statement/return_stm/mod.rs
  63. 1
      boa/src/syntax/parser/statement/return_stm/tests.rs
  64. 101
      boa/src/syntax/parser/statement/switch/mod.rs
  65. 1
      boa/src/syntax/parser/statement/switch/tests.rs
  66. 54
      boa/src/syntax/parser/statement/throw/mod.rs
  67. 6
      boa/src/syntax/parser/statement/throw/tests.rs
  68. 109
      boa/src/syntax/parser/statement/try_stm/mod.rs
  69. 1
      boa/src/syntax/parser/statement/try_stm/tests.rs
  70. 179
      boa/src/syntax/parser/statement/variable.rs
  71. 799
      boa/src/syntax/parser/tests.rs

20
.vscode/launch.json vendored

@ -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",
}
]
}
}

42
Cargo.lock generated

@ -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",
]

5
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

2
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 }

1
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())),

124
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);
}

27
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);
}

12
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()),

239
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<bool>,
) {
let obj: Option<Object> = 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<InternalStateCell> {
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::<usize>() {
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<T: Any + InternalState>(&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::<Map<String, JSONValue>>();
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<Self, &'static str> {
Ok(v.to_int() as usize)
Ok(v.to_int() as Self)
}
}

4
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) => {

59
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"));
}

4
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),

48
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<String> for Const {
fn from(s: String) -> Self {
Const::String(s)
}
}
impl From<f64> for Const {
fn from(num: f64) -> Self {
Self::Num(num)
}
}
impl From<i32> for Const {
fn from(i: i32) -> Self {
Self::Int(i)
}
}
impl From<bool> 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"),
}
}
}

144
boa/src/syntax/ast/keyword.rs

@ -456,42 +456,42 @@ impl FromStr for Keyword {
type Err = KeywordError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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",
}
)
}

417
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<N>(nodes: N) -> Self
where
N: Into<Vec<Node>>,
{
Self::ArrayDecl(nodes.into())
}
/// Creates an `ArraowFunctionDecl` AST node.
pub fn arrow_function_decl<P, B>(params: P, body: B) -> Self
where
P: Into<Vec<FormalParameter>>,
B: Into<Box<Node>>,
{
Self::ArrowFunctionDecl(params.into(), body.into())
}
/// Creates an `Assign` AST node.
pub fn assign<L, R>(lhs: L, rhs: R) -> Self
where
L: Into<Box<Node>>,
R: Into<Box<Node>>,
{
Self::Assign(lhs.into(), rhs.into())
}
/// Creates a `BinOp` AST node.
pub fn bin_op<O, L, R>(op: O, lhs: L, rhs: R) -> Self
where
O: Into<BinOp>,
L: Into<Box<Node>>,
R: Into<Box<Node>>,
{
Self::BinOp(op.into(), lhs.into(), rhs.into())
}
/// Creates a `Block` AST node.
pub fn block<N>(nodes: N) -> Self
where
N: Into<Vec<Node>>,
{
Self::Block(nodes.into())
}
/// Creates a `Break` AST node.
pub fn break_node<OL, L>(label: OL) -> Self
where
L: Into<String>,
OL: Into<Option<L>>,
{
Self::Break(label.into().map(L::into))
}
/// Creates a `Call` AST node.
pub fn call<F, P>(function: F, params: P) -> Self
where
F: Into<Box<Node>>,
P: Into<Vec<Node>>,
{
Self::Call(function.into(), params.into())
}
/// Creates a `ConditionalOp` AST node.
pub fn conditional_op<C, T, F>(condition: C, if_true: T, if_false: F) -> Self
where
C: Into<Box<Node>>,
T: Into<Box<Node>>,
F: Into<Box<Node>>,
{
Self::ConditionalOp(condition.into(), if_true.into(), if_false.into())
}
/// Creates a `Const` AST node.
pub fn const_node<C>(node: C) -> Self
where
C: Into<Const>,
{
Self::Const(node.into())
}
/// Creates a `ConstDecl` AST node.
pub fn const_decl<D>(decl: D) -> Self
where
D: Into<Vec<(String, Node)>>,
{
Self::ConstDecl(decl.into())
}
/// Creates a `Continue` AST node.
pub fn continue_node<OL, L>(label: OL) -> Self
where
L: Into<String>,
OL: Into<Option<L>>,
{
Self::Continue(label.into().map(L::into))
}
/// Creates a `DoWhileLoop` AST node.
pub fn do_while_loop<B, C>(body: B, condition: C) -> Self
where
B: Into<Box<Node>>,
C: Into<Box<Node>>,
{
Self::DoWhileLoop(body.into(), condition.into())
}
/// Creates a `FunctionDecl` AST node.
pub fn function_decl<ON, N, P, B>(name: ON, params: P, body: B) -> Self
where
N: Into<String>,
ON: Into<Option<N>>,
P: Into<Vec<FormalParameter>>,
B: Into<Box<Node>>,
{
Self::FunctionDecl(name.into().map(N::into), params.into(), body.into())
}
/// Creates a `GetConstField` AST node.
pub fn get_const_field<V, L>(value: V, label: L) -> Self
where
V: Into<Box<Node>>,
L: Into<String>,
{
Self::GetConstField(value.into(), label.into())
}
/// Creates a `GetField` AST node.
pub fn get_field<V, F>(value: V, field: F) -> Self
where
V: Into<Box<Node>>,
F: Into<Box<Node>>,
{
Self::GetField(value.into(), field.into())
}
/// Creates a `ForLoop` AST node.
pub fn for_loop<OI, OC, OS, I, C, S, B>(init: OI, condition: OC, step: OS, body: B) -> Self
where
OI: Into<Option<I>>,
OC: Into<Option<C>>,
OS: Into<Option<S>>,
I: Into<Box<Node>>,
C: Into<Box<Node>>,
S: Into<Box<Node>>,
B: Into<Box<Node>>,
{
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<C, B, E, OE>(condition: C, body: B, else_node: OE) -> Self
where
C: Into<Box<Node>>,
B: Into<Box<Node>>,
E: Into<Box<Node>>,
OE: Into<Option<E>>,
{
Self::If(condition.into(), body.into(), else_node.into().map(E::into))
}
/// Creates a `LetDecl` AST node.
pub fn let_decl<I>(init: I) -> Self
where
I: Into<Vec<(String, Option<Node>)>>,
{
Self::LetDecl(init.into())
}
/// Creates a `Local` AST node.
pub fn local<N>(name: N) -> Self
where
N: Into<String>,
{
Self::Local(name.into())
}
/// Creates a `New` AST node.
pub fn new<N>(node: N) -> Self
where
N: Into<Box<Node>>,
{
Self::New(node.into())
}
/// Creates an `Object` AST node.
pub fn object<D>(def: D) -> Self
where
D: Into<Vec<PropertyDefinition>>,
{
Self::Object(def.into())
}
/// Creates a `Return` AST node.
pub fn return_node<E, OE>(expr: OE) -> Self
where
E: Into<Box<Node>>,
OE: Into<Option<E>>,
{
Self::Return(expr.into().map(E::into))
}
/// Creates a `Switch` AST node.
pub fn switch<V, C, OD, D>(val: V, cases: C, default: OD) -> Self
where
V: Into<Box<Node>>,
C: Into<Vec<(Node, Vec<Node>)>>,
OD: Into<Option<D>>,
D: Into<Box<Node>>,
{
Self::Switch(val.into(), cases.into(), default.into().map(D::into))
}
/// Creates a `Spread` AST node.
pub fn spread<V>(val: V) -> Self
where
V: Into<Box<Node>>,
{
Self::Spread(val.into())
}
/// Creates a `StatementList` AST node.
pub fn statement_list<L>(list: L) -> Self
where
L: Into<Vec<Node>>,
{
Self::StatementList(list.into())
}
/// Creates a `Throw` AST node.
pub fn throw<V>(val: V) -> Self
where
V: Into<Box<Node>>,
{
Self::Throw(val.into())
}
/// Creates a `TypeOf` AST node.
pub fn type_of<E>(expr: E) -> Self
where
E: Into<Box<Node>>,
{
Self::TypeOf(expr.into())
}
/// Creates a `Try` AST node.
pub fn try_node<T, OC, OP, OF, C, P, F>(try_node: T, catch: OC, param: OP, finally: OF) -> Self
where
T: Into<Box<Node>>,
OC: Into<Option<C>>,
OP: Into<Option<P>>,
OF: Into<Option<F>>,
C: Into<Box<Node>>,
P: Into<Box<Node>>,
F: Into<Box<Node>>,
{
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<V>(op: UnaryOp, val: V) -> Self
where
V: Into<Box<Node>>,
{
Self::UnaryOp(op, val.into())
}
/// Creates a `VarDecl` AST node.
pub fn var_decl<I>(init: I) -> Self
where
I: Into<Vec<(String, Option<Node>)>>,
{
Self::VarDecl(init.into())
}
/// Creates a `WhileLoop` AST node.
pub fn while_loop<C, B>(condition: C, body: B) -> Self
where
C: Into<Box<Node>>,
B: Into<Box<Node>>,
{
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<FormalParameter>;
impl FormalParameter {
pub fn new(name: String, init: Option<Box<Node>>, is_rest_param: bool) -> FormalParameter {
FormalParameter {
name,
pub fn new<N>(name: N, init: Option<Box<Node>>, is_rest_param: bool) -> Self
where
N: Into<String>,
{
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<I>(ident: I) -> Self
where
I: Into<String>,
{
Self::IdentifierReference(ident.into())
}
/// Creates a `Property` definition.
pub fn property<N, V>(name: N, value: V) -> Self
where
N: Into<String>,
V: Into<Node>,
{
Self::Property(name.into(), value.into())
}
/// Creates a `MethodDefinition`.
pub fn method_definition<N, B>(kind: MethodDefinitionKind, name: N, body: B) -> Self
where
N: Into<String>,
B: Into<Node>,
{
Self::MethodDefinition(kind, name.into(), body.into())
}
/// Creates a `SpreadObject`.
pub fn spread_object<O>(obj: O) -> Self
where
O: Into<Node>,
{
Self::SpreadObject(obj.into())
}
}
/// Method definition kinds.
///
/// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced.

160
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<NumOp> for BinOp {
fn from(op: NumOp) -> Self {
Self::Num(op)
}
}
impl From<BitOp> for BinOp {
fn from(op: BitOp) -> Self {
Self::Bit(op)
}
}
impl From<CompOp> for BinOp {
fn from(op: CompOp) -> Self {
Self::Comp(op)
}
}
impl From<LogOp> for BinOp {
fn from(op: LogOp) -> Self {
Self::Log(op)
}
}
impl From<AssignOp> 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 => ">>=",
}
)
}

159
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<BinOp> {
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 => "^",
}
)
}

97
boa/src/syntax/ast/token.rs

@ -95,21 +95,94 @@ pub enum TokenKind {
LineTerminator,
}
impl From<bool> for TokenKind {
fn from(oth: bool) -> Self {
Self::BooleanLiteral(oth)
}
}
impl From<Keyword> for TokenKind {
fn from(kw: Keyword) -> Self {
Self::Keyword(kw)
}
}
impl From<Punctuator> 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<I>(ident: I) -> Self
where
I: Into<String>,
{
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<S>(lit: S) -> Self
where
S: Into<String>,
{
Self::StringLiteral(lit.into())
}
/// Creates a `RegularExpressionLiteral` token kind.
pub fn regular_expression_literal<B, F>(body: B, flags: F) -> Self
where
B: Into<String>,
F: Into<String>,
{
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"),
}
}
}

9
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),

25
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")
);
}

222
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<P>(&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<P>(&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<K>(&mut self, kind: K, routine: &'static str) -> Result<(), ParseError>
where
K: Into<TokenKind>,
{
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<K>(&mut self, kind: K) -> Option<&'a Token>
where
K: Into<TokenKind>,
{
let next_token = self.peek(0)?;
if next_token.kind == kind.into() {
self.next()
} else {
None
}
}
}

106
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<Node, ParseError>;
/// `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<TokenKind>, 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<Position>),
}
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::<String>()
)
},
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!"),
}
}
}

161
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<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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))
}
}
/// <https://tc39.es/ecma262/#prod-ConciseBody>
#[derive(Debug, Clone, Copy)]
struct ConciseBody {
allow_in: AllowIn,
}
impl ConciseBody {
/// Creates a new `ConcideBody` parser.
fn new<I>(allow_in: I) -> Self
where
I: Into<AllowIn>,
{
Self {
allow_in: allow_in.into(),
}
}
}
impl TokenParser for ConciseBody {
type Output = Node;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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)?,
)),
}
}
}
/// <https://tc39.es/ecma262/#prod-ExpressionBody>
#[derive(Debug, Clone, Copy)]
struct ExpressionBody {
allow_in: AllowIn,
allow_await: AllowAwait,
}
impl ExpressionBody {
/// Creates a new `ExpressionBody` parser.
fn new<I, A>(allow_in: I, allow_await: A) -> Self
where
I: Into<AllowIn>,
A: Into<AllowAwait>,
{
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)
}
}

79
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<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

94
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

124
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<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

94
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl TokenParser for Arguments {
type Output = Vec<Node>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Vec<Node>, 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)
}
}

102
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<Y, A>(allow_yield: Y, allow_await: A, first_member_expr: Node) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

88
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

61
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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?
}
}
}

479
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
expression!(
MultiplicativeExpression,
ExponentiationExpression,
[Punctuator::Mul, Punctuator::Div, Punctuator::Mod],
[allow_yield, allow_await]
);

82
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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))
}
}

102
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),
])],
);
}

60
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))
}
}

94
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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"),
)),
}
}
}

290
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Self::Output, ParseError> {
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<Y, A, I>(allow_yield: Y, allow_await: A, identifier: I) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
I: Into<String>,
{
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<Self::Output, ParseError> {
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<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

131
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),
)])],
);
}

10
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")]);
}

293
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::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)),
)],
);
}

79
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}
}
}

82
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

238
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl TokenParser for FormalParameters {
type Output = Vec<node::FormalParameter>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Self::Output, ParseError> {
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<Self::Output, ParseError> {
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl TokenParser for FunctionStatementList {
type Output = Vec<Node>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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)
}
}

169
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)]),
)],
);
}

1799
boa/src/syntax/parser/mod.rs

File diff suppressed because it is too large Load Diff

203
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Y, A, R>(
allow_yield: Y,
allow_await: A,
allow_return: R,
break_when_closingbrase: bool,
) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Node>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, 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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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)
}
}
}
}

78
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

92
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)]),
)],
);
}

78
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

98
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)]),
)],
);
}

118
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<Y, A, D>(allow_yield: Y, allow_await: A, allow_default: D) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
D: Into<AllowDefault>,
{
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<Y, A, D>(allow_yield: Y, allow_await: A, allow_default: D) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
D: Into<AllowDefault>,
{
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))
}
}

172
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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: <https://tc39.es/ecma262/#prod-BindingList>.
#[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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A, is_const: bool) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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))
}
}
}

62
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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"),
}
}
}

132
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)),
])],
);
}

73
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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))
}
}

1
boa/src/syntax/parser/statement/if_stm/tests.rs

@ -0,0 +1 @@

82
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<Y, A, R>(
allow_yield: Y,
allow_await: A,
allow_return: R,
) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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))
}
}

102
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<Y, A, R>(
allow_yield: Y,
allow_await: A,
allow_return: R,
) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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]))
}
}

10
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,
};

50
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")],
),
],
);
}

60
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<Y, A, R>(
allow_yield: Y,
allow_await: A,
allow_return: R,
) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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))
}
}

318
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Y, A, R>(
allow_yield: Y,
allow_await: A,
allow_return: R,
break_when_closingbrase: bool,
) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Node>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Vec<Node>, 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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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)
}
}

63
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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))
}
}

1
boa/src/syntax/parser/statement/return_stm/tests.rs

@ -0,0 +1 @@

101
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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<Node>)>, Option<Node>);
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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")
}
}

1
boa/src/syntax/parser/statement/switch/tests.rs

@ -0,0 +1 @@

54
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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))
}
}

6
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"))]);
}

109
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<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
R: Into<AllowReturn>,
{
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,
))
}
}

1
boa/src/syntax/parser/statement/try_stm/tests.rs

@ -0,0 +1 @@

179
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(
allow_in: I,
allow_yield: Y,
allow_await: A,
) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_in: allow_in.into(),
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl TokenParser for VariableDeclaration {
type Output = (String, Option<Node>);
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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)),
}
}
}

799
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::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)),
)],
);
}

Loading…
Cancel
Save