Browse Source

Execution and Node modularization (#392)

pull/259/head
Iban Eguia 5 years ago committed by GitHub
parent
commit
4beadfc9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      .vscode/launch.json
  2. 17
      Cargo.lock
  3. 5
      boa/Cargo.toml
  4. 1740
      boa/src/builtins/array/mod.rs
  5. 50
      boa/src/builtins/array/tests.rs
  6. 175
      boa/src/builtins/bigint/mod.rs
  7. 22
      boa/src/builtins/bigint/tests.rs
  8. 166
      boa/src/builtins/boolean/mod.rs
  9. 12
      boa/src/builtins/boolean/tests.rs
  10. 41
      boa/src/builtins/console/mod.rs
  11. 68
      boa/src/builtins/error.rs
  12. 84
      boa/src/builtins/error/mod.rs
  13. 95
      boa/src/builtins/error/range.rs
  14. 65
      boa/src/builtins/function/mod.rs
  15. 11
      boa/src/builtins/json/mod.rs
  16. 4
      boa/src/builtins/json/tests.rs
  17. 81
      boa/src/builtins/math/mod.rs
  18. 56
      boa/src/builtins/math/tests.rs
  19. 46
      boa/src/builtins/mod.rs
  20. 746
      boa/src/builtins/number/mod.rs
  21. 63
      boa/src/builtins/number/tests.rs
  22. 18
      boa/src/builtins/object/mod.rs
  23. 24
      boa/src/builtins/property/mod.rs
  24. 794
      boa/src/builtins/regexp/mod.rs
  25. 16
      boa/src/builtins/regexp/tests.rs
  26. 1887
      boa/src/builtins/string/mod.rs
  27. 184
      boa/src/builtins/string/tests.rs
  28. 10
      boa/src/builtins/symbol/mod.rs
  29. 8
      boa/src/builtins/symbol/tests.rs
  30. 12
      boa/src/builtins/value/conversions.rs
  31. 42
      boa/src/builtins/value/mod.rs
  32. 11
      boa/src/builtins/value/operations.rs
  33. 8
      boa/src/builtins/value/tests.rs
  34. 18
      boa/src/environment/lexical_environment.rs
  35. 2
      boa/src/environment/object_environment_record.rs
  36. 26
      boa/src/exec/array/mod.rs
  37. 34
      boa/src/exec/block/mod.rs
  38. 130
      boa/src/exec/declaration/mod.rs
  39. 81
      boa/src/exec/expression/mod.rs
  40. 42
      boa/src/exec/iteration/mod.rs
  41. 874
      boa/src/exec/mod.rs
  42. 215
      boa/src/exec/operator/mod.rs
  43. 26
      boa/src/exec/statement_list.rs
  44. 112
      boa/src/exec/tests.rs
  45. 54
      boa/src/exec/try_node/mod.rs
  46. 95
      boa/src/exec/try_node/tests.rs
  47. 32
      boa/src/lib.rs
  48. 14
      boa/src/syntax/ast/constant.rs
  49. 102
      boa/src/syntax/ast/keyword.rs
  50. 13
      boa/src/syntax/ast/mod.rs
  51. 60
      boa/src/syntax/ast/node/array.rs
  52. 68
      boa/src/syntax/ast/node/block.rs
  53. 550
      boa/src/syntax/ast/node/declaration.rs
  54. 118
      boa/src/syntax/ast/node/expression.rs
  55. 58
      boa/src/syntax/ast/node/identifier.rs
  56. 139
      boa/src/syntax/ast/node/iteration.rs
  57. 803
      boa/src/syntax/ast/node/mod.rs
  58. 168
      boa/src/syntax/ast/node/operator.rs
  59. 67
      boa/src/syntax/ast/node/statement_list.rs
  60. 172
      boa/src/syntax/ast/node/try_node.rs
  61. 86
      boa/src/syntax/ast/op.rs
  62. 36
      boa/src/syntax/ast/pos.rs
  63. 291
      boa/src/syntax/ast/position.rs
  64. 0
      boa/src/syntax/ast/punctuator.rs
  65. 190
      boa/src/syntax/ast/token.rs
  66. 267
      boa/src/syntax/lexer/mod.rs
  67. 72
      boa/src/syntax/lexer/tests.rs
  68. 22
      boa/src/syntax/parser/cursor.rs
  69. 128
      boa/src/syntax/parser/error.rs
  70. 42
      boa/src/syntax/parser/expression/assignment/arrow_function.rs
  71. 2
      boa/src/syntax/parser/expression/assignment/conditional.rs
  72. 14
      boa/src/syntax/parser/expression/assignment/exponentiation.rs
  73. 18
      boa/src/syntax/parser/expression/assignment/mod.rs
  74. 12
      boa/src/syntax/parser/expression/left_hand_side/arguments.rs
  75. 15
      boa/src/syntax/parser/expression/left_hand_side/call.rs
  76. 15
      boa/src/syntax/parser/expression/left_hand_side/member.rs
  77. 2
      boa/src/syntax/parser/expression/left_hand_side/mod.rs
  78. 13
      boa/src/syntax/parser/expression/mod.rs
  79. 14
      boa/src/syntax/parser/expression/primary/array_initializer/mod.rs
  80. 78
      boa/src/syntax/parser/expression/primary/array_initializer/tests.rs
  81. 14
      boa/src/syntax/parser/expression/primary/function_expression.rs
  82. 51
      boa/src/syntax/parser/expression/primary/mod.rs
  83. 31
      boa/src/syntax/parser/expression/primary/object_initializer/mod.rs
  84. 73
      boa/src/syntax/parser/expression/primary/object_initializer/tests.rs
  85. 6
      boa/src/syntax/parser/expression/primary/tests.rs
  86. 259
      boa/src/syntax/parser/expression/tests.rs
  87. 20
      boa/src/syntax/parser/expression/unary.rs
  88. 16
      boa/src/syntax/parser/expression/update.rs
  89. 21
      boa/src/syntax/parser/function/mod.rs
  90. 99
      boa/src/syntax/parser/function/tests.rs
  91. 12
      boa/src/syntax/parser/mod.rs
  92. 203
      boa/src/syntax/parser/statement/block.rs
  93. 78
      boa/src/syntax/parser/statement/block/mod.rs
  94. 104
      boa/src/syntax/parser/statement/block/tests.rs
  95. 28
      boa/src/syntax/parser/statement/break_stm/mod.rs
  96. 75
      boa/src/syntax/parser/statement/break_stm/tests.rs
  97. 28
      boa/src/syntax/parser/statement/continue_stm/mod.rs
  98. 81
      boa/src/syntax/parser/statement/continue_stm/tests.rs
  99. 30
      boa/src/syntax/parser/statement/declaration/hoistable.rs
  100. 25
      boa/src/syntax/parser/statement/declaration/lexical.rs
  101. Some files were not shown because too many files have changed in this diff Show More

23
.vscode/launch.json vendored

@ -4,7 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
@ -37,16 +36,16 @@
"symbolSearchPath": "https://msdl.microsoft.com/download/symbols"
},
{
"name": "(Windows) Run Test Debugger",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/target/debug/boa-ea5ed1ef3ee0cbe1.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"preLaunchTask": "Cargo Test Build",
}
"name": "(Windows) Run Test Debugger",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/target/debug/boa-ea5ed1ef3ee0cbe1.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": true,
"preLaunchTask": "Cargo Test Build",
}
]
}

17
Cargo.lock generated

@ -4,6 +4,7 @@
name = "Boa"
version = "0.8.0"
dependencies = [
"bitflags",
"criterion",
"gc",
"jemallocator",
@ -253,18 +254,18 @@ checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
[[package]]
name = "gc"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4917b7233397091baf9136eec3c669c8551b097d69ca2b00a2606e5f07641d1"
checksum = "1a56d49bc3e3be03d3432ba45e0503aa13f7e0ad2fc2e6d8ab50b33725b3d767"
dependencies = [
"gc_derive",
]
[[package]]
name = "gc_derive"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a5b968c8044a119af2671a52de57689cbf502d6686847abd9e6252ee4c39313"
checksum = "034ffcc744b2fc6ec8d23695a4562428093b6f37c74746d9ee9ce606bfc869b8"
dependencies = [
"proc-macro2",
"quote",
@ -481,9 +482,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.13"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639"
checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
dependencies = [
"unicode-xid",
]
@ -789,9 +790,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "walkdir"

5
boa/Cargo.toml

@ -11,16 +11,17 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"]
edition = "2018"
[dependencies]
gc = { version = "0.3.4", features = ["derive"] }
gc = { version = "0.3.5", features = ["derive"] }
serde_json = "1.0.53"
rand = "0.7.3"
num-traits = "0.2.11"
regex = "1.3.7"
rustc-hash = "1.1.0"
num-bigint = {version = "0.2.6", features = ["serde"]}
num-bigint = { version = "0.2.6", features = ["serde"] }
# Optional Dependencies
serde = { version = "1.0.110", features = ["derive"], optional = true }
bitflags = "1.2.1"
[dev-dependencies]
criterion = "0.3.2"

1740
boa/src/builtins/array/mod.rs

File diff suppressed because it is too large Load Diff

50
boa/src/builtins/array/tests.rs

@ -1,11 +1,9 @@
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
use crate::{exec::Interpreter, forward, realm::Realm};
#[test]
fn is_array() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [];
var new_arr = new Array();
@ -46,7 +44,7 @@ fn is_array() {
fn concat() {
//TODO: array display formatter
// let realm = Realm::create();
// let mut engine = Executor::new(realm);
// let mut engine = Interpreter::new(realm);
// let init = r#"
// var empty = new Array();
// var one = new Array(1);
@ -69,7 +67,7 @@ fn concat() {
#[test]
fn join() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
@ -90,7 +88,7 @@ fn join() {
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
@ -111,7 +109,7 @@ fn to_string() {
#[test]
fn every() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
let init = r#"
var empty = [];
@ -156,7 +154,7 @@ fn every() {
#[test]
fn find() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
function comp(a) {
return a == "a";
@ -171,7 +169,7 @@ fn find() {
#[test]
fn find_index() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let code = r#"
function comp(item) {
@ -197,7 +195,7 @@ fn find_index() {
#[test]
fn push() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var arr = [1, 2];
"#;
@ -212,7 +210,7 @@ fn push() {
#[test]
fn pop() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = [1];
@ -234,7 +232,7 @@ fn pop() {
#[test]
fn shift() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = [1];
@ -256,7 +254,7 @@ fn shift() {
#[test]
fn unshift() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var arr = [3, 4];
"#;
@ -271,7 +269,7 @@ fn unshift() {
#[test]
fn reverse() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var arr = [1, 2];
var reversed = arr.reverse();
@ -286,7 +284,7 @@ fn reverse() {
#[test]
fn index_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
@ -350,7 +348,7 @@ fn index_of() {
#[test]
fn last_index_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
@ -414,7 +412,7 @@ fn last_index_of() {
#[test]
fn fill() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
forward(&mut engine, "var a = [1, 2, 3];");
assert_eq!(
@ -511,7 +509,7 @@ fn fill() {
#[test]
fn includes_value() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
@ -550,7 +548,7 @@ fn includes_value() {
#[test]
fn map() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let js = r#"
var empty = [];
@ -611,7 +609,7 @@ fn map() {
#[test]
fn slice() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [ ].slice();
var one = ["a"].slice();
@ -635,7 +633,7 @@ fn slice() {
#[test]
fn for_each() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = [2, 3, 4, 5];
var sum = 0;
@ -658,7 +656,7 @@ fn for_each() {
#[test]
fn for_each_push_value() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = [1, 2, 3, 4];
function callingCallback(item, index, list) {
@ -679,7 +677,7 @@ fn for_each_push_value() {
#[test]
fn filter() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let js = r#"
var empty = [];
@ -746,7 +744,7 @@ fn filter() {
#[test]
fn some() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = [];
@ -795,7 +793,7 @@ fn some() {
#[test]
fn call_array_constructor_with_one_argument() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = new Array(0);

175
boa/src/builtins/bigint/mod.rs

@ -14,93 +14,122 @@
use crate::{
builtins::{
function::make_constructor_fn,
function::{make_builtin_fn, make_constructor_fn},
value::{ResultValue, Value},
RangeError,
},
exec::Interpreter,
syntax::ast::bigint::BigInt,
syntax::ast::bigint::BigInt as AstBigInt,
};
#[cfg(test)]
mod tests;
/// `BigInt()`
///
/// The `BigInt()` constructor is used to create BigInt objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
pub fn make_bigint(_this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => {
if let Some(bigint) = value.to_bigint() {
Value::from(bigint)
} else {
panic!("RangeError: The value cannot be converted to a BigInt because it is not an integer");
/// `BigInt` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct BigInt;
impl BigInt {
/// `BigInt()`
///
/// The `BigInt()` constructor is used to create BigInt objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
pub(crate) fn make_bigint(
_this: &mut Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => {
if let Some(bigint) = value.to_bigint() {
Value::from(bigint)
} else {
return Err(RangeError::run_new(
format!(
"{} can't be converted to BigInt because it isn't an integer",
value
),
ctx,
)?);
}
}
}
None => Value::from(BigInt::from(0)),
};
Ok(data)
}
None => Value::from(AstBigInt::from(0)),
};
Ok(data)
}
/// `BigInt.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified BigInt object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
pub fn to_string(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let radix = if !args.is_empty() {
args[0].to_integer()
} else {
10
};
if radix < 2 && radix > 36 {
panic!("RangeError: toString() radix argument must be between 2 and 36");
/// `BigInt.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified BigInt object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &mut Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
let radix = if !args.is_empty() {
args[0].to_integer()
} else {
10
};
if radix < 2 && radix > 36 {
return Err(RangeError::run_new(
"radix must be an integer at least 2 and no greater than 36",
ctx,
)?);
}
Ok(Value::from(
this.to_bigint().unwrap().to_str_radix(radix as u32),
))
}
Ok(Value::from(
this.to_bigint().unwrap().to_str_radix(radix as u32),
))
}
// /// `BigInt.prototype.valueOf()`
// ///
// /// The `valueOf()` method returns the wrapped primitive value of a Number object.
// ///
// /// More information:
// /// - [ECMAScript reference][spec]
// /// - [MDN documentation][mdn]
// ///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf
pub fn value_of(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
Ok(Value::from(
this.to_bigint().expect("BigInt.prototype.valueOf"),
))
}
// /// `BigInt.prototype.valueOf()`
// ///
// /// The `valueOf()` method returns the wrapped primitive value of a Number object.
// ///
// /// More information:
// /// - [ECMAScript reference][spec]
// /// - [MDN documentation][mdn]
// ///
/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf
pub(crate) fn value_of(
this: &mut Value,
_args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Value::from(
this.to_bigint().expect("BigInt.prototype.valueOf"),
))
}
/// Create a new `Number` object
pub fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BigIntData", Value::from(BigInt::from(0)));
/// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BigIntData", Value::from(AstBigInt::from(0)));
make_builtin_fn!(to_string, named "toString", with length 1, of prototype);
make_builtin_fn!(value_of, named "valueOf", of prototype);
make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
make_constructor_fn(make_bigint, global, prototype)
}
make_constructor_fn(Self::make_bigint, global, prototype)
}
/// Initialise the `BigInt` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("BigInt", create(global));
/// Initialise the `BigInt` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) {
global.set_field("BigInt", Self::create(global));
}
}

22
boa/src/builtins/bigint/tests.rs

@ -1,9 +1,9 @@
use crate::{forward, Executor, Realm};
use crate::{forward, Interpreter, Realm};
#[test]
fn equality() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "0n == 0n"), "true");
assert_eq!(forward(&mut engine, "1n == 0n"), "false");
@ -57,7 +57,7 @@ fn equality() {
#[test]
fn bigint_function_conversion() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "BigInt(1000)"), "1000n");
assert_eq!(
@ -73,7 +73,7 @@ fn bigint_function_conversion() {
#[test]
fn add() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "10000n + 1000n"), "11000n");
}
@ -81,7 +81,7 @@ fn add() {
#[test]
fn sub() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "10000n - 1000n"), "9000n");
}
@ -89,7 +89,7 @@ fn sub() {
#[test]
fn mul() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(
forward(&mut engine, "123456789n * 102030n"),
@ -100,7 +100,7 @@ fn mul() {
#[test]
fn div() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "15000n / 50n"), "300n");
}
@ -108,7 +108,7 @@ fn div() {
#[test]
fn div_with_truncation() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "15001n / 50n"), "300n");
}
@ -116,7 +116,7 @@ fn div_with_truncation() {
#[test]
fn r#mod() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "15007n % 10n"), "7n");
}
@ -124,7 +124,7 @@ fn r#mod() {
#[test]
fn pow() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(
forward(&mut engine, "100n ** 10n"),
@ -135,7 +135,7 @@ fn pow() {
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "1000n.toString()"), "1000");

166
boa/src/builtins/boolean/mod.rs

@ -12,7 +12,7 @@
#[cfg(test)]
mod tests;
use super::function::make_constructor_fn;
use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{
builtins::{
object::{internal_methods_trait::ObjectInternalMethods, ObjectKind},
@ -22,93 +22,105 @@ use crate::{
};
use std::{borrow::Borrow, ops::Deref};
/// `[[Construct]]` Create a new boolean object
///
/// `[[Call]]` Creates a new boolean primitive
pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.set_kind(ObjectKind::Boolean);
/// Boolean implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Boolean;
// Get the argument, if any
if let Some(ref value) = args.get(0) {
this.set_internal_slot("BooleanData", to_boolean(value));
} else {
this.set_internal_slot("BooleanData", to_boolean(&Value::from(false)));
}
impl Boolean {
/// `[[Construct]]` Create a new boolean object
///
/// `[[Call]]` Creates a new boolean primitive
pub(crate) fn construct_boolean(
this: &mut Value,
args: &[Value],
_: &mut Interpreter,
) -> ResultValue {
this.set_kind(ObjectKind::Boolean);
// Get the argument, if any
if let Some(ref value) = args.get(0) {
this.set_internal_slot("BooleanData", Self::to_boolean(value));
} else {
this.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false)));
}
match args.get(0) {
Some(ref value) => Ok(to_boolean(value)),
None => Ok(to_boolean(&Value::from(false))),
match args.get(0) {
Some(ref value) => Ok(Self::to_boolean(value)),
None => Ok(Self::to_boolean(&Value::from(false))),
}
}
}
/// The `toString()` method returns a string representing the specified `Boolean` object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-boolean-object
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString
pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let b = this_boolean_value(this);
Ok(Value::from(b.to_string()))
}
/// The `toString()` method returns a string representing the specified `Boolean` object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-boolean-object
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let b = Self::this_boolean_value(this);
Ok(Value::from(b.to_string()))
}
/// The valueOf() method returns the primitive value of a `Boolean` object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf
pub fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(this_boolean_value(this))
}
/// The valueOf() method returns the primitive value of a `Boolean` object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf
pub(crate) fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(Self::this_boolean_value(this))
}
// === Utility Functions ===
/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean)
/// Creates a new boolean value from the input
pub fn to_boolean(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Object(_) => Value::from(true),
ValueData::String(ref s) if !s.is_empty() => Value::from(true),
ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true),
ValueData::Integer(n) if n != 0 => Value::from(true),
ValueData::Boolean(v) => Value::from(v),
_ => Value::from(false),
// === Utility Functions ===
/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean)
/// Creates a new boolean value from the input
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_boolean(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Object(_) => Value::from(true),
ValueData::String(ref s) if !s.is_empty() => Value::from(true),
ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true),
ValueData::Integer(n) if n != 0 => Value::from(true),
ValueData::Boolean(v) => Value::from(v),
_ => Value::from(false),
}
}
}
/// An Utility function used to get the internal BooleanData.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
pub fn this_boolean_value(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Boolean(v) => Value::from(v),
ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"),
_ => Value::from(false),
/// An Utility function used to get the internal BooleanData.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
pub(crate) fn this_boolean_value(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Boolean(v) => Value::from(v),
ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"),
_ => Value::from(false),
}
}
}
/// Create a new `Boolean` object.
pub fn create(global: &Value) -> Value {
// Create Prototype
// https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BooleanData", to_boolean(&Value::from(false)));
/// Create a new `Boolean` object.
pub(crate) fn create(global: &Value) -> Value {
// Create Prototype
// https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false)));
make_builtin_fn!(to_string, named "toString", of prototype);
make_builtin_fn!(value_of, named "valueOf", of prototype);
make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
make_constructor_fn(construct_boolean, global, prototype)
}
make_constructor_fn(Self::construct_boolean, global, prototype)
}
/// Initialise the `Boolean` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("Boolean", create(global));
/// Initialise the `Boolean` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) {
global.set_field("Boolean", Self::create(global));
}
}

12
boa/src/builtins/boolean/tests.rs

@ -1,12 +1,10 @@
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{builtins::value::same_value, forward, forward_val};
use crate::{builtins::value::same_value, exec::Interpreter, forward, forward_val, realm::Realm};
#[test]
fn check_boolean_constructor_is_function() {
let global = Value::new_object(None);
let boolean_constructor = create(&global);
let boolean_constructor = Boolean::create(&global);
assert_eq!(boolean_constructor.is_function(), true);
}
@ -15,7 +13,7 @@ fn check_boolean_constructor_is_function() {
#[test]
fn construct_and_call() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var one = new Boolean(1);
var zero = Boolean(0);
@ -31,7 +29,7 @@ fn construct_and_call() {
#[test]
fn constructor_gives_true_instance() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var trueVal = new Boolean(true);
var trueNum = new Boolean(1);
@ -61,7 +59,7 @@ fn constructor_gives_true_instance() {
#[test]
fn instances_have_correct_proto_set() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var boolInstance = new Boolean(true);
var boolProto = Boolean.prototype;

41
boa/src/builtins/console/mod.rs

@ -18,6 +18,7 @@ mod tests;
use crate::{
builtins::{
function::make_builtin_fn,
object::InternalState,
value::{display_obj, ResultValue, Value},
},
@ -491,25 +492,25 @@ pub fn dir(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue
pub fn create(global: &Value) -> Value {
let console = Value::new_object(Some(global));
make_builtin_fn!(assert, named "assert", of console);
make_builtin_fn!(clear, named "clear", of console);
make_builtin_fn!(debug, named "debug", of console);
make_builtin_fn!(error, named "error", of console);
make_builtin_fn!(info, named "info", of console);
make_builtin_fn!(log, named "log", of console);
make_builtin_fn!(trace, named "trace", of console);
make_builtin_fn!(warn, named "warn", of console);
make_builtin_fn!(error, named "exception", of console);
make_builtin_fn!(count, named "count", of console);
make_builtin_fn!(count_reset, named "countReset", of console);
make_builtin_fn!(group, named "group", of console);
make_builtin_fn!(group, named "groupCollapsed", of console);
make_builtin_fn!(group_end , named "groupEnd", of console);
make_builtin_fn!(time, named "time", of console);
make_builtin_fn!(time_log, named "timeLog", of console);
make_builtin_fn!(time_end, named "timeEnd", of console);
make_builtin_fn!(dir, named "dir", of console);
make_builtin_fn!(dir, named "dirxml", of console);
make_builtin_fn(assert, "assert", &console, 0);
make_builtin_fn(clear, "clear", &console, 0);
make_builtin_fn(debug, "debug", &console, 0);
make_builtin_fn(error, "error", &console, 0);
make_builtin_fn(info, "info", &console, 0);
make_builtin_fn(log, "log", &console, 0);
make_builtin_fn(trace, "trace", &console, 0);
make_builtin_fn(warn, "warn", &console, 0);
make_builtin_fn(error, "exception", &console, 0);
make_builtin_fn(count, "count", &console, 0);
make_builtin_fn(count_reset, "countReset", &console, 0);
make_builtin_fn(group, "group", &console, 0);
make_builtin_fn(group, "groupCollapsed", &console, 0);
make_builtin_fn(group_end, "groupEnd", &console, 0);
make_builtin_fn(time, "time", &console, 0);
make_builtin_fn(time_log, "timeLog", &console, 0);
make_builtin_fn(time_end, "timeEnd", &console, 0);
make_builtin_fn(dir, "dir", &console, 0);
make_builtin_fn(dir, "dirxml", &console, 0);
console.set_internal_state(ConsoleState::default());
@ -519,5 +520,5 @@ pub fn create(global: &Value) -> Value {
/// Initialise the `console` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("console", create(global));
global.set_field("console", create(global));
}

68
boa/src/builtins/error.rs

@ -1,68 +0,0 @@
//! This module implements the global `Error` object.
//!
//! Error objects are thrown when runtime errors occur.
//! The Error object can also be used as a base object for user-defined exceptions.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
use super::function::make_constructor_fn;
use crate::{
builtins::{
object::ObjectKind,
value::{ResultValue, Value},
},
exec::Interpreter,
};
/// Create a new error object.
pub fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() {
this.set_field_slice(
"message",
Value::from(
args.get(0)
.expect("failed getting error message")
.to_string(),
),
);
}
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error);
Ok(Value::undefined())
}
/// `Error.prototype.toString()`
///
/// The toString() method returns a string representing the specified Error object.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field_slice("name");
let message = this.get_field_slice("message");
Ok(Value::from(format!("{}: {}", name, message)))
}
/// Create a new `Error` object.
pub fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_field_slice("message", Value::from(""));
prototype.set_field_slice("name", Value::from("Error"));
make_builtin_fn!(to_string, named "toString", of prototype);
make_constructor_fn(make_error, global, prototype)
}
/// Initialise the global object with the `Error` object.
pub fn init(global: &Value) {
global.set_field_slice("Error", create(global));
}

84
boa/src/builtins/error/mod.rs

@ -0,0 +1,84 @@
//! This module implements the global `Error` object.
//!
//! Error objects are thrown when runtime errors occur.
//! The Error object can also be used as a base object for user-defined exceptions.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
use crate::{
builtins::{
function::{make_builtin_fn, make_constructor_fn},
object::ObjectKind,
value::{ResultValue, Value},
},
exec::Interpreter,
};
// mod eval;
pub(crate) mod range;
// mod reference;
// mod syntax;
// mod type_err;
// mod uri;
pub(crate) use self::range::RangeError;
/// Built-in `Error` object.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Error;
impl Error {
/// Create a new error object.
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() {
this.set_field(
"message",
Value::from(
args.get(0)
.expect("failed getting error message")
.to_string(),
),
);
}
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error);
Ok(Value::undefined())
}
/// `Error.prototype.toString()`
///
/// The toString() method returns a string representing the specified Error object.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name");
let message = this.get_field("message");
Ok(Value::from(format!("{}: {}", name, message)))
}
/// Create a new `Error` object.
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_field("message", Value::from(""));
prototype.set_field("name", Value::from("Error"));
make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn(Self::make_error, global, prototype)
}
/// Initialise the global object with the `Error` object.
pub(crate) fn init(global: &Value) {
global.set_field("Error", Self::create(global));
}
}

95
boa/src/builtins/error/range.rs

@ -0,0 +1,95 @@
//! This module implements the global `RangeError` object.
//!
//! Indicates a value that is not in the set or range of allowable values.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
use crate::{
builtins::{
function::make_builtin_fn,
function::make_constructor_fn,
object::ObjectKind,
value::{ResultValue, Value},
},
exec::Interpreter,
};
/// JavaScript `RangeError` impleentation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct RangeError;
impl RangeError {
/// Create a new error object.
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() {
this.set_field(
"message",
Value::from(
args.get(0)
.expect("failed getting error message")
.to_string(),
),
);
}
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error);
Ok(Value::undefined())
}
/// `Error.prototype.toString()`
///
/// The toString() method returns a string representing the specified Error object.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name");
let message = this.get_field("message");
Ok(Value::from(format!("{}: {}", name, message)))
}
/// Create a new `RangeError` object.
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_field("message", Value::from(""));
prototype.set_field("name", Value::from("RangeError"));
make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn(Self::make_error, global, prototype)
}
/// Runs a `new RangeError(message)`.
pub(crate) fn run_new<M>(message: M, interpreter: &mut Interpreter) -> ResultValue
where
M: Into<String>,
{
use crate::{
exec::Executable,
syntax::ast::{
node::{Call, Identifier, New},
Const,
},
};
New::from(Call::new(
Identifier::from("RangeError"),
vec![Const::from(message.into()).into()],
))
.run(interpreter)
}
/// Initialise the global object with the `RangeError` object.
pub(crate) fn init(global: &Value) {
global.set_field("RangeError", Self::create(global));
}
}

65
boa/src/builtins/function/mod.rs

@ -13,18 +13,15 @@
use crate::{
builtins::{
array,
array::Array,
object::{Object, ObjectInternalMethods, ObjectKind, PROTOTYPE},
property::Property,
value::{ResultValue, Value},
},
environment::{
function_environment_record::BindingStatus,
lexical_environment::{new_function_environment, Environment},
},
exec::Executor,
syntax::ast::node::{FormalParameter, Node},
Interpreter,
environment::function_environment_record::BindingStatus,
environment::lexical_environment::{new_function_environment, Environment},
exec::{Executable, Interpreter},
syntax::ast::node::{FormalParameter, StatementList},
};
use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{self, Debug};
@ -52,14 +49,14 @@ pub enum ThisMode {
#[derive(Clone, Finalize)]
pub enum FunctionBody {
BuiltIn(NativeFunctionData),
Ordinary(Node),
Ordinary(StatementList),
}
impl Debug for FunctionBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BuiltIn(_) => write!(f, "native code"),
Self::Ordinary(node) => write!(f, "{}", node),
Self::Ordinary(statements) => write!(f, "{:?}", statements),
}
}
}
@ -174,7 +171,7 @@ impl Function {
for i in 0..self.params.len() {
let param = self.params.get(i).expect("Could not get param");
// Rest Parameters
if param.is_rest_param {
if param.is_rest_param() {
self.add_rest_param(param, i, args_list, interpreter, &local_env);
break;
}
@ -196,7 +193,7 @@ impl Function {
// Call body should be set before reaching here
let result = match &self.body {
FunctionBody::Ordinary(ref body) => interpreter.run(body),
FunctionBody::Ordinary(ref body) => body.run(interpreter),
_ => panic!("Ordinary function should not have BuiltIn Function body"),
};
@ -238,7 +235,7 @@ impl Function {
// Add argument bindings to the function environment
for (i, param) in self.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param {
if param.is_rest_param() {
self.add_rest_param(param, i, args_list, interpreter, &local_env);
break;
}
@ -260,7 +257,7 @@ impl Function {
// Call body should be set before reaching here
let _ = match &self.body {
FunctionBody::Ordinary(ref body) => interpreter.run(body),
FunctionBody::Ordinary(ref body) => body.run(interpreter),
_ => panic!("Ordinary function should not have BuiltIn Function body"),
};
@ -281,18 +278,18 @@ impl Function {
local_env: &Environment,
) {
// Create array of values
let array = array::new_array(interpreter).unwrap();
array::add_to_array_object(&array, &args_list[index..]).unwrap();
let array = Array::new_array(interpreter).unwrap();
Array::add_to_array_object(&array, &args_list[index..]).unwrap();
// Create binding
local_env
.borrow_mut()
.create_mutable_binding(param.name.clone(), false);
.create_mutable_binding(param.name().to_owned(), false);
// Set Binding to value
local_env
.borrow_mut()
.initialize_binding(&param.name, array);
.initialize_binding(param.name(), array);
}
// Adds an argument to the environment
@ -305,12 +302,12 @@ impl Function {
// Create binding
local_env
.borrow_mut()
.create_mutable_binding(param.name.clone(), false);
.create_mutable_binding(param.name().to_owned(), false);
// Set Binding to value
local_env
.borrow_mut()
.initialize_binding(&param.name, value);
.initialize_binding(param.name(), value);
}
}
@ -387,9 +384,7 @@ pub fn make_constructor_fn(body: NativeFunctionData, global: &Value, proto: Valu
);
// Get reference to Function.prototype
let func_prototype = global
.get_field_slice("Function")
.get_field_slice(PROTOTYPE);
let func_prototype = global.get_field("Function").get_field(PROTOTYPE);
// Create the function object and point its instance prototype to Function.prototype
let mut constructor_obj = Object::function();
@ -399,14 +394,32 @@ pub fn make_constructor_fn(body: NativeFunctionData, global: &Value, proto: Valu
let constructor_val = Value::from(constructor_obj);
// Set proto.constructor -> constructor_obj
proto.set_field_slice("constructor", constructor_val.clone());
constructor_val.set_field_slice(PROTOTYPE, proto);
proto.set_field("constructor", constructor_val.clone());
constructor_val.set_field(PROTOTYPE, proto);
constructor_val
}
/// Macro to create a new member function of a prototype.
///
/// If no length is provided, the length will be set to 0.
pub fn make_builtin_fn<N>(function: NativeFunctionData, name: N, parent: &Value, length: i32)
where
N: Into<String>,
{
let func = Function::create_builtin(vec![], FunctionBody::BuiltIn(function));
let mut new_func = Object::function();
new_func.set_func(func);
let new_func_obj = Value::from(new_func);
new_func_obj.set_field("length", length);
parent.set_field(name.into(), new_func_obj);
}
/// Initialise the `Function` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("Function", create(global));
global.set_field("Function", create(global));
}

11
boa/src/builtins/json/mod.rs

@ -13,7 +13,10 @@
//! [json]: https://www.json.org/json-en.html
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
use crate::builtins::value::{ResultValue, Value};
use crate::builtins::{
function::make_builtin_fn,
value::{ResultValue, Value},
};
use crate::exec::Interpreter;
use serde_json::{self, Value as JSONValue};
@ -72,8 +75,8 @@ pub fn stringify(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVa
pub fn create(global: &Value) -> Value {
let json = Value::new_object(Some(global));
make_builtin_fn!(parse, named "parse", with length 2, of json);
make_builtin_fn!(stringify, named "stringify", with length 3, of json);
make_builtin_fn(parse, "parse", &json, 2);
make_builtin_fn(stringify, "stringify", &json, 3);
json
}
@ -81,5 +84,5 @@ pub fn create(global: &Value) -> Value {
/// Initialise the `JSON` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("JSON", create(global));
global.set_field("JSON", create(global));
}

4
boa/src/builtins/json/tests.rs

@ -1,9 +1,9 @@
use crate::{exec::Executor, forward, realm::Realm};
use crate::{exec::Interpreter, forward, realm::Realm};
#[test]
fn json_sanity() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(
forward(&mut engine, r#"JSON.parse('{"aaa":"bbb"}').aaa == 'bbb'"#),
"true"

81
boa/src/builtins/math/mod.rs

@ -12,7 +12,10 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
use crate::{
builtins::value::{ResultValue, Value},
builtins::{
function::make_builtin_fn,
value::{ResultValue, Value},
},
exec::Interpreter,
};
use rand::random;
@ -506,43 +509,43 @@ pub fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue
pub fn create(global: &Value) -> Value {
let math = Value::new_object(Some(global));
math.set_field_slice("E", Value::from(f64::consts::E));
math.set_field_slice("LN2", Value::from(f64::consts::LN_2));
math.set_field_slice("LN10", Value::from(f64::consts::LN_10));
math.set_field_slice("LOG2E", Value::from(f64::consts::LOG2_E));
math.set_field_slice("LOG10E", Value::from(f64::consts::LOG10_E));
math.set_field_slice("SQRT1_2", Value::from(0.5_f64.sqrt()));
math.set_field_slice("SQRT2", Value::from(f64::consts::SQRT_2));
math.set_field_slice("PI", Value::from(f64::consts::PI));
make_builtin_fn!(abs, named "abs", with length 1, of math);
make_builtin_fn!(acos, named "acos", with length 1, of math);
make_builtin_fn!(acosh, named "acosh", with length 1, of math);
make_builtin_fn!(asin, named "asin", with length 1, of math);
make_builtin_fn!(asinh, named "asinh", with length 1, of math);
make_builtin_fn!(atan, named "atan", with length 1, of math);
make_builtin_fn!(atanh, named "atanh", with length 1, of math);
make_builtin_fn!(atan2, named "atan2", with length 2, of math);
make_builtin_fn!(cbrt, named "cbrt", with length 1, of math);
make_builtin_fn!(ceil, named "ceil", with length 1, of math);
make_builtin_fn!(cos, named "cos", with length 1, of math);
make_builtin_fn!(cosh, named "cosh", with length 1, of math);
make_builtin_fn!(exp, named "exp", with length 1, of math);
make_builtin_fn!(floor, named "floor", with length 1, of math);
make_builtin_fn!(log, named "log", with length 1, of math);
make_builtin_fn!(log10, named "log10", with length 1, of math);
make_builtin_fn!(log2, named "log2", with length 1, of math);
make_builtin_fn!(max, named "max", with length 2, of math);
make_builtin_fn!(min, named "min", with length 2, of math);
make_builtin_fn!(pow, named "pow", with length 2, of math);
make_builtin_fn!(_random, named "random", of math);
make_builtin_fn!(round, named "round", with length 1, of math);
make_builtin_fn!(sign, named "sign", with length 1, of math);
make_builtin_fn!(sin, named "sin", with length 1, of math);
make_builtin_fn!(sinh, named "sinh", with length 1, of math);
make_builtin_fn!(sqrt, named "sqrt", with length 1, of math);
make_builtin_fn!(tan, named "tan", with length 1, of math);
make_builtin_fn!(tanh, named "tanh", with length 1, of math);
make_builtin_fn!(trunc, named "trunc", with length 1, of math);
math.set_field("E", Value::from(f64::consts::E));
math.set_field("LN2", Value::from(f64::consts::LN_2));
math.set_field("LN10", Value::from(f64::consts::LN_10));
math.set_field("LOG2E", Value::from(f64::consts::LOG2_E));
math.set_field("LOG10E", Value::from(f64::consts::LOG10_E));
math.set_field("SQRT1_2", Value::from(0.5_f64.sqrt()));
math.set_field("SQRT2", Value::from(f64::consts::SQRT_2));
math.set_field("PI", Value::from(f64::consts::PI));
make_builtin_fn(abs, "abs", &math, 1);
make_builtin_fn(acos, "acos", &math, 1);
make_builtin_fn(acosh, "acosh", &math, 1);
make_builtin_fn(asin, "asin", &math, 1);
make_builtin_fn(asinh, "asinh", &math, 1);
make_builtin_fn(atan, "atan", &math, 1);
make_builtin_fn(atanh, "atanh", &math, 1);
make_builtin_fn(atan2, "atan2", &math, 2);
make_builtin_fn(cbrt, "cbrt", &math, 1);
make_builtin_fn(ceil, "ceil", &math, 1);
make_builtin_fn(cos, "cos", &math, 1);
make_builtin_fn(cosh, "cosh", &math, 1);
make_builtin_fn(exp, "exp", &math, 1);
make_builtin_fn(floor, "floor", &math, 1);
make_builtin_fn(log, "log", &math, 1);
make_builtin_fn(log10, "log10", &math, 1);
make_builtin_fn(log2, "log2", &math, 1);
make_builtin_fn(max, "max", &math, 2);
make_builtin_fn(min, "min", &math, 2);
make_builtin_fn(pow, "pow", &math, 2);
make_builtin_fn(_random, "random", &math, 0);
make_builtin_fn(round, "round", &math, 1);
make_builtin_fn(sign, "sign", &math, 1);
make_builtin_fn(sin, "sin", &math, 1);
make_builtin_fn(sinh, "sinh", &math, 1);
make_builtin_fn(sqrt, "sqrt", &math, 1);
make_builtin_fn(tan, "tan", &math, 1);
make_builtin_fn(tanh, "tanh", &math, 1);
make_builtin_fn(trunc, "trunc", &math, 1);
math
}
@ -550,5 +553,5 @@ pub fn create(global: &Value) -> Value {
/// Initialise the `Math` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("Math", create(global));
global.set_field("Math", create(global));
}

56
boa/src/builtins/math/tests.rs

@ -1,12 +1,12 @@
#![allow(clippy::float_cmp)]
use crate::{exec::Executor, forward, forward_val, realm::Realm};
use crate::{exec::Interpreter, forward, forward_val, realm::Realm};
use std::f64;
#[test]
fn abs() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.abs(3 - 5);
var b = Math.abs(1.23456 - 7.89012);
@ -24,7 +24,7 @@ fn abs() {
#[test]
fn acos() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.acos(8 / 10);
var b = Math.acos(5 / 3);
@ -48,7 +48,7 @@ fn acos() {
#[test]
fn acosh() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.acosh(2);
var b = Math.acosh(-1);
@ -69,7 +69,7 @@ fn acosh() {
#[test]
fn asin() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.asin(6 / 10);
var b = Math.asin(5 / 3);
@ -87,7 +87,7 @@ fn asin() {
#[test]
fn asinh() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.asinh(1);
var b = Math.asinh(0);
@ -105,7 +105,7 @@ fn asinh() {
#[test]
fn atan() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.atan(1);
var b = Math.atan(0);
@ -126,7 +126,7 @@ fn atan() {
#[test]
fn atan2() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.atan2(90, 15);
var b = Math.atan2(15, 90);
@ -144,7 +144,7 @@ fn atan2() {
#[test]
fn cbrt() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.cbrt(64);
var b = Math.cbrt(-1);
@ -165,7 +165,7 @@ fn cbrt() {
#[test]
fn ceil() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.ceil(1.95);
var b = Math.ceil(4);
@ -186,7 +186,7 @@ fn ceil() {
#[test]
fn cos() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.cos(0);
var b = Math.cos(1);
@ -204,7 +204,7 @@ fn cos() {
#[test]
fn cosh() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.cosh(0);
var b = Math.cosh(1);
@ -225,7 +225,7 @@ fn cosh() {
#[test]
fn exp() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.exp(0);
var b = Math.exp(-1);
@ -246,7 +246,7 @@ fn exp() {
#[test]
fn floor() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.floor(1.95);
var b = Math.floor(-3.01);
@ -267,7 +267,7 @@ fn floor() {
#[test]
fn log() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.log(1);
var b = Math.log(10);
@ -288,7 +288,7 @@ fn log() {
#[test]
fn log10() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.log10(2);
var b = Math.log10(1);
@ -309,7 +309,7 @@ fn log10() {
#[test]
fn log2() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.log2(3);
var b = Math.log2(1);
@ -330,7 +330,7 @@ fn log2() {
#[test]
fn max() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.max(10, 20);
var b = Math.max(-10, -20);
@ -351,7 +351,7 @@ fn max() {
#[test]
fn min() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.min(10, 20);
var b = Math.min(-10, -20);
@ -372,7 +372,7 @@ fn min() {
#[test]
fn pow() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.pow(2, 10);
var b = Math.pow(-7, 2);
@ -396,7 +396,7 @@ fn pow() {
#[test]
fn round() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.round(20.5);
var b = Math.round(-20.3);
@ -414,7 +414,7 @@ fn round() {
#[test]
fn sign() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.sign(3);
var b = Math.sign(-3);
@ -435,7 +435,7 @@ fn sign() {
#[test]
fn sin() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.sin(0);
var b = Math.sin(1);
@ -453,7 +453,7 @@ fn sin() {
#[test]
fn sinh() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.sinh(0);
var b = Math.sinh(1);
@ -471,7 +471,7 @@ fn sinh() {
#[test]
fn sqrt() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.sqrt(0);
var b = Math.sqrt(2);
@ -494,7 +494,7 @@ fn sqrt() {
// #[test]
// fn tan() {
// let realm = Realm::create();
// let mut engine = Executor::new(realm);
// let mut engine = Interpreter::new(realm);
// let init = r#"
// var a = Math.tan(1.1);
// "#;
@ -509,7 +509,7 @@ fn sqrt() {
#[test]
fn tanh() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.tanh(1);
var b = Math.tanh(0);
@ -527,7 +527,7 @@ fn tanh() {
#[test]
fn trunc() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = Math.trunc(13.37);
var b = Math.trunc(0.123);

46
boa/src/builtins/mod.rs

@ -1,26 +1,5 @@
//! Builtins live here, such as Object, String, Math etc
/// Macro to create a new member function of a prototype.
///
/// If no length is provided, the length will be set to 0.
macro_rules! make_builtin_fn {
($fn:ident, named $name:expr, with length $l:tt, of $p:ident) => {
let func = crate::builtins::function::Function::create_builtin(
vec![],
crate::builtins::function::FunctionBody::BuiltIn($fn),
);
let mut new_func = crate::builtins::object::Object::function();
new_func.set_func(func);
let new_func_obj = Value::from(new_func);
new_func_obj.set_field_slice("length", Value::from($l));
$p.set_field_slice($name, new_func_obj);
};
($fn:ident, named $name:expr, of $p:ident) => {
make_builtin_fn!($fn, named $name, with length 0, of $p);
};
}
pub mod array;
pub mod bigint;
pub mod boolean;
@ -37,21 +16,32 @@ pub mod string;
pub mod symbol;
pub mod value;
use value::Value;
pub(crate) use self::{
array::Array,
bigint::BigInt,
boolean::Boolean,
error::{Error, RangeError},
number::Number,
regexp::RegExp,
string::String,
value::{ResultValue, Value},
};
/// Initializes builtin objects and functions
#[inline]
pub fn init(global: &Value) {
array::init(global);
bigint::init(global);
boolean::init(global);
Array::init(global);
BigInt::init(global);
Boolean::init(global);
json::init(global);
math::init(global);
number::init(global);
Number::init(global);
object::init(global);
function::init(global);
regexp::init(global);
string::init(global);
RegExp::init(global);
String::init(global);
symbol::init(global);
console::init(global);
Error::init(global);
RangeError::init(global);
}

746
boa/src/builtins/number/mod.rs

@ -16,405 +16,457 @@
#[cfg(test)]
mod tests;
use super::{function::make_constructor_fn, object::ObjectKind};
use super::{
function::{make_builtin_fn, make_constructor_fn},
object::ObjectKind,
};
use crate::{
builtins::{
object::internal_methods_trait::ObjectInternalMethods,
value::{ResultValue, Value, ValueData},
RangeError,
},
exec::Interpreter,
};
use num_traits::float::FloatCore;
use std::{borrow::Borrow, f64, ops::Deref};
/// Helper function that converts a Value to a Number.
fn to_number(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Boolean(b) => {
if b {
Value::from(1)
} else {
Value::from(0)
const BUF_SIZE: usize = 2200;
/// `Number` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Number;
impl Number {
/// Helper function that converts a Value to a Number.
#[allow(clippy::wrong_self_convention)]
fn to_number(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Boolean(b) => {
if b {
Value::from(1)
} else {
Value::from(0)
}
}
ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN),
ValueData::Integer(i) => Value::from(f64::from(i)),
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"),
ValueData::Null => Value::from(0),
ValueData::Rational(n) => Value::from(n),
ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()),
ValueData::String(ref s) => match s.parse::<f64>() {
Ok(n) => Value::from(n),
Err(_) => Value::from(f64::NAN),
},
}
ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN),
ValueData::Integer(i) => Value::from(f64::from(i)),
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"),
ValueData::Null => Value::from(0),
ValueData::Rational(n) => Value::from(n),
ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()),
ValueData::String(ref s) => match s.parse::<f64>() {
Ok(n) => Value::from(n),
Err(_) => Value::from(f64::NAN),
},
}
}
/// Helper function that formats a float as a ES6-style exponential number string.
fn num_to_exponential(n: f64) -> String {
match n.abs() {
x if x > 1.0 => format!("{:e}", n).replace("e", "e+"),
x if x == 0.0 => format!("{:e}", n).replace("e", "e+"),
_ => format!("{:e}", n),
/// Helper function that formats a float as a ES6-style exponential number string.
fn num_to_exponential(n: f64) -> String {
match n.abs() {
x if x > 1.0 => format!("{:e}", n).replace("e", "e+"),
x if x == 0.0 => format!("{:e}", n).replace("e", "e+"),
_ => format!("{:e}", n),
}
}
}
/// `[[Construct]]` - Creates a Number instance
///
/// `[[Call]]` - Creates a number primitive
pub fn make_number(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => to_number(value),
None => to_number(&Value::from(0)),
};
this.set_kind(ObjectKind::Number);
this.set_internal_slot("NumberData", data.clone());
Ok(data)
}
/// `Number()` function.
///
/// More Information https://tc39.es/ecma262/#sec-number-constructor-number-value
pub fn call_number(_this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => to_number(value),
None => to_number(&Value::from(0)),
};
Ok(data)
}
/// `[[Construct]]` - Creates a Number instance
///
/// `[[Call]]` - Creates a number primitive
pub(crate) fn make_number(
this: &mut Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => Self::to_number(value),
None => Self::to_number(&Value::from(0)),
};
this.set_kind(ObjectKind::Number);
this.set_internal_slot("NumberData", data.clone());
/// `Number.prototype.toExponential( [fractionDigits] )`
///
/// The `toExponential()` method returns a string representing the Number object in exponential notation.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential
pub fn to_exponential(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let this_num = to_number(this).to_number();
let this_str_num = num_to_exponential(this_num);
Ok(Value::from(this_str_num))
}
Ok(data)
}
/// `Number.prototype.toFixed( [digits] )`
///
/// The `toFixed()` method formats a number using fixed-point notation
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed
pub fn to_fixed(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let this_num = to_number(this).to_number();
let precision = match args.get(0) {
Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize,
_ => 0,
},
None => 0,
};
let this_fixed_num = format!("{:.*}", precision, this_num);
Ok(Value::from(this_fixed_num))
}
/// `Number()` function.
///
/// More Information https://tc39.es/ecma262/#sec-number-constructor-number-value
pub(crate) fn call_number(
_this: &mut Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let data = match args.get(0) {
Some(ref value) => Self::to_number(value),
None => Self::to_number(&Value::from(0)),
};
Ok(data)
}
/// `Number.prototype.toLocaleString( [locales [, options]] )`
///
/// The `toLocaleString()` method returns a string with a language-sensitive representation of this number.
///
/// Note that while this technically conforms to the Ecma standard, it does no actual
/// internationalization logic.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
pub fn to_locale_string(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let this_num = to_number(this).to_number();
let this_str_num = format!("{}", this_num);
Ok(Value::from(this_str_num))
}
/// `Number.prototype.toExponential( [fractionDigits] )`
///
/// The `toExponential()` method returns a string representing the Number object in exponential notation.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_exponential(
this: &mut Value,
_args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let this_num = Self::to_number(this).to_number();
let this_str_num = Self::num_to_exponential(this_num);
Ok(Value::from(this_str_num))
}
/// `Number.prototype.toPrecision( [precision] )`
///
/// The `toPrecision()` method returns a string representing the Number object to the specified precision.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision
pub fn to_precision(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
let this_num = to_number(this);
let _num_str_len = format!("{}", this_num.to_number()).len();
let _precision = match args.get(0) {
Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize,
_ => 0,
},
None => 0,
};
// TODO: Implement toPrecision
unimplemented!("TODO: Implement toPrecision");
}
/// `Number.prototype.toFixed( [digits] )`
///
/// The `toFixed()` method formats a number using fixed-point notation
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_fixed(
this: &mut Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let this_num = Self::to_number(this).to_number();
let precision = match args.get(0) {
Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize,
_ => 0,
},
None => 0,
};
let this_fixed_num = format!("{:.*}", precision, this_num);
Ok(Value::from(this_fixed_num))
}
const BUF_SIZE: usize = 2200;
/// `Number.prototype.toLocaleString( [locales [, options]] )`
///
/// The `toLocaleString()` method returns a string with a language-sensitive representation of this number.
///
/// Note that while this technically conforms to the Ecma standard, it does no actual
/// internationalization logic.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_locale_string(
this: &mut Value,
_args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let this_num = Self::to_number(this).to_number();
let this_str_num = format!("{}", this_num);
Ok(Value::from(this_str_num))
}
// https://golang.org/src/math/nextafter.go
#[inline]
fn next_after(x: f64, y: f64) -> f64 {
if x.is_nan() || y.is_nan() {
f64::NAN
} else if (x - y) == 0. {
x
} else if x == 0.0 {
f64::from_bits(1).copysign(y)
} else if y > x || x > 0.0 {
f64::from_bits(x.to_bits() + 1)
} else {
f64::from_bits(x.to_bits() - 1)
/// `Number.prototype.toPrecision( [precision] )`
///
/// The `toPrecision()` method returns a string representing the Number object to the specified precision.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_precision(
this: &mut Value,
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let this_num = Self::to_number(this);
let _num_str_len = format!("{}", this_num.to_number()).len();
let _precision = match args.get(0) {
Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize,
_ => 0,
},
None => 0,
};
// TODO: Implement toPrecision
unimplemented!("TODO: Implement toPrecision");
}
}
// https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/numbers/conversions.cc#1230
pub fn num_to_string(mut value: f64, radix: u8) -> String {
assert!(radix >= 2);
assert!(radix <= 36);
assert!(value.is_finite());
// assert_ne!(0.0, value);
// Character array used for conversion.
// Temporary buffer for the result. We start with the decimal point in the
// middle and write to the left for the integer part and to the right for the
// fractional part. 1024 characters for the exponent and 52 for the mantissa
// either way, with additional space for sign, decimal point and string
// termination should be sufficient.
let mut buffer: [u8; BUF_SIZE] = [0; BUF_SIZE];
let (int_buf, frac_buf) = buffer.split_at_mut(BUF_SIZE / 2);
let mut fraction_cursor = 0;
let negative = value.is_sign_negative();
if negative {
value = -value
// https://golang.org/src/math/nextafter.go
#[inline]
fn next_after(x: f64, y: f64) -> f64 {
if x.is_nan() || y.is_nan() {
f64::NAN
} else if (x - y) == 0. {
x
} else if x == 0.0 {
f64::from_bits(1).copysign(y)
} else if y > x || x > 0.0 {
f64::from_bits(x.to_bits() + 1)
} else {
f64::from_bits(x.to_bits() - 1)
}
}
// Split the value into an integer part and a fractional part.
// let mut integer = value.trunc();
// let mut fraction = value.fract();
let mut integer = value.floor();
let mut fraction = value - integer;
// We only compute fractional digits up to the input double's precision.
let mut delta = 0.5 * (next_after(value, f64::MAX) - value);
delta = next_after(0.0, f64::MAX).max(delta);
assert!(delta > 0.0);
if fraction >= delta {
// Insert decimal point.
frac_buf[fraction_cursor] = b'.';
fraction_cursor += 1;
loop {
// Shift up by one digit.
fraction *= radix as f64;
delta *= radix as f64;
// Write digit.
let digit = fraction as u32;
frac_buf[fraction_cursor] = std::char::from_digit(digit, radix as u32).unwrap() as u8;
// https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/numbers/conversions.cc#1230
pub(crate) fn num_to_string(mut value: f64, radix: u8) -> String {
assert!(radix >= 2);
assert!(radix <= 36);
assert!(value.is_finite());
// assert_ne!(0.0, value);
// Character array used for conversion.
// Temporary buffer for the result. We start with the decimal point in the
// middle and write to the left for the integer part and to the right for the
// fractional part. 1024 characters for the exponent and 52 for the mantissa
// either way, with additional space for sign, decimal point and string
// termination should be sufficient.
let mut buffer: [u8; BUF_SIZE] = [0; BUF_SIZE];
let (int_buf, frac_buf) = buffer.split_at_mut(BUF_SIZE / 2);
let mut fraction_cursor = 0;
let negative = value.is_sign_negative();
if negative {
value = -value
}
// Split the value into an integer part and a fractional part.
// let mut integer = value.trunc();
// let mut fraction = value.fract();
let mut integer = value.floor();
let mut fraction = value - integer;
// We only compute fractional digits up to the input double's precision.
let mut delta = 0.5 * (Self::next_after(value, f64::MAX) - value);
delta = Self::next_after(0.0, f64::MAX).max(delta);
assert!(delta > 0.0);
if fraction >= delta {
// Insert decimal point.
frac_buf[fraction_cursor] = b'.';
fraction_cursor += 1;
// Calculate remainder.
fraction -= digit as f64;
// Round to even.
if fraction + delta > 1.0
&& (fraction > 0.5 || (fraction - 0.5) < f64::EPSILON && digit & 1 != 0)
{
loop {
// We need to back trace already written digits in case of carry-over.
fraction_cursor -= 1;
if fraction_cursor == 0 {
// CHECK_EQ('.', buffer[fraction_cursor]);
// Carry over to the integer part.
integer += 1.;
break;
} else {
let c: u8 = frac_buf[fraction_cursor];
// Reconstruct digit.
let digit_0 = (c as char).to_digit(10).unwrap();
if digit_0 + 1 >= radix as u32 {
continue;
loop {
// Shift up by one digit.
fraction *= radix as f64;
delta *= radix as f64;
// Write digit.
let digit = fraction as u32;
frac_buf[fraction_cursor] =
std::char::from_digit(digit, radix as u32).unwrap() as u8;
fraction_cursor += 1;
// Calculate remainder.
fraction -= digit as f64;
// Round to even.
if fraction + delta > 1.0
&& (fraction > 0.5 || (fraction - 0.5) < f64::EPSILON && digit & 1 != 0)
{
loop {
// We need to back trace already written digits in case of carry-over.
fraction_cursor -= 1;
if fraction_cursor == 0 {
// CHECK_EQ('.', buffer[fraction_cursor]);
// Carry over to the integer part.
integer += 1.;
break;
} else {
let c: u8 = frac_buf[fraction_cursor];
// Reconstruct digit.
let digit_0 = (c as char).to_digit(10).unwrap();
if digit_0 + 1 >= radix as u32 {
continue;
}
frac_buf[fraction_cursor] =
std::char::from_digit(digit_0 + 1, radix as u32).unwrap() as u8;
fraction_cursor += 1;
break;
}
frac_buf[fraction_cursor] =
std::char::from_digit(digit_0 + 1, radix as u32).unwrap() as u8;
fraction_cursor += 1;
break;
}
break;
}
if fraction < delta {
break;
}
break;
}
if fraction < delta {
}
// Compute integer digits. Fill unrepresented digits with zero.
let mut int_iter = int_buf.iter_mut().enumerate().rev(); //.rev();
while FloatCore::integer_decode(integer / f64::from(radix)).1 > 0 {
integer /= radix as f64;
*int_iter.next().unwrap().1 = b'0';
}
loop {
let remainder = integer % (radix as f64);
*int_iter.next().unwrap().1 =
std::char::from_digit(remainder as u32, radix as u32).unwrap() as u8;
integer = (integer - remainder) / radix as f64;
if integer <= 0f64 {
break;
}
}
}
// Add sign and terminate string.
if negative {
*int_iter.next().unwrap().1 = b'-';
}
assert!(fraction_cursor < BUF_SIZE);
// Compute integer digits. Fill unrepresented digits with zero.
let mut int_iter = int_buf.iter_mut().enumerate().rev(); //.rev();
while FloatCore::integer_decode(integer / f64::from(radix)).1 > 0 {
integer /= radix as f64;
*int_iter.next().unwrap().1 = b'0';
let integer_cursor = int_iter.next().unwrap().0 + 1;
let fraction_cursor = fraction_cursor + BUF_SIZE / 2;
// dbg!("Number: {}, Radix: {}, Cursors: {}, {}", value, radix, integer_cursor, fraction_cursor);
String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into()
}
loop {
let remainder = integer % (radix as f64);
*int_iter.next().unwrap().1 =
std::char::from_digit(remainder as u32, radix as u32).unwrap() as u8;
integer = (integer - remainder) / radix as f64;
if integer <= 0f64 {
break;
/// `Number.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified Number object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &mut Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
// 1. Let x be ? thisNumberValue(this value).
let x = Self::to_number(this).to_number();
// 2. If radix is undefined, let radixNumber be 10.
// 3. Else, let radixNumber be ? ToInteger(radix).
let radix_number = args.get(0).map_or(10, |arg| arg.to_integer()) as u8;
if x == -0. {
return Ok(Value::from("0"));
} else if x.is_nan() {
return Ok(Value::from("NaN"));
} else if x.is_infinite() && x.is_sign_positive() {
return Ok(Value::from("Infinity"));
} else if x.is_infinite() && x.is_sign_negative() {
return Ok(Value::from("-Infinity"));
}
}
// Add sign and terminate string.
if negative {
*int_iter.next().unwrap().1 = b'-';
}
assert!(fraction_cursor < BUF_SIZE);
let integer_cursor = int_iter.next().unwrap().0 + 1;
let fraction_cursor = fraction_cursor + BUF_SIZE / 2;
// dbg!("Number: {}, Radix: {}, Cursors: {}, {}", value, radix, integer_cursor, fraction_cursor);
String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into()
}
// 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
if radix_number < 2 || radix_number > 36 {
return Err(RangeError::run_new(
"radix must be an integer at least 2 and no greater than 36",
ctx,
)?);
}
/// `Number.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified Number object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString
pub fn to_string(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
// 1. Let x be ? thisNumberValue(this value).
let x = to_number(this).to_number();
// 2. If radix is undefined, let radixNumber be 10.
// 3. Else, let radixNumber be ? ToInteger(radix).
let radix_number = args.get(0).map_or(10, |arg| arg.to_integer()) as u8;
if x == -0. {
return Ok(Value::from("0"));
} else if x.is_nan() {
return Ok(Value::from("NaN"));
} else if x.is_infinite() && x.is_sign_positive() {
return Ok(Value::from("Infinity"));
} else if x.is_infinite() && x.is_sign_negative() {
return Ok(Value::from("-Infinity"));
}
// 5. If radixNumber = 10, return ! ToString(x).
// This part should use exponential notations for long integer numbers commented tests
if radix_number == 10 {
// return Ok(to_value(format!("{}", Self::to_number(this).to_num())));
return Ok(Value::from(format!("{}", x)));
}
// 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
if radix_number < 2 || radix_number > 36 {
panic!("Radix must be between 2 and 36");
}
// This is a Optimization from the v8 source code to print values that can fit in a single character
// Since the actual num_to_string allocates a 2200 bytes buffer for actual conversion
// I am not sure if this part is effective as the v8 equivalent https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/builtins/number.tq#53
// // Fast case where the result is a one character string.
// if x.is_sign_positive() && x.fract() == 0.0 && x < radix_number as f64 {
// return Ok(to_value(format!("{}", std::char::from_digit(x as u32, radix_number as u32).unwrap())))
// }
// 5. If radixNumber = 10, return ! ToString(x).
// This part should use exponential notations for long integer numbers commented tests
if radix_number == 10 {
// return Ok(to_value(format!("{}", to_number(this).to_num())));
return Ok(Value::from(format!("{}", x)));
// 6. Return the String representation of this Number value using the radix specified by radixNumber.
Ok(Value::from(Self::num_to_string(x, radix_number)))
}
// This is a Optimization from the v8 source code to print values that can fit in a single character
// Since the actual num_to_string allocates a 2200 bytes buffer for actual conversion
// I am not sure if this part is effective as the v8 equivalent https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/builtins/number.tq#53
// // Fast case where the result is a one character string.
// if x.is_sign_positive() && x.fract() == 0.0 && x < radix_number as f64 {
// return Ok(to_value(format!("{}", std::char::from_digit(x as u32, radix_number as u32).unwrap())))
// }
// 6. Return the String representation of this Number value using the radix specified by radixNumber.
Ok(Value::from(num_to_string(x, radix_number)))
}
/// `Number.prototype.toString()`
///
/// The `valueOf()` method returns the wrapped primitive value of a Number object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/valueOf
pub fn value_of(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue {
Ok(to_number(this))
}
/// Create a new `Number` object
pub fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("NumberData", Value::from(0));
make_builtin_fn!(to_exponential, named "toExponential", with length 1, of prototype);
make_builtin_fn!(to_fixed, named "toFixed", with length 1, of prototype);
make_builtin_fn!(to_locale_string, named "toLocaleString", of prototype);
make_builtin_fn!(to_precision, named "toPrecision", with length 1, of prototype);
make_builtin_fn!(to_string, named "toString", with length 1, of prototype);
make_builtin_fn!(value_of, named "valueOf", of prototype);
/// `Number.prototype.toString()`
///
/// The `valueOf()` method returns the wrapped primitive value of a Number object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/valueOf
pub(crate) fn value_of(
this: &mut Value,
_args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
Ok(Self::to_number(this))
}
make_constructor_fn(make_number, global, prototype)
}
/// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("NumberData", Value::from(0));
/// Initialise the `Number` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("Number", create(global));
}
make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1);
make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1);
make_builtin_fn(Self::to_locale_string, "toLocaleString", &prototype, 0);
make_builtin_fn(Self::to_precision, "toPrecision", &prototype, 1);
make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
/// The abstract operation Number::equal takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-equal
#[allow(clippy::float_cmp)]
pub fn equals(a: f64, b: f64) -> bool {
a == b
}
make_constructor_fn(Self::make_number, global, prototype)
}
/// The abstract operation Number::sameValue takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue
#[allow(clippy::float_cmp)]
pub fn same_value(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
/// Initialise the `Number` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) {
global.set_field("Number", Self::create(global));
}
if a == 0.0 && b == 0.0 {
if (a.is_sign_negative() && b.is_sign_positive())
|| (a.is_sign_positive() && b.is_sign_negative())
{
return false;
};
true
} else {
/// The abstract operation Number::equal takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-equal
#[allow(clippy::float_cmp)]
pub(crate) fn equals(a: f64, b: f64) -> bool {
a == b
}
}
/// The abstract operation Number::sameValueZero takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero
#[allow(clippy::float_cmp)]
pub fn same_value_zero(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
/// The abstract operation Number::sameValue takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue
#[allow(clippy::float_cmp)]
pub(crate) fn same_value(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
}
if a == 0.0 && b == 0.0 {
if (a.is_sign_negative() && b.is_sign_positive())
|| (a.is_sign_positive() && b.is_sign_negative())
{
return false;
};
true
} else {
a == b
}
}
a == b
/// The abstract operation Number::sameValueZero takes arguments
/// x (a Number) and y (a Number). It performs the following steps when called:
///
/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero
#[allow(clippy::float_cmp)]
pub(crate) fn same_value_zero(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
}
a == b
}
}

63
boa/src/builtins/number/tests.rs

@ -1,18 +1,23 @@
#![allow(clippy::float_cmp)]
use crate::{builtins::value::Value, exec::Executor, forward, forward_val, realm::Realm};
use crate::{
builtins::{Number, Value},
exec::Interpreter,
forward, forward_val,
realm::Realm,
};
#[test]
fn check_number_constructor_is_function() {
let global = Value::new_object(None);
let number_constructor = super::create(&global);
let number_constructor = Number::create(&global);
assert_eq!(number_constructor.is_function(), true);
}
#[test]
fn call_number() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var default_zero = Number();
var int_one = Number(1);
@ -47,7 +52,7 @@ fn call_number() {
#[test]
fn to_exponential() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var default_exp = Number().toExponential();
var int_exp = Number(5).toExponential();
@ -76,7 +81,7 @@ fn to_exponential() {
#[test]
fn to_fixed() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var default_fixed = Number().toFixed();
var pos_fixed = Number("3.456e+4").toFixed();
@ -102,7 +107,7 @@ fn to_fixed() {
#[test]
fn to_locale_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var default_locale = Number().toLocaleString();
var small_locale = Number(5).toLocaleString();
@ -129,7 +134,7 @@ fn to_locale_string() {
#[ignore]
fn to_precision() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var default_precision = Number().toPrecision();
var low_precision = Number(123456789).toPrecision(1);
@ -161,7 +166,7 @@ fn to_precision() {
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!("NaN", &forward(&mut engine, "Number(NaN).toString()"));
assert_eq!("Infinity", &forward(&mut engine, "Number(1/0).toString()"));
@ -328,7 +333,7 @@ fn to_string() {
// https://github.com/jasonwilliams/boa/pull/381#discussion_r422458544
fn num_to_string_exponential() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(
String::from("111111111111111110000"),
@ -371,7 +376,7 @@ fn num_to_string_exponential() {
#[test]
fn value_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
// TODO: In addition to parsing numbers from strings, parse them bare As of October 2019
// the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation.
let init = r#"
@ -398,39 +403,39 @@ fn value_of() {
#[test]
fn equal() {
assert_eq!(super::equals(0.0, 0.0), true);
assert_eq!(super::equals(-0.0, 0.0), true);
assert_eq!(super::equals(0.0, -0.0), true);
assert_eq!(super::equals(f64::NAN, -0.0), false);
assert_eq!(super::equals(0.0, f64::NAN), false);
assert_eq!(Number::equals(0.0, 0.0), true);
assert_eq!(Number::equals(-0.0, 0.0), true);
assert_eq!(Number::equals(0.0, -0.0), true);
assert_eq!(Number::equals(f64::NAN, -0.0), false);
assert_eq!(Number::equals(0.0, f64::NAN), false);
assert_eq!(super::equals(1.0, 1.0), true);
assert_eq!(Number::equals(1.0, 1.0), true);
}
#[test]
fn same_value() {
assert_eq!(super::same_value(0.0, 0.0), true);
assert_eq!(super::same_value(-0.0, 0.0), false);
assert_eq!(super::same_value(0.0, -0.0), false);
assert_eq!(super::same_value(f64::NAN, -0.0), false);
assert_eq!(super::same_value(0.0, f64::NAN), false);
assert_eq!(super::equals(1.0, 1.0), true);
assert_eq!(Number::same_value(0.0, 0.0), true);
assert_eq!(Number::same_value(-0.0, 0.0), false);
assert_eq!(Number::same_value(0.0, -0.0), false);
assert_eq!(Number::same_value(f64::NAN, -0.0), false);
assert_eq!(Number::same_value(0.0, f64::NAN), false);
assert_eq!(Number::equals(1.0, 1.0), true);
}
#[test]
fn same_value_zero() {
assert_eq!(super::same_value_zero(0.0, 0.0), true);
assert_eq!(super::same_value_zero(-0.0, 0.0), true);
assert_eq!(super::same_value_zero(0.0, -0.0), true);
assert_eq!(super::same_value_zero(f64::NAN, -0.0), false);
assert_eq!(super::same_value_zero(0.0, f64::NAN), false);
assert_eq!(super::equals(1.0, 1.0), true);
assert_eq!(Number::same_value_zero(0.0, 0.0), true);
assert_eq!(Number::same_value_zero(-0.0, 0.0), true);
assert_eq!(Number::same_value_zero(0.0, -0.0), true);
assert_eq!(Number::same_value_zero(f64::NAN, -0.0), false);
assert_eq!(Number::same_value_zero(0.0, f64::NAN), false);
assert_eq!(Number::equals(1.0, 1.0), true);
}
#[test]
fn from_bigint() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(&forward(&mut engine, "Number(0n)"), "0",);
assert_eq!(&forward(&mut engine, "Number(100000n)"), "100000",);

18
boa/src/builtins/object/mod.rs

@ -29,7 +29,7 @@ use std::{
ops::Deref,
};
use super::function::make_constructor_fn;
use super::function::{make_builtin_fn, make_constructor_fn};
pub use internal_methods_trait::ObjectInternalMethods;
pub use internal_state::{InternalState, InternalStateCell};
@ -548,7 +548,7 @@ pub fn make_object(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu
/// Get the `prototype` of an object.
pub fn get_prototype_of(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("Cannot get object");
Ok(obj.get_field_slice(INSTANCE_PROTOTYPE))
Ok(obj.get_field(INSTANCE_PROTOTYPE))
}
/// Set the `prototype` of an object.
@ -611,15 +611,15 @@ pub fn has_own_property(this: &mut Value, args: &[Value], _: &mut Interpreter) -
pub fn create(global: &Value) -> Value {
let prototype = Value::new_object(None);
make_builtin_fn!(has_own_property, named "hasOwnProperty", of prototype);
make_builtin_fn!(to_string, named "toString", of prototype);
make_builtin_fn(has_own_property, "hasOwnProperty", &prototype, 0);
make_builtin_fn(to_string, "toString", &prototype, 0);
let object = make_constructor_fn(make_object, global, prototype);
object.set_field_slice("length", Value::from(1));
make_builtin_fn!(set_prototype_of, named "setPrototypeOf", with length 2, of object);
make_builtin_fn!(get_prototype_of, named "getPrototypeOf", with length 1, of object);
make_builtin_fn!(define_property, named "defineProperty", with length 3, of object);
object.set_field("length", Value::from(1));
make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2);
make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1);
make_builtin_fn(define_property, "defineProperty", &object, 3);
object
}
@ -627,5 +627,5 @@ pub fn create(global: &Value) -> Value {
/// Initialise the `Object` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("Object", create(global));
global.set_field("Object", create(global));
}

24
boa/src/builtins/property/mod.rs

@ -173,12 +173,12 @@ impl Default for Property {
impl From<&Property> for Value {
fn from(value: &Property) -> Value {
let property = Value::new_object(None);
property.set_field_slice("configurable", Value::from(value.configurable));
property.set_field_slice("enumerable", Value::from(value.enumerable));
property.set_field_slice("writable", Value::from(value.writable));
property.set_field_slice("value", value.value.clone().unwrap_or_else(Value::null));
property.set_field_slice("get", value.get.clone().unwrap_or_else(Value::null));
property.set_field_slice("set", value.set.clone().unwrap_or_else(Value::null));
property.set_field("configurable", Value::from(value.configurable));
property.set_field("enumerable", Value::from(value.enumerable));
property.set_field("writable", Value::from(value.writable));
property.set_field("value", value.value.clone().unwrap_or_else(Value::null));
property.set_field("get", value.get.clone().unwrap_or_else(Value::null));
property.set_field("set", value.set.clone().unwrap_or_else(Value::null));
property
}
}
@ -188,12 +188,12 @@ impl<'a> From<&'a Value> for Property {
/// if they're not there default to false
fn from(value: &Value) -> Self {
Self {
configurable: { Some(bool::from(&value.get_field_slice("configurable"))) },
enumerable: { Some(bool::from(&value.get_field_slice("enumerable"))) },
writable: { Some(bool::from(&value.get_field_slice("writable"))) },
value: Some(value.get_field_slice("value")),
get: Some(value.get_field_slice("get")),
set: Some(value.get_field_slice("set")),
configurable: { Some(bool::from(&value.get_field("configurable"))) },
enumerable: { Some(bool::from(&value.get_field("enumerable"))) },
writable: { Some(bool::from(&value.get_field("writable"))) },
value: Some(value.get_field("value")),
get: Some(value.get_field("get")),
set: Some(value.get_field("set")),
}
}
}

794
boa/src/builtins/regexp/mod.rs

@ -13,7 +13,7 @@ use std::ops::Deref;
use regex::Regex;
use super::function::make_constructor_fn;
use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{
builtins::{
object::{InternalState, ObjectKind},
@ -28,7 +28,7 @@ mod tests;
/// The internal representation on a `RegExp` object.
#[derive(Debug)]
struct RegExp {
pub(crate) struct RegExp {
/// Regex matcher.
matcher: Regex,
@ -59,428 +59,438 @@ struct RegExp {
impl InternalState for RegExp {}
/// Create a new `RegExp`
pub fn make_regexp(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if args.is_empty() {
return Err(Value::undefined());
}
let mut regex_body = String::new();
let mut regex_flags = String::new();
#[allow(clippy::indexing_slicing)] // length has been checked
match args[0].deref() {
ValueData::String(ref body) => {
// first argument is a string -> use it as regex pattern
regex_body = body.into();
impl RegExp {
/// Create a new `RegExp`
pub(crate) fn make_regexp(
this: &mut Value,
args: &[Value],
_: &mut Interpreter,
) -> ResultValue {
if args.is_empty() {
return Err(Value::undefined());
}
ValueData::Object(ref obj) => {
let slots = &obj.borrow().internal_slots;
if slots.get("RegExpMatcher").is_some() {
// first argument is another `RegExp` object, so copy its pattern and flags
if let Some(body) = slots.get("OriginalSource") {
regex_body = String::from(body);
}
if let Some(flags) = slots.get("OriginalFlags") {
regex_flags = String::from(flags);
let mut regex_body = String::new();
let mut regex_flags = String::new();
#[allow(clippy::indexing_slicing)] // length has been checked
match args[0].deref() {
ValueData::String(ref body) => {
// first argument is a string -> use it as regex pattern
regex_body = body.into();
}
ValueData::Object(ref obj) => {
let slots = &obj.borrow().internal_slots;
if slots.get("RegExpMatcher").is_some() {
// first argument is another `RegExp` object, so copy its pattern and flags
if let Some(body) = slots.get("OriginalSource") {
regex_body = String::from(body);
}
if let Some(flags) = slots.get("OriginalFlags") {
regex_flags = String::from(flags);
}
}
}
_ => return Err(Value::undefined()),
}
_ => return Err(Value::undefined()),
}
// if a second argument is given and it's a string, use it as flags
match args.get(1) {
None => {}
Some(flags) => {
if let ValueData::String(flags) = flags.deref() {
regex_flags = flags.into();
// if a second argument is given and it's a string, use it as flags
match args.get(1) {
None => {}
Some(flags) => {
if let ValueData::String(flags) = flags.deref() {
regex_flags = flags.into();
}
}
}
}
// parse flags
let mut sorted_flags = String::new();
let mut pattern = String::new();
let mut dot_all = false;
let mut global = false;
let mut ignore_case = false;
let mut multiline = false;
let mut sticky = false;
let mut unicode = false;
if regex_flags.contains('g') {
global = true;
sorted_flags.push('g');
}
if regex_flags.contains('i') {
ignore_case = true;
sorted_flags.push('i');
pattern.push('i');
}
if regex_flags.contains('m') {
multiline = true;
sorted_flags.push('m');
pattern.push('m');
}
if regex_flags.contains('s') {
dot_all = true;
sorted_flags.push('s');
pattern.push('s');
}
if regex_flags.contains('u') {
unicode = true;
sorted_flags.push('u');
//pattern.push('s'); // rust uses utf-8 anyway
// parse flags
let mut sorted_flags = String::new();
let mut pattern = String::new();
let mut dot_all = false;
let mut global = false;
let mut ignore_case = false;
let mut multiline = false;
let mut sticky = false;
let mut unicode = false;
if regex_flags.contains('g') {
global = true;
sorted_flags.push('g');
}
if regex_flags.contains('i') {
ignore_case = true;
sorted_flags.push('i');
pattern.push('i');
}
if regex_flags.contains('m') {
multiline = true;
sorted_flags.push('m');
pattern.push('m');
}
if regex_flags.contains('s') {
dot_all = true;
sorted_flags.push('s');
pattern.push('s');
}
if regex_flags.contains('u') {
unicode = true;
sorted_flags.push('u');
//pattern.push('s'); // rust uses utf-8 anyway
}
if regex_flags.contains('y') {
sticky = true;
sorted_flags.push('y');
}
// the `regex` crate uses '(?{flags})` inside the pattern to enable flags
if !pattern.is_empty() {
pattern = format!("(?{})", pattern);
}
pattern.push_str(regex_body.as_str());
let matcher = Regex::new(pattern.as_str()).expect("failed to create matcher");
let regexp = RegExp {
matcher,
use_last_index: global || sticky,
flags: sorted_flags,
dot_all,
global,
ignore_case,
multiline,
sticky,
unicode,
};
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Ordinary);
this.set_internal_slot("RegExpMatcher", Value::undefined());
this.set_internal_slot("OriginalSource", Value::from(regex_body));
this.set_internal_slot("OriginalFlags", Value::from(regex_flags));
this.set_internal_state(regexp);
Ok(this.clone())
}
if regex_flags.contains('y') {
sticky = true;
sorted_flags.push('y');
/// `RegExp.prototype.dotAll`
///
/// The `dotAll` property indicates whether or not the "`s`" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll
fn get_dot_all(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.dot_all)))
}
// the `regex` crate uses '(?{flags})` inside the pattern to enable flags
if !pattern.is_empty() {
pattern = format!("(?{})", pattern);
/// `RegExp.prototype.flags`
///
/// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags
/// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2
fn get_flags(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.flags.clone())))
}
pattern.push_str(regex_body.as_str());
let matcher = Regex::new(pattern.as_str()).expect("failed to create matcher");
let regexp = RegExp {
matcher,
use_last_index: global || sticky,
flags: sorted_flags,
dot_all,
global,
ignore_case,
multiline,
sticky,
unicode,
};
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Ordinary);
this.set_internal_slot("RegExpMatcher", Value::undefined());
this.set_internal_slot("OriginalSource", Value::from(regex_body));
this.set_internal_slot("OriginalFlags", Value::from(regex_flags));
this.set_internal_state(regexp);
Ok(this.clone())
}
/// `RegExp.prototype.dotAll`
///
/// The `dotAll` property indicates whether or not the "`s`" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll
fn get_dot_all(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.dot_all)))
}
/// `RegExp.prototype.global`
///
/// The `global` property indicates whether or not the "`g`" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global
fn get_global(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.global)))
}
/// `RegExp.prototype.flags`
///
/// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags
/// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2
fn get_flags(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.flags.clone())))
}
/// `RegExp.prototype.ignoreCase`
///
/// The `ignoreCase` property indicates whether or not the "`i`" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase
fn get_ignore_case(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.ignore_case)))
}
/// `RegExp.prototype.global`
///
/// The `global` property indicates whether or not the "`g`" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global
fn get_global(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.global)))
}
/// `RegExp.prototype.multiline`
///
/// The multiline property indicates whether or not the "m" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline
fn get_multiline(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.multiline)))
}
/// `RegExp.prototype.ignoreCase`
///
/// The `ignoreCase` property indicates whether or not the "`i`" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase
fn get_ignore_case(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.ignore_case)))
}
/// `RegExp.prototype.source`
///
/// The `source` property returns a `String` containing the source text of the regexp object,
/// and it doesn't contain the two forward slashes on both sides and any flags.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.source
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source
fn get_source(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(this.get_internal_slot("OriginalSource"))
}
/// `RegExp.prototype.multiline`
///
/// The multiline property indicates whether or not the "m" flag is used with the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline
fn get_multiline(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.multiline)))
}
/// `RegExp.prototype.sticky`
///
/// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky
fn get_sticky(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.sticky)))
}
/// `RegExp.prototype.source`
///
/// The `source` property returns a `String` containing the source text of the regexp object,
/// and it doesn't contain the two forward slashes on both sides and any flags.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.source
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source
fn get_source(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(this.get_internal_slot("OriginalSource"))
}
/// `RegExp.prototype.unicode`
///
/// The unicode property indicates whether or not the "`u`" flag is used with a regular expression.
/// unicode is a read-only property of an individual regular expression instance.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode
fn get_unicode(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.unicode)))
}
/// `RegExp.prototype.sticky`
///
/// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky
fn get_sticky(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.sticky)))
}
/// `RegExp.prototype.test( string )`
///
/// The `test()` method executes a search for a match between a regular expression and a specified string.
///
/// Returns `true` or `false`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
pub(crate) fn test(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = String::from(args.get(0).expect("could not get argument"));
let mut last_index = usize::from(&this.get_field("lastIndex"));
let result = this.with_internal_state_ref(|regex: &RegExp| {
let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) {
if regex.use_last_index {
last_index = m.end();
}
true
} else {
if regex.use_last_index {
last_index = 0;
}
false
};
Ok(Value::boolean(result))
});
this.set_field("lastIndex", Value::from(last_index));
result
}
/// `RegExp.prototype.unicode`
///
/// The unicode property indicates whether or not the "`u`" flag is used with a regular expression.
/// unicode is a read-only property of an individual regular expression instance.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode
fn get_unicode(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.unicode)))
}
/// `RegExp.prototype.exec( string )`
///
/// The exec() method executes a search for a match in a specified string.
///
/// Returns a result array, or `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
pub(crate) fn exec(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = String::from(args.get(0).expect("could not get argument"));
let mut last_index = usize::from(&this.get_field("lastIndex"));
let result = this.with_internal_state_ref(|regex: &RegExp| {
let mut locations = regex.matcher.capture_locations();
let result = if let Some(m) =
regex
.matcher
.captures_read_at(&mut locations, arg_str.as_str(), last_index)
{
if regex.use_last_index {
last_index = m.end();
}
let mut result = Vec::with_capacity(locations.len());
for i in 0..locations.len() {
if let Some((start, end)) = locations.get(i) {
result.push(Value::from(
arg_str.get(start..end).expect("Could not get slice"),
));
} else {
result.push(Value::undefined());
}
}
/// `RegExp.prototype.test( string )`
///
/// The `test()` method executes a search for a match between a regular expression and a specified string.
///
/// Returns `true` or `false`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
pub fn test(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = String::from(args.get(0).expect("could not get argument"));
let mut last_index = usize::from(&this.get_field_slice("lastIndex"));
let result = this.with_internal_state_ref(|regex: &RegExp| {
let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) {
if regex.use_last_index {
last_index = m.end();
}
true
} else {
if regex.use_last_index {
last_index = 0;
}
false
};
Ok(Value::boolean(result))
});
this.set_field_slice("lastIndex", Value::from(last_index));
result
}
let result = Value::from(result);
result
.set_property_slice("index", Property::default().value(Value::from(m.start())));
result.set_property_slice("input", Property::default().value(Value::from(arg_str)));
result
} else {
if regex.use_last_index {
last_index = 0;
}
Value::null()
};
Ok(result)
});
this.set_field("lastIndex", Value::from(last_index));
result
}
/// `RegExp.prototype.exec( string )`
///
/// The exec() method executes a search for a match in a specified string.
///
/// Returns a result array, or `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
pub fn exec(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = String::from(args.get(0).expect("could not get argument"));
let mut last_index = usize::from(&this.get_field_slice("lastIndex"));
let result = this.with_internal_state_ref(|regex: &RegExp| {
let mut locations = regex.matcher.capture_locations();
let result = if let Some(m) =
regex
.matcher
.captures_read_at(&mut locations, arg_str.as_str(), last_index)
{
if regex.use_last_index {
last_index = m.end();
/// `RegExp.prototype[ @@match ]( string )`
///
/// This method retrieves the matches when matching a string against a regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype-@@match
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match
pub(crate) fn r#match(this: &mut Value, arg: String, ctx: &mut Interpreter) -> ResultValue {
let (matcher, flags) = this
.with_internal_state_ref(|regex: &RegExp| (regex.matcher.clone(), regex.flags.clone()));
if flags.contains('g') {
let mut matches = Vec::new();
for mat in matcher.find_iter(&arg) {
matches.push(Value::from(mat.as_str()));
}
let mut result = Vec::with_capacity(locations.len());
for i in 0..locations.len() {
if let Some((start, end)) = locations.get(i) {
result.push(Value::from(
arg_str.get(start..end).expect("Could not get slice"),
));
} else {
result.push(Value::undefined());
}
if matches.is_empty() {
return Ok(Value::null());
}
let result = Value::from(result);
result.set_property_slice("index", Property::default().value(Value::from(m.start())));
result.set_property_slice("input", Property::default().value(Value::from(arg_str)));
result
Ok(Value::from(matches))
} else {
if regex.use_last_index {
last_index = 0;
}
Value::null()
};
Ok(result)
});
this.set_field_slice("lastIndex", Value::from(last_index));
result
}
/// `RegExp.prototype[ @@match ]( string )`
///
/// This method retrieves the matches when matching a string against a regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype-@@match
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match
pub fn r#match(this: &mut Value, arg: String, ctx: &mut Interpreter) -> ResultValue {
let (matcher, flags) =
this.with_internal_state_ref(|regex: &RegExp| (regex.matcher.clone(), regex.flags.clone()));
if flags.contains('g') {
let mut matches = Vec::new();
for mat in matcher.find_iter(&arg) {
matches.push(Value::from(mat.as_str()));
Self::exec(this, &[Value::from(arg)], ctx)
}
if matches.is_empty() {
return Ok(Value::null());
}
Ok(Value::from(matches))
} else {
exec(this, &[Value::from(arg)], ctx)
}
}
/// `RegExp.prototype.toString()`
///
/// Return a string representing the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString
pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let body = String::from(&this.get_internal_slot("OriginalSource"));
let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone());
Ok(Value::from(format!("/{}/{}", body, flags)))
}
/// `RegExp.prototype.toString()`
///
/// Return a string representing the regular expression.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let body = String::from(&this.get_internal_slot("OriginalSource"));
let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone());
Ok(Value::from(format!("/{}/{}", body, flags)))
}
/// `RegExp.prototype[ @@matchAll ]( string )`
///
/// The `[@@matchAll]` method returns all matches of the regular expression against a string.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@matchAll
// TODO: it's returning an array, it should return an iterator
pub fn match_all(this: &mut Value, arg_str: String) -> ResultValue {
let matches: Vec<Value> = this.with_internal_state_ref(|regex: &RegExp| {
let mut matches = Vec::new();
for m in regex.matcher.find_iter(&arg_str) {
if let Some(caps) = regex.matcher.captures(&m.as_str()) {
let match_vec = caps
.iter()
.map(|group| match group {
Some(g) => Value::from(g.as_str()),
None => Value::undefined(),
})
.collect::<Vec<Value>>();
let match_val = Value::from(match_vec);
match_val
.set_property_slice("index", Property::default().value(Value::from(m.start())));
match_val.set_property_slice(
"input",
Property::default().value(Value::from(arg_str.clone())),
);
matches.push(match_val);
if !regex.flags.contains('g') {
break;
/// `RegExp.prototype[ @@matchAll ]( string )`
///
/// The `[@@matchAll]` method returns all matches of the regular expression against a string.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@matchAll
// TODO: it's returning an array, it should return an iterator
pub(crate) fn match_all(this: &mut Value, arg_str: String) -> ResultValue {
let matches: Vec<Value> = this.with_internal_state_ref(|regex: &RegExp| {
let mut matches = Vec::new();
for m in regex.matcher.find_iter(&arg_str) {
if let Some(caps) = regex.matcher.captures(&m.as_str()) {
let match_vec = caps
.iter()
.map(|group| match group {
Some(g) => Value::from(g.as_str()),
None => Value::undefined(),
})
.collect::<Vec<Value>>();
let match_val = Value::from(match_vec);
match_val.set_property_slice(
"index",
Property::default().value(Value::from(m.start())),
);
match_val.set_property_slice(
"input",
Property::default().value(Value::from(arg_str.clone())),
);
matches.push(match_val);
if !regex.flags.contains('g') {
break;
}
}
}
}
matches
});
matches
});
let length = matches.len();
let result = Value::from(matches);
result.set_field_slice("length", Value::from(length));
result.set_kind(ObjectKind::Array);
let length = matches.len();
let result = Value::from(matches);
result.set_field("length", Value::from(length));
result.set_kind(ObjectKind::Array);
Ok(result)
}
Ok(result)
}
/// Create a new `RegExp` object.
pub fn create(global: &Value) -> Value {
// Create prototype
let prototype = Value::new_object(Some(global));
prototype.set_field_slice("lastIndex", Value::from(0));
make_builtin_fn!(test, named "test", with length 1, of prototype);
make_builtin_fn!(exec, named "exec", with length 1, of prototype);
make_builtin_fn!(to_string, named "toString", of prototype);
make_builtin_fn!(get_dot_all, named "dotAll", of prototype);
make_builtin_fn!(get_flags, named "flags", of prototype);
make_builtin_fn!(get_global, named "global", of prototype);
make_builtin_fn!(get_ignore_case, named "ignoreCase", of prototype);
make_builtin_fn!(get_multiline, named "multiline", of prototype);
make_builtin_fn!(get_source, named "source", of prototype);
make_builtin_fn!(get_sticky, named "sticky", of prototype);
make_builtin_fn!(get_unicode, named "unicode", of prototype);
make_constructor_fn(make_regexp, global, prototype)
}
/// Create a new `RegExp` object.
pub(crate) fn create(global: &Value) -> Value {
// Create prototype
let prototype = Value::new_object(Some(global));
prototype.set_field("lastIndex", Value::from(0));
make_builtin_fn(Self::test, "test", &prototype, 1);
make_builtin_fn(Self::exec, "exec", &prototype, 1);
make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_builtin_fn(Self::get_dot_all, "dotAll", &prototype, 0);
make_builtin_fn(Self::get_flags, "flags", &prototype, 0);
make_builtin_fn(Self::get_global, "global", &prototype, 0);
make_builtin_fn(Self::get_ignore_case, "ignoreCase", &prototype, 0);
make_builtin_fn(Self::get_multiline, "multiline", &prototype, 0);
make_builtin_fn(Self::get_source, "source", &prototype, 0);
make_builtin_fn(Self::get_sticky, "sticky", &prototype, 0);
make_builtin_fn(Self::get_unicode, "unicode", &prototype, 0);
make_constructor_fn(Self::make_regexp, global, prototype)
}
/// Initialise the `RegExp` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("RegExp", create(global));
/// Initialise the `RegExp` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) {
global.set_field("RegExp", Self::create(global));
}
}

16
boa/src/builtins/regexp/tests.rs

@ -1,12 +1,10 @@
use super::*;
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
use crate::{exec::Interpreter, forward, realm::Realm};
#[test]
fn constructors() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var constructed = new RegExp("[0-9]+(\\.[0-9]+)?");
var literal = /[0-9]+(\.[0-9]+)?/;
@ -22,7 +20,7 @@ fn constructors() {
#[test]
fn check_regexp_constructor_is_function() {
let global = Value::new_object(None);
let regexp_constructor = create(&global);
let regexp_constructor = RegExp::create(&global);
assert_eq!(regexp_constructor.is_function(), true);
}
@ -30,7 +28,7 @@ fn check_regexp_constructor_is_function() {
// #[test]
// fn flags() {
// let mut engine = Executor::new();
// let mut engine = Interpreter::new();
// let init = r#"
// var re_gi = /test/gi;
// var re_sm = /test/sm;
@ -57,7 +55,7 @@ fn check_regexp_constructor_is_function() {
#[test]
fn last_index() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var regex = /[0-9]+(\.[0-9]+)?/g;
"#;
@ -73,7 +71,7 @@ fn last_index() {
#[test]
fn exec() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
@ -93,7 +91,7 @@ fn exec() {
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(
forward(&mut engine, "(new RegExp('a+b+c')).toString()"),

1887
boa/src/builtins/string/mod.rs

File diff suppressed because it is too large Load Diff

184
boa/src/builtins/string/tests.rs

@ -1,12 +1,10 @@
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
use crate::{exec::Interpreter, forward, forward_val, realm::Realm};
#[test]
fn check_string_constructor_is_function() {
let global = Value::new_object(None);
let string_constructor = create(&global);
let string_constructor = String::create(&global);
assert_eq!(string_constructor.is_function(), true);
}
@ -14,7 +12,7 @@ fn check_string_constructor_is_function() {
// TODO: re-enable when getProperty() is finished;
// fn length() {
// //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js
// let mut engine = Executor::new();
// let mut engine = Interpreter::new();
// let init = r#"
// const a = new String(' ');
// const b = new String('\ud834\udf06');
@ -39,7 +37,7 @@ fn check_string_constructor_is_function() {
#[test]
fn new_string_has_length() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
let a = new String("1234");
a
@ -52,7 +50,7 @@ fn new_string_has_length() {
#[test]
fn new_utf8_string_has_length() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
let a = new String("中文");
a
@ -65,7 +63,7 @@ fn new_utf8_string_has_length() {
#[test]
fn concat() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var hello = new String('Hello, ');
var world = new String('world! ');
@ -85,12 +83,14 @@ fn concat() {
/// Test the correct type is returned from call and construct
fn construct_and_call() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var hello = new String('Hello');
var world = String('world');
"#;
eprintln!("{}", forward(&mut engine, init));
forward(&mut engine, init);
let hello = forward_val(&mut engine, "hello").unwrap();
let world = forward_val(&mut engine, "world").unwrap();
@ -101,50 +101,44 @@ fn construct_and_call() {
#[test]
fn repeat() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
"#;
eprintln!("{}", forward(&mut engine, init));
let empty = String::from("");
assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty);
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "en.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "empty.repeat(0)"), "");
assert_eq!(forward(&mut engine, "empty.repeat(1)"), "");
assert_eq!(
forward(&mut engine, "en.repeat(1)"),
String::from("english")
);
assert_eq!(
forward(&mut engine, "zh.repeat(2)"),
String::from("中文中文")
);
assert_eq!(forward(&mut engine, "en.repeat(0)"), "");
assert_eq!(forward(&mut engine, "zh.repeat(0)"), "");
assert_eq!(forward(&mut engine, "en.repeat(1)"), "english");
assert_eq!(forward(&mut engine, "zh.repeat(2)"), "中文中文");
}
#[test]
fn replace() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = "abc";
a = a.replace("a", "2");
a
"#;
eprintln!("{}", forward(&mut engine, init));
let empty = String::from("2bc");
assert_eq!(forward(&mut engine, "a"), empty);
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "a"), "2bc");
}
#[test]
fn replace_with_function() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var a = "ecmascript is cool";
var p1, p2, p3;
@ -158,21 +152,20 @@ fn replace_with_function() {
a = a.replace(/c(o)(o)(l)/, replacer);
a;
"#;
eprintln!("{}", forward(&mut engine, init));
assert_eq!(
forward(&mut engine, "a"),
String::from("ecmascript is awesome!")
);
assert_eq!(forward(&mut engine, "p1"), String::from("o"));
assert_eq!(forward(&mut engine, "p2"), String::from("o"));
assert_eq!(forward(&mut engine, "p3"), String::from("l"));
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "a"), "ecmascript is awesome!");
assert_eq!(forward(&mut engine, "p1"), "o");
assert_eq!(forward(&mut engine, "p2"), "o");
assert_eq!(forward(&mut engine, "p3"), "l");
}
#[test]
fn starts_with() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
@ -182,21 +175,22 @@ fn starts_with() {
var enLiteral = 'english';
var zhLiteral = '';
"#;
eprintln!("{}", forward(&mut engine, init));
let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass);
assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass);
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "empty.startsWith('')"), "true");
assert_eq!(forward(&mut engine, "en.startsWith('e')"), "true");
assert_eq!(forward(&mut engine, "zh.startsWith('中')"), "true");
assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), "true");
assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), "true");
assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), "true");
}
#[test]
fn ends_with() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
@ -206,70 +200,45 @@ fn ends_with() {
var enLiteral = 'english';
var zhLiteral = '';
"#;
eprintln!("{}", forward(&mut engine, init));
let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass);
assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass);
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "empty.endsWith('')"), "true");
assert_eq!(forward(&mut engine, "en.endsWith('h')"), "true");
assert_eq!(forward(&mut engine, "zh.endsWith('文')"), "true");
assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), "true");
assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), "true");
assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), "true");
}
#[test]
fn match_all() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(null).length"),
String::from("0")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/b/).length"),
String::from("0")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/a/).length"),
String::from("1")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/a/g).length"),
String::from("2")
);
assert_eq!(forward(&mut engine, "'aa'.matchAll(null).length"), "0");
assert_eq!(forward(&mut engine, "'aa'.matchAll(/b/).length"), "0");
assert_eq!(forward(&mut engine, "'aa'.matchAll(/a/).length"), "1");
assert_eq!(forward(&mut engine, "'aa'.matchAll(/a/g).length"), "2");
forward(
&mut engine,
"var groupMatches = 'test1test2'.matchAll(/t(e)(st(\\d?))/g)",
);
assert_eq!(
forward(&mut engine, "groupMatches.length"),
String::from("2")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][1]"),
String::from("e")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][2]"),
String::from("st1")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][3]"),
String::from("1")
);
assert_eq!(
forward(&mut engine, "groupMatches[1][3]"),
String::from("2")
);
assert_eq!(forward(&mut engine, "groupMatches.length"), "2");
assert_eq!(forward(&mut engine, "groupMatches[0][1]"), "e");
assert_eq!(forward(&mut engine, "groupMatches[0][2]"), "st1");
assert_eq!(forward(&mut engine, "groupMatches[0][3]"), "1");
assert_eq!(forward(&mut engine, "groupMatches[1][3]"), "2");
assert_eq!(
forward(
&mut engine,
"'test1test2'.matchAll(/t(e)(st(\\d?))/).length"
),
String::from("1")
"1"
);
let init = r#"
@ -277,23 +246,19 @@ fn match_all() {
var str = 'table football, foosball';
var matches = str.matchAll(regexp);
"#;
eprintln!("{}", forward(&mut engine, init));
assert_eq!(
forward(&mut engine, "matches[0][0]"),
String::from("football")
);
assert_eq!(forward(&mut engine, "matches[0].index"), String::from("6"));
assert_eq!(
forward(&mut engine, "matches[1][0]"),
String::from("foosball")
);
assert_eq!(forward(&mut engine, "matches[1].index"), String::from("16"));
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "matches[0][0]"), "football");
assert_eq!(forward(&mut engine, "matches[0].index"), "6");
assert_eq!(forward(&mut engine, "matches[1][0]"), "foosball");
assert_eq!(forward(&mut engine, "matches[1].index"), "16");
}
#[test]
fn test_match() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog');
var result1 = str.match(/quick\s(brown).+?(jumps)/i);
@ -302,7 +267,8 @@ fn test_match() {
var result4 = str.match(RegExp("B", 'g'));
"#;
eprintln!("{}", forward(&mut engine, init));
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "result1[0]"), "Quick Brown Fox Jumps");
assert_eq!(forward(&mut engine, "result1[1]"), "Brown");
assert_eq!(forward(&mut engine, "result1[2]"), "Jumps");

10
boa/src/builtins/symbol/mod.rs

@ -18,7 +18,7 @@
#[cfg(test)]
mod tests;
use super::function::make_constructor_fn;
use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{
builtins::{
object::{
@ -63,8 +63,8 @@ pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu
let proto = ctx
.realm
.global_obj
.get_field_slice("Symbol")
.get_field_slice(PROTOTYPE);
.get_field("Symbol")
.get_field(PROTOTYPE);
sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto);
Ok(Value(Gc::new(ValueData::Symbol(Box::new(GcCell::new(
@ -92,12 +92,12 @@ pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultVa
pub fn create(global: &Value) -> Value {
// Create prototype object
let prototype = Value::new_object(Some(global));
make_builtin_fn!(to_string, named "toString", of prototype);
make_builtin_fn(to_string, "toString", &prototype, 0);
make_constructor_fn(call_symbol, global, prototype)
}
/// Initialise the `Symbol` object on the global object.
#[inline]
pub fn init(global: &Value) {
global.set_field_slice("Symbol", create(global));
global.set_field("Symbol", create(global));
}

8
boa/src/builtins/symbol/tests.rs

@ -1,7 +1,5 @@
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
use crate::{exec::Interpreter, forward, forward_val, realm::Realm};
#[test]
fn check_symbol_constructor_is_function() {
@ -13,7 +11,7 @@ fn check_symbol_constructor_is_function() {
#[test]
fn call_symbol_and_check_return_type() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var sym = Symbol();
"#;
@ -25,7 +23,7 @@ fn call_symbol_and_check_return_type() {
#[test]
fn print_symbol_expect_description() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let init = r#"
var sym = Symbol("Hello");
"#;

12
boa/src/builtins/value/conversions.rs

@ -13,6 +13,12 @@ impl From<String> for Value {
}
}
impl From<Box<str>> for Value {
fn from(value: Box<str>) -> Self {
Self::string(value)
}
}
impl From<&Value> for String {
fn from(value: &Value) -> Self {
value.to_string()
@ -25,6 +31,12 @@ impl From<&str> for Value {
}
}
impl From<&Box<str>> for Value {
fn from(value: &Box<str>) -> Self {
Self::string(value.as_ref())
}
}
impl From<char> for Value {
fn from(value: char) -> Self {
Value::string(value.to_string())

42
boa/src/builtins/value/mod.rs

@ -123,7 +123,7 @@ impl Value {
/// Returns a new empty object
pub fn new_object(global: Option<&Value>) -> Self {
if let Some(global) = global {
let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE);
let object_prototype = global.get_field("Object").get_field(PROTOTYPE);
let object = Object::create(object_prototype);
Self::object(object)
@ -484,8 +484,11 @@ impl ValueData {
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
/// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]]
/// TODO: this function should use the get Value if its set
pub fn get_field(&self, field: Value) -> Value {
match *field {
pub fn get_field<F>(&self, field: F) -> Value
where
F: Into<Value>,
{
match *field.into() {
// Our field will either be a String or a Symbol
Self::String(ref s) => {
match self.get_property(s) {
@ -586,24 +589,23 @@ impl ValueData {
self.get_property(field).is_some()
}
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
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 = Value::string(field.to_string());
self.get_field(f)
}
/// Set the field in the value
/// 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 {
pub fn set_field<F, V>(&self, field: F, val: V) -> Value
where
F: Into<Value>,
V: Into<Value>,
{
let field = field.into();
let val = val.into();
if let Self::Object(ref obj) = *self {
if obj.borrow().kind == ObjectKind::Array {
if let Ok(num) = field.to_string().parse::<usize>() {
if num > 0 {
let len = i32::from(&self.get_field_slice("length"));
let len = i32::from(&self.get_field("length"));
if len < (num + 1) as i32 {
self.set_field_slice("length", Value::from(num + 1));
self.set_field("length", Value::from(num + 1));
}
}
}
@ -621,14 +623,6 @@ impl ValueData {
val
}
/// Set the field in the value
pub fn set_field_slice(&self, field: &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 = Value::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 Self::Object(ref obj) = *self {
@ -679,7 +673,7 @@ impl ValueData {
// Wrap Object in GC'd Value
let new_func_val = Value::from(new_func);
// Set length to parameters
new_func_val.set_field_slice("length", Value::from(length));
new_func_val.set_field("length", Value::from(length));
new_func_val
}
@ -730,7 +724,7 @@ impl ValueData {
.borrow()
.properties
.iter()
.map(|(k, _)| (k.clone(), self.get_field_slice(k).to_json()))
.map(|(k, _)| (k.clone(), self.get_field(k.as_str()).to_json()))
.collect::<Map<String, JSONValue>>();
JSONValue::Object(new_obj)
}

11
boa/src/builtins/value/operations.rs

@ -1,6 +1,5 @@
use super::*;
use crate::builtins::number;
use crate::Interpreter;
use crate::{builtins::Number, Interpreter};
use std::borrow::Borrow;
use std::convert::TryFrom;
@ -16,7 +15,7 @@ impl Value {
}
if self.is_number() {
return number::equals(f64::from(self), f64::from(other));
return Number::equals(f64::from(self), f64::from(other));
}
//Null has to be handled specially because "typeof null" returns object and if we managed
@ -59,7 +58,7 @@ impl Value {
| (ValueData::Integer(_), ValueData::Boolean(_)) => {
let a: &Value = self.borrow();
let b: &Value = other.borrow();
number::equals(f64::from(a), f64::from(b))
Number::equals(f64::from(a), f64::from(b))
}
// 6. If Type(x) is BigInt and Type(y) is String, then
@ -150,7 +149,7 @@ pub fn same_value(x: &Value, y: &Value, strict: bool) -> bool {
// TODO: check BigInt
// https://github.com/jasonwilliams/boa/pull/358
if x.is_number() {
return number::same_value(f64::from(x), f64::from(y));
return Number::same_value(f64::from(x), f64::from(y));
}
same_value_non_number(x, y)
@ -330,7 +329,7 @@ pub fn same_value_zero(x: &Value, y: &Value) -> bool {
}
if x.is_number() {
return number::same_value_zero(f64::from(x), f64::from(y));
return Number::same_value_zero(f64::from(x), f64::from(y));
}
same_value_non_number(x, y)

8
boa/src/builtins/value/tests.rs

@ -1,5 +1,5 @@
use super::*;
use crate::{forward, Executor, Realm};
use crate::{forward, Interpreter, Realm};
#[test]
fn check_is_object() {
@ -27,8 +27,8 @@ fn check_get_set_field() {
let obj = Value::new_object(None);
// Create string and convert it to a Value
let s = Value::from("bar");
obj.set_field_slice("foo", s);
assert_eq!(obj.get_field_slice("foo").to_string(), "bar");
obj.set_field("foo", s);
assert_eq!(obj.get_field("foo").to_string(), "bar");
}
#[test]
@ -52,7 +52,7 @@ fn check_number_is_true() {
#[test]
fn abstract_equality_comparison() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
assert_eq!(forward(&mut engine, "undefined == undefined"), "true");
assert_eq!(forward(&mut engine, "null == null"), "true");

18
boa/src/environment/lexical_environment.rs

@ -99,10 +99,8 @@ impl LexicalEnvironment {
self.environment_stack.pop_back()
}
pub fn environments(&self) -> impl Iterator<Item = Environment> {
std::iter::successors(Some(self.get_current_environment_ref().clone()), |env| {
env.borrow().get_outer_environment()
})
pub fn environments(&self) -> impl Iterator<Item = &Environment> {
self.environment_stack.iter().rev()
}
pub fn get_global_object(&self) -> Option<Value> {
@ -114,8 +112,10 @@ impl LexicalEnvironment {
}
pub fn get_this_binding(&self) -> Value {
let env = self.environment_stack.back().expect("").borrow();
env.get_this_binding()
self.environments()
.find(|env| env.borrow().has_this_binding())
.map(|env| env.borrow().get_this_binding())
.unwrap_or_else(Value::undefined)
}
pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) {
@ -192,10 +192,8 @@ impl LexicalEnvironment {
/// get_current_environment_ref is used when you only need to borrow the environment
/// (you only need to add a new variable binding, or you want to fetch a value)
pub fn get_current_environment_ref(&self) -> &Environment {
let index = self.environment_stack.len().wrapping_sub(1);
&self
.environment_stack
.get(index)
self.environment_stack
.back()
.expect("Could not get current environment")
}

2
boa/src/environment/object_environment_record.rs

@ -68,7 +68,7 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn get_binding_value(&self, name: &str, strict: bool) -> Value {
if self.bindings.has_field(name) {
self.bindings.get_field_slice(name)
self.bindings.get_field(name)
} else {
if strict {
// TODO: throw error here

26
boa/src/exec/array/mod.rs

@ -0,0 +1,26 @@
//! Array declaration execution.
use super::{Executable, Interpreter};
use crate::{
builtins::{Array, ResultValue},
syntax::ast::node::{ArrayDecl, Node},
};
impl Executable for ArrayDecl {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let array = Array::new_array(interpreter)?;
let mut elements = Vec::new();
for elem in self.as_ref() {
if let Node::Spread(ref x) = elem {
let val = x.run(interpreter)?;
let mut vals = interpreter.extract_array_properties(&val).unwrap();
elements.append(&mut vals);
continue; // Don't push array after spread
}
elements.push(elem.run(interpreter)?);
}
Array::add_to_array_object(&array, &elements)?;
Ok(array)
}
}

34
boa/src/exec/block/mod.rs

@ -0,0 +1,34 @@
//! Block statement execution.
use super::{Executable, Interpreter};
use crate::{
builtins::value::{ResultValue, Value},
environment::lexical_environment::new_declarative_environment,
syntax::ast::node::Block,
};
impl Executable for Block {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
{
let env = &mut interpreter.realm_mut().environment;
env.push(new_declarative_environment(Some(
env.get_current_environment_ref().clone(),
)));
}
let mut obj = Value::null();
for statement in self.statements() {
obj = statement.run(interpreter)?;
// early return
if interpreter.is_return {
break;
}
}
// pop the block env
let _ = interpreter.realm_mut().environment.pop();
Ok(obj)
}
}

130
boa/src/exec/declaration/mod.rs

@ -0,0 +1,130 @@
//! Declaration execution.
use super::{Executable, Interpreter};
use crate::{
builtins::{
function::ThisMode,
value::{ResultValue, Value},
},
environment::lexical_environment::VariableScope,
syntax::ast::node::{
ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList,
},
};
impl Executable for FunctionDecl {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let val = interpreter.create_function(
self.parameters().to_vec(),
self.body().to_vec(),
ThisMode::NonLexical,
);
// Set the name and assign it in the current environment
val.set_field("name", self.name());
interpreter.realm_mut().environment.create_mutable_binding(
self.name().to_owned(),
false,
VariableScope::Function,
);
interpreter
.realm_mut()
.environment
.initialize_binding(self.name(), val.clone());
Ok(val)
}
}
impl Executable for FunctionExpr {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let val = interpreter.create_function(
self.parameters().to_vec(),
self.body().to_vec(),
ThisMode::NonLexical,
);
if let Some(name) = self.name() {
val.set_field("name", Value::from(name));
}
Ok(val)
}
}
impl Executable for VarDeclList {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
for var in self.as_ref() {
let val = match var.init() {
Some(v) => v.run(interpreter)?,
None => Value::undefined(),
};
let environment = &mut interpreter.realm_mut().environment;
if environment.has_binding(var.name()) {
if var.init().is_some() {
environment.set_mutable_binding(var.name(), val, true);
}
} else {
environment.create_mutable_binding(
var.name().to_owned(),
false,
VariableScope::Function,
);
environment.initialize_binding(var.name(), val);
}
}
Ok(Value::undefined())
}
}
impl Executable for ConstDeclList {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
for decl in self.as_ref() {
let val = decl.init().run(interpreter)?;
interpreter
.realm_mut()
.environment
.create_immutable_binding(decl.name().to_owned(), false, VariableScope::Block);
interpreter
.realm_mut()
.environment
.initialize_binding(decl.name(), val);
}
Ok(Value::undefined())
}
}
impl Executable for LetDeclList {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
for var in self.as_ref() {
let val = match var.init() {
Some(v) => v.run(interpreter)?,
None => Value::undefined(),
};
interpreter.realm_mut().environment.create_mutable_binding(
var.name().to_owned(),
false,
VariableScope::Block,
);
interpreter
.realm_mut()
.environment
.initialize_binding(var.name(), val);
}
Ok(Value::undefined())
}
}
impl Executable for ArrowFunctionDecl {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
Ok(interpreter.create_function(
self.params().to_vec(),
self.body().to_vec(),
ThisMode::Lexical,
))
}
}

81
boa/src/exec/expression/mod.rs

@ -0,0 +1,81 @@
//! Expression execution.
use super::{Executable, Interpreter};
use crate::{
builtins::{
object::{INSTANCE_PROTOTYPE, PROTOTYPE},
value::{ResultValue, Value, ValueData},
},
syntax::ast::node::{Call, New, Node},
};
impl Executable for Call {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let (mut this, func) = match self.expr() {
Node::GetConstField(ref obj, ref field) => {
let mut obj = obj.run(interpreter)?;
if obj.get_type() != "object" || obj.get_type() != "symbol" {
obj = interpreter
.to_object(&obj)
.expect("failed to convert to object");
}
(obj.clone(), obj.get_field(field))
}
Node::GetField(ref obj, ref field) => {
let obj = obj.run(interpreter)?;
let field = field.run(interpreter)?;
(obj.clone(), obj.get_field(field.to_string()))
}
_ => (
interpreter.realm().global_obj.clone(),
self.expr().run(interpreter)?,
), // 'this' binding should come from the function's self-contained environment
};
let mut v_args = Vec::with_capacity(self.args().len());
for arg in self.args() {
if let Node::Spread(ref x) = arg {
let val = x.run(interpreter)?;
let mut vals = interpreter.extract_array_properties(&val).unwrap();
v_args.append(&mut vals);
break; // after spread we don't accept any new arguments
}
v_args.push(arg.run(interpreter)?);
}
// execute the function call itself
let fnct_result = interpreter.call(&func, &mut this, &v_args);
// unset the early return flag
interpreter.is_return = false;
fnct_result
}
}
impl Executable for New {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
// let (callee, args) = match call.as_ref() {
// Node::Call(callee, args) => (callee, args),
// _ => unreachable!("Node::New(ref call): 'call' must only be Node::Call type."),
// };
let func_object = self.expr().run(interpreter)?;
let mut v_args = Vec::with_capacity(self.args().len());
for arg in self.args() {
v_args.push(arg.run(interpreter)?);
}
let mut this = Value::new_object(None);
// Create a blank object, then set its __proto__ property to the [Constructor].prototype
this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE));
match func_object.data() {
ValueData::Object(ref o) => o.clone().borrow_mut().func.as_ref().unwrap().construct(
&mut func_object.clone(),
&v_args,
interpreter,
&mut this,
),
_ => Ok(Value::undefined()),
}
}
}

42
boa/src/exec/iteration/mod.rs

@ -0,0 +1,42 @@
//! Iteration node execution.
use super::{Executable, Interpreter};
use crate::{
builtins::value::{ResultValue, Value},
environment::lexical_environment::new_declarative_environment,
syntax::ast::node::ForLoop,
};
impl Executable for ForLoop {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
// Create the block environment.
{
let env = &mut interpreter.realm_mut().environment;
env.push(new_declarative_environment(Some(
env.get_current_environment_ref().clone(),
)));
}
if let Some(init) = self.init() {
init.run(interpreter)?;
}
while self
.condition()
.map(|cond| cond.run(interpreter).map(|v| v.is_true()))
.transpose()?
.unwrap_or(true)
{
self.body().run(interpreter)?;
if let Some(final_expr) = self.final_expr() {
final_expr.run(interpreter)?;
}
}
// pop the block env
let _ = interpreter.realm_mut().environment.pop();
Ok(Value::undefined())
}
}

874
boa/src/exec/mod.rs

File diff suppressed because it is too large Load Diff

215
boa/src/exec/operator/mod.rs

@ -0,0 +1,215 @@
//! Operator execution.
use super::{Executable, Interpreter};
use crate::{
builtins::value::{ResultValue, Value},
environment::lexical_environment::VariableScope,
syntax::ast::{
node::{Assign, BinOp, Node, UnaryOp},
op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp},
},
};
use std::borrow::BorrowMut;
impl Executable for Assign {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let val = self.rhs().run(interpreter)?;
match self.lhs() {
Node::Identifier(ref name) => {
let environment = &mut interpreter.realm_mut().environment;
if environment.has_binding(name.as_ref()) {
// Binding already exists
environment.set_mutable_binding(name.as_ref(), val.clone(), true);
} else {
environment.create_mutable_binding(
name.as_ref().to_owned(),
true,
VariableScope::Function,
);
environment.initialize_binding(name.as_ref(), val.clone());
}
}
Node::GetConstField(ref obj, ref field) => {
let val_obj = obj.run(interpreter)?;
val_obj.set_field(field, val.clone());
}
Node::GetField(ref obj, ref field) => {
let val_obj = obj.run(interpreter)?;
let val_field = field.run(interpreter)?;
val_obj.set_field(val_field, val.clone());
}
_ => (),
}
Ok(val)
}
}
impl Executable for BinOp {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
match self.op() {
op::BinOp::Num(op) => {
let v_a = self.lhs().run(interpreter)?;
let v_b = self.rhs().run(interpreter)?;
Ok(match op {
NumOp::Add => v_a + v_b,
NumOp::Sub => v_a - v_b,
NumOp::Mul => v_a * v_b,
NumOp::Exp => v_a.as_num_to_power(v_b),
NumOp::Div => v_a / v_b,
NumOp::Mod => v_a % v_b,
})
}
op::BinOp::Bit(op) => {
let v_a = self.lhs().run(interpreter)?;
let v_b = self.rhs().run(interpreter)?;
Ok(match op {
BitOp::And => v_a & v_b,
BitOp::Or => v_a | v_b,
BitOp::Xor => v_a ^ v_b,
BitOp::Shl => v_a << v_b,
BitOp::Shr => v_a >> v_b,
// TODO Fix
BitOp::UShr => v_a >> v_b,
})
}
op::BinOp::Comp(op) => {
let mut v_a = self.lhs().run(interpreter)?;
let mut v_b = self.rhs().run(interpreter)?;
Ok(Value::from(match op {
CompOp::Equal => v_a.equals(v_b.borrow_mut(), interpreter),
CompOp::NotEqual => !v_a.equals(v_b.borrow_mut(), interpreter),
CompOp::StrictEqual => v_a.strict_equals(&v_b),
CompOp::StrictNotEqual => !v_a.strict_equals(&v_b),
CompOp::GreaterThan => v_a.to_number() > v_b.to_number(),
CompOp::GreaterThanOrEqual => v_a.to_number() >= v_b.to_number(),
CompOp::LessThan => v_a.to_number() < v_b.to_number(),
CompOp::LessThanOrEqual => v_a.to_number() <= v_b.to_number(),
CompOp::In => {
if !v_b.is_object() {
panic!("TypeError: {} is not an Object.", v_b);
}
let key = interpreter.to_property_key(&mut v_a);
interpreter.has_property(&mut v_b, &key)
}
}))
}
op::BinOp::Log(op) => {
// turn a `Value` into a `bool`
let to_bool = |value| bool::from(&value);
Ok(match op {
LogOp::And => Value::from(
to_bool(self.lhs().run(interpreter)?)
&& to_bool(self.rhs().run(interpreter)?),
),
LogOp::Or => Value::from(
to_bool(self.lhs().run(interpreter)?)
|| to_bool(self.rhs().run(interpreter)?),
),
})
}
op::BinOp::Assign(op) => match self.lhs() {
Node::Identifier(ref name) => {
let v_a = interpreter
.realm()
.environment
.get_binding_value(name.as_ref());
let v_b = self.rhs().run(interpreter)?;
let value = Self::run_assign(op, v_a, v_b);
interpreter.realm.environment.set_mutable_binding(
name.as_ref(),
value.clone(),
true,
);
Ok(value)
}
Node::GetConstField(ref obj, ref field) => {
let v_r_a = obj.run(interpreter)?;
let v_a = v_r_a.get_field(field);
let v_b = self.rhs().run(interpreter)?;
let value = Self::run_assign(op, v_a, v_b);
v_r_a.set_field(&field.clone(), value.clone());
Ok(value)
}
_ => Ok(Value::undefined()),
},
}
}
}
impl BinOp {
/// Runs the assignment operators.
fn run_assign(op: AssignOp, v_a: Value, v_b: Value) -> Value {
match op {
AssignOp::Add => v_a + v_b,
AssignOp::Sub => v_a - v_b,
AssignOp::Mul => v_a * v_b,
AssignOp::Exp => v_a.as_num_to_power(v_b),
AssignOp::Div => v_a / v_b,
AssignOp::Mod => v_a % v_b,
AssignOp::And => v_a & v_b,
AssignOp::Or => v_a | v_b,
AssignOp::Xor => v_a ^ v_b,
AssignOp::Shl => v_a << v_b,
AssignOp::Shr => v_a << v_b,
}
}
}
impl Executable for UnaryOp {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let v_a = self.target().run(interpreter)?;
Ok(match self.op() {
op::UnaryOp::Minus => -v_a,
op::UnaryOp::Plus => Value::from(v_a.to_number()),
op::UnaryOp::IncrementPost => {
let ret = v_a.clone();
interpreter.set_value(self.target(), Value::from(v_a.to_number() + 1.0))?;
ret
}
op::UnaryOp::IncrementPre => {
interpreter.set_value(self.target(), Value::from(v_a.to_number() + 1.0))?
}
op::UnaryOp::DecrementPost => {
let ret = v_a.clone();
interpreter.set_value(self.target(), Value::from(v_a.to_number() - 1.0))?;
ret
}
op::UnaryOp::DecrementPre => {
interpreter.set_value(self.target(), Value::from(v_a.to_number() - 1.0))?
}
op::UnaryOp::Not => !v_a,
op::UnaryOp::Tilde => {
let num_v_a = v_a.to_number();
// NOTE: possible UB: https://github.com/rust-lang/rust/issues/10184
Value::from(if num_v_a.is_nan() {
-1
} else {
!(num_v_a as i32)
})
}
op::UnaryOp::Void => Value::undefined(),
op::UnaryOp::Delete => match *self.target() {
Node::GetConstField(ref obj, ref field) => {
Value::boolean(obj.run(interpreter)?.remove_property(field))
}
Node::GetField(ref obj, ref field) => Value::boolean(
obj.run(interpreter)?
.remove_property(&field.run(interpreter)?.to_string()),
),
Node::Identifier(_) => Value::boolean(false),
Node::ArrayDecl(_)
| Node::Block(_)
| Node::Const(_)
| Node::FunctionDecl(_)
| Node::FunctionExpr(_)
| Node::New(_)
| Node::Object(_)
| Node::UnaryOp(_) => Value::boolean(true),
_ => panic!("SyntaxError: wrong delete argument {}", self),
},
op::UnaryOp::TypeOf => Value::from(v_a.get_type()),
})
}
}

26
boa/src/exec/statement_list.rs

@ -0,0 +1,26 @@
//! Statement list execution.
use super::{Executable, Interpreter};
use crate::{
builtins::value::{ResultValue, Value},
syntax::ast::node::StatementList,
};
impl Executable for StatementList {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let mut obj = Value::null();
for (i, item) in self.statements().iter().enumerate() {
let val = item.run(interpreter)?;
// early return
if interpreter.is_return {
obj = val;
break;
}
if i + 1 == self.statements().len() {
obj = val;
}
}
Ok(obj)
}
}

112
boa/src/exec/tests.rs

@ -1,7 +1,4 @@
use crate::exec;
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
use crate::{exec, exec::Interpreter, forward, realm::Realm};
#[test]
fn empty_let_decl_undefined() {
@ -47,7 +44,7 @@ fn object_field_set() {
#[test]
fn spread_with_arguments() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let scenario = r#"
const a = [1, "test", 3, 4];
@ -74,7 +71,7 @@ fn spread_with_arguments() {
#[test]
fn array_rest_with_arguments() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let scenario = r#"
var b = [4, 5, 6]
@ -329,10 +326,7 @@ fn test_for_loop() {
a
"#;
assert_eq!(
exec(body_should_not_execute_on_false_condition),
String::from("0")
);
assert_eq!(&exec(body_should_not_execute_on_false_condition), "0");
let inner_scope = r#"
for (let i = 0;false;) {}
@ -626,7 +620,7 @@ mod in_operator {
#[test]
fn should_set_this_value() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let scenario = r#"
function Foo() {
@ -645,7 +639,7 @@ mod in_operator {
fn new_instance_should_point_to_prototype() {
// A new instance should point to a prototype object created with the constructor function
let realm = Realm::create();
let mut engine = Executor::new(realm);
let mut engine = Interpreter::new(realm);
let scenario = r#"
function Foo() {}
@ -656,3 +650,97 @@ mod in_operator {
assert!(a.get_internal_slot(INSTANCE_PROTOTYPE).is_object(), true);
}
}
#[test]
fn var_decl_hoisting() {
let scenario = r#"
x = 5;
var x;
x;
"#;
assert_eq!(&exec(scenario), "5");
let scenario = r#"
x = 5;
var x = 10;
x;
"#;
assert_eq!(&exec(scenario), "10");
let scenario = r#"
x = y;
var x = 10;
var y = 5;
x;
"#;
assert_eq!(&exec(scenario), "10");
let scenario = r#"
var x = y;
var y = 5;
x;
"#;
assert_eq!(&exec(scenario), "undefined");
let scenario = r#"
let y = x;
x = 5;
var x = 10;
y;
"#;
assert_eq!(&exec(scenario), "undefined");
}
#[test]
fn function_decl_hoisting() {
let scenario = r#"
let a = hello();
function hello() { return 5 }
a;
"#;
assert_eq!(&exec(scenario), "5");
let scenario = r#"
x = hello();
function hello() {return 5}
var x;
x;
"#;
assert_eq!(&exec(scenario), "5");
let scenario = r#"
hello = function() { return 5 }
x = hello();
x;
"#;
assert_eq!(&exec(scenario), "5");
let scenario = r#"
let x = b();
function a() {return 5}
function b() {return a()}
x;
"#;
assert_eq!(&exec(scenario), "5");
let scenario = r#"
let x = b();
function b() {return a()}
function a() {return 5}
x;
"#;
assert_eq!(&exec(scenario), "5");
}

54
boa/src/exec/try_node/mod.rs

@ -0,0 +1,54 @@
//! Try..catch node execution.
use super::{Executable, Interpreter};
use crate::{
builtins::value::ResultValue,
environment::lexical_environment::{new_declarative_environment, VariableScope},
syntax::ast::node::Try,
};
#[cfg(test)]
mod tests;
impl Executable for Try {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let res = self.block().run(interpreter).map_or_else(
|err| {
if let Some(catch) = self.catch() {
{
let env = &mut interpreter.realm_mut().environment;
env.push(new_declarative_environment(Some(
env.get_current_environment_ref().clone(),
)));
if let Some(param) = catch.parameter() {
env.create_mutable_binding(
param.to_owned(),
false,
VariableScope::Block,
);
env.initialize_binding(param, err);
}
}
let res = catch.block().run(interpreter);
// pop the block env
let _ = interpreter.realm_mut().environment.pop();
res
} else {
Err(err)
}
},
Ok,
);
if let Some(finally) = self.finally() {
finally.run(interpreter)?;
}
res
}
}

95
boa/src/exec/try_node/tests.rs

@ -0,0 +1,95 @@
use crate::exec;
#[test]
fn simple_try() {
let scenario = r#"
let a = 10;
try {
a = 20;
} catch {
a = 30;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn finally() {
let scenario = r#"
let a = 10;
try {
a = 20;
} finally {
a = 30;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}
#[test]
fn catch_finally() {
let scenario = r#"
let a = 10;
try {
a = 20;
} catch {
a = 40;
} finally {
a = 30;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}
#[test]
fn catch() {
let scenario = r#"
let a = 10;
try {
throw "error";
} catch {
a = 20;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn catch_binding() {
let scenario = r#"
let a = 10;
try {
throw 20;
} catch(err) {
a = err;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn catch_binding_finally() {
let scenario = r#"
let a = 10;
try {
throw 20;
} catch(err) {
a = err;
} finally {
a = 30;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}

32
boa/src/lib.rs

@ -38,20 +38,21 @@ pub mod environment;
pub mod exec;
pub mod realm;
pub mod syntax;
use crate::{
builtins::value::ResultValue,
exec::{Executor, Interpreter},
use crate::{builtins::value::ResultValue, syntax::ast::node::StatementList};
pub use crate::{
exec::{Executable, Interpreter},
realm::Realm,
syntax::{ast::node::Node, lexer::Lexer, parser::Parser},
syntax::{lexer::Lexer, parser::Parser},
};
fn parser_expr(src: &str) -> Result<Node, String> {
fn parser_expr(src: &str) -> Result<StatementList, String> {
let mut lexer = Lexer::new(src);
lexer.lex().map_err(|e| format!("SyntaxError: {}", e))?;
lexer.lex().map_err(|e| format!("Syntax Error: {}", e))?;
let tokens = lexer.tokens;
Parser::new(&tokens)
.parse_all()
.map_err(|e| format!("ParsingError: {}", e))
.map_err(|e| format!("Parsing Error: {}", e))
}
/// Execute the code using an existing Interpreter
@ -59,16 +60,11 @@ fn parser_expr(src: &str) -> Result<Node, String> {
pub fn forward(engine: &mut Interpreter, src: &str) -> String {
// Setup executor
let expr = match parser_expr(src) {
Ok(v) => v,
Err(error_string) => {
return error_string;
}
Ok(res) => res,
Err(e) => return e,
};
let result = engine.run(&expr);
match result {
Ok(v) => v.to_string(),
Err(v) => format!("{}: {}", "Error", v.to_string()),
}
expr.run(engine)
.map_or_else(|e| format!("Error: {}", e), |v| v.to_string())
}
/// Execute the code using an existing Interpreter.
@ -78,7 +74,7 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String {
pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue {
// Setup executor
match parser_expr(src) {
Ok(expr) => engine.run(&expr),
Ok(expr) => expr.run(engine),
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
@ -90,6 +86,6 @@ pub fn forward_val(engine: &mut Interpreter, src: &str) -> ResultValue {
pub fn exec(src: &str) -> String {
// Create new Realm
let realm = Realm::create();
let mut engine: Interpreter = Executor::new(realm);
let mut engine = Interpreter::new(realm);
forward(&mut engine, src)
}

14
boa/src/syntax/ast/constant.rs

@ -40,7 +40,7 @@ pub enum Const {
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-string-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#String_literals
String(String),
String(Box<str>),
/// A floating-point number literal.
///
@ -115,19 +115,25 @@ pub enum Const {
impl From<&str> for Const {
fn from(s: &str) -> Self {
Self::String(s.into())
Self::String(s.to_owned().into_boxed_str())
}
}
impl From<&String> for Const {
fn from(s: &String) -> Self {
Self::String(s.clone())
Self::String(s.clone().into_boxed_str())
}
}
impl From<Box<str>> for Const {
fn from(s: Box<str>) -> Self {
Self::String(s)
}
}
impl From<String> for Const {
fn from(s: String) -> Self {
Self::String(s)
Self::String(s.into_boxed_str())
}
}

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

@ -8,12 +8,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
use crate::syntax::ast::op::{BinOp, CompOp};
use std::{
convert::TryInto,
error,
fmt::{Display, Error, Formatter},
str::FromStr,
};
use std::{convert::TryInto, error, fmt, str::FromStr};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -436,12 +431,55 @@ pub enum Keyword {
}
impl Keyword {
/// Gets the keyword as a binary operation, if this keyword is the `in` keyword.
pub fn as_binop(self) -> Option<BinOp> {
match self {
Keyword::In => Some(BinOp::Comp(CompOp::In)),
_ => None,
}
}
/// Gets the keyword as a string.
pub fn as_str(self) -> &'static str {
match self {
Self::Await => "await",
Self::Break => "break",
Self::Case => "case",
Self::Catch => "catch",
Self::Class => "class",
Self::Continue => "continue",
Self::Const => "const",
Self::Debugger => "debugger",
Self::Default => "default",
Self::Delete => "delete",
Self::Do => "do",
Self::Else => "else",
Self::Enum => "enum",
Self::Extends => "extends",
Self::Export => "export",
Self::Finally => "finally",
Self::For => "for",
Self::Function => "function",
Self::If => "if",
Self::In => "in",
Self::InstanceOf => "instanceof",
Self::Import => "import",
Self::Let => "let",
Self::New => "new",
Self::Return => "return",
Self::Super => "super",
Self::Switch => "switch",
Self::This => "this",
Self::Throw => "throw",
Self::Try => "try",
Self::TypeOf => "typeof",
Self::Var => "var",
Self::Void => "void",
Self::While => "while",
Self::With => "with",
Self::Yield => "yield",
}
}
}
impl TryInto<BinOp> for Keyword {
@ -454,8 +492,8 @@ impl TryInto<BinOp> for Keyword {
#[derive(Debug, Clone, Copy)]
pub struct KeywordError;
impl Display for KeywordError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
impl fmt::Display for KeywordError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid token")
}
}
@ -515,49 +553,9 @@ impl FromStr for Keyword {
}
}
}
impl Display for Keyword {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(
f,
"{}",
match *self {
Self::Await => "await",
Self::Break => "break",
Self::Case => "case",
Self::Catch => "catch",
Self::Class => "class",
Self::Continue => "continue",
Self::Const => "const",
Self::Debugger => "debugger",
Self::Default => "default",
Self::Delete => "delete",
Self::Do => "do",
Self::Else => "else",
Self::Enum => "enum",
Self::Extends => "extends",
Self::Export => "export",
Self::Finally => "finally",
Self::For => "for",
Self::Function => "function",
Self::If => "if",
Self::In => "in",
Self::InstanceOf => "instanceof",
Self::Import => "import",
Self::Let => "let",
Self::New => "new",
Self::Return => "return",
Self::Super => "super",
Self::Switch => "switch",
Self::This => "this",
Self::Throw => "throw",
Self::Try => "try",
Self::TypeOf => "typeof",
Self::Var => "var",
Self::Void => "void",
Self::While => "while",
Self::With => "with",
Self::Yield => "yield",
}
)
impl fmt::Display for Keyword {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}

13
boa/src/syntax/ast/mod.rs

@ -5,6 +5,15 @@ pub mod constant;
pub mod keyword;
pub mod node;
pub mod op;
pub mod pos;
pub mod punc;
pub mod position;
pub mod punctuator;
pub mod token;
pub use self::{
constant::Const,
keyword::Keyword,
node::Node,
position::{Position, Span},
punctuator::Punctuator,
token::{Token, TokenKind},
};

60
boa/src/syntax/ast/node/array.rs

@ -0,0 +1,60 @@
//! Array declaration node.
use super::{join_nodes, Node};
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// An array is an ordered collection of data (either primitive or object depending upon the
/// language).
///
/// Arrays are used to store multiple values in a single variable.
/// This is compared to a variable that can store only one value.
///
/// Each item in an array has a number attached to it, called a numeric index, that allows you
/// to access it. In JavaScript, arrays start at index zero and can be manipulated with various
/// methods.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ArrayDecl {
#[cfg_attr(feature = "serde", serde(flatten))]
arr: Box<[Node]>,
}
impl AsRef<[Node]> for ArrayDecl {
fn as_ref(&self) -> &[Node] {
&self.arr
}
}
impl<T> From<T> for ArrayDecl
where
T: Into<Box<[Node]>>,
{
fn from(decl: T) -> Self {
Self { arr: decl.into() }
}
}
impl fmt::Display for ArrayDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[")?;
join_nodes(f, &self.arr)?;
f.write_str("]")
}
}
impl From<ArrayDecl> for Node {
fn from(arr: ArrayDecl) -> Self {
Self::ArrayDecl(arr)
}
}

68
boa/src/syntax/ast/node/block.rs

@ -0,0 +1,68 @@
//! Block AST node.
use super::{Node, StatementList};
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// A `block` statement (or compound statement in other languages) is used to group zero or
/// more statements.
///
/// The block statement is often called compound statement in other languages.
/// It allows you to use multiple statements where JavaScript expects only one statement.
/// Combining statements into blocks is a common practice in JavaScript. The opposite behavior
/// is possible using an empty statement, where you provide no statement, although one is
/// required.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Block {
#[cfg_attr(feature = "serde", serde(flatten))]
statements: StatementList,
}
impl Block {
/// Gets the list of statements in this block.
pub(crate) fn statements(&self) -> &[Node] {
self.statements.statements()
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
writeln!(f, "{{")?;
self.statements.display(f, indentation + 1)?;
write!(f, "{}}}", " ".repeat(indentation))
}
}
impl<T> From<T> for Block
where
T: Into<StatementList>,
{
fn from(list: T) -> Self {
Self {
statements: list.into(),
}
}
}
impl fmt::Display for Block {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<Block> for Node {
fn from(block: Block) -> Self {
Self::Block(block)
}
}

550
boa/src/syntax/ast/node/declaration.rs

@ -0,0 +1,550 @@
//! Declaration nodes.
use super::{join_nodes, FormalParameter, Identifier, Node, StatementList};
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// The `var` statement declares a variable, optionally initializing it to a value.
///
/// var declarations, wherever they occur, are processed before any code is executed. This is
/// called hoisting, and is discussed further below.
///
/// The scope of a variable declared with var is its current execution context, which is either
/// the enclosing function or, for variables declared outside any function, global. If you
/// re-declare a JavaScript variable, it will not lose its value.
///
/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it
/// becomes a property of the global object) when the assignment is executed.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct VarDeclList {
#[cfg_attr(feature = "serde", serde(flatten))]
vars: Box<[VarDecl]>,
}
impl<T> From<T> for VarDeclList
where
T: Into<Box<[VarDecl]>>,
{
fn from(list: T) -> Self {
Self { vars: list.into() }
}
}
impl From<VarDecl> for VarDeclList {
fn from(decl: VarDecl) -> Self {
Self {
vars: Box::new([decl]),
}
}
}
impl AsRef<[VarDecl]> for VarDeclList {
fn as_ref(&self) -> &[VarDecl] {
&self.vars
}
}
impl fmt::Display for VarDeclList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.vars.is_empty() {
write!(f, "var ")?;
join_nodes(f, &self.vars)
} else {
Ok(())
}
}
}
impl From<VarDeclList> for Node {
fn from(list: VarDeclList) -> Self {
Self::VarDeclList(list)
}
}
/// Individual variable declaration.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct VarDecl {
name: Identifier,
init: Option<Node>,
}
impl fmt::Display for VarDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.name, f)?;
if let Some(ref init) = self.init {
write!(f, " = {}", init)?;
}
Ok(())
}
}
impl VarDecl {
/// Creates a new variable declaration.
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self
where
N: Into<Identifier>,
I: Into<Option<Node>>,
{
Self {
name: name.into(),
init: init.into(),
}
}
/// Gets the name of the variable.
pub fn name(&self) -> &str {
self.name.as_ref()
}
/// Gets the initialization node for the variable, if any.
pub fn init(&self) -> Option<&Node> {
self.init.as_ref()
}
}
/// The `function` expression defines a function with the specified parameters.
///
/// A function created with a function expression is a `Function` object and has all the
/// properties, methods and behavior of `Function`.
///
/// A function can also be created using a declaration (see function expression).
///
/// By default, functions return `undefined`. To return any other value, the function must have
/// a return statement that specifies the value to return.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct FunctionExpr {
name: Option<Box<str>>,
parameters: Box<[FormalParameter]>,
body: StatementList,
}
impl FunctionExpr {
/// Creates a new function expression
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Option<Box<str>>>,
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
Self {
name: name.into(),
parameters: parameters.into(),
body: body.into(),
}
}
/// Gets the name of the function declaration.
pub fn name(&self) -> Option<&str> {
self.name.as_ref().map(Box::as_ref)
}
/// Gets the list of parameters of the function declaration.
pub fn parameters(&self) -> &[FormalParameter] {
&self.parameters
}
/// Gets the body of the function declaration.
pub fn body(&self) -> &[Node] {
self.body.statements()
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
f.write_str("function")?;
if let Some(ref name) = self.name {
write!(f, " {}", name)?;
}
f.write_str("(")?;
join_nodes(f, &self.parameters)?;
f.write_str(") {{")?;
self.body.display(f, indentation + 1)?;
writeln!(f, "}}")
}
}
impl fmt::Display for FunctionExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<FunctionExpr> for Node {
fn from(expr: FunctionExpr) -> Self {
Self::FunctionExpr(expr)
}
}
/// The `function` declaration (function statement) defines a function with the specified
/// parameters.
///
/// A function created with a function declaration is a `Function` object and has all the
/// properties, methods and behavior of `Function`.
///
/// A function can also be created using an expression (see [function expression][func_expr]).
///
/// By default, functions return `undefined`. To return any other value, the function must have
/// a return statement that specifies the value to return.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
/// [func_expr]: ../enum.Node.html#variant.FunctionExpr
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct FunctionDecl {
name: Box<str>,
parameters: Box<[FormalParameter]>,
body: StatementList,
}
impl FunctionDecl {
/// Creates a new function declaration.
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Box<str>>,
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
Self {
name: name.into(),
parameters: parameters.into(),
body: body.into(),
}
}
/// Gets the name of the function declaration.
pub fn name(&self) -> &str {
&self.name
}
/// Gets the list of parameters of the function declaration.
pub fn parameters(&self) -> &[FormalParameter] {
&self.parameters
}
/// Gets the body of the function declaration.
pub fn body(&self) -> &[Node] {
self.body.statements()
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
write!(f, "function {}(", self.name)?;
join_nodes(f, &self.parameters)?;
f.write_str(") {{")?;
self.body.display(f, indentation + 1)?;
writeln!(f, "}}")
}
}
impl From<FunctionDecl> for Node {
fn from(decl: FunctionDecl) -> Self {
Self::FunctionDecl(decl)
}
}
impl fmt::Display for FunctionDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
/// An arrow function expression is a syntactically compact alternative to a regular function
/// expression.
///
/// Arrow function expressions are ill suited as methods, and they cannot be used as
/// constructors. Arrow functions cannot be used as constructors and will throw an error when
/// used with new.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ArrowFunctionDecl {
params: Box<[FormalParameter]>,
body: StatementList,
}
impl ArrowFunctionDecl {
/// Creates a new `ArrowFunctionDecl` AST node.
pub(in crate::syntax) fn new<P, B>(params: P, body: B) -> Self
where
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
Self {
params: params.into(),
body: body.into(),
}
}
/// Gets the list of parameters of the arrow function.
pub(crate) fn params(&self) -> &[FormalParameter] {
&self.params
}
/// Gets the body of the arrow function.
pub(crate) fn body(&self) -> &[Node] {
&self.body.statements()
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
write!(f, "(")?;
join_nodes(f, &self.params)?;
f.write_str(") => ")?;
self.body.display(f, indentation)
}
}
impl fmt::Display for ArrowFunctionDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<ArrowFunctionDecl> for Node {
fn from(decl: ArrowFunctionDecl) -> Self {
Self::ArrowFunctionDecl(decl)
}
}
/// The `const` statements are block-scoped, much like variables defined using the `let`
/// keyword.
///
/// This declaration creates a constant whose scope can be either global or local to the block
/// in which it is declared. Global constants do not become properties of the window object,
/// unlike var variables.
///
/// An initializer for a constant is required. You must specify its value in the same statement
/// in which it's declared. (This makes sense, given that it can't be changed later.)
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
/// [identifier]: https://developer.mozilla.org/en-US/docs/Glossary/identifier
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ConstDeclList {
#[cfg_attr(feature = "serde", serde(flatten))]
list: Box<[ConstDecl]>,
}
impl<T> From<T> for ConstDeclList
where
T: Into<Box<[ConstDecl]>>,
{
fn from(list: T) -> Self {
Self { list: list.into() }
}
}
impl From<ConstDecl> for ConstDeclList {
fn from(decl: ConstDecl) -> Self {
Self {
list: Box::new([decl]),
}
}
}
impl AsRef<[ConstDecl]> for ConstDeclList {
fn as_ref(&self) -> &[ConstDecl] {
&self.list
}
}
impl fmt::Display for ConstDeclList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.list.is_empty() {
write!(f, "const ")?;
join_nodes(f, &self.list)
} else {
Ok(())
}
}
}
impl From<ConstDeclList> for Node {
fn from(list: ConstDeclList) -> Self {
Self::ConstDeclList(list)
}
}
/// Individual constant declaration.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ConstDecl {
name: Identifier,
init: Node,
}
impl fmt::Display for ConstDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", self.name, self.init)
}
}
impl ConstDecl {
/// Creates a new variable declaration.
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self
where
N: Into<Identifier>,
I: Into<Node>,
{
Self {
name: name.into(),
init: init.into(),
}
}
/// Gets the name of the variable.
pub fn name(&self) -> &str {
self.name.as_ref()
}
/// Gets the initialization node for the variable, if any.
pub fn init(&self) -> &Node {
&self.init
}
}
/// The `let` statement declares a block scope local variable, optionally initializing it to a
/// value.
///
///
/// `let` allows you to declare variables that are limited to a scope of a block statement, or
/// expression on which it is used, unlike the `var` keyword, which defines a variable
/// globally, or locally to an entire function regardless of block scope.
///
/// Just like const the `let` does not create properties of the window object when declared
/// globally (in the top-most scope).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct LetDeclList {
#[cfg_attr(feature = "serde", serde(flatten))]
list: Box<[LetDecl]>,
}
impl<T> From<T> for LetDeclList
where
T: Into<Box<[LetDecl]>>,
{
fn from(list: T) -> Self {
Self { list: list.into() }
}
}
impl From<LetDecl> for LetDeclList {
fn from(decl: LetDecl) -> Self {
Self {
list: Box::new([decl]),
}
}
}
impl AsRef<[LetDecl]> for LetDeclList {
fn as_ref(&self) -> &[LetDecl] {
&self.list
}
}
impl fmt::Display for LetDeclList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.list.is_empty() {
write!(f, "let ")?;
join_nodes(f, &self.list)
} else {
Ok(())
}
}
}
impl From<LetDeclList> for Node {
fn from(list: LetDeclList) -> Self {
Self::LetDeclList(list)
}
}
/// Individual constant declaration.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct LetDecl {
name: Identifier,
init: Option<Node>,
}
impl fmt::Display for LetDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.name, f)?;
if let Some(ref init) = self.init {
write!(f, " = {}", init)?;
}
Ok(())
}
}
impl LetDecl {
/// Creates a new variable declaration.
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self
where
N: Into<Identifier>,
I: Into<Option<Node>>,
{
Self {
name: name.into(),
init: init.into(),
}
}
/// Gets the name of the variable.
pub fn name(&self) -> &str {
self.name.as_ref()
}
/// Gets the initialization node for the variable, if any.
pub fn init(&self) -> Option<&Node> {
self.init.as_ref()
}
}

118
boa/src/syntax/ast/node/expression.rs

@ -0,0 +1,118 @@
//! Expression nodes.
use super::{join_nodes, Node};
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Calling the function actually performs the specified actions with the indicated parameters.
///
/// Defining a function does not execute it. Defining it simply names the function and
/// specifies what to do when the function is called. Functions must be in scope when they are
/// called, but the function declaration can be hoisted. The scope of a function is the
/// function in which it is declared (or the entire program, if it is declared at the top
/// level).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CallExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Call {
expr: Box<Node>,
args: Box<[Node]>,
}
impl Call {
/// Creates a new `Call` AST node.
pub fn new<E, A>(expr: E, args: A) -> Self
where
E: Into<Node>,
A: Into<Box<[Node]>>,
{
Self {
expr: Box::new(expr.into()),
args: args.into(),
}
}
/// Gets the name of the function call.
pub fn expr(&self) -> &Node {
&self.expr
}
/// Retrieves the arguments passed to the function.
pub fn args(&self) -> &[Node] {
&self.args
}
}
impl fmt::Display for Call {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.expr)?;
join_nodes(f, &self.args)?;
f.write_str(")")
}
}
impl From<Call> for Node {
fn from(call: Call) -> Self {
Self::Call(call)
}
}
/// The `new` operator lets developers create an instance of a user-defined object type or of
/// one of the built-in object types that has a constructor function.
///
/// The new keyword does the following things:
/// - Creates a blank, plain JavaScript object;
/// - Links (sets the constructor of) this object to another object;
/// - Passes the newly created object from Step 1 as the this context;
/// - Returns this if the function doesn't return its own object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-NewExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct New {
call: Call,
}
impl New {
/// Gets the name of the function call.
pub fn expr(&self) -> &Node {
&self.call.expr()
}
/// Retrieves the arguments passed to the function.
pub fn args(&self) -> &[Node] {
&self.call.args()
}
}
impl From<Call> for New {
fn from(call: Call) -> Self {
Self { call }
}
}
impl fmt::Display for New {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "new {}", self.call)
}
}
impl From<New> for Node {
fn from(new: New) -> Self {
Self::New(new)
}
}

58
boa/src/syntax/ast/node/identifier.rs

@ -0,0 +1,58 @@
//! Local identifier node.
use super::Node;
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// An `identifier` is a sequence of characters in the code that identifies a variable,
/// function, or property.
///
/// In JavaScript, identifiers are case-sensitive and can contain Unicode letters, $, _, and
/// digits (0-9), but may not start with a digit.
///
/// An identifier differs from a string in that a string is data, while an identifier is part
/// of the code. In JavaScript, there is no way to convert identifiers to strings, but
/// sometimes it is possible to parse strings into identifiers.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-Identifier
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Identifier {
ident: Box<str>,
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.ident, f)
}
}
impl AsRef<str> for Identifier {
fn as_ref(&self) -> &str {
&self.ident
}
}
impl<T> From<T> for Identifier
where
T: Into<Box<str>>,
{
fn from(stm: T) -> Self {
Self { ident: stm.into() }
}
}
impl From<Identifier> for Node {
fn from(local: Identifier) -> Self {
Self::Identifier(local)
}
}

139
boa/src/syntax/ast/node/iteration.rs

@ -0,0 +1,139 @@
use super::Node;
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// The `for` statement creates a loop that consists of three optional expressions.
///
/// A `for` loop repeats until a specified condition evaluates to `false`.
/// The JavaScript for loop is similar to the Java and C for loop.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ForLoop {
#[cfg_attr(feature = "serde", serde(flatten))]
inner: Box<InnerForLoop>,
}
impl ForLoop {
/// Creates a new for loop AST node.
pub(in crate::syntax) fn new<I, C, E, B>(init: I, condition: C, final_expr: E, body: B) -> Self
where
I: Into<Option<Node>>,
C: Into<Option<Node>>,
E: Into<Option<Node>>,
B: Into<Node>,
{
Self {
inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)),
}
}
/// Gets the initialization node.
pub fn init(&self) -> Option<&Node> {
self.inner.init()
}
/// Gets the loop condition node.
pub fn condition(&self) -> Option<&Node> {
self.inner.condition()
}
/// Gets the final expression node.
pub fn final_expr(&self) -> Option<&Node> {
self.inner.final_expr()
}
/// Gets the body of the for loop.
pub fn body(&self) -> &Node {
self.inner.body()
}
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
f.write_str("for (")?;
if let Some(init) = self.init() {
fmt::Display::fmt(init, f)?;
}
f.write_str(";")?;
if let Some(condition) = self.condition() {
fmt::Display::fmt(condition, f)?;
}
f.write_str(";")?;
if let Some(final_expr) = self.final_expr() {
fmt::Display::fmt(final_expr, f)?;
}
writeln!(f, ") {{")?;
self.inner.body().display(f, indentation + 1)?;
write!(f, "}}")
}
}
impl fmt::Display for ForLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<ForLoop> for Node {
fn from(for_loop: ForLoop) -> Self {
Self::ForLoop(for_loop)
}
}
/// Inner structure to avoid multiple indirections in the heap.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
struct InnerForLoop {
init: Option<Node>,
condition: Option<Node>,
final_expr: Option<Node>,
body: Node,
}
impl InnerForLoop {
/// Creates a new inner for loop.
fn new<I, C, E, B>(init: I, condition: C, final_expr: E, body: B) -> Self
where
I: Into<Option<Node>>,
C: Into<Option<Node>>,
E: Into<Option<Node>>,
B: Into<Node>,
{
Self {
init: init.into(),
condition: condition.into(),
final_expr: final_expr.into(),
body: body.into(),
}
}
/// Gets the initialization node.
fn init(&self) -> Option<&Node> {
self.init.as_ref()
}
/// Gets the loop condition node.
fn condition(&self) -> Option<&Node> {
self.condition.as_ref()
}
/// Gets the final expression node.
fn final_expr(&self) -> Option<&Node> {
self.final_expr.as_ref()
}
/// Gets the body of the for loop.
fn body(&self) -> &Node {
&self.body
}
}

803
boa/src/syntax/ast/node.rs → boa/src/syntax/ast/node/mod.rs

File diff suppressed because it is too large Load Diff

168
boa/src/syntax/ast/node/operator.rs

@ -0,0 +1,168 @@
use super::Node;
use crate::syntax::ast::op;
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// An assignment operator assigns a value to its left operand based on the value of its right
/// operand.
///
/// Assignment operator (`=`), assigns the value of its right operand to its left operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Assign {
lhs: Box<Node>,
rhs: Box<Node>,
}
impl Assign {
/// Creates an `Assign` AST node.
pub(in crate::syntax) fn new<L, R>(lhs: L, rhs: R) -> Self
where
L: Into<Node>,
R: Into<Node>,
{
Self {
lhs: Box::new(lhs.into()),
rhs: Box::new(rhs.into()),
}
}
/// Gets the left hand side of the assignment operation.
pub fn lhs(&self) -> &Node {
&self.lhs
}
/// Gets the right hand side of the assignment operation.
pub fn rhs(&self) -> &Node {
&self.rhs
}
}
impl fmt::Display for Assign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", self.lhs, self.rhs)
}
}
impl From<Assign> for Node {
fn from(op: Assign) -> Self {
Self::Assign(op)
}
}
/// Binary operators requires two operands, one before the operator and one after the operator.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct BinOp {
op: op::BinOp,
lhs: Box<Node>,
rhs: Box<Node>,
}
impl BinOp {
/// Creates a `BinOp` AST node.
pub(in crate::syntax) fn new<O, L, R>(op: O, lhs: L, rhs: R) -> Self
where
O: Into<op::BinOp>,
L: Into<Node>,
R: Into<Node>,
{
Self {
op: op.into(),
lhs: Box::new(lhs.into()),
rhs: Box::new(rhs.into()),
}
}
/// Gets the binary operation of the node.
pub fn op(&self) -> op::BinOp {
self.op
}
/// Gets the left hand side of the binary operation.
pub fn lhs(&self) -> &Node {
&self.lhs
}
/// Gets the right hand side of the binary operation.
pub fn rhs(&self) -> &Node {
&self.rhs
}
}
impl fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.lhs, self.op, self.rhs)
}
}
impl From<BinOp> for Node {
fn from(op: BinOp) -> Self {
Self::BinOp(op)
}
}
/// A unary operation is an operation with only one operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct UnaryOp {
op: op::UnaryOp,
target: Box<Node>,
}
impl UnaryOp {
/// Creates a new `UnaryOp` AST node.
pub(in crate::syntax) fn new<V>(op: op::UnaryOp, target: V) -> Self
where
V: Into<Node>,
{
Self {
op,
target: Box::new(target.into()),
}
}
/// Gets the unary operation of the node.
pub fn op(&self) -> op::UnaryOp {
self.op
}
/// Gets the target of this unary operator.
pub fn target(&self) -> &Node {
self.target.as_ref()
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.op, self.target)
}
}
impl From<UnaryOp> for Node {
fn from(op: UnaryOp) -> Self {
Self::UnaryOp(op)
}
}

67
boa/src/syntax/ast/node/statement_list.rs

@ -0,0 +1,67 @@
//! Statement list node.
use super::Node;
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// List of statements.
///
/// Similar to `Node::Block` but without the braces.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct StatementList {
#[cfg_attr(feature = "serde", serde(flatten))]
statements: Box<[Node]>,
}
impl StatementList {
/// Gets the list of statements.
pub fn statements(&self) -> &[Node] {
&self.statements
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
let indent = " ".repeat(indentation);
// Print statements
for node in self.statements.iter() {
f.write_str(&indent)?;
node.display(f, indentation + 1)?;
match node {
Node::Block(_)
| Node::If(_, _, _)
| Node::Switch(_, _, _)
| Node::WhileLoop(_, _) => {}
_ => write!(f, ";")?,
}
writeln!(f)?;
}
Ok(())
}
}
impl<T> From<T> for StatementList
where
T: Into<Box<[Node]>>,
{
fn from(stm: T) -> Self {
Self {
statements: stm.into(),
}
}
}
impl fmt::Display for StatementList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}

172
boa/src/syntax/ast/node/try_node.rs

@ -0,0 +1,172 @@
use super::{Block, Identifier, Node};
use gc::{Finalize, Trace};
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// The `try...catch` statement marks a block of statements to try and specifies a response
/// should an exception be thrown.
///
/// The `try` statement consists of a `try`-block, which contains one or more statements. `{}`
/// must always be used, even for single statements. At least one `catch`-block, or a
/// `finally`-block, must be present.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-TryStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Try {
block: Block,
catch: Option<Catch>,
finally: Option<Finally>,
}
impl Try {
/// Creates a new `Try` AST node.
pub(in crate::syntax) fn new<B>(
block: B,
catch: Option<Catch>,
finally: Option<Finally>,
) -> Self
where
B: Into<Block>,
{
assert!(
catch.is_some() || finally.is_some(),
"one of catch or finally must be pressent"
);
Self {
block: block.into(),
catch,
finally,
}
}
/// Gets the `try` block.
pub fn block(&self) -> &Block {
&self.block
}
/// Gets the `catch` block, if any.
pub fn catch(&self) -> Option<&Catch> {
self.catch.as_ref()
}
/// Gets the `finally` block, if any.
pub fn finally(&self) -> Option<&Block> {
self.finally.as_ref().map(Finally::block)
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
write!(f, "{}try ", " ".repeat(indentation))?;
self.block.display(f, indentation)?;
if let Some(ref catch) = self.catch {
catch.display(f, indentation)?;
}
if let Some(ref finally) = self.finally {
finally.display(f, indentation)?;
}
Ok(())
}
}
impl fmt::Display for Try {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<Try> for Node {
fn from(try_catch: Try) -> Self {
Self::Try(try_catch)
}
}
/// Catch block.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Catch {
parameter: Option<Identifier>,
block: Block,
}
impl Catch {
/// Creates a new catch block.
pub(in crate::syntax) fn new<OI, I, B>(parameter: OI, block: B) -> Self
where
OI: Into<Option<I>>,
I: Into<Identifier>,
B: Into<Block>,
{
Self {
parameter: parameter.into().map(I::into),
block: block.into(),
}
}
/// Gets the parameter of the catch block.
pub fn parameter(&self) -> Option<&str> {
self.parameter.as_ref().map(Identifier::as_ref)
}
/// Retrieves the catch execution block.
pub fn block(&self) -> &Block {
&self.block
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
f.write_str(" catch")?;
if let Some(ref param) = self.parameter {
write!(f, "({})", param)?;
}
f.write_str(" ")?;
self.block.display(f, indentation)
}
}
impl fmt::Display for Catch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
/// Finally block.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct Finally {
block: Block,
}
impl Finally {
/// Gets the finally block.
pub fn block(&self) -> &Block {
&self.block
}
/// Implements the display formatting with indentation.
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
f.write_str(" finally ")?;
self.block.display(f, indentation)
}
}
impl<T> From<T> for Finally
where
T: Into<Block>,
{
fn from(block: T) -> Self {
Self {
block: block.into(),
}
}
}

86
boa/src/syntax/ast/op.rs

@ -1,23 +1,11 @@
//! This module implements various structure for logic handling.
use gc::{Finalize, Trace};
use gc::{unsafe_empty_trace, Finalize, Trace};
use std::fmt::{Display, Formatter, Result};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Represents an operator
pub trait Operator {
/// Get the associativity as a boolean that is true if it goes rightwards
fn get_assoc(&self) -> bool;
/// Get the precedence as an unsigned integer, where the lower it is, the more precedence/priority it has
fn get_precedence(&self) -> u64;
/// Get the precedence and associativity of this operator
fn get_precedence_and_assoc(&self) -> (u64, bool) {
(self.get_precedence(), self.get_assoc())
}
}
/// Arithmetic operators take numerical values (either literals or variables)
/// as their operands and return a single numerical value.
///
@ -26,7 +14,7 @@ pub trait Operator {
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Arithmetic
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum NumOp {
/// The addition operator produces the sum of numeric operands or string concatenation.
///
@ -124,6 +112,10 @@ impl Display for NumOp {
}
}
unsafe impl Trace for NumOp {
unsafe_empty_trace!();
}
/// A unary operator is one that takes a single operand/argument and performs an operation.
///
/// A unary operation is an operation with only one operand. This operand comes either
@ -137,7 +129,7 @@ impl Display for NumOp {
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum UnaryOp {
/// The increment operator increments (adds one to) its operand and returns a value.
///
@ -336,6 +328,10 @@ impl Display for UnaryOp {
}
}
unsafe impl Trace for UnaryOp {
unsafe_empty_trace!();
}
/// A bitwise operator is an operator used to perform bitwise operations
/// on bit patterns or binary numerals that involve the manipulation of individual bits.
///
@ -344,7 +340,7 @@ impl Display for UnaryOp {
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum BitOp {
/// Performs the AND operation on each pair of bits. a AND b yields 1 only if both a and b are 1.
///
@ -447,6 +443,10 @@ impl Display for BitOp {
}
}
unsafe impl Trace for BitOp {
unsafe_empty_trace!();
}
/// A comparison operator compares its operands and returns a logical value based on whether the comparison is true.
///
/// The operands can be numerical, string, logical, or object values. Strings are compared based on standard
@ -463,7 +463,7 @@ impl Display for BitOp {
/// [spec]: tc39.es/ecma262/#sec-testing-and-comparison-operations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Comparison
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum CompOp {
/// The equality operator converts the operands if they are not of the same type, then applies strict comparison.
///
@ -579,6 +579,7 @@ pub enum CompOp {
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_or_equal_operator
LessThanOrEqual,
/// The `in` operator returns true if the specified property is in the specified object or its prototype chain.
///
/// Syntax: `prop in object`
@ -614,6 +615,10 @@ impl Display for CompOp {
}
}
unsafe impl Trace for CompOp {
unsafe_empty_trace!();
}
/// Logical operators are typically used with Boolean (logical) values; when they are, they return a Boolean value.
///
/// However, the `&&` and `||` operators actually return the value of one of the specified operands,
@ -626,7 +631,7 @@ impl Display for CompOp {
/// [spec]: https://tc39.es/ecma262/#sec-binary-logical-operators
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Logical
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum LogOp {
/// The logical AND operator returns the value of the first operand if it can be coerced into `false`;
/// otherwise, it returns the second operand.
@ -668,9 +673,13 @@ impl Display for LogOp {
}
}
unsafe impl Trace for LogOp {
unsafe_empty_trace!();
}
/// This represents a binary operation between two values.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum BinOp {
/// Numeric operation.
///
@ -728,35 +737,6 @@ impl From<AssignOp> for BinOp {
}
}
impl Operator for BinOp {
fn get_assoc(&self) -> bool {
true
}
fn get_precedence(&self) -> u64 {
match *self {
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)
| Self::Comp(CompOp::In) => 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,
}
}
}
impl Display for BinOp {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(
@ -773,6 +753,10 @@ impl Display for BinOp {
}
}
unsafe impl Trace for BinOp {
unsafe_empty_trace!();
}
/// An assignment operator assigns a value to its left operand based on the value of its right operand.
///
/// The simple assignment operator is equal (`=`), which assigns the value of its right operand to its
@ -787,7 +771,7 @@ impl Display for BinOp {
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
#[derive(Clone, Copy, Debug, Finalize, PartialEq)]
pub enum AssignOp {
/// The addition assignment operator adds the value of the right operand to a variable and assigns the result to the variable.
///
@ -928,6 +912,10 @@ pub enum AssignOp {
// TODO: Add UShl (unsigned shift left).
}
unsafe impl Trace for AssignOp {
unsafe_empty_trace!();
}
impl Display for AssignOp {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(

36
boa/src/syntax/ast/pos.rs

@ -1,36 +0,0 @@
//! This module implements the `Pos` structure, which represents a position in the source code.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// A position in the Javascript source code.
///
/// Stores both the column number and the line number
///
/// ## Similar Implementations
/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216)
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Position {
// Column number
pub column_number: u64,
// Line number
pub line_number: u64,
}
impl Position {
/// Creates a new `Position`.
///
/// Positions are usually created by a [`Token`](struct.token/Token.html).
///
/// # Arguments
///
/// * `line_number` - The line number the token starts at
/// * `column_number` - The column number the token starts at
pub fn new(line_number: u64, column_number: u64) -> Self {
Self {
line_number,
column_number,
}
}
}

291
boa/src/syntax/ast/position.rs

@ -0,0 +1,291 @@
//! This module implements the `Pos` structure, which represents a position in the source code.
use std::{cmp::Ordering, fmt, num::NonZeroU32};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// A position in the JavaScript source code.
///
/// Stores both the column number and the line number
///
/// ## Similar Implementations
/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216)
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position {
/// Line number.
line_number: NonZeroU32,
/// Column number.
column_number: NonZeroU32,
}
impl Position {
/// Creates a new `Position`.
#[inline]
pub fn new(line_number: u32, column_number: u32) -> Self {
Self {
line_number: NonZeroU32::new(line_number).expect("line number cannot be 0"),
column_number: NonZeroU32::new(column_number).expect("column number cannot be 0"),
}
}
/// Gets the line number of the position.
#[inline]
pub fn line_number(self) -> u32 {
self.line_number.get()
}
/// Gets the column number of the position.
#[inline]
pub fn column_number(self) -> u32 {
self.column_number.get()
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line_number, self.column_number)
}
}
/// A span in the JavaScript source code.
///
/// Stores a start position and an end position.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
start: Position,
end: Position,
}
impl Span {
/// Creates a new `Span`.
#[inline]
pub fn new(start: Position, end: Position) -> Self {
assert!(start <= end, "a span cannot start after its end");
Self { start, end }
}
/// Gets the starting position of the span.
#[inline]
pub fn start(self) -> Position {
self.start
}
/// Gets the final position of the span.
#[inline]
pub fn end(self) -> Position {
self.end
}
/// Checks if this span inclusively contains another span or position.
#[inline]
pub fn contains<S>(self, other: S) -> bool
where
S: Into<Self>,
{
let other = other.into();
self.start <= other.start && self.end >= other.end
}
}
impl From<Position> for Span {
fn from(pos: Position) -> Self {
Self {
start: pos,
end: pos,
}
}
}
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
Some(Ordering::Equal)
} else if self.end < other.start {
Some(Ordering::Less)
} else if self.start > other.end {
Some(Ordering::Greater)
} else {
None
}
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}..{}]", self.start, self.end)
}
}
#[cfg(test)]
mod tests {
use super::{Position, Span};
/// Checks that we cannot create a position with 0 as the column.
#[test]
#[should_panic]
fn invalid_position_column() {
Position::new(10, 0);
}
/// Checks that we cannot create a position with 0 as the line.
#[test]
#[should_panic]
fn invalid_position_line() {
Position::new(0, 10);
}
/// Checks that the `PartialEq` implementation of `Position` is consistent.
#[test]
fn position_equality() {
assert_eq!(Position::new(10, 50), Position::new(10, 50));
assert_ne!(Position::new(10, 50), Position::new(10, 51));
assert_ne!(Position::new(10, 50), Position::new(11, 50));
assert_ne!(Position::new(10, 50), Position::new(11, 51));
}
/// Checks that the `PartialOrd` implementation of `Position` is consistent.
#[test]
fn position_order() {
assert!(Position::new(10, 50) < Position::new(10, 51));
assert!(Position::new(9, 50) < Position::new(10, 50));
assert!(Position::new(10, 50) < Position::new(11, 51));
assert!(Position::new(10, 50) < Position::new(11, 49));
assert!(Position::new(10, 51) > Position::new(10, 50));
assert!(Position::new(10, 50) > Position::new(9, 50));
assert!(Position::new(11, 51) > Position::new(10, 50));
assert!(Position::new(11, 49) > Position::new(10, 50));
}
/// Checks that the position getters actually retreive correct values.
#[test]
fn position_getters() {
let pos = Position::new(10, 50);
assert_eq!(pos.line_number(), 10);
assert_eq!(pos.column_number(), 50);
}
/// Checks that the string representation of a position is correct.
#[test]
fn position_to_string() {
let pos = Position::new(10, 50);
assert_eq!("10:50", pos.to_string());
assert_eq!("10:50", format!("{}", pos));
}
/// Checks that we cannot create an invalid span.
#[test]
#[should_panic]
fn invalid_span() {
let a = Position::new(10, 30);
let b = Position::new(10, 50);
Span::new(b, a);
}
/// Checks that we can create valid spans.
#[test]
fn span_creation() {
let a = Position::new(10, 30);
let b = Position::new(10, 50);
let _ = Span::new(a, b);
let _ = Span::new(a, a);
let _ = Span::from(a);
}
/// Checks that the `PartialEq` implementation of `Span` is consistent.
#[test]
fn span_equality() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let span_ab = Span::new(a, b);
let span_ab_2 = Span::new(a, b);
let span_ac = Span::new(a, c);
let span_bc = Span::new(b, c);
assert_eq!(span_ab, span_ab_2);
assert_ne!(span_ab, span_ac);
assert_ne!(span_ab, span_bc);
assert_ne!(span_bc, span_ac);
let span_a = Span::from(a);
let span_aa = Span::new(a, a);
assert_eq!(span_a, span_aa);
}
/// Checks that the getters retrieve the correct value.
#[test]
fn span_getters() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let span = Span::new(a, b);
assert_eq!(span.start(), a);
assert_eq!(span.end(), b);
}
/// Checks that the `Span::contains()` method works properly.
#[test]
fn span_contains() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let d = Position::new(12, 5);
let span_ac = Span::new(a, c);
assert!(span_ac.contains(b));
let span_ab = Span::new(a, b);
let span_cd = Span::new(c, d);
assert!(!span_ab.contains(span_cd));
assert!(span_ab.contains(b));
let span_ad = Span::new(a, d);
let span_bc = Span::new(b, c);
assert!(span_ad.contains(span_bc));
assert!(!span_bc.contains(span_ad));
let span_ac = Span::new(a, c);
let span_bd = Span::new(b, d);
assert!(!span_ac.contains(span_bd));
assert!(!span_bd.contains(span_ac));
}
/// Checks that the string representation of a span is correct.
#[test]
fn span_to_string() {
let a = Position::new(10, 50);
let b = Position::new(11, 20);
let span = Span::new(a, b);
assert_eq!("[10:50..11:20]", span.to_string());
assert_eq!("[10:50..11:20]", format!("{}", span));
}
/// Checks that the ordering of spans is correct.
#[test]
fn span_ordering() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let d = Position::new(12, 5);
let span_ab = Span::new(a, b);
let span_cd = Span::new(c, d);
assert!(span_ab < span_cd);
assert!(span_cd > span_ab);
}
}

0
boa/src/syntax/ast/punc.rs → boa/src/syntax/ast/punctuator.rs

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

@ -5,11 +5,18 @@
//!
//! [spec]: https://tc39.es/ecma262/#sec-tokens
use crate::syntax::ast::{bigint::BigInt, keyword::Keyword, pos::Position, punc::Punctuator};
use std::fmt::{Debug, Display, Formatter, Result};
use crate::syntax::{
ast::{bigint::BigInt, Keyword, Punctuator, Span},
lexer::LexerError,
};
use bitflags::bitflags;
use std::{
fmt::{self, Debug, Display, Formatter},
str::FromStr,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// This represents the smallest individual words, phrases, or characters that JavaScript can understand.
///
@ -21,38 +28,31 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
/// The token kind, which contains the actual data of the token.
pub kind: TokenKind,
/// The token position from origina source code.
pub pos: Position,
pub(crate) kind: TokenKind,
/// The token position in the original source code.
span: Span,
}
impl Token {
/// Create a new detailed token from the token data, line number and column number
pub fn new(kind: TokenKind, line_number: u64, column_number: u64) -> Self {
Self {
kind,
pos: Position::new(line_number, column_number),
}
pub fn new(kind: TokenKind, span: Span) -> Self {
Self { kind, span }
}
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.kind)
/// Gets the kind of the token.
pub fn kind(&self) -> &TokenKind {
&self.kind
}
}
/// A continuous sequence of tokens.
pub struct VecToken(Vec<Token>);
/// Gets the token span in the original source code.
pub fn span(&self) -> Span {
self.span
}
}
impl Debug for VecToken {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let mut buffer = String::new();
for token in &self.0 {
buffer.push_str(&token.to_string());
}
write!(f, "{}", buffer)
impl Display for Token {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)
}
}
@ -88,6 +88,125 @@ impl From<BigInt> for NumericLiteral {
}
}
bitflags! {
#[derive(Default)]
pub struct RegExpFlags: u8 {
const GLOBAL = 0b0000_0001;
const IGNORE_CASE = 0b0000_0010;
const MULTILINE = 0b0000_0100;
const DOT_ALL = 0b0000_1000;
const UNICODE = 0b0001_0000;
const STICKY = 0b0010_0000;
}
}
impl FromStr for RegExpFlags {
type Err = LexerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut flags = Self::default();
for c in s.bytes() {
let new_flag = match c {
b'g' => Self::GLOBAL,
b'i' => Self::IGNORE_CASE,
b'm' => Self::MULTILINE,
b's' => Self::DOT_ALL,
b'u' => Self::UNICODE,
b'y' => Self::STICKY,
_ => {
return Err(LexerError::new(format!(
"invalid regular expression flag {}",
char::from(c)
)))
}
};
if !flags.contains(new_flag) {
flags.insert(new_flag);
} else {
return Err(LexerError::new(format!(
"invalid regular expression flag {}",
char::from(c)
)));
}
}
Ok(flags)
}
}
impl Display for RegExpFlags {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use fmt::Write;
if self.contains(Self::GLOBAL) {
f.write_char('g')?;
}
if self.contains(Self::IGNORE_CASE) {
f.write_char('i')?;
}
if self.contains(Self::MULTILINE) {
f.write_char('m')?;
}
if self.contains(Self::DOT_ALL) {
f.write_char('s')?;
}
if self.contains(Self::UNICODE) {
f.write_char('u')?;
}
if self.contains(Self::STICKY) {
f.write_char('y')?;
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl Serialize for RegExpFlags {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for RegExpFlags {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{self, Visitor};
/// Deserializer visitor implementation for `RegExpFlags`.
#[derive(Debug, Clone, Copy)]
struct RegExpFlagsVisitor;
impl<'de> Visitor<'de> for RegExpFlagsVisitor {
type Value = RegExpFlags;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("a string representing JavaScript regular expression flags")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(E::custom)
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(&value)
}
}
deserializer.deserialize_str(RegExpFlagsVisitor)
}
}
/// Represents the type of Token and the data it has inside.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq, Debug)]
@ -99,7 +218,7 @@ pub enum TokenKind {
EOF,
/// An identifier.
Identifier(String),
Identifier(Box<str>),
/// A keyword.
///
@ -118,10 +237,10 @@ pub enum TokenKind {
Punctuator(Punctuator),
/// A string literal.
StringLiteral(String),
StringLiteral(Box<str>),
/// A regular expression, consisting of body and flags.
RegularExpressionLiteral(String, String),
RegularExpressionLiteral(Box<str>, RegExpFlags),
/// Indicates the end of a line (`\n`).
LineTerminator,
@ -159,7 +278,7 @@ impl TokenKind {
/// Creates an `Identifier` token type.
pub fn identifier<I>(ident: I) -> Self
where
I: Into<String>,
I: Into<Box<str>>,
{
Self::Identifier(ident.into())
}
@ -185,18 +304,17 @@ impl TokenKind {
/// Creates a `StringLiteral` token type.
pub fn string_literal<S>(lit: S) -> Self
where
S: Into<String>,
S: Into<Box<str>>,
{
Self::StringLiteral(lit.into())
}
/// Creates a `RegularExpressionLiteral` token kind.
pub fn regular_expression_literal<B, F>(body: B, flags: F) -> Self
pub fn regular_expression_literal<B>(body: B, flags: RegExpFlags) -> Self
where
B: Into<String>,
F: Into<String>,
B: Into<Box<str>>,
{
Self::RegularExpressionLiteral(body.into(), flags.into())
Self::RegularExpressionLiteral(body.into(), flags)
}
/// Creates a `LineTerminator` token kind.
@ -206,7 +324,7 @@ impl TokenKind {
}
impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::BooleanLiteral(ref val) => write!(f, "{}", val),
Self::EOF => write!(f, "end of file"),

267
boa/src/syntax/lexer/mod.rs

@ -8,8 +8,8 @@ mod tests;
use crate::syntax::ast::bigint::BigInt;
use crate::syntax::ast::{
punc::Punctuator,
token::{NumericLiteral, Token, TokenKind},
Position, Punctuator, Span,
};
use std::{
char::{decode_utf16, from_u32},
@ -23,38 +23,38 @@ use std::{
/// If the next value is not an assignment operation it will pattern match the provided values and return the corresponding token.
macro_rules! vop {
($this:ident, $assign_op:expr, $op:expr) => ({
let preview = $this.preview_next().ok_or_else(|| LexerError::new("Could not preview next value"))?;
let preview = $this.preview_next().ok_or_else(|| LexerError::new("could not preview next value"))?;
match preview {
'=' => {
$this.next();
$this.column_number += 1;
$this.next_column();
$assign_op
}
_ => $op,
}
});
($this:ident, $assign_op:expr, $op:expr, {$($case:pat => $block:expr), +}) => ({
let preview = $this.preview_next().ok_or_else(|| LexerError::new("Could not preview next value"))?;
let preview = $this.preview_next().ok_or_else(|| LexerError::new("could not preview next value"))?;
match preview {
'=' => {
$this.next();
$this.column_number += 1;
$this.next_column();
$assign_op
},
$($case => {
$this.next();
$this.column_number += 1;
$this.next_column();
$block
})+,
_ => $op
}
});
($this:ident, $op:expr, {$($case:pat => $block:expr),+}) => {
let preview = $this.preview_next().ok_or_else(|| LexerError::new("Could not preview next value"))?;
let preview = $this.preview_next().ok_or_else(|| LexerError::new("could not preview next value"))?;
match preview {
$($case => {
$this.next()?;
$this.column_number += 1;
$this.next_column();
$block
})+,
_ => $op
@ -64,17 +64,13 @@ macro_rules! vop {
/// The `op` macro handles binary operations or assignment operations and converts them into tokens.
macro_rules! op {
($this:ident, $assign_op:expr, $op:expr) => ({
($this:ident, $start_pos:expr, $assign_op:expr, $op:expr) => ({
let punc = vop!($this, $assign_op, $op);
$this.push_punc(punc);
$this.push_punc(punc, $start_pos);
});
($this:ident, $assign_op:expr, $op:expr, {$($case:pat => $block:expr),+}) => ({
($this:ident, $start_pos:expr, $assign_op:expr, $op:expr, {$($case:pat => $block:expr),+}) => ({
let punc = vop!($this, $assign_op, $op, {$($case => $block),+});
$this.push_punc(punc);
});
($this:ident, $op:expr, {$($case:pat => $block:expr),+}) => ({
let punc = vop!($this, $op, {$($case => $block),+});
$this.push_punc();
$this.push_punc(punc, $start_pos);
});
}
@ -91,9 +87,12 @@ impl LexerError {
/// Create a new LexerError struct
///
/// * `msg` - The message to show when LexerError is displayed
fn new(msg: &str) -> Self {
pub(crate) fn new<M>(msg: M) -> Self
where
M: Into<String>,
{
Self {
details: msg.to_string(),
details: msg.into(),
}
}
}
@ -122,10 +121,8 @@ pub struct Lexer<'a> {
///
/// This field is public so you can use them once lexing has finished.
pub tokens: Vec<Token>,
/// The current line number in the script
line_number: u64,
/// the current column number in the script
column_number: u64,
/// The current position in the source code.
position: Position,
/// The full Peekable buffer, an array of [Char]s
buffer: Peekable<Chars<'a>>,
}
@ -137,21 +134,62 @@ impl<'a> Lexer<'a> {
pub fn new(buffer: &'a str) -> Lexer<'a> {
Lexer {
tokens: Vec::new(),
line_number: 1,
column_number: 0,
position: Position::new(1, 1),
buffer: buffer.chars().peekable(),
}
}
/// Push a token onto the token queue.
fn push_token(&mut self, tk: TokenKind) {
self.tokens
.push(Token::new(tk, self.line_number, self.column_number))
fn push_token(&mut self, tk: TokenKind, start: Position) {
let end = if let TokenKind::LineTerminator = tk {
self.position
} else {
Position::new(
self.position.line_number(),
self.position.column_number() - 1,
)
};
self.tokens.push(Token::new(tk, Span::new(start, end)))
}
/// Push a punctuation token
fn push_punc(&mut self, punc: Punctuator) {
self.push_token(TokenKind::Punctuator(punc));
fn push_punc(&mut self, punc: Punctuator, start: Position) {
self.push_token(TokenKind::Punctuator(punc), start);
}
/// Changes the current position by advancing to the next column.
fn next_column(&mut self) {
let pos = Position::new(
self.position.line_number(),
self.position.column_number() + 1,
);
self.position = pos;
}
/// Changes the current position by advancing the given number of columns.
fn move_columns(&mut self, columns: u32) {
let pos = Position::new(
self.position.line_number(),
self.position.column_number() + columns,
);
self.position = pos;
}
fn carriage_return(&mut self) {
let pos = Position::new(self.position.line_number(), 1);
self.position = pos;
}
/// Changes the current position by advancing to the next line.
fn next_line(&mut self) {
let pos = Position::new(self.position.line_number() + 1, 1);
self.position = pos;
}
/// Changes the current position by advancing the given number of lines.
fn move_lines(&mut self, lines: u32) {
let pos = Position::new(self.position.line_number() + lines, 1);
self.position = pos;
}
/// next fetches the next token and return it, or a LexerError if there are no more.
@ -202,6 +240,7 @@ impl<'a> Lexer<'a> {
fn next_is(&mut self, peek: char) -> bool {
let result = self.preview_next() == Some(peek);
if result {
self.next_column();
self.buffer.next();
}
result
@ -235,29 +274,30 @@ impl<'a> Lexer<'a> {
/// This is a helper structure
///
/// This structure helps with identifying what numerical type it is and what base is it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NumericKind {
Rational,
Integer(u32),
BigInt(u32),
Integer(u8),
BigInt(u8),
}
impl NumericKind {
/// Get the base of the number kind.
fn base(&self) -> u32 {
fn base(self) -> u32 {
match self {
Self::Rational => 10,
Self::Integer(ref base) => *base,
Self::BigInt(ref base) => *base,
Self::Integer(base) => base as u32,
Self::BigInt(base) => base as u32,
}
}
/// Converts `self` to BigInt kind.
fn convert_to_bigint(&mut self) {
*self = match *self {
fn to_bigint(self) -> Self {
match self {
Self::Rational => unreachable!("can not convert rational number to BigInt"),
Self::Integer(base) => Self::BigInt(base),
Self::BigInt(base) => Self::BigInt(base),
};
}
}
}
@ -265,28 +305,31 @@ impl<'a> Lexer<'a> {
let strict_mode = false;
let mut buf = ch.to_string();
let mut position_offset = 0;
let mut kind = NumericKind::Integer(10);
let start_pos = self.position;
if ch == '0' {
match self.preview_next() {
None => {
self.push_token(TokenKind::NumericLiteral(NumericLiteral::Integer(0)));
self.column_number += 1;
self.next_column();
self.push_token(
TokenKind::NumericLiteral(NumericLiteral::Integer(0)),
start_pos,
);
return Ok(());
}
Some('x') | Some('X') => {
self.next();
position_offset += 1;
self.next_column();
kind = NumericKind::Integer(16);
}
Some('o') | Some('O') => {
self.next();
position_offset += 1;
self.next_column();
kind = NumericKind::Integer(8);
}
Some('b') | Some('B') => {
self.next();
position_offset += 1;
self.next_column();
kind = NumericKind::Integer(2);
}
Some(ch) if ch.is_ascii_digit() => {
@ -327,7 +370,7 @@ impl<'a> Lexer<'a> {
}
if self.next_is('n') {
kind.convert_to_bigint()
kind = kind.to_bigint();
}
if let NumericKind::Integer(10) = kind {
@ -391,14 +434,12 @@ impl<'a> Lexer<'a> {
}
}
if let Err(e) = self.check_after_numeric_literal() {
return Err(e);
};
self.check_after_numeric_literal()?;
let num = match kind {
NumericKind::BigInt(base) => {
NumericLiteral::BigInt(
BigInt::from_str_radix(&buf, base).expect("Could not conver to BigInt")
BigInt::from_str_radix(&buf, base as u32).expect("Could not conver to BigInt")
)
}
NumericKind::Rational /* base: 10 */ => {
@ -408,7 +449,7 @@ impl<'a> Lexer<'a> {
)
}
NumericKind::Integer(base) => {
if let Ok(num) = i32::from_str_radix(&buf, base) {
if let Ok(num) = i32::from_str_radix(&buf, base as u32) {
NumericLiteral::Integer(
num
)
@ -416,7 +457,7 @@ impl<'a> Lexer<'a> {
let b = f64::from(base);
let mut result = 0.0_f64;
for c in buf.chars() {
let digit = f64::from(c.to_digit(base).unwrap());
let digit = f64::from(c.to_digit(base as u32).unwrap());
result = result * b + digit;
}
@ -426,8 +467,8 @@ impl<'a> Lexer<'a> {
}
};
self.push_token(TokenKind::NumericLiteral(num));
self.column_number += (buf.len() as u64) + position_offset - 1;
self.move_columns(buf.len() as u32);
self.push_token(TokenKind::NumericLiteral(num), start_pos);
Ok(())
}
@ -450,7 +491,8 @@ impl<'a> Lexer<'a> {
if self.preview_next().is_none() {
return Ok(());
}
self.column_number += 1;
let start_pos = self.position;
self.next_column();
let ch = self.next();
match ch {
'"' | '\'' => {
@ -470,6 +512,7 @@ impl<'a> Lexer<'a> {
if self.preview_next().is_none() {
return Err(LexerError::new("Unterminated String"));
}
let escape_pos = self.position;
let escape = self.next();
if escape != '\n' {
let escaped_ch = match escape {
@ -487,7 +530,7 @@ impl<'a> Lexer<'a> {
}
nums.push(self.next());
}
self.column_number += 2;
self.move_columns(2);
let as_num = match u64::from_str_radix(&nums, 16) {
Ok(v) => v,
Err(_) => 0,
@ -495,8 +538,8 @@ impl<'a> Lexer<'a> {
match from_u32(as_num as u32) {
Some(v) => v,
None => panic!(
"{}:{}: {} is not a valid unicode scalar value",
self.line_number, self.column_number, as_num
"{}: {} is not a valid unicode scalar value",
self.position, as_num
),
}
}
@ -523,8 +566,7 @@ impl<'a> Lexer<'a> {
return Err(LexerError::new("Unterminated String"));
}
self.next(); // '}'
self.column_number +=
(s.len() as u64).wrapping_add(3);
self.move_columns(s.len() as u32);
c
} else {
let mut codepoints: Vec<u16> = vec![];
@ -541,8 +583,7 @@ impl<'a> Lexer<'a> {
};
codepoints.push(as_num);
self.column_number +=
(s.len() as u64).wrapping_add(2);
self.move_columns(s.len() as u32);
// Check for another UTF-16 codepoint
if self.next_is('\\') && self.next_is('u') {
@ -561,7 +602,7 @@ impl<'a> Lexer<'a> {
}
'\'' | '"' | '\\' => escape,
ch => {
let details = format!("{}:{}: Invalid escape `{}`", self.line_number, self.column_number, ch);
let details = format!("invalid escape sequence `{}` at line {}, column {}", escape_pos.line_number(), escape_pos.column_number(), ch);
return Err(LexerError { details });
}
};
@ -571,12 +612,12 @@ impl<'a> Lexer<'a> {
next_ch => buf.push(next_ch),
}
}
let str_length = buf.len() as u64;
self.push_token(TokenKind::StringLiteral(buf));
let str_length = buf.len() as u32;
// Why +1? Quotation marks are not included,
// So technically it would be +2, (for both " ") but we want to be 1 less
// to compensate for the incrementing at the top
self.column_number += str_length.wrapping_add(1);
self.move_columns( str_length.wrapping_add(1));
self.push_token(TokenKind::string_literal(buf), start_pos);
}
_ if ch.is_digit(10) => self.reed_numerical_literal(ch)?,
_ if ch.is_alphabetic() || ch == '$' || ch == '_' => {
@ -588,8 +629,7 @@ impl<'a> Lexer<'a> {
break;
}
}
self.push_token(match buf.as_str() {
let tk = match buf.as_str() {
"true" => TokenKind::BooleanLiteral(true),
"false" => TokenKind::BooleanLiteral(false),
"null" => TokenKind::NullLiteral,
@ -601,33 +641,35 @@ impl<'a> Lexer<'a> {
TokenKind::identifier(slice)
}
}
});
// Move position forward the length of keyword
self.column_number += (buf.len().wrapping_sub(1)) as u64;
};
// Move position forward the length of the token
self.move_columns( (buf.len().wrapping_sub(1)) as u32);
self.push_token(tk, start_pos);
}
';' => self.push_punc(Punctuator::Semicolon),
':' => self.push_punc(Punctuator::Colon),
';' => self.push_punc(Punctuator::Semicolon, start_pos),
':' => self.push_punc(Punctuator::Colon, start_pos),
'.' => {
// . or ...
if self.next_is('.') {
if self.next_is('.') {
self.push_punc(Punctuator::Spread);
self.column_number += 2;
self.push_punc(Punctuator::Spread, start_pos);
} else {
return Err(LexerError::new("Expecting Token ."));
}
} else {
self.push_punc(Punctuator::Dot);
self.push_punc(Punctuator::Dot, start_pos);
};
}
'(' => self.push_punc(Punctuator::OpenParen),
')' => self.push_punc(Punctuator::CloseParen),
',' => self.push_punc(Punctuator::Comma),
'{' => self.push_punc(Punctuator::OpenBlock),
'}' => self.push_punc(Punctuator::CloseBlock),
'[' => self.push_punc(Punctuator::OpenBracket),
']' => self.push_punc(Punctuator::CloseBracket),
'?' => self.push_punc(Punctuator::Question),
'(' => self.push_punc(Punctuator::OpenParen, start_pos),
')' => self.push_punc(Punctuator::CloseParen, start_pos),
',' => self.push_punc(Punctuator::Comma, start_pos),
'{' => self.push_punc(Punctuator::OpenBlock, start_pos),
'}' => self.push_punc(Punctuator::CloseBlock, start_pos),
'[' => self.push_punc(Punctuator::OpenBracket, start_pos),
']' => self.push_punc(Punctuator::CloseBracket, start_pos),
'?' => self.push_punc(Punctuator::Question, start_pos),
// Comments
'/' => {
if let Some(ch) = self.preview_next() {
@ -639,15 +681,14 @@ impl<'a> Lexer<'a> {
break;
}
}
self.line_number += 1;
self.column_number = 0;
self.next_line()
}
// block comment
'*' => {
let mut lines = 0;
loop {
if self.preview_next().is_none() {
return Err(LexerError::new("Unterminated Multiline Comment"));
return Err(LexerError::new("unterminated multiline comment"));
}
match self.next() {
'*' => {
@ -662,19 +703,19 @@ impl<'a> Lexer<'a> {
},
}
}
self.line_number += lines;
self.column_number = 0;
self.move_lines(lines);
}
// division, assigndiv or regex literal
_ => {
// if we fail to parse a regex literal, store a copy of the current
// buffer to restore later on
let original_buffer = self.buffer.clone();
let original_pos = self.position;
// first, try to parse a regex literal
let mut body = String::new();
let mut regex = false;
loop {
self.column_number +=1;
self.next_column();
match self.buffer.next() {
// end of body
Some('/') => {
@ -684,14 +725,14 @@ impl<'a> Lexer<'a> {
// newline/eof not allowed in regex literal
n @ Some('\n') | n @ Some('\r') | n @ Some('\u{2028}')
| n @ Some('\u{2029}') => {
self.column_number = 0;
self.carriage_return();
if n != Some('\r') {
self.line_number += 1;
self.next_line();
}
break
},
None => {
self.column_number -= 1;
self.position = Position::new(self.position.line_number(), self.position.column_number()-1);
break
}
// escape sequence
@ -712,19 +753,21 @@ impl<'a> Lexer<'a> {
if regex {
// body was parsed, now look for flags
let flags = self.take_char_while(char::is_alphabetic)?;
self.push_token(TokenKind::RegularExpressionLiteral(
body, flags,
));
self.move_columns(body.len() as u32 + 1 + flags.len() as u32);
self.push_token(TokenKind::regular_expression_literal(
body, flags.parse()?,
), start_pos);
} else {
// failed to parse regex, restore original buffer position and
// parse either div or assigndiv
self.buffer = original_buffer;
self.position = original_pos;
if self.next_is('=') {
self.push_token(TokenKind::Punctuator(
Punctuator::AssignDiv,
));
), start_pos);
} else {
self.push_token(TokenKind::Punctuator(Punctuator::Div));
self.push_token(TokenKind::Punctuator(Punctuator::Div), start_pos);
}
}
}
@ -733,26 +776,26 @@ impl<'a> Lexer<'a> {
return Err(LexerError::new("Expecting Token /,*,= or regex"));
}
}
'*' => op!(self, Punctuator::AssignMul, Punctuator::Mul, {
'*' => op!(self, start_pos, Punctuator::AssignMul, Punctuator::Mul, {
'*' => vop!(self, Punctuator::AssignPow, Punctuator::Exp)
}),
'+' => op!(self, Punctuator::AssignAdd, Punctuator::Add, {
'+' => op!(self, start_pos, Punctuator::AssignAdd, Punctuator::Add, {
'+' => Punctuator::Inc
}),
'-' => op!(self, Punctuator::AssignSub, Punctuator::Sub, {
'-' => op!(self, start_pos, Punctuator::AssignSub, Punctuator::Sub, {
'-' => {
Punctuator::Dec
}
}),
'%' => op!(self, Punctuator::AssignMod, Punctuator::Mod),
'|' => op!(self, Punctuator::AssignOr, Punctuator::Or, {
'%' => op!(self, start_pos, Punctuator::AssignMod, Punctuator::Mod),
'|' => op!(self, start_pos, Punctuator::AssignOr, Punctuator::Or, {
'|' => Punctuator::BoolOr
}),
'&' => op!(self, Punctuator::AssignAnd, Punctuator::And, {
'&' => op!(self, start_pos, Punctuator::AssignAnd, Punctuator::And, {
'&' => Punctuator::BoolAnd
}),
'^' => op!(self, Punctuator::AssignXor, Punctuator::Xor),
'=' => op!(self, if self.next_is('=') {
'^' => op!(self, start_pos, Punctuator::AssignXor, Punctuator::Xor),
'=' => op!(self, start_pos, if self.next_is('=') {
Punctuator::StrictEq
} else {
Punctuator::Eq
@ -761,27 +804,27 @@ impl<'a> Lexer<'a> {
Punctuator::Arrow
}
}),
'<' => op!(self, Punctuator::LessThanOrEq, Punctuator::LessThan, {
'<' => op!(self, start_pos, Punctuator::LessThanOrEq, Punctuator::LessThan, {
'<' => vop!(self, Punctuator::AssignLeftSh, Punctuator::LeftSh)
}),
'>' => op!(self, Punctuator::GreaterThanOrEq, Punctuator::GreaterThan, {
'>' => op!(self, start_pos, Punctuator::GreaterThanOrEq, Punctuator::GreaterThan, {
'>' => vop!(self, Punctuator::AssignRightSh, Punctuator::RightSh, {
'>' => vop!(self, Punctuator::AssignURightSh, Punctuator::URightSh)
})
}),
'!' => op!(
self,
start_pos,
vop!(self, Punctuator::StrictNotEq, Punctuator::NotEq),
Punctuator::Not
),
'~' => self.push_punc(Punctuator::Neg),
'~' => self.push_punc(Punctuator::Neg, start_pos),
'\n' | '\u{2028}' | '\u{2029}' => {
self.push_token(TokenKind::LineTerminator);
self.line_number += 1;
self.column_number = 0;
self.next_line();
self.push_token(TokenKind::LineTerminator, start_pos);
}
'\r' => {
self.column_number = 0;
self.carriage_return();
}
// The rust char::is_whitespace function and the ecma standard use different sets
// of characters as whitespaces:
@ -793,7 +836,7 @@ impl<'a> Lexer<'a> {
// Unicode Space_Seperator category (minus \u{0020} and \u{00A0} which are allready stated above)
'\u{1680}' | '\u{2000}'..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' => (),
_ => {
let details = format!("{}:{}: Unexpected '{}'", self.line_number, self.column_number, ch);
let details = format!("Unexpected '{}' at line {}, column {}", start_pos.line_number(), start_pos.column_number(), ch);
return Err(LexerError { details });
},
}

72
boa/src/syntax/lexer/tests.rs

@ -2,7 +2,11 @@
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::syntax::ast::keyword::Keyword;
use crate::syntax::ast::Keyword;
fn span(start: (u32, u32), end: (u32, u32)) -> Span {
Span::new(Position::new(start.0, start.1), Position::new(end.0, end.1))
}
#[test]
fn check_single_line_comment() {
@ -294,31 +298,30 @@ fn check_variable_definition_tokens() {
#[test]
fn check_positions() {
let s = "console.log(\"hello world\"); // Test";
// ------123456789
let s = r#"console.log("hello world\u{2764}"); // Test"#;
// --------123456789
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
// The first column is 1 (not zero indexed)
assert_eq!(lexer.tokens[0].pos.column_number, 1);
assert_eq!(lexer.tokens[0].pos.line_number, 1);
assert_eq!(lexer.tokens[0].span(), span((1, 1), (1, 7)));
// Dot Token starts on column 8
assert_eq!(lexer.tokens[1].pos.column_number, 8);
assert_eq!(lexer.tokens[1].pos.line_number, 1);
assert_eq!(lexer.tokens[1].span(), span((1, 8), (1, 8)));
// Log Token starts on column 9
assert_eq!(lexer.tokens[2].pos.column_number, 9);
assert_eq!(lexer.tokens[2].pos.line_number, 1);
assert_eq!(lexer.tokens[2].span(), span((1, 9), (1, 11)));
// Open parenthesis token starts on column 12
assert_eq!(lexer.tokens[3].pos.column_number, 12);
assert_eq!(lexer.tokens[3].pos.line_number, 1);
assert_eq!(lexer.tokens[3].span(), span((1, 12), (1, 12)));
// String token starts on column 13
assert_eq!(lexer.tokens[4].pos.column_number, 13);
assert_eq!(lexer.tokens[4].pos.line_number, 1);
// Close parenthesis token starts on column 26
assert_eq!(lexer.tokens[5].pos.column_number, 26);
assert_eq!(lexer.tokens[5].pos.line_number, 1);
// Semi Colon token starts on column 27
assert_eq!(lexer.tokens[6].pos.column_number, 27);
assert_eq!(lexer.tokens[6].pos.line_number, 1);
assert_eq!(lexer.tokens[4].span(), span((1, 13), (1, 33)));
// Close parenthesis token starts on column 34
assert_eq!(lexer.tokens[5].span(), span((1, 34), (1, 34)));
// Semi Colon token starts on column 35
assert_eq!(lexer.tokens[6].span(), span((1, 35), (1, 35)));
}
#[test]
@ -329,8 +332,7 @@ fn two_divisions_in_expression() {
lexer.lex().expect("failed to lex");
// dbg!(&lexer.tokens);
assert_eq!(lexer.tokens[11].pos.column_number, 37);
assert_eq!(lexer.tokens[11].pos.line_number, 1);
assert_eq!(lexer.tokens[11].span(), span((1, 37), (1, 37)));
}
#[test]
@ -340,17 +342,10 @@ fn check_line_numbers() {
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].pos.column_number, 1);
assert_eq!(lexer.tokens[0].pos.line_number, 1);
assert_eq!(lexer.tokens[1].pos.column_number, 2);
assert_eq!(lexer.tokens[1].pos.line_number, 1);
assert_eq!(lexer.tokens[2].pos.column_number, 1);
assert_eq!(lexer.tokens[2].pos.line_number, 2);
assert_eq!(lexer.tokens[3].pos.column_number, 2);
assert_eq!(lexer.tokens[3].pos.line_number, 2);
assert_eq!(lexer.tokens[0].span(), span((1, 1), (1, 1)));
assert_eq!(lexer.tokens[1].span(), span((1, 2), (2, 1)));
assert_eq!(lexer.tokens[2].span(), span((2, 1), (2, 1)));
assert_eq!(lexer.tokens[3].span(), span((2, 2), (3, 1)));
}
// Increment/Decrement
@ -376,9 +371,9 @@ fn check_nan() {
match lexer.tokens[3].kind {
TokenKind::NumericLiteral(NumericLiteral::Rational(a)) => {
assert_eq!(a.is_nan(), true);
assert!(a.is_nan());
}
_ => assert!(false),
ref other => panic!("Incorrect token kind found for NaN: {}", other),
}
}
@ -428,10 +423,7 @@ fn hexadecimal_edge_case() {
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].kind, TokenKind::numeric_literal(0xffff));
assert_eq!(lexer.tokens[1].kind, TokenKind::Punctuator(Punctuator::Dot));
assert_eq!(
lexer.tokens[2].kind,
TokenKind::Identifier(String::from("ff"))
);
assert_eq!(lexer.tokens[2].kind, TokenKind::identifier("ff"));
assert_eq!(
lexer.tokens[3].kind,
@ -459,7 +451,7 @@ fn regex_literal() {
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].kind,
TokenKind::regular_expression_literal("(?:)", "")
TokenKind::regular_expression_literal("(?:)", "".parse().unwrap())
);
}
@ -469,7 +461,7 @@ fn regex_literal_flags() {
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].kind,
TokenKind::regular_expression_literal("\\/[^\\/]*\\/*", "gmi")
TokenKind::regular_expression_literal("\\/[^\\/]*\\/*", "gmi".parse().unwrap())
);
}

22
boa/src/syntax/parser/cursor.rs

@ -2,8 +2,8 @@
use super::ParseError;
use crate::syntax::ast::{
punc::Punctuator,
token::{Token, TokenKind},
Punctuator,
};
/// Token cursor.
@ -119,7 +119,7 @@ impl<'a> Cursor<'a> {
/// 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>
pub(super) fn expect<K>(&mut self, kind: K, context: &'static str) -> Result<(), ParseError>
where
K: Into<TokenKind>,
{
@ -129,10 +129,10 @@ impl<'a> Cursor<'a> {
if next_token.kind == kind {
Ok(())
} else {
Err(ParseError::Expected(
Err(ParseError::expected(
vec![kind],
next_token.clone(),
routine,
context,
))
}
}
@ -180,7 +180,7 @@ impl<'a> Cursor<'a> {
pub(super) fn expect_semicolon(
&mut self,
do_while: bool,
routine: &'static str,
context: &'static str,
) -> Result<(), ParseError> {
match self.peek_semicolon(do_while) {
(true, Some(tk)) => match tk.kind {
@ -191,10 +191,10 @@ impl<'a> Cursor<'a> {
_ => Ok(()),
},
(true, None) => Ok(()),
(false, Some(tk)) => Err(ParseError::Expected(
(false, Some(tk)) => Err(ParseError::expected(
vec![TokenKind::Punctuator(Punctuator::Semicolon)],
tk.clone(),
routine,
context,
)),
(false, None) => unreachable!(),
}
@ -203,11 +203,7 @@ impl<'a> Cursor<'a> {
/// 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> {
pub(super) fn peek_expect_no_lineterminator(&mut self, skip: usize) -> Result<(), ParseError> {
let mut count = 0;
let mut skipped = 0;
loop {
@ -215,7 +211,7 @@ impl<'a> Cursor<'a> {
count += 1;
if let Some(tk) = token {
if skipped == skip && tk.kind == TokenKind::LineTerminator {
break Err(ParseError::Unexpected(tk.clone(), Some(routine)));
break Err(ParseError::unexpected(tk.clone(), None));
} else if skipped == skip && tk.kind != TokenKind::LineTerminator {
break Ok(());
} else if tk.kind != TokenKind::LineTerminator {

128
boa/src/syntax/parser/error.rs

@ -1,40 +1,96 @@
//! Error and result implementation for the parser.
use crate::syntax::ast::{
keyword::Keyword,
node::Node,
pos::Position,
position::Position,
token::{Token, TokenKind},
Node,
};
use std::fmt;
/// Result of a parsing operation.
pub type ParseResult = Result<Node, ParseError>;
pub(crate) trait ErrorContext {
fn context(self, context: &'static str) -> Self;
}
impl<T> ErrorContext for Result<T, ParseError> {
fn context(self, context: &'static str) -> Self {
self.map_err(|e| e.context(context))
}
}
/// `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),
Expected {
expected: Box<[TokenKind]>,
found: Token,
context: &'static str,
},
/// When a token is unexpected
Unexpected(Token, Option<&'static str>),
Unexpected {
found: Token,
message: 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>),
General {
message: &'static str,
position: Position,
},
}
impl ParseError {
/// Changes the context of the error, if any.
fn context(self, new_context: &'static str) -> Self {
match self {
Self::Expected {
expected, found, ..
} => Self::expected(expected, found, new_context),
e => e,
}
}
/// Creates an `Expected` parsing error.
pub(super) fn expected<E>(expected: E, found: Token, context: &'static str) -> Self
where
E: Into<Box<[TokenKind]>>,
{
Self::Expected {
expected: expected.into(),
found,
context,
}
}
/// Creates an `Expected` parsing error.
pub(super) fn unexpected<C>(found: Token, message: C) -> Self
where
C: Into<Option<&'static str>>,
{
Self::Unexpected {
found,
message: message.into(),
}
}
pub(super) fn general(message: &'static str, position: Position) -> Self {
Self::General { message, position }
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Expected(expected, actual, routine) => write!(
Self::Expected {
expected,
found,
context,
} => write!(
f,
"Expected {}, got '{}' in {} at line {}, col {}",
"expected {}, got '{}' in {} at line {}, col {}",
if expected.len() == 1 {
format!(
"token '{}'",
@ -62,45 +118,31 @@ impl fmt::Display for ParseError {
.collect::<String>()
)
},
actual,
routine,
actual.pos.line_number,
actual.pos.column_number
found,
context,
found.span().start().line_number(),
found.span().start().column_number()
),
Self::ExpectedExpr(expected, actual, pos) => write!(
Self::Unexpected { found, message } => 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 {
"unexpected token '{}'{} at line {}, col {}",
found,
if let Some(m) = message {
format!(", {}", m)
} else {
String::new()
},
tok.pos.line_number,
tok.pos.column_number
found.span().start().line_number(),
found.span().start().column_number()
),
Self::AbruptEnd => write!(f, "Abrupt End"),
Self::General(msg, pos) => write!(
Self::General { message, position } => write!(
f,
"{}{}",
msg,
if let Some(pos) = pos {
format!(" at line {}, col {}", pos.line_number, pos.column_number)
} else {
String::new()
}
"{} at line {}, col {}",
message,
position.line_number(),
position.column_number()
),
Self::RangeError => write!(f, "RangeError!"),
}
}
}

42
boa/src/syntax/parser/expression/assignment/arrow_function.rs

@ -10,14 +10,14 @@
use super::AssignmentExpression;
use crate::syntax::{
ast::{
node::{FormalParameter, Node},
punc::Punctuator,
token::TokenKind,
node::{ArrowFunctionDecl, FormalParameter, Node, StatementList},
Punctuator, TokenKind,
},
parser::{
error::{ErrorContext, ParseError, ParseResult},
function::{FormalParameters, FunctionBody},
statement::BindingIdentifier,
AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
AllowAwait, AllowIn, AllowYield, Cursor, TokenParser,
},
};
@ -57,40 +57,30 @@ impl ArrowFunction {
}
impl TokenParser for ArrowFunction {
type Output = Node;
type Output = ArrowFunctionDecl;
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
let next_token = cursor.peek(0).ok_or(ParseError::AbruptEnd)?;
let params = if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind {
// CoverParenthesizedExpressionAndArrowParameterList
cursor.expect(Punctuator::OpenParen, "arrow function")?;
let params = FormalParameters::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::CloseParen, "arrow function")?;
params.into_boxed_slice()
params
} else {
let param = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor)
.map_err(|e| match e {
ParseError::Expected(mut exp, tok, _) => {
exp.push(Punctuator::OpenParen.into());
ParseError::Expected(exp, tok, "arrow function")
}
e => e,
})?;
Box::new([FormalParameter {
init: None,
name: param,
is_rest_param: false,
}])
.context("arrow function")?;
Box::new([FormalParameter::new(param, None, false)])
};
cursor.peek_expect_no_lineterminator(0, "arrow function")?;
cursor.peek_expect_no_lineterminator(0)?;
cursor.expect(Punctuator::Arrow, "arrow function")?;
let body = ConciseBody::new(self.allow_in).parse(cursor)?;
Ok(Node::arrow_function_decl(params, body))
Ok(ArrowFunctionDecl::new(params, body))
}
}
@ -113,21 +103,19 @@ impl ConciseBody {
}
impl TokenParser for ConciseBody {
type Output = Node;
type Output = StatementList;
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::statement_list)?;
let body = FunctionBody::new(false, false).parse(cursor)?;
cursor.expect(Punctuator::CloseBlock, "arrow function")?;
Ok(body)
}
_ => Ok(Node::return_node(
_ => Ok(StatementList::from(vec![Node::return_node(
ExpressionBody::new(self.allow_in, false).parse(cursor)?,
)),
)])),
}
}
}

2
boa/src/syntax/parser/expression/assignment/conditional.rs

@ -8,7 +8,7 @@
//! [spec]: https://tc39.es/ecma262/#sec-conditional-operator
use crate::syntax::{
ast::{node::Node, punc::Punctuator, token::TokenKind},
ast::{Node, Punctuator, TokenKind},
parser::{
expression::{AssignmentExpression, LogicalORExpression},
AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser,

14
boa/src/syntax/parser/expression/assignment/exponentiation.rs

@ -9,11 +9,9 @@
use crate::syntax::{
ast::{
keyword::Keyword,
node::Node,
op::{BinOp, NumOp},
punc::Punctuator,
token::TokenKind,
node::{BinOp, Node},
op::NumOp,
Keyword, Punctuator, TokenKind,
},
parser::{
expression::{unary::UnaryExpression, update::UpdateExpression},
@ -80,11 +78,7 @@ impl TokenParser for ExponentiationExpression {
let lhs = UpdateExpression::new(self.allow_yield, self.allow_await).parse(cursor)?;
if let Some(tok) = cursor.next() {
if let TokenKind::Punctuator(Punctuator::Exp) = tok.kind {
return Ok(Node::bin_op(
BinOp::Num(NumOp::Exp),
lhs,
self.parse(cursor)?,
));
return Ok(BinOp::new(NumOp::Exp, lhs, self.parse(cursor)?).into());
} else {
cursor.back();
}

18
boa/src/syntax/parser/expression/assignment/mod.rs

@ -13,7 +13,10 @@ mod exponentiation;
use self::{arrow_function::ArrowFunction, conditional::ConditionalExpression};
use crate::syntax::{
ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind},
ast::{
node::{Assign, BinOp, Node},
Keyword, Punctuator, TokenKind,
},
parser::{AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser},
};
pub(super) use exponentiation::ExponentiationExpression;
@ -74,9 +77,7 @@ impl TokenParser for AssignmentExpression {
TokenKind::Identifier(_)
| TokenKind::Keyword(Keyword::Yield)
| TokenKind::Keyword(Keyword::Await)
if cursor
.peek_expect_no_lineterminator(1, "arrow function")
.is_ok() =>
if cursor.peek_expect_no_lineterminator(1).is_ok() =>
{
if let Some(tok) = cursor.peek(1) {
if tok.kind == TokenKind::Punctuator(Punctuator::Arrow) {
@ -85,7 +86,8 @@ impl TokenParser for AssignmentExpression {
self.allow_yield,
self.allow_await,
)
.parse(cursor);
.parse(cursor)
.map(Node::ArrowFunctionDecl);
}
}
}
@ -94,6 +96,7 @@ impl TokenParser for AssignmentExpression {
if let Some(node) =
ArrowFunction::new(self.allow_in, self.allow_yield, self.allow_await)
.try_parse(cursor)
.map(Node::ArrowFunctionDecl)
{
return Ok(node);
}
@ -103,17 +106,16 @@ impl TokenParser for AssignmentExpression {
let mut lhs = ConditionalExpression::new(self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor)?;
// let mut lhs = self.read_block()?;
if let Some(tok) = cursor.next() {
match tok.kind {
TokenKind::Punctuator(Punctuator::Assign) => {
lhs = Node::assign(lhs, self.parse(cursor)?)
lhs = Assign::new(lhs, self.parse(cursor)?).into();
}
TokenKind::Punctuator(p) if p.as_binop().is_some() => {
let expr = self.parse(cursor)?;
let binop = p.as_binop().expect("binop disappeared");
lhs = Node::bin_op(binop, lhs, expr);
lhs = BinOp::new(binop, lhs, expr).into();
}
_ => {
cursor.back();

12
boa/src/syntax/parser/expression/left_hand_side/arguments.rs

@ -8,7 +8,7 @@
//! [spec]: https://tc39.es/ecma262/#prod-Arguments
use crate::syntax::{
ast::{node::Node, punc::Punctuator, token::TokenKind},
ast::{Node, Punctuator, TokenKind},
parser::{
expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser,
},
@ -43,9 +43,9 @@ impl Arguments {
}
impl TokenParser for Arguments {
type Output = Vec<Node>;
type Output = Box<[Node]>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Vec<Node>, ParseError> {
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
cursor.expect(Punctuator::OpenParen, "arguments")?;
let mut args = Vec::new();
loop {
@ -54,7 +54,7 @@ impl TokenParser for Arguments {
TokenKind::Punctuator(Punctuator::CloseParen) => break,
TokenKind::Punctuator(Punctuator::Comma) => {
if args.is_empty() {
return Err(ParseError::Unexpected(next_token.clone(), None));
return Err(ParseError::unexpected(next_token.clone(), None));
}
if cursor.next_if(Punctuator::CloseParen).is_some() {
@ -63,7 +63,7 @@ impl TokenParser for Arguments {
}
_ => {
if !args.is_empty() {
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![
TokenKind::Punctuator(Punctuator::Comma),
TokenKind::Punctuator(Punctuator::CloseParen),
@ -89,6 +89,6 @@ impl TokenParser for Arguments {
);
}
}
Ok(args)
Ok(args.into_boxed_slice())
}
}

15
boa/src/syntax/parser/expression/left_hand_side/call.rs

@ -9,7 +9,10 @@
use super::arguments::Arguments;
use crate::syntax::{
ast::{node::Node, punc::Punctuator, token::TokenKind},
ast::{
node::{Call, Node},
Punctuator, TokenKind,
},
parser::{
expression::Expression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult,
TokenParser,
@ -51,11 +54,11 @@ impl TokenParser for CallExpression {
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)
Node::from(Call::new(self.first_member_expr, args))
}
_ => {
let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?;
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![TokenKind::Punctuator(Punctuator::OpenParen)],
next_token.clone(),
"call expression",
@ -67,19 +70,19 @@ impl TokenParser for CallExpression {
match tok.kind {
TokenKind::Punctuator(Punctuator::OpenParen) => {
let args = Arguments::new(self.allow_yield, self.allow_await).parse(cursor)?;
lhs = Node::call(lhs, args);
lhs = Node::from(Call::new(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);
lhs = Node::get_const_field(lhs, name.clone());
}
TokenKind::Keyword(kw) => {
lhs = Node::get_const_field(lhs, kw.to_string());
}
_ => {
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![TokenKind::identifier("identifier")],
tok.clone(),
"call expression",

15
boa/src/syntax/parser/expression/left_hand_side/member.rs

@ -7,7 +7,10 @@
use super::arguments::Arguments;
use crate::syntax::{
ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind},
ast::{
node::{Call, New, Node},
Keyword, Punctuator, TokenKind,
},
parser::{
expression::{primary::PrimaryExpression, Expression},
AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
@ -50,9 +53,9 @@ impl TokenParser for MemberExpression {
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);
let call_node = Call::new(lhs, args);
Node::new(call_node)
Node::from(New::from(call_node))
} else {
PrimaryExpression::new(self.allow_yield, self.allow_await).parse(cursor)?
};
@ -61,10 +64,12 @@ impl TokenParser for MemberExpression {
TokenKind::Punctuator(Punctuator::Dot) => {
let _ = cursor.next().ok_or(ParseError::AbruptEnd)?; // We move the cursor forward.
match &cursor.next().ok_or(ParseError::AbruptEnd)?.kind {
TokenKind::Identifier(name) => lhs = Node::get_const_field(lhs, name),
TokenKind::Identifier(name) => {
lhs = Node::get_const_field(lhs, name.clone())
}
TokenKind::Keyword(kw) => lhs = Node::get_const_field(lhs, kw.to_string()),
_ => {
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![TokenKind::identifier("identifier")],
tok.clone(),
"member expression",

2
boa/src/syntax/parser/expression/left_hand_side/mod.rs

@ -13,7 +13,7 @@ mod member;
use self::{call::CallExpression, member::MemberExpression};
use crate::syntax::{
ast::{node::Node, punc::Punctuator, token::TokenKind},
ast::{Node, Punctuator, TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser},
};

13
boa/src/syntax/parser/expression/mod.rs

@ -18,7 +18,10 @@ mod update;
use self::assignment::ExponentiationExpression;
pub(super) use self::{assignment::AssignmentExpression, primary::Initializer};
use super::{AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser};
use crate::syntax::ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind};
use crate::syntax::ast::{
node::{BinOp, Node},
Keyword, Punctuator, TokenKind,
};
// For use in the expression! macro to allow for both Punctuator and Keyword parameters.
// Always returns false.
@ -53,19 +56,19 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo
match tok.kind {
TokenKind::Punctuator(op) if $( op == $op )||* => {
let _ = cursor.next().expect("token disappeared");
lhs = Node::bin_op(
lhs = BinOp::new(
op.as_binop().expect("Could not get binary operation."),
lhs,
$lower::new($( self.$low_param ),*).parse(cursor)?
)
).into();
}
TokenKind::Keyword(op) if $( op == $op )||* => {
let _ = cursor.next().expect("token disappeared");
lhs = Node::bin_op(
lhs = BinOp::new(
op.as_binop().expect("Could not get binary operation."),
lhs,
$lower::new($( self.$low_param ),*).parse(cursor)?
)
).into();
}
_ => break
}

14
boa/src/syntax/parser/expression/primary/array_initializer/mod.rs

@ -11,10 +11,12 @@
mod tests;
use crate::syntax::{
ast::{constant::Const, node::Node, punc::Punctuator},
ast::{
node::{ArrayDecl, Node},
Const, Punctuator,
},
parser::{
expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, ParseResult,
TokenParser,
expression::AssignmentExpression, AllowAwait, AllowYield, Cursor, ParseError, TokenParser,
},
};
@ -47,9 +49,9 @@ impl ArrayLiteral {
}
impl TokenParser for ArrayLiteral {
type Output = Node;
type Output = ArrayDecl;
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
let mut elements = Vec::new();
loop {
@ -77,6 +79,6 @@ impl TokenParser for ArrayLiteral {
cursor.next_if(Punctuator::Comma);
}
Ok(Node::array_decl(elements))
Ok(elements.into())
}
}

78
boa/src/syntax/parser/expression/primary/array_initializer/tests.rs

@ -1,14 +1,14 @@
// ! Tests for array initializer parsing.
use crate::syntax::{
ast::{constant::Const, node::Node},
ast::{node::ArrayDecl, Const},
parser::tests::check_parser,
};
/// Checks an empty array.
#[test]
fn check_empty() {
check_parser("[]", vec![Node::array_decl(Vec::new())]);
check_parser("[]", vec![ArrayDecl::from(vec![]).into()]);
}
/// Checks an array with empty slot.
@ -16,7 +16,7 @@ fn check_empty() {
fn check_empty_slot() {
check_parser(
"[,]",
vec![Node::array_decl(vec![Node::Const(Const::Undefined)])],
vec![ArrayDecl::from(vec![Const::Undefined.into()]).into()],
);
}
@ -25,11 +25,12 @@ fn check_empty_slot() {
fn check_numeric_array() {
check_parser(
"[1, 2, 3]",
vec![Node::array_decl(vec![
Node::const_node(1),
Node::const_node(2),
Node::const_node(3),
])],
vec![ArrayDecl::from(vec![
Const::from(1).into(),
Const::from(2).into(),
Const::from(3).into(),
])
.into()],
);
}
@ -38,11 +39,12 @@ fn check_numeric_array() {
fn check_numeric_array_trailing() {
check_parser(
"[1, 2, 3,]",
vec![Node::array_decl(vec![
Node::const_node(1),
Node::const_node(2),
Node::const_node(3),
])],
vec![ArrayDecl::from(vec![
Const::from(1).into(),
Const::from(2).into(),
Const::from(3).into(),
])
.into()],
);
}
@ -51,12 +53,13 @@ fn check_numeric_array_trailing() {
fn check_numeric_array_elision() {
check_parser(
"[1, 2, , 3]",
vec![Node::array_decl(vec![
Node::const_node(1),
Node::const_node(2),
Node::Const(Const::Undefined),
Node::const_node(3),
])],
vec![ArrayDecl::from(vec![
Const::from(1).into(),
Const::from(2).into(),
Const::Undefined.into(),
Const::from(3).into(),
])
.into()],
);
}
@ -65,13 +68,14 @@ fn check_numeric_array_elision() {
fn check_numeric_array_repeated_elision() {
check_parser(
"[1, 2, ,, 3]",
vec![Node::array_decl(vec![
Node::const_node(1),
Node::const_node(2),
Node::Const(Const::Undefined),
Node::Const(Const::Undefined),
Node::const_node(3),
])],
vec![ArrayDecl::from(vec![
Const::from(1).into(),
Const::from(2).into(),
Const::Undefined.into(),
Const::Undefined.into(),
Const::from(3).into(),
])
.into()],
);
}
@ -80,11 +84,12 @@ fn check_numeric_array_repeated_elision() {
fn check_combined() {
check_parser(
"[1, \"a\", 2]",
vec![Node::array_decl(vec![
Node::const_node(1),
Node::const_node("a"),
Node::const_node(2),
])],
vec![ArrayDecl::from(vec![
Const::from(1).into(),
Const::from("a").into(),
Const::from(2).into(),
])
.into()],
);
}
@ -93,10 +98,11 @@ fn check_combined() {
fn check_combined_empty_str() {
check_parser(
"[1, \"\", 2]",
vec![Node::array_decl(vec![
Node::const_node(1),
Node::const_node(""),
Node::const_node(2),
])],
vec![ArrayDecl::from(vec![
Const::from(1).into(),
Const::from("").into(),
Const::from(2).into(),
])
.into()],
);
}

14
boa/src/syntax/parser/expression/primary/function_expression.rs

@ -8,11 +8,11 @@
//! [spec]: https://tc39.es/ecma262/#prod-FunctionExpression
use crate::syntax::{
ast::{node::Node, punc::Punctuator},
ast::{node::FunctionExpr, Punctuator},
parser::{
function::{FormalParameters, FunctionBody},
statement::BindingIdentifier,
Cursor, ParseResult, TokenParser,
Cursor, ParseError, TokenParser,
},
};
@ -28,9 +28,9 @@ use crate::syntax::{
pub(super) struct FunctionExpression;
impl TokenParser for FunctionExpression {
type Output = Node;
type Output = FunctionExpr;
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
let name = BindingIdentifier::new(false, false).try_parse(cursor);
cursor.expect(Punctuator::OpenParen, "function expression")?;
@ -40,12 +40,10 @@ impl TokenParser for FunctionExpression {
cursor.expect(Punctuator::CloseParen, "function expression")?;
cursor.expect(Punctuator::OpenBlock, "function expression")?;
let body = FunctionBody::new(false, false)
.parse(cursor)
.map(Node::statement_list)?;
let body = FunctionBody::new(false, false).parse(cursor)?;
cursor.expect(Punctuator::CloseBlock, "function expression")?;
Ok(Node::function_expr::<_, String, _, _>(name, params, body))
Ok(FunctionExpr::new(name, params, body))
}
}

51
boa/src/syntax/parser/expression/primary/mod.rs

@ -20,8 +20,9 @@ use self::{
use super::Expression;
use crate::syntax::{
ast::{
constant::Const, keyword::Keyword, node::Node, punc::Punctuator, token::NumericLiteral,
token::TokenKind,
node::{Call, Identifier, New, Node},
token::NumericLiteral,
Const, Keyword, Punctuator, TokenKind,
},
parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser},
};
@ -64,7 +65,9 @@ impl TokenParser for PrimaryExpression {
match &tok.kind {
TokenKind::Keyword(Keyword::This) => Ok(Node::This),
// TokenKind::Keyword(Keyword::Arguments) => Ok(Node::new(NodeBase::Arguments, tok.pos)),
TokenKind::Keyword(Keyword::Function) => FunctionExpression.parse(cursor),
TokenKind::Keyword(Keyword::Function) => {
FunctionExpression.parse(cursor).map(Node::from)
}
TokenKind::Punctuator(Punctuator::OpenParen) => {
let expr =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
@ -72,30 +75,38 @@ impl TokenParser for PrimaryExpression {
Ok(expr)
}
TokenKind::Punctuator(Punctuator::OpenBracket) => {
ArrayLiteral::new(self.allow_yield, self.allow_await).parse(cursor)
ArrayLiteral::new(self.allow_yield, self.allow_await)
.parse(cursor)
.map(Node::ArrayDecl)
}
TokenKind::Punctuator(Punctuator::OpenBlock) => {
ObjectLiteral::new(self.allow_yield, self.allow_await).parse(cursor)
}
TokenKind::BooleanLiteral(boolean) => Ok(Node::const_node(*boolean)),
TokenKind::BooleanLiteral(boolean) => Ok(Const::from(*boolean).into()),
// TODO: ADD TokenKind::UndefinedLiteral
TokenKind::Identifier(ref i) if i == "undefined" => Ok(Node::Const(Const::Undefined)),
TokenKind::NullLiteral => Ok(Node::Const(Const::Null)),
TokenKind::Identifier(ident) => Ok(Node::local(ident)), // TODO: IdentifierReference
TokenKind::StringLiteral(s) => Ok(Node::const_node(s)),
TokenKind::NumericLiteral(NumericLiteral::Integer(num)) => Ok(Node::const_node(*num)),
TokenKind::NumericLiteral(NumericLiteral::Rational(num)) => Ok(Node::const_node(*num)),
TokenKind::Identifier(ref i) if i.as_ref() == "undefined" => {
Ok(Const::Undefined.into())
}
TokenKind::NullLiteral => Ok(Const::Null.into()),
TokenKind::Identifier(ident) => Ok(Identifier::from(ident.as_ref()).into()), // TODO: IdentifierReference
TokenKind::StringLiteral(s) => Ok(Const::from(s.as_ref()).into()),
TokenKind::NumericLiteral(NumericLiteral::Integer(num)) => Ok(Const::from(*num).into()),
TokenKind::NumericLiteral(NumericLiteral::Rational(num)) => {
Ok(Const::from(*num).into())
}
TokenKind::NumericLiteral(NumericLiteral::BigInt(num)) => {
Ok(Node::const_node(num.clone()))
Ok(Const::from(num.clone()).into())
}
TokenKind::RegularExpressionLiteral(body, flags) => {
Ok(Node::from(New::from(Call::new(
Identifier::from("RegExp"),
vec![
Const::from(body.as_ref()).into(),
Const::from(flags.to_string()).into(),
],
))))
}
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"),
)),
_ => Err(ParseError::unexpected(tok.clone(), "primary expression")),
}
}
}

31
boa/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -12,9 +12,9 @@ mod tests;
use crate::syntax::{
ast::{
node::{self, MethodDefinitionKind, Node},
punc::Punctuator,
node::{self, FunctionExpr, MethodDefinitionKind, Node},
token::{Token, TokenKind},
Punctuator,
},
parser::{
expression::AssignmentExpression,
@ -71,7 +71,7 @@ impl TokenParser for ObjectLiteral {
if cursor.next_if(Punctuator::Comma).is_none() {
let next_token = cursor.next().ok_or(ParseError::AbruptEnd)?;
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![
TokenKind::Punctuator(Punctuator::Comma),
TokenKind::Punctuator(Punctuator::CloseBlock),
@ -129,7 +129,7 @@ impl TokenParser for PropertyDefinition {
if cursor.next_if(Punctuator::Colon).is_some() {
let val = AssignmentExpression::new(true, self.allow_yield, self.allow_await)
.parse(cursor)?;
return Ok(node::PropertyDefinition::Property(prop_name, val));
return Ok(node::PropertyDefinition::property(prop_name, val));
}
if cursor
@ -143,12 +143,9 @@ impl TokenParser for PropertyDefinition {
let pos = cursor
.peek(0)
.map(|tok| tok.pos)
.map(|tok| tok.span().start())
.ok_or(ParseError::AbruptEnd)?;
Err(ParseError::General(
"expected property definition",
Some(pos),
))
Err(ParseError::general("expected property definition", pos))
}
}
@ -200,17 +197,17 @@ impl TokenParser for MethodDefinition {
cursor.expect(Punctuator::CloseParen, "method definition")?;
if idn == "get" {
if !params.is_empty() {
return Err(ParseError::Unexpected(
return Err(ParseError::unexpected(
first_param,
Some("getter functions must have no arguments"),
"getter functions must have no arguments",
));
}
(MethodDefinitionKind::Get, prop_name, params)
} else {
if params.len() != 1 {
return Err(ParseError::Unexpected(
return Err(ParseError::unexpected(
first_param,
Some("setter functions must have one argument"),
"setter functions must have one argument",
));
}
(MethodDefinitionKind::Set, prop_name, params)
@ -231,18 +228,16 @@ impl TokenParser for MethodDefinition {
TokenKind::Punctuator(Punctuator::OpenBlock),
"property method definition",
)?;
let body = FunctionBody::new(false, false)
.parse(cursor)
.map(Node::statement_list)?;
let body = FunctionBody::new(false, false).parse(cursor)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"property method definition",
)?;
Ok(node::PropertyDefinition::MethodDefinition(
Ok(node::PropertyDefinition::method_definition(
methodkind,
prop_name,
Node::function_expr::<_, String, _, _>(None, params, body),
FunctionExpr::new(None, params, body),
))
}
}

73
boa/src/syntax/parser/expression/primary/object_initializer/tests.rs

@ -1,5 +1,11 @@
use crate::syntax::{
ast::node::{FormalParameter, MethodDefinitionKind, Node, PropertyDefinition},
ast::{
node::{
ConstDecl, ConstDeclList, FormalParameter, FunctionExpr, MethodDefinitionKind, Node,
PropertyDefinition,
},
Const,
},
parser::tests::check_parser,
};
@ -7,8 +13,8 @@ use crate::syntax::{
#[test]
fn check_object_literal() {
let object_properties = vec![
PropertyDefinition::property("a", Node::const_node(true)),
PropertyDefinition::property("b", Node::const_node(false)),
PropertyDefinition::property("a", Const::from(true)),
PropertyDefinition::property("b", Const::from(false)),
];
check_parser(
@ -17,10 +23,9 @@ fn check_object_literal() {
b: false,
};
",
vec![Node::const_decl(vec![(
String::from("x"),
Node::object(object_properties),
)])],
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Node::object(object_properties))]).into(),
],
);
}
@ -28,11 +33,11 @@ fn check_object_literal() {
#[test]
fn check_object_short_function() {
let object_properties = vec![
PropertyDefinition::property("a", Node::const_node(true)),
PropertyDefinition::property("a", Const::from(true)),
PropertyDefinition::method_definition(
MethodDefinitionKind::Ordinary,
"b",
Node::function_expr::<_, String, _, _>(None, Vec::new(), Node::statement_list(vec![])),
FunctionExpr::new(None, vec![], vec![]),
),
];
@ -42,10 +47,9 @@ fn check_object_short_function() {
b() {},
};
",
vec![Node::const_decl(vec![(
String::from("x"),
Node::object(object_properties),
)])],
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Node::object(object_properties))]).into(),
],
);
}
@ -53,14 +57,14 @@ fn check_object_short_function() {
#[test]
fn check_object_short_function_arguments() {
let object_properties = vec![
PropertyDefinition::property("a", Node::const_node(true)),
PropertyDefinition::property("a", Const::from(true)),
PropertyDefinition::method_definition(
MethodDefinitionKind::Ordinary,
"b",
Node::FunctionExpr(
FunctionExpr::new(
None,
Box::new([FormalParameter::new("test", None, false)]),
Box::new(Node::StatementList(Box::new([]))),
vec![FormalParameter::new("test", None, false)],
vec![],
),
),
];
@ -71,25 +75,20 @@ fn check_object_short_function_arguments() {
b(test) {}
};
",
vec![Node::const_decl(vec![(
String::from("x"),
Node::object(object_properties),
)])],
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Node::object(object_properties))]).into(),
],
);
}
#[test]
fn check_object_getter() {
let object_properties = vec![
PropertyDefinition::property("a", Node::const_node(true)),
PropertyDefinition::property("a", Const::from(true)),
PropertyDefinition::method_definition(
MethodDefinitionKind::Get,
"b",
Node::FunctionExpr(
None,
Box::new([]),
Box::new(Node::statement_list(Vec::new())),
),
FunctionExpr::new(None, vec![], vec![]),
),
];
@ -99,24 +98,23 @@ fn check_object_getter() {
get b() {}
};
",
vec![Node::const_decl(vec![(
String::from("x"),
Node::object(object_properties),
)])],
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Node::object(object_properties))]).into(),
],
);
}
#[test]
fn check_object_setter() {
let object_properties = vec![
PropertyDefinition::property("a", Node::const_node(true)),
PropertyDefinition::property("a", Const::from(true)),
PropertyDefinition::method_definition(
MethodDefinitionKind::Set,
"b",
Node::function_expr::<_, String, _, _>(
FunctionExpr::new(
None,
vec![FormalParameter::new("test", None, false)],
Node::statement_list(Vec::new()),
vec![],
),
),
];
@ -127,9 +125,8 @@ fn check_object_setter() {
set b(test) {}
};
",
vec![Node::const_decl(vec![(
String::from("x"),
Node::object(object_properties),
)])],
vec![
ConstDeclList::from(vec![ConstDecl::new("x", Node::object(object_properties))]).into(),
],
);
}

6
boa/src/syntax/parser/expression/primary/tests.rs

@ -1,10 +1,10 @@
use crate::syntax::{ast::node::Node, parser::tests::check_parser};
use crate::syntax::{ast::Const, parser::tests::check_parser};
#[test]
fn check_string() {
// Check empty string
check_parser("\"\"", vec![Node::const_node("")]);
check_parser("\"\"", vec![Const::from("").into()]);
// Check non-empty string
check_parser("\"hello\"", vec![Node::const_node("hello")]);
check_parser("\"hello\"", vec![Const::from("hello").into()]);
}

259
boa/src/syntax/parser/expression/tests.rs

@ -1,6 +1,9 @@
use crate::syntax::{
ast::node::Node,
ast::op::{AssignOp, BinOp, BitOp, CompOp, NumOp},
ast::op::{AssignOp, BitOp, CompOp, NumOp},
ast::{
node::{BinOp, Identifier},
Const,
},
parser::tests::check_parser,
};
@ -9,75 +12,51 @@ use crate::syntax::{
fn check_numeric_operations() {
check_parser(
"a + b",
vec![Node::bin_op(NumOp::Add, Node::local("a"), Node::local("b"))],
vec![BinOp::new(NumOp::Add, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a+1",
vec![Node::bin_op(
NumOp::Add,
Node::local("a"),
Node::const_node(1),
)],
vec![BinOp::new(NumOp::Add, Identifier::from("a"), Const::from(1)).into()],
);
check_parser(
"a - b",
vec![Node::bin_op(NumOp::Sub, Node::local("a"), Node::local("b"))],
vec![BinOp::new(NumOp::Sub, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a-1",
vec![Node::bin_op(
NumOp::Sub,
Node::local("a"),
Node::const_node(1),
)],
vec![BinOp::new(NumOp::Sub, Identifier::from("a"), Const::from(1)).into()],
);
check_parser(
"a / b",
vec![Node::bin_op(NumOp::Div, Node::local("a"), Node::local("b"))],
vec![BinOp::new(NumOp::Div, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a/2",
vec![Node::bin_op(
NumOp::Div,
Node::local("a"),
Node::const_node(2),
)],
vec![BinOp::new(NumOp::Div, Identifier::from("a"), Const::from(2)).into()],
);
check_parser(
"a * b",
vec![Node::bin_op(NumOp::Mul, Node::local("a"), Node::local("b"))],
vec![BinOp::new(NumOp::Mul, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a*2",
vec![Node::bin_op(
NumOp::Mul,
Node::local("a"),
Node::const_node(2),
)],
vec![BinOp::new(NumOp::Mul, Identifier::from("a"), Const::from(2)).into()],
);
check_parser(
"a ** b",
vec![Node::bin_op(NumOp::Exp, Node::local("a"), Node::local("b"))],
vec![BinOp::new(NumOp::Exp, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a**2",
vec![Node::bin_op(
NumOp::Exp,
Node::local("a"),
Node::const_node(2),
)],
vec![BinOp::new(NumOp::Exp, Identifier::from("a"), Const::from(2)).into()],
);
check_parser(
"a % b",
vec![Node::bin_op(NumOp::Mod, Node::local("a"), Node::local("b"))],
vec![BinOp::new(NumOp::Mod, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a%2",
vec![Node::bin_op(
NumOp::Mod,
Node::local("a"),
Node::const_node(2),
)],
vec![BinOp::new(NumOp::Mod, Identifier::from("a"), Const::from(2)).into()],
);
}
@ -86,19 +65,20 @@ fn check_numeric_operations() {
fn check_complex_numeric_operations() {
check_parser(
"a + d*(b-3)+1",
vec![Node::bin_op(
vec![BinOp::new(
NumOp::Add,
Node::bin_op(
BinOp::new(
NumOp::Add,
Node::local("a"),
Node::bin_op(
Identifier::from("a"),
BinOp::new(
NumOp::Mul,
Node::local("d"),
Node::bin_op(NumOp::Sub, Node::local("b"), Node::const_node(3)),
Identifier::from("d"),
BinOp::new(NumOp::Sub, Identifier::from("b"), Const::from(3)),
),
),
Node::const_node(1),
)],
Const::from(1),
)
.into()],
);
}
@ -107,87 +87,47 @@ fn check_complex_numeric_operations() {
fn check_bitwise_operations() {
check_parser(
"a & b",
vec![Node::bin_op(
BinOp::Bit(BitOp::And),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::And, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a&b",
vec![Node::bin_op(
BinOp::Bit(BitOp::And),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::And, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a | b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Or),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Or, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a|b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Or),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Or, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a ^ b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Xor),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Xor, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a^b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Xor),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Xor, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a << b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Shl),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Shl, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a<<b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Shl),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Shl, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a >> b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Shr),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Shr, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a>>b",
vec![Node::bin_op(
BinOp::Bit(BitOp::Shr),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(BitOp::Shr, Identifier::from("a"), Identifier::from("b")).into()],
);
}
@ -196,99 +136,56 @@ fn check_bitwise_operations() {
fn check_assign_operations() {
check_parser(
"a += b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Add),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Add, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a -= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Sub),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Sub, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a *= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Mul),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Mul, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a **= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Exp),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Exp, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a /= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Div),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Div, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a %= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Mod),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Mod, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a &= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::And),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::And, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a |= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Or),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Or, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a ^= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Xor),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Xor, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a <<= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Shl),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Shl, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a >>= b",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Shr),
Node::local("a"),
Node::local("b"),
)],
vec![BinOp::new(AssignOp::Shr, Identifier::from("a"), Identifier::from("b")).into()],
);
check_parser(
"a %= 10 / 2",
vec![Node::bin_op(
BinOp::Assign(AssignOp::Mod),
Node::local("a"),
Node::bin_op(NumOp::Div, Node::const_node(10), Node::const_node(2)),
)],
vec![BinOp::new(
AssignOp::Mod,
Identifier::from("a"),
BinOp::new(NumOp::Div, Const::from(10), Const::from(2)),
)
.into()],
);
}
@ -296,42 +193,42 @@ fn check_assign_operations() {
fn check_relational_operations() {
check_parser(
"a < b",
vec![Node::bin_op(
BinOp::Comp(CompOp::LessThan),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
vec![BinOp::new(
CompOp::LessThan,
Identifier::from("a"),
Identifier::from("b"),
)
.into()],
);
check_parser(
"a > b",
vec![Node::bin_op(
BinOp::Comp(CompOp::GreaterThan),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
vec![BinOp::new(
CompOp::GreaterThan,
Identifier::from("a"),
Identifier::from("b"),
)
.into()],
);
check_parser(
"a <= b",
vec![Node::bin_op(
BinOp::Comp(CompOp::LessThanOrEqual),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
vec![BinOp::new(
CompOp::LessThanOrEqual,
Identifier::from("a"),
Identifier::from("b"),
)
.into()],
);
check_parser(
"a >= b",
vec![Node::bin_op(
BinOp::Comp(CompOp::GreaterThanOrEqual),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
vec![BinOp::new(
CompOp::GreaterThanOrEqual,
Identifier::from("a"),
Identifier::from("b"),
)
.into()],
);
check_parser(
"p in o",
vec![Node::bin_op(
BinOp::Comp(CompOp::In),
Node::Local(String::from("p")),
Node::Local(String::from("o")),
)],
vec![BinOp::new(CompOp::In, Identifier::from("p"), Identifier::from("o")).into()],
);
}

20
boa/src/syntax/parser/expression/unary.rs

@ -8,7 +8,11 @@
//! [spec]: https://tc39.es/ecma262/#sec-unary-operators
use crate::syntax::{
ast::{keyword::Keyword, node::Node, op::UnaryOp, punc::Punctuator, token::TokenKind},
ast::{
node::{self, Node},
op::UnaryOp,
Keyword, Punctuator, TokenKind,
},
parser::{
expression::update::UpdateExpression, AllowAwait, AllowYield, Cursor, ParseError,
ParseResult, TokenParser,
@ -50,25 +54,25 @@ impl TokenParser for UnaryExpression {
let tok = cursor.next().ok_or(ParseError::AbruptEnd)?;
match tok.kind {
TokenKind::Keyword(Keyword::Delete) => {
Ok(Node::unary_op(UnaryOp::Delete, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::Delete, self.parse(cursor)?).into())
}
TokenKind::Keyword(Keyword::Void) => {
Ok(Node::unary_op(UnaryOp::Void, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::Void, self.parse(cursor)?).into())
}
TokenKind::Keyword(Keyword::TypeOf) => {
Ok(Node::unary_op(UnaryOp::TypeOf, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::TypeOf, self.parse(cursor)?).into())
}
TokenKind::Punctuator(Punctuator::Add) => {
Ok(Node::unary_op(UnaryOp::Plus, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::Plus, self.parse(cursor)?).into())
}
TokenKind::Punctuator(Punctuator::Sub) => {
Ok(Node::unary_op(UnaryOp::Minus, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::Minus, self.parse(cursor)?).into())
}
TokenKind::Punctuator(Punctuator::Neg) => {
Ok(Node::unary_op(UnaryOp::Tilde, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::Tilde, self.parse(cursor)?).into())
}
TokenKind::Punctuator(Punctuator::Not) => {
Ok(Node::unary_op(UnaryOp::Not, self.parse(cursor)?))
Ok(node::UnaryOp::new(UnaryOp::Not, self.parse(cursor)?).into())
}
_ => {
cursor.back();

16
boa/src/syntax/parser/expression/update.rs

@ -7,7 +7,7 @@
use super::left_hand_side::LeftHandSideExpression;
use crate::syntax::{
ast::{node::Node, op::UnaryOp, punc::Punctuator, token::TokenKind},
ast::{node, op::UnaryOp, Node, Punctuator, TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser},
};
@ -45,19 +45,21 @@ impl TokenParser for UpdateExpression {
match tok.kind {
TokenKind::Punctuator(Punctuator::Inc) => {
cursor.next().expect("token disappeared");
return Ok(Node::unary_op(
return Ok(node::UnaryOp::new(
UnaryOp::IncrementPre,
LeftHandSideExpression::new(self.allow_yield, self.allow_await)
.parse(cursor)?,
));
)
.into());
}
TokenKind::Punctuator(Punctuator::Dec) => {
cursor.next().expect("token disappeared");
return Ok(Node::unary_op(
return Ok(node::UnaryOp::new(
UnaryOp::DecrementPre,
LeftHandSideExpression::new(self.allow_yield, self.allow_await)
.parse(cursor)?,
));
)
.into());
}
_ => {}
}
@ -67,11 +69,11 @@ impl TokenParser for UpdateExpression {
match tok.kind {
TokenKind::Punctuator(Punctuator::Inc) => {
cursor.next().expect("token disappeared");
return Ok(Node::unary_op(UnaryOp::IncrementPost, lhs));
return Ok(node::UnaryOp::new(UnaryOp::IncrementPost, lhs).into());
}
TokenKind::Punctuator(Punctuator::Dec) => {
cursor.next().expect("token disappeared");
return Ok(Node::unary_op(UnaryOp::DecrementPost, lhs));
return Ok(node::UnaryOp::new(UnaryOp::DecrementPost, lhs).into());
}
_ => {}
}

21
boa/src/syntax/parser/function/mod.rs

@ -12,9 +12,8 @@ mod tests;
use crate::syntax::{
ast::{
node::{self, Node},
punc::Punctuator,
token::TokenKind,
node::{self},
Punctuator, TokenKind,
},
parser::{
expression::Initializer,
@ -52,7 +51,7 @@ impl FormalParameters {
}
impl TokenParser for FormalParameters {
type Output = Vec<node::FormalParameter>;
type Output = Box<[node::FormalParameter]>;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
let mut params = Vec::new();
@ -60,7 +59,7 @@ impl TokenParser for FormalParameters {
if cursor.peek(0).ok_or(ParseError::AbruptEnd)?.kind
== TokenKind::Punctuator(Punctuator::CloseParen)
{
return Ok(params);
return Ok(params.into_boxed_slice());
}
loop {
@ -80,19 +79,19 @@ impl TokenParser for FormalParameters {
}
if rest_param {
return Err(ParseError::Unexpected(
return Err(ParseError::unexpected(
cursor
.peek_prev()
.expect("current token disappeared")
.clone(),
Some("rest parameter must be the last formal parameter"),
"rest parameter must be the last formal parameter",
));
}
cursor.expect(Punctuator::Comma, "parameter list")?;
}
Ok(params)
Ok(params.into_boxed_slice())
}
}
@ -186,7 +185,7 @@ impl TokenParser for FormalParameter {
let init = Initializer::new(true, self.allow_yield, self.allow_await).try_parse(cursor);
Ok(Self::Output::new(param, init.map(Box::new), false))
Ok(Self::Output::new(param, init, false))
}
}
@ -225,12 +224,12 @@ impl FunctionStatementList {
}
impl TokenParser for FunctionStatementList {
type Output = Vec<Node>;
type Output = node::StatementList;
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());
return Ok(Vec::new().into());
}
}

99
boa/src/syntax/parser/function/tests.rs

@ -1,5 +1,5 @@
use crate::syntax::{
ast::node::{FormalParameter, Node},
ast::node::{ArrowFunctionDecl, BinOp, FormalParameter, FunctionDecl, Identifier, Node},
ast::op::NumOp,
parser::tests::check_parser,
};
@ -9,11 +9,12 @@ use crate::syntax::{
fn check_basic() {
check_parser(
"function foo(a) { return a; }",
vec![Node::function_decl(
"foo",
vec![FunctionDecl::new(
Box::from("foo"),
vec![FormalParameter::new("a", None, false)],
Node::statement_list(vec![Node::return_node(Node::local("a"))]),
)],
vec![Node::return_node(Identifier::from("a"))],
)
.into()],
);
}
@ -22,11 +23,12 @@ fn check_basic() {
fn check_basic_semicolon_insertion() {
check_parser(
"function foo(a) { return a }",
vec![Node::function_decl(
"foo",
vec![FunctionDecl::new(
Box::from("foo"),
vec![FormalParameter::new("a", None, false)],
Node::statement_list(vec![Node::return_node(Node::local("a"))]),
)],
vec![Node::return_node(Identifier::from("a"))],
)
.into()],
);
}
@ -35,11 +37,12 @@ fn check_basic_semicolon_insertion() {
fn check_empty_return() {
check_parser(
"function foo(a) { return; }",
vec![Node::function_decl(
"foo",
vec![FunctionDecl::new(
Box::from("foo"),
vec![FormalParameter::new("a", None, false)],
Node::statement_list(vec![Node::Return(None)]),
)],
vec![Node::Return(None)],
)
.into()],
);
}
@ -48,11 +51,12 @@ fn check_empty_return() {
fn check_empty_return_semicolon_insertion() {
check_parser(
"function foo(a) { return }",
vec![Node::function_decl(
"foo",
vec![FunctionDecl::new(
Box::from("foo"),
vec![FormalParameter::new("a", None, false)],
Node::statement_list(vec![Node::Return(None)]),
)],
vec![Node::Return(None)],
)
.into()],
);
}
@ -61,14 +65,15 @@ fn check_empty_return_semicolon_insertion() {
fn check_rest_operator() {
check_parser(
"function foo(a, ...b) {}",
vec![Node::function_decl(
"foo",
vec![FunctionDecl::new(
Box::from("foo"),
vec![
FormalParameter::new("a", None, false),
FormalParameter::new("b", None, true),
],
Node::StatementList(Box::new([])),
)],
vec![],
)
.into()],
);
}
@ -77,10 +82,7 @@ fn check_rest_operator() {
fn check_arrow_only_rest() {
check_parser(
"(...a) => {}",
vec![Node::arrow_function_decl(
vec![FormalParameter::new("a", None, true)],
Node::StatementList(Box::new([])),
)],
vec![ArrowFunctionDecl::new(vec![FormalParameter::new("a", None, true)], vec![]).into()],
);
}
@ -89,14 +91,15 @@ fn check_arrow_only_rest() {
fn check_arrow_rest() {
check_parser(
"(a, b, ...c) => {}",
vec![Node::arrow_function_decl(
vec![ArrowFunctionDecl::new(
vec![
FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false),
FormalParameter::new("c", None, true),
],
Node::StatementList(Box::new([])),
)],
vec![],
)
.into()],
);
}
@ -105,17 +108,18 @@ fn check_arrow_rest() {
fn check_arrow() {
check_parser(
"(a, b) => { return a + b; }",
vec![Node::arrow_function_decl(
vec![ArrowFunctionDecl::new(
vec![
FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false),
],
Node::statement_list(vec![Node::return_node(Node::bin_op(
vec![Node::return_node(BinOp::new(
NumOp::Add,
Node::local("a"),
Node::local("b"),
))]),
)],
Identifier::from("a"),
Identifier::from("b"),
))],
)
.into()],
);
}
@ -124,17 +128,18 @@ fn check_arrow() {
fn check_arrow_semicolon_insertion() {
check_parser(
"(a, b) => { return a + b }",
vec![Node::arrow_function_decl(
vec![ArrowFunctionDecl::new(
vec![
FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false),
],
Node::statement_list(vec![Node::return_node(Node::bin_op(
vec![Node::return_node(BinOp::new(
NumOp::Add,
Node::local("a"),
Node::local("b"),
))]),
)],
Identifier::from("a"),
Identifier::from("b"),
))],
)
.into()],
);
}
@ -143,13 +148,14 @@ fn check_arrow_semicolon_insertion() {
fn check_arrow_epty_return() {
check_parser(
"(a, b) => { return; }",
vec![Node::arrow_function_decl(
vec![ArrowFunctionDecl::new(
vec![
FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false),
],
Node::statement_list(vec![Node::Return(None)]),
)],
vec![Node::Return(None)],
)
.into()],
);
}
@ -158,12 +164,13 @@ fn check_arrow_epty_return() {
fn check_arrow_empty_return_semicolon_insertion() {
check_parser(
"(a, b) => { return }",
vec![Node::arrow_function_decl(
vec![ArrowFunctionDecl::new(
vec![
FormalParameter::new("a", None, false),
FormalParameter::new("b", None, false),
],
Node::statement_list(vec![Node::Return(None)]),
)],
vec![Node::Return(None)],
)
.into()],
);
}

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

@ -9,7 +9,7 @@ mod statement;
mod tests;
use self::error::{ParseError, ParseResult};
use crate::syntax::ast::{node::Node, token::Token};
use crate::syntax::ast::{node::StatementList, Token};
use cursor::Cursor;
/// Trait implemented by parsers.
@ -103,8 +103,8 @@ impl<'a> Parser<'a> {
}
/// Parse all expressions in the token array
pub fn parse_all(&mut self) -> ParseResult {
Script.parse(&mut self.cursor).map(Node::statement_list)
pub fn parse_all(&mut self) -> Result<StatementList, ParseError> {
Script.parse(&mut self.cursor)
}
}
@ -118,13 +118,13 @@ impl<'a> Parser<'a> {
pub struct Script;
impl TokenParser for Script {
type Output = Vec<Node>;
type Output = StatementList;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
if cursor.peek(0).is_some() {
ScriptBody.parse(cursor)
} else {
Ok(Vec::new())
Ok(StatementList::from(Vec::new()))
}
}
}
@ -139,7 +139,7 @@ impl TokenParser for Script {
pub struct ScriptBody;
impl TokenParser for ScriptBody {
type Output = Vec<Node>;
type Output = StatementList;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
self::statement::StatementList::new(false, false, false, false).parse(cursor)

203
boa/src/syntax/parser/statement/block.rs

@ -1,203 +0,0 @@
//! 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(Box::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/block/mod.rs

@ -0,0 +1,78 @@
//! 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
#[cfg(test)]
mod tests;
use super::StatementList;
use crate::syntax::{
ast::{node, Punctuator, TokenKind},
parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, 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::Block;
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
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::from(vec![]));
}
}
let statement_list =
StatementList::new(self.allow_yield, self.allow_await, self.allow_return, true)
.parse(cursor)
.map(node::Block::from)?;
cursor.expect(Punctuator::CloseBlock, "block")?;
Ok(statement_list)
}
}

104
boa/src/syntax/parser/statement/block/tests.rs

@ -0,0 +1,104 @@
//! Block statement parsing tests.
use crate::syntax::{
ast::{
node::{
Assign, Block, Call, FunctionDecl, Identifier, Node, UnaryOp, VarDecl, VarDeclList,
},
op, Const,
},
parser::tests::check_parser,
};
/// Helper function to check a block.
// TODO: #[track_caller]: https://github.com/rust-lang/rust/issues/47809
fn check_block<B>(js: &str, block: B)
where
B: Into<Box<[Node]>>,
{
check_parser(js, vec![Block::from(block.into()).into()]);
}
#[test]
fn empty() {
check_block("{}", vec![]);
}
#[test]
fn non_empty() {
check_block(
r"{
var a = 10;
a++;
}",
vec![
VarDeclList::from(vec![VarDecl::new("a", Some(Const::from(10).into()))]).into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(),
],
);
check_block(
r"{
function hello() {
return 10
}
var a = hello();
a++;
}",
vec![
FunctionDecl::new(
"hello".to_owned().into_boxed_str(),
vec![],
vec![Node::return_node(Const::from(10))],
)
.into(),
VarDeclList::from(vec![VarDecl::new(
"a",
Node::from(Call::new(Identifier::from("hello"), vec![])),
)])
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(),
],
);
}
#[test]
fn hoisting() {
check_block(
r"{
var a = hello();
a++;
function hello() { return 10 }
}",
vec![
FunctionDecl::new(
"hello".to_owned().into_boxed_str(),
vec![],
vec![Node::return_node(Const::from(10))],
)
.into(),
VarDeclList::from(vec![VarDecl::new(
"a",
Node::from(Call::new(Identifier::from("hello"), vec![])),
)])
.into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(),
],
);
check_block(
r"{
a = 10;
a++;
var a;
}",
vec![
Assign::new(Identifier::from("a"), Const::from(10)).into(),
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(),
VarDeclList::from(vec![VarDecl::new("a", None)]).into(),
],
);
}

28
boa/src/syntax/parser/statement/break_stm/mod.rs

@ -10,9 +10,10 @@
#[cfg(test)]
mod tests;
use super::LabelIdentifier;
use crate::syntax::{
ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser},
ast::{Keyword, Node, Punctuator, TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser},
};
/// Break statement parsing
@ -49,7 +50,7 @@ impl TokenParser for BreakStatement {
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
cursor.expect(Keyword::Break, "break statement")?;
if let (true, tok) = cursor.peek_semicolon(false) {
let label = if let (true, tok) = cursor.peek_semicolon(false) {
match tok {
Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => {
let _ = cursor.next();
@ -57,23 +58,14 @@ impl TokenParser for BreakStatement {
_ => {}
}
return Ok(Node::Break(None));
}
let tok = cursor.next().ok_or(ParseError::AbruptEnd)?;
// TODO: LabelIdentifier
let node = if let TokenKind::Identifier(name) = &tok.kind {
Node::break_node(name)
None
} else {
return Err(ParseError::Expected(
vec![TokenKind::identifier("identifier")],
tok.clone(),
"break statement",
));
};
let label = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect_semicolon(false, "continue statement")?;
cursor.expect_semicolon(false, "break statement")?;
Some(label)
};
Ok(node)
Ok(Node::Break(label))
}
}

75
boa/src/syntax/parser/statement/break_stm/tests.rs

@ -1,92 +1,121 @@
use crate::syntax::{ast::node::Node, parser::tests::check_parser};
use crate::syntax::{
ast::{
node::{Block, Node},
Const,
},
parser::tests::check_parser,
};
#[test]
fn check_inline() {
fn inline() {
check_parser(
"while (true) break;",
vec![Node::while_loop(Node::const_node(true), Node::Break(None))],
vec![Node::while_loop(Const::from(true), Node::Break(None))],
);
}
#[test]
fn check_new_line() {
fn new_line() {
check_parser(
"while (true)
break;",
vec![Node::while_loop(Node::const_node(true), Node::Break(None))],
vec![Node::while_loop(Const::from(true), Node::Break(None))],
);
}
#[test]
fn check_inline_block_semicolon_insertion() {
fn inline_block_semicolon_insertion() {
check_parser(
"while (true) {break}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Break(None)]),
Const::from(true),
Block::from(vec![Node::Break(None)]),
)],
);
}
#[test]
fn check_new_line_semicolon_insertion() {
fn new_line_semicolon_insertion() {
check_parser(
"while (true) {
break test
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::break_node("test")]),
Const::from(true),
Block::from(vec![Node::break_node("test")]),
)],
);
}
#[test]
fn check_inline_block() {
fn inline_block() {
check_parser(
"while (true) {break;}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Break(None)]),
Const::from(true),
Block::from(vec![Node::Break(None)]),
)],
);
}
#[test]
fn check_new_line_block() {
fn new_line_block() {
check_parser(
"while (true) {
break test;
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::break_node("test")]),
Const::from(true),
Block::from(vec![Node::break_node("test")]),
)],
);
}
#[test]
fn check_new_line_block_empty() {
fn reserved_label() {
check_parser(
"while (true) {
break await;
}",
vec![Node::while_loop(
Const::from(true),
Block::from(vec![Node::break_node("await")]),
)],
);
check_parser(
"while (true) {
break yield;
}",
vec![Node::while_loop(
Const::from(true),
Block::from(vec![Node::break_node("yield")]),
)],
);
}
#[test]
fn new_line_block_empty() {
check_parser(
"while (true) {
break;
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Break(None)]),
Const::from(true),
Block::from(vec![Node::Break(None)]),
)],
);
}
#[test]
fn check_new_line_block_empty_semicolon_insertion() {
fn new_line_block_empty_semicolon_insertion() {
check_parser(
"while (true) {
break
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Break(None)]),
Const::from(true),
Block::from(vec![Node::Break(None)]),
)],
);
}

28
boa/src/syntax/parser/statement/continue_stm/mod.rs

@ -10,9 +10,10 @@
#[cfg(test)]
mod tests;
use super::LabelIdentifier;
use crate::syntax::{
ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser},
ast::{Keyword, Node, Punctuator, TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser},
};
/// For statement parsing
@ -49,7 +50,7 @@ impl TokenParser for ContinueStatement {
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
cursor.expect(Keyword::Continue, "continue statement")?;
if let (true, tok) = cursor.peek_semicolon(false) {
let label = if let (true, tok) = cursor.peek_semicolon(false) {
match tok {
Some(tok) if tok.kind == TokenKind::Punctuator(Punctuator::Semicolon) => {
let _ = cursor.next();
@ -57,23 +58,14 @@ impl TokenParser for ContinueStatement {
_ => {}
}
return Ok(Node::Continue(None));
}
let tok = cursor.next().ok_or(ParseError::AbruptEnd)?;
// TODO: LabelIdentifier
let node = if let TokenKind::Identifier(name) = &tok.kind {
Node::continue_node(name)
None
} else {
return Err(ParseError::Expected(
vec![TokenKind::identifier("identifier")],
tok.clone(),
"continue statement",
));
};
let label = LabelIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect_semicolon(false, "continue statement")?;
cursor.expect_semicolon(false, "continue statement")?;
Some(label)
};
Ok(node)
Ok(Node::Continue(label))
}
}

81
boa/src/syntax/parser/statement/continue_stm/tests.rs

@ -1,98 +1,121 @@
use crate::syntax::{ast::node::Node, parser::tests::check_parser};
use crate::syntax::{
ast::{
node::{Block, Node},
Const,
},
parser::tests::check_parser,
};
#[test]
fn check_inline() {
fn inline() {
check_parser(
"while (true) continue;",
vec![Node::while_loop(
Node::const_node(true),
Node::Continue(None),
)],
vec![Node::while_loop(Const::from(true), Node::Continue(None))],
);
}
#[test]
fn check_new_line() {
fn new_line() {
check_parser(
"while (true)
continue;",
vec![Node::while_loop(
Node::const_node(true),
Node::Continue(None),
)],
vec![Node::while_loop(Const::from(true), Node::Continue(None))],
);
}
#[test]
fn check_inline_block_semicolon_insertion() {
fn inline_block_semicolon_insertion() {
check_parser(
"while (true) {continue}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Continue(None)]),
Const::from(true),
Block::from(vec![Node::Continue(None)]),
)],
);
}
#[test]
fn check_new_line_semicolon_insertion() {
fn new_line_semicolon_insertion() {
check_parser(
"while (true) {
continue test
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::continue_node("test")]),
Const::from(true),
Block::from(vec![Node::continue_node("test")]),
)],
);
}
#[test]
fn check_inline_block() {
fn inline_block() {
check_parser(
"while (true) {continue;}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Continue(None)]),
Const::from(true),
Block::from(vec![Node::Continue(None)]),
)],
);
}
#[test]
fn check_new_line_block() {
fn new_line_block() {
check_parser(
"while (true) {
continue test;
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::continue_node("test")]),
Const::from(true),
Block::from(vec![Node::continue_node("test")]),
)],
);
}
#[test]
fn reserved_label() {
check_parser(
"while (true) {
continue await;
}",
vec![Node::while_loop(
Const::from(true),
Block::from(vec![Node::continue_node("await")]),
)],
);
check_parser(
"while (true) {
continue yield;
}",
vec![Node::while_loop(
Const::from(true),
Block::from(vec![Node::continue_node("yield")]),
)],
);
}
#[test]
fn check_new_line_block_empty() {
fn new_line_block_empty() {
check_parser(
"while (true) {
continue;
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Continue(None)]),
Const::from(true),
Block::from(vec![Node::Continue(None)]),
)],
);
}
#[test]
fn check_new_line_block_empty_semicolon_insertion() {
fn new_line_block_empty_semicolon_insertion() {
check_parser(
"while (true) {
continue
}",
vec![Node::while_loop(
Node::const_node(true),
Node::block(vec![Node::Continue(None)]),
Const::from(true),
Block::from(vec![Node::Continue(None)]),
)],
);
}

30
boa/src/syntax/parser/statement/declaration/hoistable.rs

@ -6,10 +6,10 @@
//! [spec]: https://tc39.es/ecma262/#prod-HoistableDeclaration
use crate::syntax::{
ast::{keyword::Keyword, node::Node, punc::Punctuator},
ast::{node::FunctionDecl, Keyword, Node, Punctuator},
parser::{
function::FormalParameters, function::FunctionBody, statement::BindingIdentifier,
AllowAwait, AllowDefault, AllowYield, Cursor, ParseResult, TokenParser,
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
},
};
@ -23,12 +23,12 @@ use crate::syntax::{
pub(super) struct HoistableDeclaration {
allow_yield: AllowYield,
allow_await: AllowAwait,
allow_default: AllowDefault,
is_default: AllowDefault,
}
impl HoistableDeclaration {
/// Creates a new `HoistableDeclaration` parser.
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, allow_default: D) -> Self
pub(super) fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
@ -37,7 +37,7 @@ impl HoistableDeclaration {
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
allow_default: allow_default.into(),
is_default: is_default.into(),
}
}
}
@ -47,8 +47,9 @@ impl TokenParser for HoistableDeclaration {
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
// TODO: check for generators and async functions + generators
FunctionDeclaration::new(self.allow_yield, self.allow_await, self.allow_default)
FunctionDeclaration::new(self.allow_yield, self.allow_await, self.is_default)
.parse(cursor)
.map(Node::from)
}
}
@ -64,12 +65,12 @@ impl TokenParser for HoistableDeclaration {
struct FunctionDeclaration {
allow_yield: AllowYield,
allow_await: AllowAwait,
allow_default: AllowDefault,
is_default: AllowDefault,
}
impl FunctionDeclaration {
/// Creates a new `FunctionDeclaration` parser.
fn new<Y, A, D>(allow_yield: Y, allow_await: A, allow_default: D) -> Self
fn new<Y, A, D>(allow_yield: Y, allow_await: A, is_default: D) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
@ -78,17 +79,18 @@ impl FunctionDeclaration {
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
allow_default: allow_default.into(),
is_default: is_default.into(),
}
}
}
impl TokenParser for FunctionDeclaration {
type Output = Node;
type Output = FunctionDecl;
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult {
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
cursor.expect(Keyword::Function, "function declaration")?;
// TODO: If self.is_default, then this can be empty.
let name = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::OpenParen, "function declaration")?;
@ -98,12 +100,10 @@ impl TokenParser for FunctionDeclaration {
cursor.expect(Punctuator::CloseParen, "function declaration")?;
cursor.expect(Punctuator::OpenBlock, "function declaration")?;
let body = FunctionBody::new(self.allow_yield, self.allow_await)
.parse(cursor)
.map(Node::statement_list)?;
let body = FunctionBody::new(self.allow_yield, self.allow_await).parse(cursor)?;
cursor.expect(Punctuator::CloseBlock, "function declaration")?;
Ok(Node::function_decl(name, params, body))
Ok(FunctionDecl::new(name, params, body))
}
}

25
boa/src/syntax/parser/statement/declaration/lexical.rs

@ -8,7 +8,10 @@
//! [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
use crate::syntax::{
ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind},
ast::{
node::{ConstDecl, ConstDeclList, LetDecl, LetDeclList, Node},
Keyword, Punctuator, TokenKind,
},
parser::{
expression::Initializer, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield,
Cursor, ParseError, ParseResult, TokenParser,
@ -108,22 +111,22 @@ impl TokenParser for BindingList {
let mut const_decls = Vec::new();
loop {
let lexical_binding =
let (ident, init) =
LexicalBinding::new(self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor)?;
if self.is_const {
if let (ident, Some(init)) = lexical_binding {
const_decls.push((ident, init));
if let Some(init) = init {
const_decls.push(ConstDecl::new(ident, init));
} else {
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![TokenKind::Punctuator(Punctuator::Assign)],
cursor.next().ok_or(ParseError::AbruptEnd)?.clone(),
"const declaration",
));
}
} else {
let_decls.push(lexical_binding);
let_decls.push(LetDecl::new(ident, init));
}
match cursor.peek_semicolon(false) {
@ -132,7 +135,7 @@ impl TokenParser for BindingList {
let _ = cursor.next();
}
_ => {
return Err(ParseError::Expected(
return Err(ParseError::expected(
vec![
TokenKind::Punctuator(Punctuator::Semicolon),
TokenKind::LineTerminator,
@ -145,9 +148,9 @@ impl TokenParser for BindingList {
}
if self.is_const {
Ok(Node::const_decl(const_decls))
Ok(ConstDeclList::from(const_decls).into())
} else {
Ok(Node::let_decl(let_decls))
Ok(LetDeclList::from(let_decls).into())
}
}
}
@ -181,9 +184,9 @@ impl LexicalBinding {
}
impl TokenParser for LexicalBinding {
type Output = (String, Option<Node>);
type Output = (Box<str>, Option<Node>);
fn parse(self, cursor: &mut Cursor<'_>) -> Result<(String, Option<Node>), ParseError> {
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> {
let ident = BindingIdentifier::new(self.allow_yield, self.allow_await).parse(cursor)?;
let initializer =
Initializer::new(self.allow_in, self.allow_yield, self.allow_await).try_parse(cursor);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save