Browse Source

Extract the ast to a crate (#2402)

This should hopefully improve our compilation times, both from a clean build and from an incremental compilation snapshot.

Next would be the parser, but it imports `Context`, so it'll require a bit more work.

The number of file changes is obviously big, but almost nothing was changed, I just moved everything to another crate and readjusted the imports of the `parser` module. (Though, I did have to change some details, because there were some functions on the ast that returned `ParseError`s, and the tests had to be moved to the parser)
pull/2403/head
José Julián Espina 2 years ago
parent
commit
b4da172f91
  1. 15
      Cargo.lock
  2. 2
      Cargo.toml
  3. 22
      boa_ast/Cargo.toml
  4. 11
      boa_ast/src/declaration/mod.rs
  5. 88
      boa_ast/src/declaration/variable.rs
  6. 24
      boa_ast/src/expression/access.rs
  7. 24
      boa_ast/src/expression/await.rs
  8. 30
      boa_ast/src/expression/call.rs
  9. 40
      boa_ast/src/expression/identifier.rs
  10. 236
      boa_ast/src/expression/literal/array.rs
  11. 4
      boa_ast/src/expression/literal/mod.rs
  12. 338
      boa_ast/src/expression/literal/object.rs
  13. 47
      boa_ast/src/expression/literal/template.rs
  14. 12
      boa_ast/src/expression/mod.rs
  15. 26
      boa_ast/src/expression/new.rs
  16. 207
      boa_ast/src/expression/operator/assign/mod.rs
  17. 2
      boa_ast/src/expression/operator/assign/op.rs
  18. 12
      boa_ast/src/expression/operator/binary/mod.rs
  19. 10
      boa_ast/src/expression/operator/binary/op.rs
  20. 10
      boa_ast/src/expression/operator/conditional.rs
  21. 3
      boa_ast/src/expression/operator/mod.rs
  22. 11
      boa_ast/src/expression/operator/unary/mod.rs
  23. 2
      boa_ast/src/expression/operator/unary/op.rs
  24. 16
      boa_ast/src/expression/optional.rs
  25. 61
      boa_ast/src/expression/spread.rs
  26. 46
      boa_ast/src/expression/tagged_template.rs
  27. 8
      boa_ast/src/expression/yield.rs
  28. 40
      boa_ast/src/function/arrow_function.rs
  29. 35
      boa_ast/src/function/async_function.rs
  30. 16
      boa_ast/src/function/async_generator.rs
  31. 178
      boa_ast/src/function/class.rs
  32. 14
      boa_ast/src/function/generator.rs
  33. 92
      boa_ast/src/function/mod.rs
  34. 226
      boa_ast/src/function/parameters.rs
  35. 10
      boa_ast/src/keyword.rs
  36. 111
      boa_ast/src/lib.rs
  37. 67
      boa_ast/src/pattern.rs
  38. 17
      boa_ast/src/position.rs
  39. 33
      boa_ast/src/property.rs
  40. 7
      boa_ast/src/punctuator.rs
  41. 40
      boa_ast/src/statement/block.rs
  42. 26
      boa_ast/src/statement/if.rs
  43. 42
      boa_ast/src/statement/iteration/break.rs
  44. 8
      boa_ast/src/statement/iteration/continue.rs
  45. 9
      boa_ast/src/statement/iteration/do_while_loop.rs
  46. 12
      boa_ast/src/statement/iteration/for_in_loop.rs
  47. 24
      boa_ast/src/statement/iteration/for_loop.rs
  48. 15
      boa_ast/src/statement/iteration/for_of_loop.rs
  49. 12
      boa_ast/src/statement/iteration/mod.rs
  50. 9
      boa_ast/src/statement/iteration/while_loop.rs
  51. 11
      boa_ast/src/statement/labelled.rs
  52. 27
      boa_ast/src/statement/mod.rs
  53. 28
      boa_ast/src/statement/return.rs
  54. 18
      boa_ast/src/statement/switch.rs
  55. 24
      boa_ast/src/statement/throw.rs
  56. 97
      boa_ast/src/statement/try.rs
  57. 41
      boa_ast/src/statement_list.rs
  58. 26
      boa_ast/src/visitor.rs
  59. 1
      boa_cli/Cargo.toml
  60. 3
      boa_cli/src/main.rs
  61. 3
      boa_engine/Cargo.toml
  62. 4
      boa_engine/src/builtins/function/arguments.rs
  63. 13
      boa_engine/src/builtins/function/mod.rs
  64. 4
      boa_engine/src/bytecompiler/function.rs
  65. 62
      boa_engine/src/bytecompiler/mod.rs
  66. 3
      boa_engine/src/context/mod.rs
  67. 4
      boa_engine/src/environments/compile.rs
  68. 4
      boa_engine/src/environments/runtime.rs
  69. 144
      boa_engine/src/syntax/ast/expression/literal/array.rs
  70. 180
      boa_engine/src/syntax/ast/expression/literal/object/mod.rs
  71. 109
      boa_engine/src/syntax/ast/expression/literal/object/tests.rs
  72. 475
      boa_engine/src/syntax/ast/expression/operator/assign/mod.rs
  73. 140
      boa_engine/src/syntax/ast/expression/operator/tests.rs
  74. 558
      boa_engine/src/syntax/ast/statement/iteration/tests.rs
  75. 243
      boa_engine/src/syntax/ast/statement/switch/tests.rs
  76. 147
      boa_engine/src/syntax/ast/statement/try/tests.rs
  77. 118
      boa_engine/src/syntax/ast/statement_list/tests.rs
  78. 6
      boa_engine/src/syntax/lexer/comment.rs
  79. 2
      boa_engine/src/syntax/lexer/cursor.rs
  80. 2
      boa_engine/src/syntax/lexer/error.rs
  81. 6
      boa_engine/src/syntax/lexer/identifier.rs
  82. 2
      boa_engine/src/syntax/lexer/mod.rs
  83. 6
      boa_engine/src/syntax/lexer/number.rs
  84. 6
      boa_engine/src/syntax/lexer/operator.rs
  85. 6
      boa_engine/src/syntax/lexer/private_identifier.rs
  86. 6
      boa_engine/src/syntax/lexer/regex.rs
  87. 6
      boa_engine/src/syntax/lexer/spread.rs
  88. 6
      boa_engine/src/syntax/lexer/string.rs
  89. 15
      boa_engine/src/syntax/lexer/template.rs
  90. 10
      boa_engine/src/syntax/lexer/tests.rs
  91. 14
      boa_engine/src/syntax/lexer/token.rs
  92. 3
      boa_engine/src/syntax/mod.rs
  93. 2
      boa_engine/src/syntax/parser/cursor/buffered_lexer/mod.rs
  94. 6
      boa_engine/src/syntax/parser/cursor/mod.rs
  95. 7
      boa_engine/src/syntax/parser/error.rs
  96. 84
      boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs
  97. 8
      boa_engine/src/syntax/parser/expression/assignment/conditional.rs
  98. 14
      boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs
  99. 28
      boa_engine/src/syntax/parser/expression/assignment/mod.rs
  100. 2
      boa_engine/src/syntax/parser/expression/assignment/yield.rs
  101. Some files were not shown because too many files have changed in this diff Show More

15
Cargo.lock generated

@ -55,10 +55,23 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "boa_ast"
version = "0.16.0"
dependencies = [
"bitflags",
"boa_interner",
"boa_macros",
"num-bigint",
"rustc-hash",
"serde",
]
[[package]]
name = "boa_cli"
version = "0.16.0"
dependencies = [
"boa_ast",
"boa_engine",
"boa_interner",
"clap 4.0.18",
@ -76,6 +89,7 @@ name = "boa_engine"
version = "0.16.0"
dependencies = [
"bitflags",
"boa_ast",
"boa_gc",
"boa_interner",
"boa_macros",
@ -117,6 +131,7 @@ dependencies = [
name = "boa_examples"
version = "0.16.0"
dependencies = [
"boa_ast",
"boa_engine",
"boa_gc",
"boa_interner",

2
Cargo.toml

@ -2,6 +2,7 @@
members = [
"boa_cli",
"boa_engine",
"boa_ast",
"boa_gc",
"boa_interner",
"boa_profiler",
@ -28,6 +29,7 @@ boa_gc = { version = "0.16.0", path = "boa_gc" }
boa_profiler = { version = "0.16.0", path = "boa_profiler" }
boa_unicode = { version = "0.16.0", path = "boa_unicode" }
boa_macros = { version = "0.16.0", path = "boa_macros" }
boa_ast = { version = "0.16.0", path = "boa_ast" }
[workspace.metadata.workspaces]
allow_branch = "main"

22
boa_ast/Cargo.toml

@ -0,0 +1,22 @@
[package]
name = "boa_ast"
description = "Abstract Syntax Tree definition for the Boa JavaScript engine."
keywords = ["javascript", "js", "syntax", "ast"]
categories = ["parser-implementations", "compilers"]
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[features]
serde = ["boa_interner/serde", "dep:serde"]
[dependencies]
boa_interner.workspace = true
boa_macros.workspace = true
rustc-hash = "1.1.0"
serde = { version = "1.0.147", features = ["derive"], optional = true }
bitflags = "1.3.2"
num-bigint = "0.4.3"

11
boa_engine/src/syntax/ast/declaration/mod.rs → boa_ast/src/declaration/mod.rs

@ -21,17 +21,16 @@ use super::{
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use tap::Tap;
mod variable;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use variable::*;
/// The `Declaration` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum Declaration {
/// See [`Function`]
@ -163,7 +162,11 @@ impl ToIndentedString for Declaration {
Declaration::AsyncFunction(af) => af.to_indented_string(interner, indentation),
Declaration::AsyncGenerator(ag) => ag.to_indented_string(interner, indentation),
Declaration::Class(c) => c.to_indented_string(interner, indentation),
Declaration::Lexical(l) => l.to_interned_string(interner).tap_mut(|s| s.push(';')),
Declaration::Lexical(l) => {
let mut s = l.to_interned_string(interner);
s.push(';');
s
}
}
}
}

88
boa_engine/src/syntax/ast/declaration/variable.rs → boa_ast/src/declaration/variable.rs

@ -3,14 +3,14 @@
use core::ops::ControlFlow;
use std::convert::TryFrom;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes,
pattern::Pattern,
ContainsSymbol, Statement,
};
use crate::try_break;
use boa_interner::{Interner, ToInternedString};
use super::Declaration;
@ -44,7 +44,7 @@ use super::Declaration;
/// [var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
/// [varstmt]: https://tc39.es/ecma262/#prod-VariableStatement
/// [hoisting]: https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct VarDeclaration(pub VariableList);
@ -91,7 +91,7 @@ impl VisitWith for VarDeclaration {
/// the variable declaration.
///
/// [lexical declaration]: https://tc39.es/ecma262/#sec-let-and-const-declarations
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum LexicalDeclaration {
/// A <code>[const]</code> variable creates a constant whose scope can be either global or local
@ -118,6 +118,7 @@ pub enum LexicalDeclaration {
impl LexicalDeclaration {
/// Gets the inner variable list of the `LexicalDeclaration`
#[must_use]
pub fn variable_list(&self) -> &VariableList {
match self {
LexicalDeclaration::Const(list) | LexicalDeclaration::Let(list) => list,
@ -185,7 +186,7 @@ impl VisitWith for LexicalDeclaration {
}
/// List of variables in a variable declaration.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct VariableList {
list: Box<[Variable]>,
@ -193,6 +194,7 @@ pub struct VariableList {
impl VariableList {
/// Creates a variable list if the provided list of [`Variable`] is not empty.
#[must_use]
pub fn new(list: Box<[Variable]>) -> Option<Self> {
if list.is_empty() {
return None;
@ -274,7 +276,7 @@ impl TryFrom<Vec<Variable>> for VariableList {
/// [spec1]: https://tc39.es/ecma262/#prod-LexicalBinding
/// [spec2]: https://tc39.es/ecma262/#prod-VariableDeclaration
/// [spec3]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Variable {
binding: Binding,
@ -295,7 +297,8 @@ impl ToInternedString for Variable {
impl Variable {
/// Creates a new variable declaration from a `BindingIdentifier`.
#[inline]
pub(in crate::syntax) fn from_identifier(ident: Identifier, init: Option<Expression>) -> Self {
#[must_use]
pub fn from_identifier(ident: Identifier, init: Option<Expression>) -> Self {
Self {
binding: Binding::Identifier(ident),
init,
@ -304,23 +307,32 @@ impl Variable {
/// Creates a new variable declaration from a `Pattern`.
#[inline]
pub(in crate::syntax) fn from_pattern(pattern: Pattern, init: Option<Expression>) -> Self {
#[must_use]
pub fn from_pattern(pattern: Pattern, init: Option<Expression>) -> Self {
Self {
binding: Binding::Pattern(pattern),
init,
}
}
/// Gets the variable declaration binding.
pub(crate) fn binding(&self) -> &Binding {
#[must_use]
pub fn binding(&self) -> &Binding {
&self.binding
}
/// Gets the initialization expression for the variable declaration, if any.
#[inline]
pub(crate) fn init(&self) -> Option<&Expression> {
#[must_use]
pub fn init(&self) -> Option<&Expression> {
self.init.as_ref()
}
/// Gets the list of declared identifiers.
#[must_use]
pub fn idents(&self) -> Vec<Identifier> {
self.binding.idents()
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
if let Some(ref node) = self.init {
@ -338,7 +350,8 @@ impl Variable {
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
#[must_use]
pub fn contains(&self, symbol: ContainsSymbol) -> bool {
if let Some(ref node) = self.init {
if node.contains(symbol) {
return true;
@ -346,11 +359,6 @@ impl Variable {
}
self.binding.contains(symbol)
}
/// Gets the list of declared identifiers.
pub(crate) fn idents(&self) -> Vec<Identifier> {
self.binding.idents()
}
}
impl VisitWith for Variable {
@ -383,7 +391,7 @@ impl VisitWith for Variable {
/// - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum Binding {
/// A single identifier binding.
@ -458,47 +466,3 @@ impl VisitWith for Binding {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt_binding_pattern() {
crate::syntax::ast::test_formatting(
r#"
var { } = {
o: "1",
};
var { o_v1 } = {
o_v1: "1",
};
var { o_v2 = "1" } = {
o_v2: "2",
};
var { a : o_v3 = "1" } = {
a: "2",
};
var { ... o_rest_v1 } = {
a: "2",
};
var { o_v4, o_v5, o_v6 = "1", a : o_v7 = "1", ... o_rest_v2 } = {
o_v4: "1",
o_v5: "1",
};
var [] = [];
var [ , ] = [];
var [ a_v1 ] = [1, 2, 3];
var [ a_v2, a_v3 ] = [1, 2, 3];
var [ a_v2, , a_v3 ] = [1, 2, 3];
var [ ... a_rest_v1 ] = [1, 2, 3];
var [ a_v4, , ... a_rest_v2 ] = [1, 2, 3];
var [ { a_v5 } ] = [{
a_v5: 1,
}, {
a_v5: 2,
}, {
a_v5: 3,
}];
"#,
);
}
}

24
boa_engine/src/syntax/ast/expression/access.rs → boa_ast/src/expression/access.rs

@ -14,16 +14,16 @@
//! [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors
//! [access]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, ContainsSymbol};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// A property access field.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyAccessField {
/// A constant property field, such as `x.prop`.
@ -87,7 +87,7 @@ impl VisitWith for PropertyAccessField {
/// A property access expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyAccess {
/// A simple property access (`x.prop`).
@ -161,7 +161,7 @@ impl VisitWith for PropertyAccess {
}
/// A simple property access, where the target object is an [`Expression`].
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct SimplePropertyAccess {
target: Box<Expression>,
@ -171,12 +171,14 @@ pub struct SimplePropertyAccess {
impl SimplePropertyAccess {
/// Gets the target object of the property access.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
&self.target
}
/// Gets the accessed field of the target object.
#[inline]
#[must_use]
pub fn field(&self) -> &PropertyAccessField {
&self.field
}
@ -252,7 +254,7 @@ impl VisitWith for SimplePropertyAccess {
///
/// [spec]: https://tc39.es/ecma262/#prod-MemberExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct PrivatePropertyAccess {
target: Box<Expression>,
@ -262,6 +264,7 @@ pub struct PrivatePropertyAccess {
impl PrivatePropertyAccess {
/// Creates a `GetPrivateField` AST Expression.
#[inline]
#[must_use]
pub fn new(value: Expression, field: Sym) -> Self {
Self {
target: value.into(),
@ -271,12 +274,14 @@ impl PrivatePropertyAccess {
/// Gets the original object from where to get the field from.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
&self.target
}
/// Gets the name of the field to retrieve.
#[inline]
#[must_use]
pub fn field(&self) -> Sym {
self.field
}
@ -335,19 +340,22 @@ impl VisitWith for PrivatePropertyAccess {
///
/// [spec]: https://tc39.es/ecma262/#prod-SuperProperty
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct SuperPropertyAccess {
field: PropertyAccessField,
}
impl SuperPropertyAccess {
pub(in crate::syntax) fn new(field: PropertyAccessField) -> Self {
/// Creates a new property access field node.
#[must_use]
pub fn new(field: PropertyAccessField) -> Self {
Self { field }
}
/// Gets the name of the field to retrieve.
#[inline]
#[must_use]
pub fn field(&self) -> &PropertyAccessField {
&self.field
}

24
boa_engine/src/syntax/ast/expression/await.rs → boa_ast/src/expression/await.rs

@ -1,10 +1,10 @@
//! Await expression Expression.
use crate::syntax::ast::ContainsSymbol;
use crate::ContainsSymbol;
use core::ops::ControlFlow;
use super::Expression;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
/// An await expression is used within an async function to pause execution and wait for a
@ -16,7 +16,7 @@ use boa_interner::{Interner, ToIndentedString, ToInternedString};
///
/// [spec]: https://tc39.es/ecma262/#prod-AwaitExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Await {
target: Box<Expression>,
@ -25,7 +25,8 @@ pub struct Await {
impl Await {
/// Return the target expression that should be awaited.
#[inline]
pub(crate) fn target(&self) -> &Expression {
#[must_use]
pub fn target(&self) -> &Expression {
&self.target
}
@ -79,18 +80,3 @@ impl VisitWith for Await {
visitor.visit_expression_mut(&mut self.target)
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
// TODO: `let a = await fn()` is invalid syntax as of writing. It should be tested here once implemented.
crate::syntax::ast::test_formatting(
r#"
async function f() {
await function_call();
}
"#,
);
}
}

30
boa_engine/src/syntax/ast/expression/call.rs → boa_ast/src/expression/call.rs

@ -1,6 +1,6 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{join_nodes, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{join_nodes, ContainsSymbol};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
@ -20,7 +20,7 @@ use super::Expression;
///
/// [spec]: https://tc39.es/ecma262/#prod-CallExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Call {
function: Box<Expression>,
@ -30,6 +30,7 @@ pub struct Call {
impl Call {
/// Creates a new `Call` AST Expression.
#[inline]
#[must_use]
pub fn new(function: Expression, args: Box<[Expression]>) -> Self {
Self {
function: function.into(),
@ -39,12 +40,14 @@ impl Call {
/// Gets the target function of this call expression.
#[inline]
#[must_use]
pub fn function(&self) -> &Expression {
&self.function
}
/// Retrieves the arguments passed to the function.
#[inline]
#[must_use]
pub fn args(&self) -> &[Expression] {
&self.args
}
@ -110,7 +113,7 @@ impl VisitWith for Call {
///
/// [spec]: https://tc39.es/ecma262/#prod-SuperCall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct SuperCall {
args: Box<[Expression]>,
@ -118,7 +121,7 @@ pub struct SuperCall {
impl SuperCall {
/// Creates a new `SuperCall` AST node.
pub(crate) fn new<A>(args: A) -> Self
pub fn new<A>(args: A) -> Self
where
A: Into<Box<[Expression]>>,
{
@ -126,7 +129,8 @@ impl SuperCall {
}
/// Retrieves the arguments of the super call.
pub(crate) fn args(&self) -> &[Expression] {
#[must_use]
pub fn arguments(&self) -> &[Expression] {
&self.args
}
@ -176,17 +180,3 @@ impl VisitWith for SuperCall {
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
call_1(1, 2, 3);
call_2("argument here");
call_3();
"#,
);
}
}

40
boa_engine/src/syntax/ast/expression/identifier.rs → boa_ast/src/expression/identifier.rs

@ -1,15 +1,27 @@
//! Local identifier Expression.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
string::ToStringEscaped,
syntax::{ast::Position, parser::ParseError},
visitor::{VisitWith, Visitor, VisitorMut},
ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// List of reserved keywords exclusive to strict mode.
pub const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [
Sym::IMPLEMENTS,
Sym::INTERFACE,
Sym::LET,
Sym::PACKAGE,
Sym::PRIVATE,
Sym::PROTECTED,
Sym::PUBLIC,
Sym::STATIC,
Sym::YIELD,
];
/// An `identifier` is a sequence of characters in the code that identifies a variable,
/// function, or property.
///
@ -27,7 +39,7 @@ use super::Expression;
/// [spec]: https://tc39.es/ecma262/#prod-Identifier
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier
#[cfg_attr(
feature = "deser",
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
@ -54,33 +66,17 @@ impl PartialEq<Identifier> for Sym {
impl Identifier {
/// Creates a new identifier AST Expression.
#[inline]
#[must_use]
pub fn new(ident: Sym) -> Self {
Self { ident }
}
/// Retrieves the identifier's string symbol in the interner.
#[inline]
#[must_use]
pub fn sym(self) -> Sym {
self.ident
}
/// Returns an error if `arguments` or `eval` are used as identifier in strict mode.
pub(crate) fn check_strict_arguments_or_eval(
self,
position: Position,
) -> Result<(), ParseError> {
match self.ident {
Sym::ARGUMENTS => Err(ParseError::general(
"unexpected identifier 'arguments' in strict mode",
position,
)),
Sym::EVAL => Err(ParseError::general(
"unexpected identifier 'eval' in strict mode",
position,
)),
_ => Ok(()),
}
}
}
impl ToInternedString for Identifier {

236
boa_ast/src/expression/literal/array.rs

@ -0,0 +1,236 @@
//! Array declaration Expression.
use crate::expression::operator::assign::AssignTarget;
use crate::pattern::{ArrayPattern, ArrayPatternElement, Pattern};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, ContainsSymbol};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// 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(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayLiteral {
arr: Box<[Option<Expression>]>,
has_trailing_comma_spread: bool,
}
impl ArrayLiteral {
/// Creates a new array literal.
pub fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self
where
A: Into<Box<[Option<Expression>]>>,
{
Self {
arr: array.into(),
has_trailing_comma_spread,
}
}
/// Indicates if a spread operator in the array literal has a trailing comma.
/// This is a syntax error in some cases.
#[must_use]
pub fn has_trailing_comma_spread(&self) -> bool {
self.has_trailing_comma_spread
}
/// Converts this `ArrayLiteral` into an [`ArrayPattern`].
#[must_use]
pub fn to_pattern(&self, strict: bool) -> Option<ArrayPattern> {
if self.has_trailing_comma_spread() {
return None;
}
let mut bindings = Vec::new();
for (i, expr) in self.arr.iter().enumerate() {
let expr = if let Some(expr) = expr {
expr
} else {
bindings.push(ArrayPatternElement::Elision);
continue;
};
match expr {
Expression::Identifier(ident) => {
if strict && *ident == Sym::ARGUMENTS {
return None;
}
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: None,
});
}
Expression::Spread(spread) => {
match spread.target() {
Expression::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleNameRest { ident: *ident });
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccessRest {
access: access.clone(),
});
}
Expression::ArrayLiteral(array) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
Expression::ObjectLiteral(object) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
_ => return None,
}
if i + 1 != self.arr.len() {
return None;
}
}
Expression::Assign(assign) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
AssignTarget::Pattern(pattern) => match pattern {
Pattern::Object(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Object(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
Pattern::Array(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Array(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
},
},
Expression::ArrayLiteral(array) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::ObjectLiteral(object) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
_ => return None,
}
}
Some(ArrayPattern::new(bindings.into()))
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.arr
.iter()
.flatten()
.any(Expression::contains_arguments)
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.arr.iter().flatten().any(|expr| expr.contains(symbol))
}
}
impl AsRef<[Option<Expression>]> for ArrayLiteral {
#[inline]
fn as_ref(&self) -> &[Option<Expression>] {
&self.arr
}
}
impl<T> From<T> for ArrayLiteral
where
T: Into<Box<[Option<Expression>]>>,
{
#[inline]
fn from(decl: T) -> Self {
Self {
arr: decl.into(),
has_trailing_comma_spread: false,
}
}
}
impl ToInternedString for ArrayLiteral {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = String::from("[");
let mut first = true;
for e in &*self.arr {
if first {
first = false;
} else {
buf.push_str(", ");
}
if let Some(e) = e {
buf.push_str(&e.to_interned_string(interner));
}
}
buf.push(']');
buf
}
}
impl From<ArrayLiteral> for Expression {
#[inline]
fn from(arr: ArrayLiteral) -> Self {
Self::ArrayLiteral(arr)
}
}
impl VisitWith for ArrayLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.arr.iter().flatten() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for expr in self.arr.iter_mut().flatten() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

4
boa_engine/src/syntax/ast/expression/literal/mod.rs → boa_ast/src/expression/literal/mod.rs

@ -16,7 +16,7 @@ use core::ops::ControlFlow;
pub use object::ObjectLiteral;
pub use template::{TemplateElement, TemplateLiteral};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use num_bigint::BigInt;
@ -32,7 +32,7 @@ use super::Expression;
///
/// [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
/// A string literal is zero or more characters enclosed in double (`"`) or single (`'`) quotation marks.

338
boa_ast/src/expression/literal/object.rs

@ -0,0 +1,338 @@
//! Object Expression.
use crate::{
block_to_string,
expression::{operator::assign::AssignTarget, Expression, RESERVED_IDENTIFIERS_STRICT},
join_nodes,
pattern::{ObjectPattern, ObjectPatternElement},
property::{MethodDefinition, PropertyDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
ContainsSymbol,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// Objects in JavaScript may be defined as an unordered collection of related data, of
/// primitive or reference types, in the form of “key: value” pairs.
///
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
/// notation.
///
/// An object initializer is an expression that describes the initialization of an
/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
/// Values of object properties can either contain [`primitive`][primitive] data types or other
/// objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectLiteral {
properties: Box<[PropertyDefinition]>,
}
impl ObjectLiteral {
/// Gets the object literal properties
#[inline]
#[must_use]
pub fn properties(&self) -> &[PropertyDefinition] {
&self.properties
}
/// Converts the object literal into an [`ObjectPattern`].
#[must_use]
pub fn to_pattern(&self, strict: bool) -> Option<ObjectPattern> {
let mut bindings = Vec::new();
let mut excluded_keys = Vec::new();
for (i, property) in self.properties.iter().enumerate() {
match property {
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => {
return None
}
PropertyDefinition::IdentifierReference(ident) => {
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: None,
});
}
PropertyDefinition::Property(name, expr) => match (name, expr) {
(PropertyName::Literal(name), Expression::Identifier(ident))
if *name == *ident =>
{
if strict && *name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ObjectLiteral(object)) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ArrayLiteral(array)) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(_, Expression::Assign(assign)) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
if let Some(name) = name.literal() {
if name == *ident {
if strict && name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(name),
default_init: Some(assign.rhs().clone()),
});
} else {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(name),
default_init: Some(assign.rhs().clone()),
});
}
} else {
return None;
}
}
AssignTarget::Pattern(pattern) => {
bindings.push(ObjectPatternElement::Pattern {
name: name.clone(),
pattern: pattern.clone(),
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: Some(assign.rhs().clone()),
});
}
},
(_, Expression::PropertyAccess(access)) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: None,
});
}
(PropertyName::Computed(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Computed(name.clone()),
default_init: None,
});
}
_ => return None,
},
PropertyDefinition::SpreadObject(spread) => {
match spread {
Expression::Identifier(ident) => {
bindings.push(ObjectPatternElement::RestProperty {
ident: *ident,
excluded_keys: excluded_keys.clone(),
});
}
Expression::PropertyAccess(access) => {
bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess {
access: access.clone(),
excluded_keys: excluded_keys.clone(),
});
}
_ => return None,
}
if i + 1 != self.properties.len() {
return None;
}
}
PropertyDefinition::MethodDefinition(_, _) => return None,
PropertyDefinition::CoverInitializedName(ident, expr) => {
if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) {
return None;
}
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: Some(expr.clone()),
});
}
}
}
Some(ObjectPattern::new(bindings.into()))
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.properties
.iter()
.any(PropertyDefinition::contains_arguments)
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.properties.iter().any(|prop| prop.contains(symbol))
}
}
impl ToIndentedString for ObjectLiteral {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let mut buf = "{\n".to_owned();
let indentation = " ".repeat(indent_n + 1);
for property in self.properties().iter() {
buf.push_str(&match property {
PropertyDefinition::IdentifierReference(ident) => {
format!("{indentation}{},\n", interner.resolve_expect(ident.sym()))
}
PropertyDefinition::Property(key, value) => {
format!(
"{indentation}{}: {},\n",
key.to_interned_string(interner),
value.to_no_indent_string(interner, indent_n + 1)
)
}
PropertyDefinition::SpreadObject(key) => {
format!("{indentation}...{},\n", key.to_interned_string(interner))
}
PropertyDefinition::MethodDefinition(key, method) => {
format!(
"{indentation}{}{}({}) {},\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
key.to_interned_string(interner),
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::Generator(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::Async(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
},
)
}
PropertyDefinition::CoverInitializedName(ident, expr) => {
format!(
"{indentation}{} = {},\n",
interner.resolve_expect(ident.sym()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
});
}
buf.push_str(&format!("{}}}", " ".repeat(indent_n)));
buf
}
}
impl<T> From<T> for ObjectLiteral
where
T: Into<Box<[PropertyDefinition]>>,
{
#[inline]
fn from(props: T) -> Self {
Self {
properties: props.into(),
}
}
}
impl From<ObjectLiteral> for Expression {
#[inline]
fn from(obj: ObjectLiteral) -> Self {
Self::ObjectLiteral(obj)
}
}
impl VisitWith for ObjectLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for pd in self.properties.iter() {
try_break!(visitor.visit_property_definition(pd));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for pd in self.properties.iter_mut() {
try_break!(visitor.visit_property_definition_mut(pd));
}
ControlFlow::Continue(())
}
}

47
boa_engine/src/syntax/ast/expression/literal/template.rs → boa_ast/src/expression/literal/template.rs

@ -5,11 +5,11 @@ use std::borrow::Cow;
use boa_interner::{Interner, Sym, ToInternedString};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
string::ToStringEscaped,
syntax::ast::{expression::Expression, ContainsSymbol},
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
ContainsSymbol, ToStringEscaped,
};
/// Template literals are string literals allowing embedded expressions.
@ -20,7 +20,7 @@ use crate::{
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct TemplateLiteral {
elements: Box<[TemplateElement]>,
@ -39,7 +39,7 @@ impl From<TemplateLiteral> for Expression {
/// node as the equivalent of the components found in a template literal.
///
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum TemplateElement {
/// A simple string.
@ -51,11 +51,14 @@ pub enum TemplateElement {
impl TemplateLiteral {
/// Creates a new `TemplateLiteral` from a list of [`TemplateElement`]s.
#[inline]
#[must_use]
pub fn new(elements: Box<[TemplateElement]>) -> Self {
Self { elements }
}
pub(crate) fn elements(&self) -> &[TemplateElement] {
/// Gets the element list of this `TemplateLiteral`.
#[must_use]
pub fn elements(&self) -> &[TemplateElement] {
&self.elements
}
@ -142,35 +145,3 @@ impl VisitWith for TemplateElement {
}
}
}
#[cfg(test)]
mod tests {
use crate::exec;
#[test]
fn template_literal() {
let scenario = r#"
let a = 10;
`result: ${a} and ${a+10}`;
"#;
assert_eq!(&exec(scenario), "\"result: 10 and 20\"");
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
function tag(t, ...args) {
let a = [];
a = a.concat([t[0], t[1], t[2]]);
a = a.concat([t.raw[0], t.raw[1], t.raw[2]]);
a = a.concat([args[0], args[1]]);
return a;
}
let a = 10;
tag`result: ${a} \x26 ${a + 10}`;
"#,
);
}
}

12
boa_engine/src/syntax/ast/expression/mod.rs → boa_ast/src/expression/mod.rs

@ -33,9 +33,9 @@ mod spread;
mod tagged_template;
mod r#yield;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use call::{Call, SuperCall};
pub use identifier::Identifier;
pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT};
pub use new::New;
pub use optional::{Optional, OptionalOperation, OptionalOperationKind};
pub use r#await::Await;
@ -50,7 +50,7 @@ pub mod operator;
/// The `Expression` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
/// The JavaScript `this` keyword refers to the object it belongs to.
@ -199,7 +199,8 @@ impl Expression {
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments
// TODO: replace with a visitor
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
#[must_use]
pub fn contains_arguments(&self) -> bool {
match self {
Expression::Identifier(ident) => *ident == Sym::ARGUMENTS,
Expression::Function(_)
@ -239,7 +240,8 @@ impl Expression {
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
// TODO: replace with a visitor
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
#[must_use]
pub fn contains(&self, symbol: ContainsSymbol) -> bool {
match self {
Expression::This => symbol == ContainsSymbol::This,
Expression::Identifier(_)

26
boa_engine/src/syntax/ast/expression/new.rs → boa_ast/src/expression/new.rs

@ -1,5 +1,5 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Call, ContainsSymbol};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Call, ContainsSymbol};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
@ -20,7 +20,7 @@ use super::Expression;
///
/// [spec]: https://tc39.es/ecma262/#prod-NewExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct New {
call: Call,
@ -29,18 +29,21 @@ pub struct New {
impl New {
/// Gets the constructor of the new expression.
#[inline]
#[must_use]
pub fn constructor(&self) -> &Expression {
self.call.function()
}
/// Retrieves the arguments passed to the constructor.
#[inline]
pub fn args(&self) -> &[Expression] {
#[must_use]
pub fn arguments(&self) -> &[Expression] {
self.call.args()
}
/// Returns the inner call expression.
pub(crate) fn call(&self) -> &Call {
#[must_use]
pub fn call(&self) -> &Call {
&self.call
}
@ -91,16 +94,3 @@ impl VisitWith for New {
visitor.visit_call_mut(&mut self.call)
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
function MyClass() {}
let inst = new MyClass();
"#,
);
}
}

207
boa_ast/src/expression/operator/assign/mod.rs

@ -0,0 +1,207 @@
//! Assignment expression nodes, as defined by the [spec].
//!
//! An [assignment operator][mdn] assigns a value to its left operand based on the value of its right
//! operand. Almost any [`LeftHandSideExpression`][lhs] Parse Node can be the target of a simple
//! assignment expression (`=`). However, the compound assignment operations such as `%=` or `??=`
//! only allow ["simple"][simple] left hand side expressions as an assignment target.
//!
//! [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
//! [lhs]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
//! [simple]: https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, Sym, ToInternedString};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{access::PropertyAccess, identifier::Identifier, Expression},
pattern::Pattern,
ContainsSymbol,
};
/// An assignment operator expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Assign {
op: AssignOp,
lhs: Box<AssignTarget>,
rhs: Box<Expression>,
}
impl Assign {
/// Creates an `Assign` AST Expression.
#[must_use]
pub fn new(op: AssignOp, lhs: AssignTarget, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
/// Gets the operator of the assignment operation.
#[inline]
#[must_use]
pub fn op(&self) -> AssignOp {
self.op
}
/// Gets the left hand side of the assignment operation.
#[inline]
#[must_use]
pub fn lhs(&self) -> &AssignTarget {
&self.lhs
}
/// Gets the right hand side of the assignment operation.
#[inline]
#[must_use]
pub fn rhs(&self) -> &Expression {
&self.rhs
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
(match &*self.lhs {
AssignTarget::Identifier(ident) => *ident == Sym::ARGUMENTS,
AssignTarget::Access(access) => access.contains_arguments(),
AssignTarget::Pattern(pattern) => pattern.contains_arguments(),
} || self.rhs.contains_arguments())
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
(match &*self.lhs {
AssignTarget::Identifier(_) => false,
AssignTarget::Access(access) => access.contains(symbol),
AssignTarget::Pattern(pattern) => pattern.contains(symbol),
} || self.rhs.contains(symbol))
}
}
impl ToInternedString for Assign {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {} {}",
self.lhs.to_interned_string(interner),
self.op,
self.rhs.to_interned_string(interner)
)
}
}
impl From<Assign> for Expression {
#[inline]
fn from(op: Assign) -> Self {
Self::Assign(op)
}
}
impl VisitWith for Assign {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_assign_target(&self.lhs));
visitor.visit_expression(&self.rhs)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_assign_target_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}
/// The valid left-hand-side expressions of an assignment operator. Also called
/// [`LeftHandSideExpression`][spec] in the spec.
///
/// [spec]: hhttps://tc39.es/ecma262/#prod-LeftHandSideExpression
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum AssignTarget {
/// A simple identifier, such as `a`.
Identifier(Identifier),
/// A property access, such as `a.prop`.
Access(PropertyAccess),
/// A pattern assignment, such as `{a, b, ...c}`.
Pattern(Pattern),
}
impl AssignTarget {
/// Converts the left-hand-side Expression of an assignment expression into an [`AssignTarget`].
/// Returns `None` if the given Expression is an invalid left-hand-side for a assignment expression.
#[must_use]
pub fn from_expression(
expression: &Expression,
strict: bool,
destructure: bool,
) -> Option<Self> {
match expression {
Expression::Identifier(id) => Some(Self::Identifier(*id)),
Expression::PropertyAccess(access) => Some(Self::Access(access.clone())),
Expression::ObjectLiteral(object) if destructure => {
let pattern = object.to_pattern(strict)?;
Some(Self::Pattern(pattern.into()))
}
Expression::ArrayLiteral(array) if destructure => {
let pattern = array.to_pattern(strict)?;
Some(Self::Pattern(pattern.into()))
}
_ => None,
}
}
}
impl ToInternedString for AssignTarget {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
AssignTarget::Identifier(id) => id.to_interned_string(interner),
AssignTarget::Access(access) => access.to_interned_string(interner),
AssignTarget::Pattern(pattern) => pattern.to_interned_string(interner),
}
}
}
impl From<Identifier> for AssignTarget {
#[inline]
fn from(target: Identifier) -> Self {
Self::Identifier(target)
}
}
impl VisitWith for AssignTarget {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
AssignTarget::Identifier(id) => visitor.visit_identifier(id),
AssignTarget::Access(pa) => visitor.visit_property_access(pa),
AssignTarget::Pattern(pat) => visitor.visit_pattern(pat),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
AssignTarget::Identifier(id) => visitor.visit_identifier_mut(id),
AssignTarget::Access(pa) => visitor.visit_property_access_mut(pa),
AssignTarget::Pattern(pat) => visitor.visit_pattern_mut(pat),
}
}
}

2
boa_engine/src/syntax/ast/expression/operator/assign/op.rs → boa_ast/src/expression/operator/assign/op.rs

@ -11,7 +11,7 @@
///
/// [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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignOp {
/// The assignment operator assigns the value of the right operand to the left operand.

12
boa_engine/src/syntax/ast/expression/operator/binary/mod.rs → boa_ast/src/expression/operator/binary/mod.rs

@ -21,14 +21,14 @@ pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, ContainsSymbol};
/// Binary operations require two operands, one before the operator and one after the operator.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Binary {
op: BinaryOp,
@ -38,7 +38,8 @@ pub struct Binary {
impl Binary {
/// Creates a `BinOp` AST Expression.
pub(in crate::syntax) fn new(op: BinaryOp, lhs: Expression, rhs: Expression) -> Self {
#[must_use]
pub fn new(op: BinaryOp, lhs: Expression, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
@ -48,18 +49,21 @@ impl Binary {
/// Gets the binary operation of the Expression.
#[inline]
#[must_use]
pub fn op(&self) -> BinaryOp {
self.op
}
/// Gets the left hand side of the binary operation.
#[inline]
#[must_use]
pub fn lhs(&self) -> &Expression {
&self.lhs
}
/// Gets the right hand side of the binary operation.
#[inline]
#[must_use]
pub fn rhs(&self) -> &Expression {
&self.rhs
}

10
boa_engine/src/syntax/ast/expression/operator/binary/op.rs → boa_ast/src/expression/operator/binary/op.rs

@ -3,7 +3,7 @@
use std::fmt::{Display, Formatter, Result};
/// This represents a binary operation between two values.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp {
/// Numeric operation.
@ -85,7 +85,7 @@ impl Display for BinaryOp {
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Arithmetic
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ArithmeticOp {
/// The addition operator produces the sum of numeric operands or string concatenation.
@ -195,7 +195,7 @@ impl Display for ArithmeticOp {
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BitwiseOp {
/// Performs the AND operation on each pair of bits. a AND b yields 1 only if both a and b are 1.
@ -318,7 +318,7 @@ impl Display for BitwiseOp {
///
/// [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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RelationalOp {
/// The equality operator converts the operands if they are not of the same type, then applies
@ -511,7 +511,7 @@ impl Display for RelationalOp {
///
/// [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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogicalOp {
/// The logical AND operator returns the value of the first operand if it can be coerced into `false`;

10
boa_engine/src/syntax/ast/expression/operator/conditional.rs → boa_ast/src/expression/operator/conditional.rs

@ -1,6 +1,6 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, ContainsSymbol};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
@ -18,7 +18,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-ConditionalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Conditional {
condition: Box<Expression>,
@ -29,24 +29,28 @@ pub struct Conditional {
impl Conditional {
/// Gets the condition of the `Conditional` expression.
#[inline]
#[must_use]
pub fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the expression returned if `condition` is truthy.
#[inline]
#[must_use]
pub fn if_true(&self) -> &Expression {
&self.if_true
}
/// Gets the expression returned if `condition` is falsy.
#[inline]
#[must_use]
pub fn if_false(&self) -> &Expression {
&self.if_false
}
/// Creates a `Conditional` AST Expression.
#[inline]
#[must_use]
pub fn new(condition: Expression, if_true: Expression, if_false: Expression) -> Self {
Self {
condition: Box::new(condition),

3
boa_engine/src/syntax/ast/expression/operator/mod.rs → boa_ast/src/expression/operator/mod.rs

@ -18,6 +18,3 @@ pub mod binary;
pub mod unary;
pub use self::{assign::Assign, binary::Binary, conditional::Conditional, unary::Unary};
#[cfg(test)]
mod tests;

11
boa_engine/src/syntax/ast/expression/operator/unary/mod.rs → boa_ast/src/expression/operator/unary/mod.rs

@ -18,8 +18,8 @@ pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, ContainsSymbol};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, ContainsSymbol};
/// A unary expression is an operation with only one operand.
///
@ -29,7 +29,7 @@ use crate::syntax::ast::{expression::Expression, ContainsSymbol};
///
/// [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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Unary {
op: UnaryOp,
@ -38,7 +38,8 @@ pub struct Unary {
impl Unary {
/// Creates a new `UnaryOp` AST Expression.
pub(in crate::syntax) fn new(op: UnaryOp, target: Expression) -> Self {
#[must_use]
pub fn new(op: UnaryOp, target: Expression) -> Self {
Self {
op,
target: Box::new(target),
@ -47,12 +48,14 @@ impl Unary {
/// Gets the unary operation of the Expression.
#[inline]
#[must_use]
pub fn op(&self) -> UnaryOp {
self.op
}
/// Gets the target of this unary operator.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
self.target.as_ref()
}

2
boa_engine/src/syntax/ast/expression/operator/unary/op.rs → boa_ast/src/expression/operator/unary/op.rs

@ -10,7 +10,7 @@
///
/// [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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryOp {
/// The increment operator increments (adds one to) its operand and returns a value.

16
boa_engine/src/syntax/ast/expression/optional.rs → boa_ast/src/expression/optional.rs

@ -1,14 +1,14 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{join_nodes, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{join_nodes, ContainsSymbol};
use super::{access::PropertyAccessField, Expression};
/// List of valid operations in an [`Optional`] chain.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum OptionalOperationKind {
/// A property access (`a?.prop`).
@ -91,7 +91,7 @@ impl VisitWith for OptionalOperationKind {
/// (`?.item`) will force the expression to return `undefined` if the target is `undefined` or `null`.
/// In contrast, a non-shorted operation (`.prop`) will try to access the property, even if the target
/// is `undefined` or `null`.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct OptionalOperation {
kind: OptionalOperationKind,
@ -101,11 +101,13 @@ pub struct OptionalOperation {
impl OptionalOperation {
/// Creates a new `OptionalOperation`.
#[inline]
#[must_use]
pub fn new(kind: OptionalOperationKind, shorted: bool) -> Self {
Self { kind, shorted }
}
/// Gets the kind of operation.
#[inline]
#[must_use]
pub fn kind(&self) -> &OptionalOperationKind {
&self.kind
}
@ -113,6 +115,7 @@ impl OptionalOperation {
/// Returns `true` if the operation short-circuits the [`Optional`] chain when the target is
/// `undefined` or `null`.
#[inline]
#[must_use]
pub fn shorted(&self) -> bool {
self.shorted
}
@ -200,7 +203,7 @@ impl VisitWith for OptionalOperation {
///
/// [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-OptionalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Optional {
target: Box<Expression>,
@ -234,6 +237,7 @@ impl VisitWith for Optional {
impl Optional {
/// Creates a new `Optional` expression.
#[inline]
#[must_use]
pub fn new(target: Expression, chain: Box<[OptionalOperation]>) -> Self {
Self {
target: Box::new(target),
@ -243,12 +247,14 @@ impl Optional {
/// Gets the target of this `Optional` expression.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
self.target.as_ref()
}
/// Gets the chain of accesses and calls that will be applied to the target at runtime.
#[inline]
#[must_use]
pub fn chain(&self) -> &[OptionalOperation] {
self.chain.as_ref()
}

61
boa_engine/src/syntax/ast/expression/spread.rs → boa_ast/src/expression/spread.rs

@ -1,8 +1,8 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::ContainsSymbol;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::ContainsSymbol;
use super::Expression;
@ -22,8 +22,8 @@ use super::Expression;
///
/// [spec]: https://tc39.es/ecma262/#prod-SpreadElement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "deser", serde(transparent))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Clone, Debug, PartialEq)]
pub struct Spread {
target: Box<Expression>,
@ -32,12 +32,14 @@ pub struct Spread {
impl Spread {
/// Gets the target expression to be expanded by the spread operator.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
&self.target
}
/// Creates a `Spread` AST Expression.
#[inline]
#[must_use]
pub fn new(target: Expression) -> Self {
Self {
target: Box::new(target),
@ -84,54 +86,3 @@ impl VisitWith for Spread {
visitor.visit_expression_mut(&mut self.target)
}
}
#[cfg(test)]
mod tests {
use crate::exec;
#[test]
fn spread_with_new() {
let scenario = r#"
function F(m) {
this.m = m;
}
function f(...args) {
return new F(...args);
}
let a = f('message');
a.m;
"#;
assert_eq!(&exec(scenario), r#""message""#);
}
#[test]
fn spread_with_call() {
let scenario = r#"
function f(m) {
return m;
}
function g(...args) {
return f(...args);
}
let a = g('message');
a;
"#;
assert_eq!(&exec(scenario), r#""message""#);
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
function f(m) {
return m;
}
function g(...args) {
return f(...args);
}
let a = g("message");
a;
"#,
);
}
}

46
boa_engine/src/syntax/ast/expression/tagged_template.rs → boa_ast/src/expression/tagged_template.rs

@ -1,9 +1,9 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::ContainsSymbol;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::ContainsSymbol;
use super::Expression;
@ -14,7 +14,7 @@ use super::Expression;
///
/// [moz]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
/// [spec]: https://tc39.es/ecma262/#sec-tagged-templates
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct TaggedTemplate {
tag: Box<Expression>,
@ -27,6 +27,7 @@ impl TaggedTemplate {
/// Creates a new tagged template with a tag, the list of raw strings, the cooked strings and
/// the expressions.
#[inline]
#[must_use]
pub fn new(
tag: Expression,
raws: Box<[Sym]>,
@ -41,23 +42,31 @@ impl TaggedTemplate {
}
}
/// Gets the tag function of the template.
#[inline]
pub(crate) fn tag(&self) -> &Expression {
#[must_use]
pub fn tag(&self) -> &Expression {
&self.tag
}
/// Gets the inner raw strings of the template.
#[inline]
pub(crate) fn raws(&self) -> &[Sym] {
#[must_use]
pub fn raws(&self) -> &[Sym] {
&self.raws
}
/// Gets the cooked strings of the template.
#[inline]
pub(crate) fn cookeds(&self) -> &[Option<Sym>] {
#[must_use]
pub fn cookeds(&self) -> &[Option<Sym>] {
&self.cookeds
}
/// Gets the interpolated expressions of the template.
#[inline]
pub(crate) fn exprs(&self) -> &[Expression] {
#[must_use]
pub fn exprs(&self) -> &[Expression] {
&self.exprs
}
@ -131,26 +140,3 @@ impl VisitWith for TaggedTemplate {
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn tagged_template() {
let scenario = r#"
function tag(t, ...args) {
let a = []
a = a.concat([t[0], t[1], t[2]]);
a = a.concat([t.raw[0], t.raw[1], t.raw[2]]);
a = a.concat([args[0], args[1]]);
return a
}
let a = 10;
tag`result: ${a} \x26 ${a+10}`;
"#;
assert_eq!(
&crate::exec(scenario),
r#"[ "result: ", " & ", "", "result: ", " \x26 ", "", 10, 20 ]"#
);
}
}

8
boa_engine/src/syntax/ast/expression/yield.rs → boa_ast/src/expression/yield.rs

@ -1,8 +1,8 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::ContainsSymbol;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::ContainsSymbol;
use super::Expression;
@ -14,7 +14,7 @@ use super::Expression;
///
/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Yield {
target: Option<Box<Expression>>,
@ -30,12 +30,14 @@ impl Yield {
/// Returns `true` if this `Yield` statement delegates to another generator or iterable object.
#[inline]
#[must_use]
pub fn delegate(&self) -> bool {
self.delegate
}
/// Creates a `Yield` AST Expression.
#[inline]
#[must_use]
pub fn new(expr: Option<Expression>, delegate: bool) -> Self {
Self {
target: expr.map(Box::new),

40
boa_engine/src/syntax/ast/function/arrow_function.rs → boa_ast/src/function/arrow_function.rs

@ -1,9 +1,9 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, ContainsSymbol, StatementList,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
@ -18,7 +18,7 @@ use super::FormalParameterList;
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrowFunction {
name: Option<Identifier>,
@ -29,11 +29,8 @@ pub struct ArrowFunction {
impl ArrowFunction {
/// Creates a new `ArrowFunctionDecl` AST Expression.
#[inline]
pub(in crate::syntax) fn new(
name: Option<Identifier>,
params: FormalParameterList,
body: StatementList,
) -> Self {
#[must_use]
pub fn new(name: Option<Identifier>, params: FormalParameterList, body: StatementList) -> Self {
Self {
name,
parameters: params,
@ -43,6 +40,7 @@ impl ArrowFunction {
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub fn name(&self) -> Option<Identifier> {
self.name
}
@ -55,13 +53,15 @@ impl ArrowFunction {
/// Gets the list of parameters of the arrow function.
#[inline]
pub(crate) fn parameters(&self) -> &FormalParameterList {
#[must_use]
pub fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the arrow function.
#[inline]
pub(crate) fn body(&self) -> &StatementList {
#[must_use]
pub fn body(&self) -> &StatementList {
&self.body
}
@ -88,7 +88,7 @@ impl ArrowFunction {
impl ToIndentedString for ArrowFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!("({}", join_nodes(interner, &self.parameters.parameters));
let mut buf = format!("({}", join_nodes(interner, self.parameters.as_ref()));
if self.body().statements().is_empty() {
buf.push_str(") => {}");
} else {
@ -131,19 +131,3 @@ impl VisitWith for ArrowFunction {
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
let arrow_func = (a, b) => {
console.log("in multi statement arrow");
console.log(b);
};
let arrow_func_2 = (a, b) => {};
"#,
);
}
}

35
boa_engine/src/syntax/ast/function/async_function.rs → boa_ast/src/function/async_function.rs

@ -1,11 +1,11 @@
//! Async Function Expression.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
@ -19,7 +19,7 @@ use super::FormalParameterList;
///
/// [spec]: https://tc39.es/ecma262/#sec-async-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncFunction {
name: Option<Identifier>,
@ -30,7 +30,8 @@ pub struct AsyncFunction {
impl AsyncFunction {
/// Creates a new function expression
#[inline]
pub(in crate::syntax) fn new(
#[must_use]
pub fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
@ -44,18 +45,21 @@ impl AsyncFunction {
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the function declaration.
#[inline]
#[must_use]
pub fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub fn body(&self) -> &StatementList {
&self.body
}
@ -69,7 +73,7 @@ impl ToIndentedString for AsyncFunction {
}
buf.push_str(&format!(
"({}",
join_nodes(interner, &self.parameters.parameters)
join_nodes(interner, self.parameters.as_ref())
));
if self.body().statements().is_empty() {
buf.push_str(") {}");
@ -121,22 +125,3 @@ impl VisitWith for AsyncFunction {
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
async function async_func(a, b) {
console.log(a);
}
async function async_func_2(a, b) {}
pass_async_func(async function(a, b) {
console.log("in async callback", a);
});
pass_async_func(async function(a, b) {});
"#,
);
}
}

16
boa_engine/src/syntax/ast/function/async_generator.rs → boa_ast/src/function/async_generator.rs

@ -1,11 +1,11 @@
//! Async Generator Expression
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
@ -18,7 +18,7 @@ use super::FormalParameterList;
///
/// [spec]: https://tc39.es/ecma262/#sec-async-generator-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncGenerator {
name: Option<Identifier>,
@ -29,7 +29,8 @@ pub struct AsyncGenerator {
impl AsyncGenerator {
/// Creates a new async generator expression
#[inline]
pub(in crate::syntax) fn new(
#[must_use]
pub fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
@ -43,18 +44,21 @@ impl AsyncGenerator {
/// Gets the name of the async generator expression
#[inline]
#[must_use]
pub fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the async generator expression
#[inline]
#[must_use]
pub fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the async generator expression
#[inline]
#[must_use]
pub fn body(&self) -> &StatementList {
&self.body
}
@ -68,7 +72,7 @@ impl ToIndentedString for AsyncGenerator {
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, &self.parameters.parameters),
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));

178
boa_engine/src/syntax/ast/function/class.rs → boa_ast/src/function/class.rs

@ -1,17 +1,14 @@
use core::ops::ControlFlow;
use std::borrow::Cow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
string::ToStringEscaped,
syntax::ast::{
block_to_string,
expression::{Expression, Identifier},
join_nodes,
property::{MethodDefinition, PropertyName},
ContainsSymbol, Declaration, StatementList, StatementListItem,
},
block_to_string,
expression::{Expression, Identifier},
join_nodes,
property::{MethodDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
ContainsSymbol, Declaration, StatementList, StatementListItem, ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
@ -24,7 +21,7 @@ use super::Function;
///
/// [spec]: https://tc39.es/ecma262/#sec-class-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Class {
name: Option<Identifier>,
@ -36,7 +33,8 @@ pub struct Class {
impl Class {
/// Creates a new class declaration.
#[inline]
pub(in crate::syntax) fn new(
#[must_use]
pub fn new(
name: Option<Identifier>,
super_ref: Option<Expression>,
constructor: Option<Function>,
@ -52,25 +50,29 @@ impl Class {
/// Returns the name of the class.
#[inline]
pub(crate) fn name(&self) -> Option<Identifier> {
#[must_use]
pub fn name(&self) -> Option<Identifier> {
self.name
}
/// Returns the super class ref of the class.
#[inline]
pub(crate) fn super_ref(&self) -> Option<&Expression> {
#[must_use]
pub fn super_ref(&self) -> Option<&Expression> {
self.super_ref.as_ref()
}
/// Returns the constructor of the class.
#[inline]
pub(crate) fn constructor(&self) -> Option<&Function> {
#[must_use]
pub fn constructor(&self) -> Option<&Function> {
self.constructor.as_ref()
}
/// Gets the list of all fields defined on the class.
#[inline]
pub(crate) fn elements(&self) -> &[ClassElement] {
#[must_use]
pub fn elements(&self) -> &[ClassElement] {
&self.elements
}
@ -126,7 +128,7 @@ impl ToIndentedString for Class {
if let Some(expr) = &self.constructor {
buf.push_str(&format!(
"{indentation}constructor({}) {}\n",
join_nodes(interner, &expr.parameters().parameters),
join_nodes(interner, expr.parameters().as_ref()),
block_to_string(expr.body(), interner, indent_n + 1)
));
}
@ -145,16 +147,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
@ -188,16 +190,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
@ -258,16 +260,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
@ -301,16 +303,16 @@ impl ToIndentedString for Class {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, &expr.parameters().parameters)
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
@ -423,7 +425,7 @@ impl VisitWith for Class {
/// An element that can be within a [`Class`], as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ClassElement {
/// A method definition, including `get` and `set` accessors.
@ -564,113 +566,3 @@ impl VisitWith for ClassElement {
}
}
}
#[cfg(test)]
mod tests {
use crate::syntax::ast::test_formatting;
#[test]
fn class_declaration_empty() {
test_formatting(
r#"
class A {}
"#,
);
}
#[test]
fn class_declaration_empty_extends() {
test_formatting(
r#"
class A extends Object {}
"#,
);
}
#[test]
fn class_declaration_constructor() {
test_formatting(
r#"
class A {
constructor(a, b, c) {
this.value = a + b + c;
}
}
"#,
);
}
#[test]
fn class_declaration_elements() {
test_formatting(
r#"
class A {
a;
b = 1;
c() {}
d(a, b, c) {
return a + b + c;
}
set e(value) {}
get e() {}
set(a, b) {}
get(a, b) {}
}
"#,
);
}
#[test]
fn class_declaration_elements_private() {
test_formatting(
r#"
class A {
#a;
#b = 1;
#c() {}
#d(a, b, c) {
return a + b + c;
}
set #e(value) {}
get #e() {}
}
"#,
);
}
#[test]
fn class_declaration_elements_static() {
test_formatting(
r#"
class A {
static a;
static b = 1;
static c() {}
static d(a, b, c) {
return a + b + c;
}
static set e(value) {}
static get e() {}
}
"#,
);
}
#[test]
fn class_declaration_elements_private_static() {
test_formatting(
r#"
class A {
static #a;
static #b = 1;
static #c() {}
static #d(a, b, c) {
return a + b + c;
}
static set #e(value) {}
static get #e() {}
}
"#,
);
}
}

14
boa_engine/src/syntax/ast/function/generator.rs → boa_ast/src/function/generator.rs

@ -1,12 +1,12 @@
use crate::syntax::ast::{
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString};
use super::FormalParameterList;
@ -20,7 +20,7 @@ use super::FormalParameterList;
///
/// [spec]: https://tc39.es/ecma262/#sec-generator-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Generator {
name: Option<Identifier>,
@ -31,7 +31,8 @@ pub struct Generator {
impl Generator {
/// Creates a new generator expression
#[inline]
pub(in crate::syntax) fn new(
#[must_use]
pub fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
@ -45,18 +46,21 @@ impl Generator {
/// Gets the name of the generator declaration.
#[inline]
#[must_use]
pub fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the generator declaration.
#[inline]
#[must_use]
pub fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the generator declaration.
#[inline]
#[must_use]
pub fn body(&self) -> &StatementList {
&self.body
}
@ -70,7 +74,7 @@ impl ToIndentedString for Generator {
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, &self.parameters.parameters),
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));

92
boa_engine/src/syntax/ast/function/mod.rs → boa_ast/src/function/mod.rs

@ -33,17 +33,15 @@ pub use async_generator::AsyncGenerator;
pub use class::{Class, ClassElement};
use core::ops::ControlFlow;
pub use generator::Generator;
pub use parameters::{FormalParameter, FormalParameterList};
pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags};
pub(crate) use parameters::FormalParameterListFlags;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{block_to_string, join_nodes, StatementList};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{block_to_string, join_nodes, StatementList};
use boa_interner::{Interner, ToIndentedString};
use super::expression::{Expression, Identifier};
use super::{ContainsSymbol, Declaration};
use super::Declaration;
/// A function definition, as defined by the [spec].
///
@ -55,7 +53,7 @@ use super::{ContainsSymbol, Declaration};
///
/// [spec]: https://tc39.es/ecma262/#sec-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Function {
name: Option<Identifier>,
@ -66,7 +64,8 @@ pub struct Function {
impl Function {
/// Creates a new function expression
#[inline]
pub(in crate::syntax) fn new(
#[must_use]
pub fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
@ -80,18 +79,21 @@ impl Function {
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the function declaration.
#[inline]
#[must_use]
pub fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub fn body(&self) -> &StatementList {
&self.body
}
@ -105,7 +107,7 @@ impl ToIndentedString for Function {
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, &self.parameters.parameters),
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
@ -127,47 +129,6 @@ impl From<Function> for Declaration {
}
}
/// Helper function to check if a function contains a super call or super property access.
pub(crate) fn function_contains_super(
body: &StatementList,
parameters: &FormalParameterList,
) -> bool {
for param in parameters.parameters.iter() {
if param.variable().contains(ContainsSymbol::SuperCall)
|| param.variable().contains(ContainsSymbol::SuperProperty)
{
return true;
}
}
for node in body.statements() {
if node.contains(ContainsSymbol::SuperCall) || node.contains(ContainsSymbol::SuperProperty)
{
return true;
}
}
false
}
/// Returns `true` if the function parameters or body contain a direct `super` call.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper
pub(crate) fn has_direct_super(body: &StatementList, parameters: &FormalParameterList) -> bool {
for param in parameters.parameters.iter() {
if param.variable().contains(ContainsSymbol::SuperCall) {
return true;
}
}
for node in body.statements() {
if node.contains(ContainsSymbol::SuperCall) {
return true;
}
}
false
}
impl VisitWith for Function {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
@ -191,34 +152,3 @@ impl VisitWith for Function {
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg(test)]
mod tests {
#[test]
fn duplicate_function_name() {
let scenario = r#"
function f () {}
function f () {return 12;}
f()
"#;
assert_eq!(&crate::exec(scenario), "12");
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
function func(a, b) {
console.log(a);
}
function func_2(a, b) {}
pass_func(function(a, b) {
console.log("in callback", a);
});
pass_func(function(a, b) {});
"#,
);
}
}

226
boa_engine/src/syntax/ast/function/parameters.rs → boa_ast/src/function/parameters.rs

@ -1,14 +1,11 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::{
ast::{
declaration::{Binding, Variable},
expression::{Expression, Identifier},
pattern::Pattern,
ContainsSymbol, Position,
},
parser::ParseError,
use crate::{
declaration::{Binding, Variable},
expression::{Expression, Identifier},
pattern::Pattern,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
ContainsSymbol,
};
use crate::try_break;
use bitflags::bitflags;
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
@ -17,30 +14,18 @@ use rustc_hash::FxHashSet;
/// A list of `FormalParameter`s that describes the parameters of a function, as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FormalParameterList {
pub(crate) parameters: Box<[FormalParameter]>,
pub(crate) flags: FormalParameterListFlags,
pub(crate) length: u32,
parameters: Box<[FormalParameter]>,
flags: FormalParameterListFlags,
length: u32,
}
impl FormalParameterList {
/// Creates a new formal parameter list.
pub(crate) fn new(
parameters: Box<[FormalParameter]>,
flags: FormalParameterListFlags,
length: u32,
) -> Self {
Self {
parameters,
flags,
length,
}
}
/// Creates a new empty formal parameter list.
pub(crate) fn empty() -> Self {
#[must_use]
pub fn new() -> Self {
Self {
parameters: Box::new([]),
flags: FormalParameterListFlags::default(),
@ -48,61 +33,101 @@ impl FormalParameterList {
}
}
/// Creates a `FormalParameterList` from a list of [`FormalParameter`]s.
#[must_use]
pub fn from_parameters(parameters: Vec<FormalParameter>) -> Self {
let mut flags = FormalParameterListFlags::default();
let mut length = 0;
let mut names = FxHashSet::default();
for parameter in &parameters {
let parameter_names = parameter.names();
for name in parameter_names {
if name == Sym::ARGUMENTS {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
if names.contains(&name) {
flags |= FormalParameterListFlags::HAS_DUPLICATES;
} else {
names.insert(name);
}
}
if parameter.is_rest_param() {
flags |= FormalParameterListFlags::HAS_REST_PARAMETER;
}
if parameter.init().is_some() {
flags |= FormalParameterListFlags::HAS_EXPRESSIONS;
}
if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier()
{
flags.remove(FormalParameterListFlags::IS_SIMPLE);
}
if !(flags.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
|| parameter.is_rest_param()
|| parameter.init().is_some())
{
length += 1;
}
}
Self {
parameters: parameters.into(),
flags,
length,
}
}
/// Returns the length of the parameter list.
/// Note that this is not equal to the length of the parameters slice.
pub(crate) fn length(&self) -> u32 {
#[must_use]
pub fn length(&self) -> u32 {
self.length
}
/// Returns the parameter list flags.
#[must_use]
pub fn flags(&self) -> FormalParameterListFlags {
self.flags
}
/// Indicates if the parameter list is simple.
pub(crate) fn is_simple(&self) -> bool {
#[must_use]
pub fn is_simple(&self) -> bool {
self.flags.contains(FormalParameterListFlags::IS_SIMPLE)
}
/// Indicates if the parameter list has duplicate parameters.
pub(crate) fn has_duplicates(&self) -> bool {
#[must_use]
pub fn has_duplicates(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_DUPLICATES)
}
/// Indicates if the parameter list has a rest parameter.
pub(crate) fn has_rest_parameter(&self) -> bool {
#[must_use]
pub fn has_rest_parameter(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_REST_PARAMETER)
}
/// Indicates if the parameter list has expressions in it's parameters.
pub(crate) fn has_expressions(&self) -> bool {
#[must_use]
pub fn has_expressions(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
}
/// Indicates if the parameter list has parameters named 'arguments'.
pub(crate) fn has_arguments(&self) -> bool {
#[must_use]
pub fn has_arguments(&self) -> bool {
self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS)
}
/// Helper to check if any parameter names are declared in the given list.
pub(crate) fn name_in_lexically_declared_names(
&self,
names: &[Identifier],
position: Position,
) -> Result<(), ParseError> {
for parameter in self.parameters.iter() {
for name in &parameter.names() {
if names.contains(name) {
return Err(ParseError::General {
message: "formal parameter declared in lexically declared names",
position,
});
}
}
}
Ok(())
}
/// Check if the any of the parameters contains a yield expression.
pub(crate) fn contains_yield_expression(&self) -> bool {
#[must_use]
pub fn contains_yield_expression(&self) -> bool {
for parameter in self.parameters.iter() {
if parameter
.variable()
@ -115,7 +140,8 @@ impl FormalParameterList {
}
/// Check if the any of the parameters contains a await expression.
pub(crate) fn contains_await_expression(&self) -> bool {
#[must_use]
pub fn contains_await_expression(&self) -> bool {
for parameter in self.parameters.iter() {
if parameter
.variable()
@ -142,75 +168,19 @@ impl FormalParameterList {
impl From<Vec<FormalParameter>> for FormalParameterList {
fn from(parameters: Vec<FormalParameter>) -> Self {
let mut flags = FormalParameterListFlags::default();
let mut length = 0;
let mut names = FxHashSet::default();
for parameter in &parameters {
let parameter_names = parameter.names();
for name in parameter_names {
if name == Sym::ARGUMENTS {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
if names.contains(&name) {
flags |= FormalParameterListFlags::HAS_DUPLICATES;
} else {
names.insert(name);
}
}
if parameter.is_rest_param() {
flags |= FormalParameterListFlags::HAS_REST_PARAMETER;
}
if parameter.init().is_some() {
flags |= FormalParameterListFlags::HAS_EXPRESSIONS;
}
if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier()
{
flags.remove(FormalParameterListFlags::IS_SIMPLE);
}
if !(flags.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
|| parameter.is_rest_param()
|| parameter.init().is_some())
{
length += 1;
}
}
Self {
parameters: parameters.into_boxed_slice(),
flags,
length,
}
Self::from_parameters(parameters)
}
}
impl From<FormalParameter> for FormalParameterList {
fn from(parameter: FormalParameter) -> Self {
let mut flags = FormalParameterListFlags::default();
if parameter.is_rest_param() {
flags |= FormalParameterListFlags::HAS_REST_PARAMETER;
}
if parameter.init().is_some() {
flags |= FormalParameterListFlags::HAS_EXPRESSIONS;
}
if parameter.names().contains(&Sym::ARGUMENTS.into()) {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier() {
flags.remove(FormalParameterListFlags::IS_SIMPLE);
}
let length = if parameter.is_rest_param() || parameter.init().is_some() {
0
} else {
1
};
Self {
parameters: Box::new([parameter]),
flags,
length,
}
Self::from_parameters(vec![parameter])
}
}
impl AsRef<[FormalParameter]> for FormalParameterList {
fn as_ref(&self) -> &[FormalParameter] {
&self.parameters
}
}
@ -240,12 +210,17 @@ impl VisitWith for FormalParameterList {
bitflags! {
/// Flags for a [`FormalParameterList`].
#[allow(clippy::unsafe_derive_deserialize)]
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
pub(crate) struct FormalParameterListFlags: u8 {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FormalParameterListFlags: u8 {
/// Has only identifier parameters with no initialization expressions.
const IS_SIMPLE = 0b0000_0001;
/// Has any duplicate parameters.
const HAS_DUPLICATES = 0b0000_0010;
/// Has a rest parameter.
const HAS_REST_PARAMETER = 0b0000_0100;
/// Has any initialization expression.
const HAS_EXPRESSIONS = 0b0000_1000;
/// Has an argument with the name `arguments`.
const HAS_ARGUMENTS = 0b0001_0000;
}
}
@ -271,7 +246,7 @@ impl Default for FormalParameterListFlags {
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_formal_parameter
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct FormalParameter {
variable: Variable,
@ -280,7 +255,7 @@ pub struct FormalParameter {
impl FormalParameter {
/// Creates a new formal parameter.
pub(in crate::syntax) fn new<D>(variable: D, is_rest_param: bool) -> Self
pub fn new<D>(variable: D, is_rest_param: bool) -> Self
where
D: Into<Variable>,
{
@ -291,6 +266,7 @@ impl FormalParameter {
}
/// Gets the name of the formal parameter.
#[must_use]
pub fn names(&self) -> Vec<Identifier> {
match self.variable.binding() {
Binding::Identifier(ident) => vec![*ident],
@ -303,21 +279,25 @@ impl FormalParameter {
}
/// Gets the variable of the formal parameter
#[must_use]
pub fn variable(&self) -> &Variable {
&self.variable
}
/// Gets the initialization node of the formal parameter, if any.
#[must_use]
pub fn init(&self) -> Option<&Expression> {
self.variable.init()
}
/// Returns `true` if the parameter is a rest parameter.
#[must_use]
pub fn is_rest_param(&self) -> bool {
self.is_rest_param
}
/// Returns `true` if the parameter is a simple [`Identifier`].
#[must_use]
pub fn is_identifier(&self) -> bool {
matches!(&self.variable.binding(), Binding::Identifier(_))
}

10
boa_engine/src/syntax/ast/keyword.rs → boa_ast/src/keyword.rs

@ -9,17 +9,15 @@
//! [spec]: https://tc39.es/ecma262/#sec-keywords-and-reserved-words
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
use crate::{
string::utf16,
syntax::ast::expression::operator::binary::{BinaryOp, RelationalOp},
};
use crate::expression::operator::binary::{BinaryOp, RelationalOp};
use boa_interner::{Interner, Sym};
use boa_macros::utf16;
use std::{convert::TryFrom, error, fmt, str::FromStr};
/// List of keywords recognized by the JavaScript grammar.
///
/// See the [module-level documentation][self] for more details.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Keyword {
/// The `await` keyword.
@ -479,6 +477,7 @@ pub enum Keyword {
impl Keyword {
/// Gets the keyword as a binary operation, if this keyword is the `in` or the `instanceof`
/// keywords.
#[must_use]
pub fn as_binary_op(self) -> Option<BinaryOp> {
match self {
Self::In => Some(BinaryOp::Relational(RelationalOp::In)),
@ -488,6 +487,7 @@ impl Keyword {
}
/// Gets the keyword as a tuple of strings.
#[must_use]
pub fn as_str(self) -> (&'static str, &'static [u16]) {
match self {
Self::Await => ("await", utf16!("await")),

111
boa_engine/src/syntax/ast/mod.rs → boa_ast/src/lib.rs

@ -1,6 +1,6 @@
//! The Javascript Abstract Syntax Tree.
//!
//! This module contains representations of [**Parse Nodes**][grammar] as defined by the ECMAScript spec.
//! This crate contains representations of [**Parse Nodes**][grammar] as defined by the ECMAScript spec.
//! Some `Parse Node`s are not represented by Boa's AST, because a lot of grammar productions are
//! only used to throw [**Early Errors**][early], and don't influence the evaluation of the AST itself.
//!
@ -11,7 +11,49 @@
//! [grammar]: https://tc39.es/ecma262/#sec-syntactic-grammar
//! [early]: https://tc39.es/ecma262/#sec-static-semantic-rules
#![deny(missing_docs)]
#![cfg_attr(not(test), forbid(clippy::unwrap_used))]
#![warn(
clippy::perf,
clippy::single_match_else,
clippy::dbg_macro,
clippy::doc_markdown,
clippy::wildcard_imports,
clippy::struct_excessive_bools,
clippy::doc_markdown,
clippy::semicolon_if_nothing_returned,
clippy::pedantic
)]
#![deny(
clippy::all,
clippy::cast_lossless,
clippy::redundant_closure_for_method_calls,
clippy::unnested_or_patterns,
clippy::trivially_copy_pass_by_ref,
clippy::needless_pass_by_value,
clippy::match_wildcard_for_single_variants,
clippy::map_unwrap_or,
unused_qualifications,
unused_import_braces,
unused_lifetimes,
unreachable_pub,
trivial_numeric_casts,
rustdoc::broken_intra_doc_links,
missing_debug_implementations,
missing_copy_implementations,
deprecated_in_future,
meta_variable_misuse,
non_ascii_idents,
rust_2018_compatibility,
rust_2018_idioms,
future_incompatible,
nonstandard_style,
missing_docs
)]
#![allow(
clippy::module_name_repetitions,
clippy::too_many_lines,
rustdoc::missing_doc_code_examples
)]
mod position;
mod punctuator;
@ -41,16 +83,26 @@ pub use self::{
/// Represents all the possible symbols searched for by the [`Contains`][contains] operation.
///
/// [contains]: https://tc39.es/ecma262/#sec-syntax-directed-operations-contains
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum ContainsSymbol {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContainsSymbol {
/// A super property access (`super.prop`).
SuperProperty,
/// A super constructor call (`super(args)`).
SuperCall,
/// A yield expression (`yield 5`).
YieldExpression,
/// An await expression (`await 4`).
AwaitExpression,
/// The new target expression (`new.target`).
NewTarget,
/// The body of a class definition.
ClassBody,
/// The super class of a class definition.
ClassHeritage,
/// A this expression (`this`).
This,
/// A method definition.
MethodDefinition,
}
@ -88,43 +140,20 @@ fn block_to_string(body: &StatementList, interner: &Interner, indentation: usize
}
}
/// This parses the given source code, and then makes sure that
/// the resulting `StatementList` is formatted in the same manner
/// as the source code. This is expected to have a preceding
/// newline.
///
/// This is a utility function for tests. It was made in case people
/// are using different indents in their source files. This fixes
/// any strings which may have been changed in a different indent
/// level.
#[cfg(test)]
fn test_formatting(source: &'static str) {
use crate::{syntax::Parser, Context};
// Remove preceding newline.
let source = &source[1..];
// Find out how much the code is indented
let first_line = &source[..source.find('\n').unwrap()];
let trimmed_first_line = first_line.trim();
let characters_to_remove = first_line.len() - trimmed_first_line.len();
/// Utility trait that adds a `UTF-16` escaped representation to every [`[u16]`][slice].
trait ToStringEscaped {
/// Decodes `self` as an `UTF-16` encoded string, escaping any unpaired surrogates by its
/// codepoint value.
fn to_string_escaped(&self) -> String;
}
let scenario = source
.lines()
.map(|l| &l[characters_to_remove..]) // Remove preceding whitespace from each line
.collect::<Vec<&'static str>>()
.join("\n");
let mut context = Context::default();
let result = Parser::new(scenario.as_bytes())
.parse_all(&mut context)
.expect("parsing failed")
.to_interned_string(context.interner());
if scenario != result {
eprint!("========= Expected:\n{scenario}");
eprint!("========= Got:\n{result}");
// Might be helpful to find differing whitespace
eprintln!("========= Expected: {scenario:?}");
eprintln!("========= Got: {result:?}");
panic!("parsing test did not give the correct result (see above)");
impl ToStringEscaped for [u16] {
fn to_string_escaped(&self) -> String {
char::decode_utf16(self.iter().copied())
.map(|r| match r {
Ok(c) => String::from(c),
Err(e) => format!("\\u{:04X}", e.unpaired_surrogate()),
})
.collect()
}
}

67
boa_engine/src/syntax/ast/pattern.rs → boa_ast/src/pattern.rs

@ -22,8 +22,8 @@
//! [spec2]: https://tc39.es/ecma262/#prod-AssignmentPattern
//! [destr]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
@ -36,7 +36,7 @@ use super::{
/// An object or array pattern binding or assignment.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
/// An object pattern (`let {a, b, c} = object`).
@ -82,6 +82,7 @@ impl Pattern {
///
/// A single pattern may have 0 to n identifiers.
#[inline]
#[must_use]
pub fn idents(&self) -> Vec<Identifier> {
match &self {
Pattern::Object(pattern) => pattern.idents(),
@ -149,7 +150,7 @@ impl VisitWith for Pattern {
///
/// [spec1]: https://tc39.es/ecma262/#prod-ObjectBindingPattern
/// [spec2]: https://tc39.es/ecma262/#prod-ObjectAssignmentPattern
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectPattern(Box<[ObjectPatternElement]>);
@ -183,19 +184,32 @@ impl ToInternedString for ObjectPattern {
impl ObjectPattern {
/// Creates a new object binding pattern.
#[inline]
pub(in crate::syntax) fn new(bindings: Box<[ObjectPatternElement]>) -> Self {
#[must_use]
pub fn new(bindings: Box<[ObjectPatternElement]>) -> Self {
Self(bindings)
}
/// Gets the bindings for the object binding pattern.
#[inline]
pub(crate) fn bindings(&self) -> &[ObjectPatternElement] {
#[must_use]
pub fn bindings(&self) -> &[ObjectPatternElement] {
&self.0
}
// Returns true if the object binding pattern has a rest element.
/// Gets the list of identifiers declared by the object binding pattern.
#[inline]
#[must_use]
pub fn idents(&self) -> Vec<Identifier> {
self.0
.iter()
.flat_map(ObjectPatternElement::idents)
.collect()
}
/// Returns true if the object binding pattern has a rest element.
#[inline]
pub(crate) fn has_rest(&self) -> bool {
#[must_use]
pub fn has_rest(&self) -> bool {
matches!(
self.0.last(),
Some(ObjectPatternElement::RestProperty { .. })
@ -211,15 +225,6 @@ impl ObjectPattern {
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.0.iter().any(|e| e.contains(symbol))
}
/// Gets the list of identifiers declared by the object binding pattern.
#[inline]
pub(crate) fn idents(&self) -> Vec<Identifier> {
self.0
.iter()
.flat_map(ObjectPatternElement::idents)
.collect()
}
}
impl VisitWith for ObjectPattern {
@ -253,7 +258,7 @@ impl VisitWith for ObjectPattern {
///
/// [spec1]: https://tc39.es/ecma262/#prod-ArrayBindingPattern
/// [spec2]: https://tc39.es/ecma262/#prod-ArrayAssignmentPattern
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayPattern(Box<[ArrayPatternElement]>);
@ -286,25 +291,18 @@ impl ToInternedString for ArrayPattern {
impl ArrayPattern {
/// Creates a new array binding pattern.
#[inline]
pub(in crate::syntax) fn new(bindings: Box<[ArrayPatternElement]>) -> Self {
#[must_use]
pub fn new(bindings: Box<[ArrayPatternElement]>) -> Self {
Self(bindings)
}
/// Gets the bindings for the array binding pattern.
#[inline]
pub(crate) fn bindings(&self) -> &[ArrayPatternElement] {
#[must_use]
pub fn bindings(&self) -> &[ArrayPatternElement] {
&self.0
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.0.iter().any(ArrayPatternElement::contains_arguments)
}
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.0.iter().any(|e| e.contains(symbol))
}
/// Gets the list of identifiers declared by the array binding pattern.
#[inline]
pub(crate) fn idents(&self) -> Vec<Identifier> {
@ -313,6 +311,15 @@ impl ArrayPattern {
.flat_map(ArrayPatternElement::idents)
.collect()
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.0.iter().any(ArrayPatternElement::contains_arguments)
}
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.0.iter().any(|e| e.contains(symbol))
}
}
impl VisitWith for ArrayPattern {
@ -343,7 +350,7 @@ impl VisitWith for ArrayPattern {
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingProperty
/// [spec2]: https://tc39.es/ecma262/#prod-AssignmentProperty
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ObjectPatternElement {
/// SingleName represents one of the following properties:
@ -752,7 +759,7 @@ impl VisitWith for ObjectPatternElement {
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingElement
/// [spec2]: https://tc39.es/ecma262/#prod-AssignmentElement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ArrayPatternElement {
/// Elision represents the elision of an item in the array binding pattern.

17
boa_engine/src/syntax/ast/position.rs → boa_ast/src/position.rs

@ -6,7 +6,7 @@ use std::{cmp::Ordering, fmt, num::NonZeroU32};
///
/// ## 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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position {
/// Line number.
@ -19,6 +19,7 @@ impl Position {
/// Creates a new `Position`.
#[inline]
#[track_caller]
#[must_use]
pub fn new(line_number: u32, column_number: u32) -> Self {
Self {
line_number: NonZeroU32::new(line_number).expect("line number cannot be 0"),
@ -28,12 +29,14 @@ impl Position {
/// Gets the line number of the position.
#[inline]
#[must_use]
pub fn line_number(self) -> u32 {
self.line_number.get()
}
/// Gets the column number of the position.
#[inline]
#[must_use]
pub fn column_number(self) -> u32 {
self.column_number.get()
}
@ -51,7 +54,7 @@ impl fmt::Display for Position {
///
/// Note that spans are of the form [start, end) i.e. that the start position is inclusive
/// and the end position is exclusive.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
start: Position,
@ -60,8 +63,13 @@ pub struct Span {
impl Span {
/// Creates a new `Span`.
///
/// # Panics
///
/// Panics if the start position is bigger than the end position.
#[inline]
#[track_caller]
#[must_use]
pub fn new(start: Position, end: Position) -> Self {
assert!(start <= end, "a span cannot start after its end");
@ -70,12 +78,14 @@ impl Span {
/// Gets the starting position of the span.
#[inline]
#[must_use]
pub fn start(self) -> Position {
self.start
}
/// Gets the final position of the span.
#[inline]
#[must_use]
pub fn end(self) -> Position {
self.end
}
@ -121,8 +131,9 @@ impl fmt::Display for Span {
}
#[cfg(test)]
#[allow(clippy::similar_names)]
mod tests {
#![allow(clippy::similar_names)]
#![allow(unused_must_use)]
use super::{Position, Span};
/// Checks that we cannot create a position with 0 as the column.

33
boa_engine/src/syntax/ast/property.rs → boa_ast/src/property.rs

@ -1,7 +1,7 @@
//! Property definition related types, used in object literals and class definitions.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
@ -24,7 +24,7 @@ use super::{
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript
// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyDefinition {
/// Puts a variable into an object.
@ -173,7 +173,7 @@ impl VisitWith for PropertyDefinition {
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum MethodDefinition {
/// The `get` syntax binds an object property to a function that will be called when that property is looked up.
@ -249,8 +249,9 @@ pub enum MethodDefinition {
}
impl MethodDefinition {
/// Return the body of the method.
pub(crate) fn body(&self) -> &StatementList {
/// Gets the body of the method.
#[must_use]
pub fn body(&self) -> &StatementList {
match self {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
@ -261,8 +262,9 @@ impl MethodDefinition {
}
}
/// Return the parameters of the method.
pub(crate) fn parameters(&self) -> &FormalParameterList {
/// Gets the parameters of the method.
#[must_use]
pub fn parameters(&self) -> &FormalParameterList {
match self {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
@ -310,7 +312,7 @@ impl VisitWith for MethodDefinition {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyName
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyName {
/// A `Literal` property name can be either an identifier, a string or a numeric literal.
@ -332,7 +334,8 @@ pub enum PropertyName {
impl PropertyName {
/// Returns the literal property name if it exists.
pub(crate) fn literal(&self) -> Option<Sym> {
#[must_use]
pub fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self {
Some(*sym)
} else {
@ -341,7 +344,8 @@ impl PropertyName {
}
/// Returns the expression if the property name is computed.
pub(crate) fn computed(&self) -> Option<&Expression> {
#[must_use]
pub fn computed(&self) -> Option<&Expression> {
if let Self::Computed(expr) = self {
Some(expr)
} else {
@ -350,7 +354,8 @@ impl PropertyName {
}
/// Returns either the literal property name or the computed const string property name.
pub(in crate::syntax) fn prop_name(&self) -> Option<Sym> {
#[must_use]
pub fn prop_name(&self) -> Option<Sym> {
match self {
PropertyName::Literal(sym)
| PropertyName::Computed(Expression::Literal(Literal::String(sym))) => Some(*sym),
@ -424,9 +429,11 @@ impl VisitWith for PropertyName {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElementName
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ClassElementName {
pub enum ClassElementName {
/// A public property.
PropertyName(PropertyName),
/// A private property.
PrivateIdentifier(Sym),
}

7
boa_engine/src/syntax/ast/punctuator.rs → boa_ast/src/punctuator.rs

@ -5,7 +5,7 @@
//!
//! [spec]: https://tc39.es/ecma262/#prod-Punctuator
use crate::syntax::ast::expression::operator::{
use crate::expression::operator::{
assign::AssignOp,
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
};
@ -20,7 +20,7 @@ use std::{
/// - [ECMAScript Reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-Punctuator
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Punctuator {
/// `+`
@ -143,6 +143,7 @@ impl Punctuator {
/// Attempts to convert a punctuator (`+`, `=`...) to an Assign Operator
///
/// If there is no match, `None` will be returned.
#[must_use]
pub const fn as_assign_op(self) -> Option<AssignOp> {
match self {
Self::Assign => Some(AssignOp::Assign),
@ -168,6 +169,7 @@ impl Punctuator {
/// Attempts to convert a punctuator (`+`, `=`...) to a Binary Operator
///
/// If there is no match, `None` will be returned.
#[must_use]
pub const fn as_binary_op(self) -> Option<BinaryOp> {
match self {
Self::Add => Some(BinaryOp::Arithmetic(ArithmeticOp::Add)),
@ -198,6 +200,7 @@ impl Punctuator {
}
/// Retrieves the punctuator as a static string.
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Add => "+",

40
boa_engine/src/syntax/ast/statement/block.rs → boa_ast/src/statement/block.rs

@ -1,8 +1,8 @@
//! Block AST node.
use super::Statement;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Identifier, ContainsSymbol, StatementList};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Identifier, ContainsSymbol, StatementList};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
@ -21,23 +21,25 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Block {
#[cfg_attr(feature = "deser", serde(flatten))]
#[cfg_attr(feature = "serde", serde(flatten))]
statements: StatementList,
}
impl Block {
/// Gets the list of statements and declarations in this block.
#[inline]
pub(crate) fn statement_list(&self) -> &StatementList {
#[must_use]
pub fn statement_list(&self) -> &StatementList {
&self.statements
}
/// Get the lexically declared names of the block.
#[inline]
pub(crate) fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> {
#[must_use]
pub fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> {
self.statements.lexically_declared_names()
}
@ -97,29 +99,3 @@ impl VisitWith for Block {
visitor.visit_statement_list_mut(&mut self.statements)
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
{
let a = function_call();
console.log("hello");
}
another_statement();
"#,
);
// TODO: Once block labels are implemtned, this should be tested:
// super::super::test_formatting(
// r#"
// block_name: {
// let a = function_call();
// console.log("hello");
// }
// another_statement();
// "#,
// );
}
}

26
boa_engine/src/syntax/ast/statement/if.rs → boa_ast/src/statement/if.rs

@ -1,8 +1,8 @@
//! If statement
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, statement::Statement, ContainsSymbol};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -22,7 +22,7 @@ use core::ops::ControlFlow;
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/truthy
/// [falsy]: https://developer.mozilla.org/en-US/docs/Glossary/falsy
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct If {
condition: Expression,
@ -33,12 +33,14 @@ pub struct If {
impl If {
/// Gets the condition of the if statement.
#[inline]
#[must_use]
pub fn cond(&self) -> &Expression {
&self.condition
}
/// Gets the body to execute if the condition is true.
#[inline]
#[must_use]
pub fn body(&self) -> &Statement {
&self.body
}
@ -50,6 +52,7 @@ impl If {
}
/// Creates an `If` AST node.
#[must_use]
pub fn new(condition: Expression, body: Statement, else_node: Option<Statement>) -> Self {
Self {
condition,
@ -123,20 +126,3 @@ impl VisitWith for If {
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
let a = true ? 5 : 6;
if (false) {
a = 10;
} else {
a = 20;
}
"#,
);
}
}

42
boa_engine/src/syntax/ast/statement/iteration/break.rs → boa_ast/src/statement/iteration/break.rs

@ -1,8 +1,8 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::Statement;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::Statement;
/// The `break` statement terminates the current loop, switch, or label statement and transfers
/// program control to the statement following the terminated statement.
@ -18,7 +18,7 @@ use crate::syntax::ast::Statement;
///
/// [spec]: https://tc39.es/ecma262/#prod-BreakStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Break {
label: Option<Sym>,
@ -26,11 +26,13 @@ pub struct Break {
impl Break {
/// Creates a `Break` AST node.
#[must_use]
pub fn new(label: Option<Sym>) -> Self {
Self { label }
}
/// Gets the label of the break statement, if any.
#[must_use]
pub fn label(&self) -> Option<Sym> {
self.label
}
@ -80,37 +82,3 @@ impl VisitWith for Break {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
// Blocks do not store their label, so we cannot test with
// the outer block having a label.
//
// TODO: Once block labels are implemented, this test should
// include them:
//
// ```
// outer: {
// while (true) {
// break outer;
// }
// skipped_call();
// }
// ```
crate::syntax::ast::test_formatting(
r#"
{
while (true) {
break outer;
}
skipped_call();
}
while (true) {
break;
}
"#,
);
}
}

8
boa_engine/src/syntax/ast/statement/iteration/continue.rs → boa_ast/src/statement/iteration/continue.rs

@ -1,5 +1,5 @@
use crate::syntax::ast::statement::Statement;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::statement::Statement;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
@ -16,7 +16,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Continue {
label: Option<Sym>,
@ -24,11 +24,13 @@ pub struct Continue {
impl Continue {
/// Creates a `Continue` AST node.
#[must_use]
pub fn new(label: Option<Sym>) -> Self {
Self { label }
}
/// Gets the label of this `Continue` statement.
#[must_use]
pub fn label(&self) -> Option<Sym> {
self.label
}

9
boa_engine/src/syntax/ast/statement/iteration/do_while_loop.rs → boa_ast/src/statement/iteration/do_while_loop.rs

@ -1,6 +1,6 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, statement::Statement, ContainsSymbol};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -16,7 +16,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct DoWhileLoop {
body: Box<Statement>,
@ -26,17 +26,20 @@ pub struct DoWhileLoop {
impl DoWhileLoop {
/// Gets the body of the do-while loop.
#[inline]
#[must_use]
pub fn body(&self) -> &Statement {
&self.body
}
/// Gets the condition of the do-while loop.
#[inline]
#[must_use]
pub fn cond(&self) -> &Expression {
&self.condition
}
/// Creates a `DoWhileLoop` AST node.
#[inline]
#[must_use]
pub fn new(body: Statement, condition: Expression) -> Self {
Self {
body: body.into(),

12
boa_engine/src/syntax/ast/statement/iteration/for_in_loop.rs → boa_ast/src/statement/iteration/for_in_loop.rs

@ -1,10 +1,10 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
ContainsSymbol,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -15,7 +15,7 @@ use core::ops::ControlFlow;
///
/// [forin]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForInLoop {
initializer: IterableLoopInitializer,
@ -26,6 +26,7 @@ pub struct ForInLoop {
impl ForInLoop {
/// Creates a new `ForInLoop`.
#[inline]
#[must_use]
pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self {
Self {
initializer,
@ -36,18 +37,21 @@ impl ForInLoop {
/// Gets the initializer of the for...in loop.
#[inline]
#[must_use]
pub fn initializer(&self) -> &IterableLoopInitializer {
&self.initializer
}
/// Gets the target object of the for...in loop.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
&self.target
}
/// Gets the body of the for...in loop.
#[inline]
#[must_use]
pub fn body(&self) -> &Statement {
&self.body
}

24
boa_engine/src/syntax/ast/statement/iteration/for_loop.rs → boa_ast/src/statement/iteration/for_loop.rs

@ -1,11 +1,11 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
declaration::{LexicalDeclaration, VarDeclaration, Variable},
expression::Identifier,
statement::Statement,
ContainsSymbol, Expression,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -19,17 +19,18 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForLoop {
#[cfg_attr(feature = "deser", serde(flatten))]
#[cfg_attr(feature = "serde", serde(flatten))]
inner: Box<InnerForLoop>,
}
impl ForLoop {
/// Creates a new for loop AST node.
#[inline]
pub(in crate::syntax) fn new(
#[must_use]
pub fn new(
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
@ -42,24 +43,28 @@ impl ForLoop {
/// Gets the initialization node.
#[inline]
#[must_use]
pub fn init(&self) -> Option<&ForLoopInitializer> {
self.inner.init()
}
/// Gets the loop condition node.
#[inline]
#[must_use]
pub fn condition(&self) -> Option<&Expression> {
self.inner.condition()
}
/// Gets the final expression node.
#[inline]
#[must_use]
pub fn final_expr(&self) -> Option<&Expression> {
self.inner.final_expr()
}
/// Gets the body of the for loop.
#[inline]
#[must_use]
pub fn body(&self) -> &Statement {
self.inner.body()
}
@ -148,7 +153,7 @@ impl VisitWith for ForLoop {
}
/// Inner structure to avoid multiple indirections in the heap.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
struct InnerForLoop {
init: Option<ForLoopInitializer>,
@ -207,7 +212,7 @@ impl InnerForLoop {
/// declarations instead of only one.
///
/// [spec]: https://tc39.es/ecma262/#prod-ForStatement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ForLoopInitializer {
/// An expression initializer.
@ -225,7 +230,8 @@ impl ForLoopInitializer {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames
pub(crate) fn bound_names(&self) -> Vec<Identifier> {
#[must_use]
pub fn bound_names(&self) -> Vec<Identifier> {
match self {
ForLoopInitializer::Lexical(lex) => lex
.variable_list()

15
boa_engine/src/syntax/ast/statement/iteration/for_of_loop.rs → boa_ast/src/statement/iteration/for_of_loop.rs

@ -1,10 +1,10 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
ContainsSymbol,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -20,7 +20,7 @@ use core::ops::ControlFlow;
/// [forof]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
/// [forawait]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForOfLoop {
init: IterableLoopInitializer,
@ -32,6 +32,7 @@ pub struct ForOfLoop {
impl ForOfLoop {
/// Creates a new "for of" loop AST node.
#[inline]
#[must_use]
pub fn new(
init: IterableLoopInitializer,
iterable: Expression,
@ -48,25 +49,29 @@ impl ForOfLoop {
/// Gets the initializer of the for...of loop.
#[inline]
#[must_use]
pub fn init(&self) -> &IterableLoopInitializer {
&self.init
}
/// Gets the iterable expression of the for...of loop.
#[inline]
#[must_use]
pub fn iterable(&self) -> &Expression {
&self.iterable
}
/// Gets the body to execute in the for...of loop.
#[inline]
#[must_use]
pub fn body(&self) -> &Statement {
&self.body
}
/// Returns true if this "for...of" loop is an "for await...of" loop.
#[inline]
pub(crate) fn r#await(&self) -> bool {
#[must_use]
pub fn r#await(&self) -> bool {
self.r#await
}

12
boa_engine/src/syntax/ast/statement/iteration/mod.rs → boa_ast/src/statement/iteration/mod.rs

@ -8,7 +8,7 @@ mod for_loop;
mod for_of_loop;
mod while_loop;
use crate::syntax::ast::{
use crate::{
declaration::Binding,
expression::{access::PropertyAccess, Identifier},
pattern::Pattern,
@ -24,14 +24,11 @@ pub use self::{
r#continue::Continue,
while_loop::WhileLoop,
};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use super::ContainsSymbol;
#[cfg(test)]
mod tests;
/// A `for-in`, `for-of` and `for-await-of` loop initializer.
///
/// The [spec] specifies only single bindings for the listed types of loops, which makes us
@ -39,7 +36,7 @@ mod tests;
/// can have more than one binding.
///
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum IterableLoopInitializer {
/// An already declared variable.
@ -65,7 +62,8 @@ impl IterableLoopInitializer {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames
pub(crate) fn bound_names(&self) -> Vec<Identifier> {
#[must_use]
pub fn bound_names(&self) -> Vec<Identifier> {
match self {
Self::Let(binding) | Self::Const(binding) => binding.idents(),
_ => Vec::new(),

9
boa_engine/src/syntax/ast/statement/iteration/while_loop.rs → boa_ast/src/statement/iteration/while_loop.rs

@ -1,6 +1,6 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, statement::Statement, ContainsSymbol};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -15,7 +15,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct WhileLoop {
condition: Expression,
@ -25,6 +25,7 @@ pub struct WhileLoop {
impl WhileLoop {
/// Creates a `WhileLoop` AST node.
#[inline]
#[must_use]
pub fn new(condition: Expression, body: Statement) -> Self {
Self {
condition,
@ -34,12 +35,14 @@ impl WhileLoop {
/// Gets the condition of the while loop.
#[inline]
#[must_use]
pub fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the body of the while loop.
#[inline]
#[must_use]
pub fn body(&self) -> &Statement {
&self.body
}

11
boa_engine/src/syntax/ast/statement/labelled.rs → boa_ast/src/statement/labelled.rs

@ -1,7 +1,7 @@
use super::Statement;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{function::Function, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{function::Function, ContainsSymbol};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
@ -14,7 +14,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-LabelledItem
/// [label-fn]: https://tc39.es/ecma262/#sec-labelled-function-declarations
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum LabelledItem {
/// A labelled [`Function`].
@ -94,7 +94,7 @@ impl VisitWith for LabelledItem {
/// See [`LabelledItem`] for more information.
///
/// [spec]: https://tc39.es/ecma262/#sec-labelled-statements
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Labelled {
item: Box<LabelledItem>,
@ -103,6 +103,7 @@ pub struct Labelled {
impl Labelled {
/// Creates a new `Labelled` statement.
#[must_use]
pub fn new(item: LabelledItem, label: Sym) -> Self {
Self {
item: Box::new(item),
@ -111,11 +112,13 @@ impl Labelled {
}
/// Gets the labelled item.
#[must_use]
pub fn item(&self) -> &LabelledItem {
&self.item
}
/// Gets the label name.
#[must_use]
pub fn label(&self) -> Sym {
self.label
}

27
boa_engine/src/syntax/ast/statement/mod.rs → boa_ast/src/statement/mod.rs

@ -24,16 +24,15 @@ pub use self::{
labelled::{Labelled, LabelledItem},
r#if::If,
r#return::Return,
r#try::{Catch, Finally, Try},
r#try::{Catch, ErrorHandler, Finally, Try},
switch::{Case, Switch},
throw::Throw,
};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use rustc_hash::FxHashSet;
use tap::Tap;
use super::{
declaration::{Binding, VarDeclaration},
@ -44,7 +43,7 @@ use super::{
/// The `Statement` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum Statement {
/// See [`Block`].
@ -115,7 +114,7 @@ impl Statement {
/// This will not prefix the value with any indentation. If you want to prefix this with proper
/// indents, use [`to_indented_string()`](Self::to_indented_string).
pub(super) fn to_no_indent_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
let mut s = match self {
Self::Block(block) => return block.to_indented_string(interner, indentation),
Self::Var(var) => var.to_interned_string(interner),
Self::Empty => return ";".to_owned(),
@ -135,11 +134,13 @@ impl Statement {
Self::Labelled(labelled) => return labelled.to_interned_string(interner),
Self::Throw(throw) => throw.to_interned_string(interner),
Self::Try(try_catch) => return try_catch.to_indented_string(interner, indentation),
}
.tap_mut(|s| s.push(';'))
};
s.push(';');
s
}
pub(crate) fn var_declared_names(&self, vars: &mut FxHashSet<Identifier>) {
/// Gets the var declared names of this `Statement`.
pub fn var_declared_names(&self, vars: &mut FxHashSet<Identifier>) {
match self {
Self::Var(VarDeclaration(list)) => {
for decl in list.as_ref() {
@ -212,7 +213,7 @@ impl Statement {
}
}
if let Some(finally) = try_statement.finally() {
for node in finally.statement_list().statements() {
for node in finally.block().statement_list().statements() {
node.var_declared_names(vars);
}
}
@ -281,8 +282,14 @@ impl Statement {
}
}
/// `IsLabelledFunction` static operation, as defined by the [spec].
///
/// Returns `true` if this `Statement` is a labelled function.
///
/// [spec]: https://tc39.es/ecma262/#sec-islabelledfunction
#[inline]
pub(crate) fn is_labelled_function(&self) -> bool {
#[must_use]
pub fn is_labelled_function(&self) -> bool {
match self {
Self::Labelled(stmt) => match stmt.item() {
LabelledItem::Function(_) => true,

28
boa_engine/src/syntax/ast/statement/return.rs → boa_ast/src/statement/return.rs

@ -1,5 +1,5 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, statement::Statement, ContainsSymbol};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
@ -20,7 +20,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Return {
target: Option<Expression>,
@ -28,11 +28,13 @@ pub struct Return {
impl Return {
/// Gets the target expression value of this `Return` statement.
#[must_use]
pub fn target(&self) -> Option<&Expression> {
self.target.as_ref()
}
/// Creates a `Return` AST node.
#[must_use]
pub fn new(expression: Option<Expression>) -> Self {
Self { target: expression }
}
@ -85,23 +87,3 @@ impl VisitWith for Return {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
function say_hello(msg) {
if (msg === "") {
return 0;
}
console.log("hello " + msg);
return;
}
say_hello("");
say_hello("world");
"#,
);
}
}

18
boa_engine/src/syntax/ast/statement/switch/mod.rs → boa_ast/src/statement/switch.rs

@ -1,16 +1,13 @@
//! Switch node.
//!
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, StatementList};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Expression, statement::Statement, StatementList};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use super::ContainsSymbol;
#[cfg(test)]
mod tests;
/// A case clause inside a [`Switch`] statement, as defined by the [spec].
///
/// Even though every [`Case`] body is a [`StatementList`], it doesn't create a new lexical
@ -19,7 +16,7 @@ mod tests;
///
/// [spec]: https://tc39.es/ecma262/#prod-CaseClause
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Case {
condition: Expression,
@ -29,18 +26,21 @@ pub struct Case {
impl Case {
/// Creates a `Case` AST node.
#[inline]
#[must_use]
pub fn new(condition: Expression, body: StatementList) -> Self {
Self { condition, body }
}
/// Gets the condition of the case.
#[inline]
#[must_use]
pub fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the statement listin the body of the case.
#[inline]
#[must_use]
pub fn body(&self) -> &StatementList {
&self.body
}
@ -95,7 +95,7 @@ impl VisitWith for Case {
///
/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Switch {
val: Expression,
@ -106,6 +106,7 @@ pub struct Switch {
impl Switch {
/// Creates a `Switch` AST node.
#[inline]
#[must_use]
pub fn new(val: Expression, cases: Box<[Case]>, default: Option<StatementList>) -> Self {
Self {
val,
@ -116,18 +117,21 @@ impl Switch {
/// Gets the value to switch.
#[inline]
#[must_use]
pub fn val(&self) -> &Expression {
&self.val
}
/// Gets the list of cases for the switch statement.
#[inline]
#[must_use]
pub fn cases(&self) -> &[Case] {
&self.cases
}
/// Gets the default statement list, if any.
#[inline]
#[must_use]
pub fn default(&self) -> Option<&StatementList> {
self.default.as_ref()
}

24
boa_engine/src/syntax/ast/statement/throw.rs → boa_ast/src/statement/throw.rs

@ -1,5 +1,5 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{statement::Statement, ContainsSymbol, Expression};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{statement::Statement, ContainsSymbol, Expression};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
@ -17,7 +17,7 @@ use core::ops::ControlFlow;
///
/// [spec]: https://tc39.es/ecma262/#prod-ThrowStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Throw {
target: Expression,
@ -25,11 +25,13 @@ pub struct Throw {
impl Throw {
/// Gets the target expression of this `Throw` statement.
#[must_use]
pub fn target(&self) -> &Expression {
&self.target
}
/// Creates a `Throw` AST node.
#[must_use]
pub fn new(target: Expression) -> Self {
Self { target }
}
@ -72,19 +74,3 @@ impl VisitWith for Throw {
visitor.visit_expression_mut(&mut self.target)
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
try {
throw "hello";
} catch(e) {
console.log(e);
}
"#,
);
}
}

97
boa_engine/src/syntax/ast/statement/try/mod.rs → boa_ast/src/statement/try.rs

@ -1,20 +1,17 @@
//! Error handling statements
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
declaration::Binding,
statement::{Block, Statement},
StatementListItem,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use super::ContainsSymbol;
#[cfg(test)]
mod tests;
/// The `try...catch` statement marks a block of statements to try and specifies a response
/// should an exception be thrown.
///
@ -28,64 +25,72 @@ mod tests;
///
/// [spec]: https://tc39.es/ecma262/#prod-TryStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Try {
block: Block,
catch: Option<Catch>,
finally: Option<Finally>,
handler: ErrorHandler,
}
/// The type of error handler in a [`Try`] statement.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ErrorHandler {
/// A [`Catch`] error handler.
Catch(Catch),
/// A [`Finally`] error handler.
Finally(Finally),
/// A [`Catch`] and [`Finally`] error handler.
Full(Catch, Finally),
}
impl Try {
/// Creates a new `Try` AST node.
#[inline]
pub(in crate::syntax) fn new(
block: Block,
catch: Option<Catch>,
finally: Option<Finally>,
) -> Self {
assert!(
catch.is_some() || finally.is_some(),
"one of catch or finally must be pressent"
);
Self {
block,
catch,
finally,
}
#[must_use]
pub fn new(block: Block, handler: ErrorHandler) -> Self {
Self { block, handler }
}
/// Gets the `try` block.
#[inline]
#[must_use]
pub fn block(&self) -> &Block {
&self.block
}
/// Gets the `catch` block, if any.
#[inline]
#[must_use]
pub fn catch(&self) -> Option<&Catch> {
self.catch.as_ref()
match &self.handler {
ErrorHandler::Catch(c) | ErrorHandler::Full(c, _) => Some(c),
ErrorHandler::Finally(_) => None,
}
}
/// Gets the `finally` block, if any.
#[inline]
pub fn finally(&self) -> Option<&Block> {
self.finally.as_ref().map(Finally::block)
#[must_use]
pub fn finally(&self) -> Option<&Finally> {
match &self.handler {
ErrorHandler::Finally(f) | ErrorHandler::Full(_, f) => Some(f),
ErrorHandler::Catch(_) => None,
}
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.block.contains_arguments()
|| matches!(self.catch, Some(ref catch) if catch.contains_arguments())
|| matches!(self.finally, Some(ref finally) if finally.contains_arguments())
|| matches!(self.catch(), Some(catch) if catch.contains_arguments())
|| matches!(self.finally(), Some(finally) if finally.contains_arguments())
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.block.contains(symbol)
|| matches!(self.catch, Some(ref catch) if catch.contains(symbol))
|| matches!(self.finally, Some(ref finally) if finally.contains(symbol))
|| matches!(self.catch(), Some(catch) if catch.contains(symbol))
|| matches!(self.finally(), Some(finally) if finally.contains(symbol))
}
}
@ -97,11 +102,11 @@ impl ToIndentedString for Try {
self.block.to_indented_string(interner, indentation)
);
if let Some(ref catch) = self.catch {
if let Some(catch) = self.catch() {
buf.push_str(&catch.to_indented_string(interner, indentation));
}
if let Some(ref finally) = self.finally {
if let Some(finally) = self.finally() {
buf.push_str(&finally.to_indented_string(interner, indentation));
}
buf
@ -121,10 +126,10 @@ impl VisitWith for Try {
V: Visitor<'a>,
{
try_break!(visitor.visit_block(&self.block));
if let Some(catch) = &self.catch {
if let Some(catch) = &self.catch() {
try_break!(visitor.visit_catch(catch));
}
if let Some(finally) = &self.finally {
if let Some(finally) = &self.finally() {
try_break!(visitor.visit_finally(finally));
}
ControlFlow::Continue(())
@ -135,18 +140,20 @@ impl VisitWith for Try {
V: VisitorMut<'a>,
{
try_break!(visitor.visit_block_mut(&mut self.block));
if let Some(catch) = &mut self.catch {
try_break!(visitor.visit_catch_mut(catch));
}
if let Some(finally) = &mut self.finally {
try_break!(visitor.visit_finally_mut(finally));
match &mut self.handler {
ErrorHandler::Catch(c) => try_break!(visitor.visit_catch_mut(c)),
ErrorHandler::Finally(f) => try_break!(visitor.visit_finally_mut(f)),
ErrorHandler::Full(c, f) => {
try_break!(visitor.visit_catch_mut(c));
try_break!(visitor.visit_finally_mut(f));
}
}
ControlFlow::Continue(())
}
}
/// Catch block.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Catch {
parameter: Option<Binding>,
@ -156,18 +163,21 @@ pub struct Catch {
impl Catch {
/// Creates a new catch block.
#[inline]
pub(in crate::syntax) fn new(parameter: Option<Binding>, block: Block) -> Self {
#[must_use]
pub fn new(parameter: Option<Binding>, block: Block) -> Self {
Self { parameter, block }
}
/// Gets the parameter of the catch block.
#[inline]
#[must_use]
pub fn parameter(&self) -> Option<&Binding> {
self.parameter.as_ref()
}
/// Retrieves the catch execution block.
#[inline]
#[must_use]
pub fn block(&self) -> &Block {
&self.block
}
@ -229,7 +239,7 @@ impl VisitWith for Catch {
}
/// Finally block.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Finally {
block: Block,
@ -238,6 +248,7 @@ pub struct Finally {
impl Finally {
/// Gets the finally block.
#[inline]
#[must_use]
pub fn block(&self) -> &Block {
&self.block
}

41
boa_engine/src/syntax/ast/statement_list/mod.rs → boa_ast/src/statement_list.rs

@ -1,24 +1,21 @@
//! Statement list node.
use super::{declaration::Binding, Declaration};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Identifier, statement::Statement, ContainsSymbol};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{expression::Identifier, statement::Statement, ContainsSymbol};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use rustc_hash::FxHashSet;
use std::cmp::Ordering;
#[cfg(test)]
mod tests;
/// An item inside a [`StatementList`] Parse Node, as defined by the [spec].
///
/// Items in a `StatementList` can be either [`Declaration`]s (functions, classes, let/const declarations)
/// or [`Statement`]s (if, while, var statement).
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementListItem
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum StatementListItem {
/// See [`Statement`].
@ -29,7 +26,8 @@ pub enum StatementListItem {
impl StatementListItem {
/// Returns a node ordering based on the hoistability of each statement.
pub(crate) fn hoistable_order(a: &Self, b: &Self) -> Ordering {
#[must_use]
pub fn hoistable_order(a: &Self, b: &Self) -> Ordering {
match (a, b) {
(
Self::Declaration(Declaration::Function(_)),
@ -42,8 +40,9 @@ impl StatementListItem {
}
}
/// Gets the var declared names of this `StatementListItem`.
#[inline]
pub(crate) fn var_declared_names(&self, vars: &mut FxHashSet<Identifier>) {
pub fn var_declared_names(&self, vars: &mut FxHashSet<Identifier>) {
match self {
StatementListItem::Statement(stmt) => stmt.var_declared_names(vars),
StatementListItem::Declaration(_) => {}
@ -57,7 +56,8 @@ impl StatementListItem {
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
#[must_use]
pub fn contains_arguments(&self) -> bool {
match self {
StatementListItem::Statement(stmt) => stmt.contains_arguments(),
StatementListItem::Declaration(decl) => decl.contains_arguments(),
@ -71,7 +71,8 @@ impl StatementListItem {
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
#[must_use]
pub fn contains(&self, symbol: ContainsSymbol) -> bool {
match self {
StatementListItem::Statement(stmt) => stmt.contains(symbol),
StatementListItem::Declaration(decl) => decl.contains(symbol),
@ -150,7 +151,7 @@ impl VisitWith for StatementListItem {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StatementList {
statements: Box<[StatementListItem]>,
@ -160,12 +161,14 @@ pub struct StatementList {
impl StatementList {
/// Gets the list of statements.
#[inline]
#[must_use]
pub fn statements(&self) -> &[StatementListItem] {
&self.statements
}
/// Get the strict mode.
#[inline]
#[must_use]
pub fn strict(&self) -> bool {
self.strict
}
@ -176,14 +179,15 @@ impl StatementList {
self.strict = strict;
}
/// Returns the var declared names of a `StatementList`.
#[inline]
pub(crate) fn var_declared_names(&self, vars: &mut FxHashSet<Identifier>) {
pub fn var_declared_names(&self, vars: &mut FxHashSet<Identifier>) {
for stmt in &*self.statements {
stmt.var_declared_names(vars);
}
}
/// Return the lexically declared names of a `StatementList`.
/// Returns the lexically declared names of a `StatementList`.
///
/// The returned list may contain duplicates.
///
@ -193,7 +197,8 @@ impl StatementList {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames
pub(crate) fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> {
#[must_use]
pub fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> {
let mut names = Vec::new();
for node in self.statements() {
@ -216,7 +221,8 @@ impl StatementList {
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames
pub(crate) fn lexically_declared_names_top_level(&self) -> Vec<Identifier> {
#[must_use]
pub fn lexically_declared_names_top_level(&self) -> Vec<Identifier> {
let mut names = Vec::new();
for node in self.statements() {
@ -254,7 +260,7 @@ impl StatementList {
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
pub fn contains_arguments(&self) -> bool {
self.statements
.iter()
.any(StatementListItem::contains_arguments)
@ -267,7 +273,8 @@ impl StatementList {
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
#[must_use]
pub fn contains(&self, symbol: ContainsSymbol) -> bool {
self.statements.iter().any(|stmt| stmt.contains(symbol))
}
}

26
boa_engine/src/syntax/ast/visitor.rs → boa_ast/src/visitor.rs

@ -14,38 +14,38 @@ macro_rules! try_break {
};
}
use crate::syntax::ast::declaration::{
use crate::declaration::{
Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList,
};
use crate::syntax::ast::expression::access::{
use crate::expression::access::{
PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess,
SuperPropertyAccess,
};
use crate::syntax::ast::expression::literal::{
use crate::expression::literal::{
ArrayLiteral, Literal, ObjectLiteral, TemplateElement, TemplateLiteral,
};
use crate::syntax::ast::expression::operator::assign::{Assign, AssignTarget};
use crate::syntax::ast::expression::operator::{Binary, Conditional, Unary};
use crate::syntax::ast::expression::{
use crate::expression::operator::assign::{Assign, AssignTarget};
use crate::expression::operator::{Binary, Conditional, Unary};
use crate::expression::{
Await, Call, Expression, Identifier, New, Optional, OptionalOperation, OptionalOperationKind,
Spread, SuperCall, TaggedTemplate, Yield,
};
use crate::syntax::ast::function::{
use crate::function::{
ArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameter,
FormalParameterList, Function, Generator,
};
use crate::syntax::ast::pattern::{
use crate::pattern::{
ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern,
};
use crate::syntax::ast::property::{MethodDefinition, PropertyDefinition, PropertyName};
use crate::syntax::ast::statement::iteration::{
use crate::property::{MethodDefinition, PropertyDefinition, PropertyName};
use crate::statement::iteration::{
Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop,
IterableLoopInitializer, WhileLoop,
};
use crate::syntax::ast::statement::{
use crate::statement::{
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try,
};
use crate::syntax::ast::{StatementList, StatementListItem};
use crate::{StatementList, StatementListItem};
use boa_interner::Sym;
/// Creates the default visit function implementation for a particular type
@ -77,7 +77,6 @@ macro_rules! define_visit_mut {
///
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
/// visitor pattern.
#[allow(unused_variables)]
pub trait Visitor<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early.
type BreakTy;
@ -162,7 +161,6 @@ pub trait Visitor<'ast>: Sized {
///
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
/// visitor pattern.
#[allow(unused_variables)]
pub trait VisitorMut<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early.
type BreakTy;

1
boa_cli/Cargo.toml

@ -13,6 +13,7 @@ rust-version.workspace = true
[dependencies]
boa_engine = { workspace = true, features = ["deser", "console"] }
boa_ast = { workspace = true, features = ["serde"]}
boa_interner.workspace = true
rustyline = "10.0.0"
rustyline-derive = "0.7.0"

3
boa_cli/src/main.rs

@ -59,7 +59,8 @@
rustdoc::missing_doc_code_examples
)]
use boa_engine::{syntax::ast::StatementList, Context};
use boa_ast::StatementList;
use boa_engine::Context;
use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize};
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};

3
boa_engine/Cargo.toml

@ -13,7 +13,7 @@ rust-version.workspace = true
[features]
profiler = ["boa_profiler/profiler"]
deser = ["boa_interner/serde"]
deser = ["boa_interner/serde", "boa_ast/serde"]
intl = [
"dep:icu_locale_canonicalizer",
"dep:icu_locid",
@ -33,6 +33,7 @@ boa_interner.workspace = true
boa_gc.workspace = true
boa_profiler.workspace = true
boa_macros.workspace = true
boa_ast.workspace = true
gc = "0.4.1"
serde = { version = "1.0.147", features = ["derive", "rc"] }
serde_json = "1.0.87"

4
boa_engine/src/builtins/function/arguments.rs

@ -3,9 +3,9 @@ use crate::{
object::{JsObject, ObjectData},
property::PropertyDescriptor,
symbol::{self, WellKnownSymbols},
syntax::ast::function::FormalParameterList,
Context, JsValue,
};
use boa_ast::function::FormalParameterList;
use boa_gc::{Finalize, Gc, Trace};
use rustc_hash::FxHashMap;
@ -199,7 +199,7 @@ impl Arguments {
let mut bindings = FxHashMap::default();
let mut property_index = 0;
'outer: for formal in formals.parameters.iter() {
'outer: for formal in formals.as_ref() {
for name in formal.names() {
if property_index >= len {
break 'outer;

13
boa_engine/src/builtins/function/mod.rs

@ -26,10 +26,11 @@ use crate::{
property::{Attribute, PropertyDescriptor, PropertyKey},
string::utf16,
symbol::WellKnownSymbols,
syntax::{ast::function::FormalParameterList, ast::StatementList, Parser},
syntax::Parser,
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
use boa_ast::{function::FormalParameterList, StatementList};
use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym;
use boa_profiler::Profiler;
@ -493,7 +494,7 @@ impl BuiltInFunctionObject {
let prototype = get_prototype_from_constructor(new_target, default, context)?;
if let Some((body_arg, args)) = args.split_last() {
let parameters = if args.is_empty() {
FormalParameterList::empty()
FormalParameterList::default()
} else {
let mut parameters = Vec::with_capacity(args.len());
for arg in args {
@ -554,7 +555,7 @@ impl BuiltInFunctionObject {
// Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if body.strict() {
for parameter in parameters.parameters.iter() {
for parameter in parameters.as_ref() {
for name in parameter.names() {
if name == Sym::ARGUMENTS || name == Sym::EVAL {
return Err(JsNativeError::syntax()
@ -588,7 +589,7 @@ impl BuiltInFunctionObject {
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
{
let lexically_declared_names = body.lexically_declared_names();
for param in parameters.parameters.as_ref() {
for param in parameters.as_ref() {
for param_name in param.names() {
if lexically_declared_names
.iter()
@ -627,7 +628,7 @@ impl BuiltInFunctionObject {
.name(Sym::ANONYMOUS)
.generator(true)
.compile(
&FormalParameterList::empty(),
&FormalParameterList::default(),
&StatementList::default(),
context,
)?;
@ -640,7 +641,7 @@ impl BuiltInFunctionObject {
Ok(function_object)
} else {
let code = FunctionCompiler::new().name(Sym::ANONYMOUS).compile(
&FormalParameterList::empty(),
&FormalParameterList::default(),
&StatementList::default(),
context,
)?;

4
boa_engine/src/bytecompiler/function.rs

@ -1,10 +1,10 @@
use crate::{
builtins::function::ThisMode,
bytecompiler::ByteCompiler,
syntax::ast::{declaration::Binding, function::FormalParameterList, StatementList},
vm::{BindingOpcode, CodeBlock, Opcode},
Context, JsResult,
};
use boa_ast::{declaration::Binding, function::FormalParameterList, StatementList};
use boa_gc::Gc;
use boa_interner::Sym;
use rustc_hash::FxHashMap;
@ -117,7 +117,7 @@ impl FunctionCompiler {
);
}
for parameter in parameters.parameters.iter() {
for parameter in parameters.as_ref() {
if parameter.is_rest_param() {
compiler.emit_opcode(Opcode::RestParameterInit);
}

62
boa_engine/src/bytecompiler/mod.rs

@ -2,33 +2,33 @@ mod function;
use crate::{
environments::{BindingLocator, CompileTimeEnvironment},
syntax::ast::{
declaration::{Binding, LexicalDeclaration, VarDeclaration},
expression::{
access::{PropertyAccess, PropertyAccessField},
literal::{self, TemplateElement},
operator::{
assign::{AssignOp, AssignTarget},
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
unary::UnaryOp,
},
Call, Identifier, New, Optional, OptionalOperationKind,
},
function::{
ArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameterList,
Function, Generator,
},
pattern::{ArrayPatternElement, ObjectPatternElement, Pattern},
property::{MethodDefinition, PropertyDefinition, PropertyName},
statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer},
Block, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, LabelledItem, WhileLoop,
},
Declaration, Expression, Statement, StatementList, StatementListItem,
},
vm::{BindingOpcode, CodeBlock, Opcode},
Context, JsBigInt, JsNativeError, JsResult, JsString, JsValue,
};
use boa_ast::{
declaration::{Binding, LexicalDeclaration, VarDeclaration},
expression::{
access::{PropertyAccess, PropertyAccessField},
literal::{self, TemplateElement},
operator::{
assign::{AssignOp, AssignTarget},
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
unary::UnaryOp,
},
Call, Identifier, New, Optional, OptionalOperationKind,
},
function::{
ArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameterList,
Function, Generator,
},
pattern::{ArrayPatternElement, ObjectPatternElement, Pattern},
property::{MethodDefinition, PropertyDefinition, PropertyName},
statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer},
Block, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, LabelledItem, WhileLoop,
},
Declaration, Expression, Statement, StatementList, StatementListItem,
};
use boa_gc::Gc;
use boa_interner::{Interner, Sym};
use rustc_hash::FxHashMap;
@ -1483,13 +1483,13 @@ impl<'b> ByteCompiler<'b> {
Expression::Class(class) => self.class(class, true)?,
Expression::SuperCall(super_call) => {
let contains_spread = super_call
.args()
.arguments()
.iter()
.any(|arg| matches!(arg, Expression::Spread(_)));
if contains_spread {
self.emit_opcode(Opcode::PushNewArray);
for arg in super_call.args() {
for arg in super_call.arguments() {
self.compile_expr(arg, true)?;
if let Expression::Spread(_) = arg {
self.emit_opcode(Opcode::InitIterator);
@ -1499,7 +1499,7 @@ impl<'b> ByteCompiler<'b> {
}
}
} else {
for arg in super_call.args() {
for arg in super_call.arguments() {
self.compile_expr(arg, true)?;
}
}
@ -1507,7 +1507,7 @@ impl<'b> ByteCompiler<'b> {
if contains_spread {
self.emit_opcode(Opcode::SuperCallSpread);
} else {
self.emit(Opcode::SuperCall, &[super_call.args().len() as u32]);
self.emit(Opcode::SuperCall, &[super_call.arguments().len() as u32]);
}
if !use_expr {
@ -2589,9 +2589,9 @@ impl<'b> ByteCompiler<'b> {
let push_env =
self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(finally.statement_list(), configurable_globals);
self.create_decls(finally.block().statement_list(), configurable_globals);
self.compile_statement_list(
finally.statement_list(),
finally.block().statement_list(),
false,
configurable_globals,
)?;
@ -3207,7 +3207,7 @@ impl<'b> ByteCompiler<'b> {
.context
.initialize_mutable_binding(Sym::ARGUMENTS.into(), false),
);
for parameter in expr.parameters().parameters.iter() {
for parameter in expr.parameters().as_ref() {
if parameter.is_rest_param() {
compiler.emit_opcode(Opcode::RestParameterInit);
}

3
boa_engine/src/context/mod.rs

@ -20,11 +20,12 @@ use crate::{
object::{FunctionBuilder, GlobalPropertyMap, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
syntax::{ast::StatementList, parser::ParseError, Parser},
syntax::{parser::ParseError, Parser},
vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm},
JsResult, JsString, JsValue,
};
use boa_ast::StatementList;
use boa_gc::Gc;
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;

4
boa_engine/src/environments/compile.rs

@ -1,7 +1,7 @@
use crate::{
environments::runtime::BindingLocator, property::PropertyDescriptor,
syntax::ast::expression::Identifier, Context, JsString, JsValue,
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue,
};
use boa_ast::expression::Identifier;
use boa_gc::{Cell, Finalize, Gc, Trace};
use rustc_hash::FxHashMap;

4
boa_engine/src/environments/runtime.rs

@ -1,11 +1,11 @@
use std::cell::Cell;
use crate::{
environments::CompileTimeEnvironment, error::JsNativeError, object::JsObject,
syntax::ast::expression::Identifier, Context, JsValue,
environments::CompileTimeEnvironment, error::JsNativeError, object::JsObject, Context, JsValue,
};
use boa_gc::{Cell as GcCell, Finalize, Gc, Trace};
use boa_ast::expression::Identifier;
use rustc_hash::FxHashSet;
/// A declarative environment holds binding values at runtime.

144
boa_engine/src/syntax/ast/expression/literal/array.rs

@ -1,144 +0,0 @@
//! Array declaration Expression.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, ContainsSymbol};
use crate::try_break;
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// 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 = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayLiteral {
arr: Box<[Option<Expression>]>,
has_trailing_comma_spread: bool,
}
impl ArrayLiteral {
/// Crate a new array literal.
pub(crate) fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self
where
A: Into<Box<[Option<Expression>]>>,
{
Self {
arr: array.into(),
has_trailing_comma_spread,
}
}
/// Indicates if a spread operator in the array literal has a trailing comma.
/// This is a syntax error in some cases.
pub(crate) fn has_trailing_comma_spread(&self) -> bool {
self.has_trailing_comma_spread
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.arr
.iter()
.flatten()
.any(Expression::contains_arguments)
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.arr.iter().flatten().any(|expr| expr.contains(symbol))
}
}
impl AsRef<[Option<Expression>]> for ArrayLiteral {
#[inline]
fn as_ref(&self) -> &[Option<Expression>] {
&self.arr
}
}
impl<T> From<T> for ArrayLiteral
where
T: Into<Box<[Option<Expression>]>>,
{
#[inline]
fn from(decl: T) -> Self {
Self {
arr: decl.into(),
has_trailing_comma_spread: false,
}
}
}
impl ToInternedString for ArrayLiteral {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = String::from("[");
let mut first = true;
for e in &*self.arr {
if first {
first = false;
} else {
buf.push_str(", ");
}
if let Some(e) = e {
buf.push_str(&e.to_interned_string(interner));
}
}
buf.push(']');
buf
}
}
impl From<ArrayLiteral> for Expression {
#[inline]
fn from(arr: ArrayLiteral) -> Self {
Self::ArrayLiteral(arr)
}
}
impl VisitWith for ArrayLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.arr.iter().flatten() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for expr in self.arr.iter_mut().flatten() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
let a = [1, 2, 3, "words", "more words"];
let b = [];
"#,
);
}
}

180
boa_engine/src/syntax/ast/expression/literal/object/mod.rs

@ -1,180 +0,0 @@
//! Object Expression.
#[cfg(test)]
mod tests;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
block_to_string,
expression::Expression,
join_nodes,
property::{MethodDefinition, PropertyDefinition},
ContainsSymbol,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// Objects in JavaScript may be defined as an unordered collection of related data, of
/// primitive or reference types, in the form of “key: value” pairs.
///
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
/// notation.
///
/// An object initializer is an expression that describes the initialization of an
/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
/// Values of object properties can either contain [`primitive`][primitive] data types or other
/// objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "deser", serde(transparent))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectLiteral {
properties: Box<[PropertyDefinition]>,
}
impl ObjectLiteral {
/// Gets the object literal properties
#[inline]
pub fn properties(&self) -> &[PropertyDefinition] {
&self.properties
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
self.properties
.iter()
.any(PropertyDefinition::contains_arguments)
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
self.properties.iter().any(|prop| prop.contains(symbol))
}
}
impl ToIndentedString for ObjectLiteral {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let mut buf = "{\n".to_owned();
let indentation = " ".repeat(indent_n + 1);
for property in self.properties().iter() {
buf.push_str(&match property {
PropertyDefinition::IdentifierReference(ident) => {
format!("{indentation}{},\n", interner.resolve_expect(ident.sym()))
}
PropertyDefinition::Property(key, value) => {
format!(
"{indentation}{}: {},\n",
key.to_interned_string(interner),
value.to_no_indent_string(interner, indent_n + 1)
)
}
PropertyDefinition::SpreadObject(key) => {
format!("{indentation}...{},\n", key.to_interned_string(interner))
}
PropertyDefinition::MethodDefinition(key, method) => {
format!(
"{indentation}{}{}({}) {},\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
key.to_interned_string(interner),
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
join_nodes(interner, &expression.parameters().parameters)
}
MethodDefinition::Generator(expression) => {
join_nodes(interner, &expression.parameters().parameters)
}
MethodDefinition::AsyncGenerator(expression) => {
join_nodes(interner, &expression.parameters().parameters)
}
MethodDefinition::Async(expression) => {
join_nodes(interner, &expression.parameters().parameters)
}
},
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
},
)
}
PropertyDefinition::CoverInitializedName(ident, expr) => {
format!(
"{indentation}{} = {},\n",
interner.resolve_expect(ident.sym()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
});
}
buf.push_str(&format!("{}}}", " ".repeat(indent_n)));
buf
}
}
impl<T> From<T> for ObjectLiteral
where
T: Into<Box<[PropertyDefinition]>>,
{
#[inline]
fn from(props: T) -> Self {
Self {
properties: props.into(),
}
}
}
impl From<ObjectLiteral> for Expression {
#[inline]
fn from(obj: ObjectLiteral) -> Self {
Self::ObjectLiteral(obj)
}
}
impl VisitWith for ObjectLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for pd in self.properties.iter() {
try_break!(visitor.visit_property_definition(pd));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for pd in self.properties.iter_mut() {
try_break!(visitor.visit_property_definition_mut(pd));
}
ControlFlow::Continue(())
}
}

109
boa_engine/src/syntax/ast/expression/literal/object/tests.rs

@ -1,109 +0,0 @@
use crate::exec;
#[test]
fn spread_shallow_clone() {
let scenario = r#"
var a = { x: {} };
var aClone = { ...a };
a.x === aClone.x
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn spread_merge() {
let scenario = r#"
var a = { x: 1, y: 2 };
var b = { x: -1, z: -3, ...a };
(b.x === 1) && (b.y === 2) && (b.z === -3)
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn spread_overriding_properties() {
let scenario = r#"
var a = { x: 0, y: 0 };
var aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
(aWithOverrides.x === 1) && (aWithOverrides.y === 2)
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn spread_getters_in_initializer() {
let scenario = r#"
var a = { x: 42 };
var aWithXGetter = { ...a, get x() { throw new Error('not thrown yet') } };
"#;
assert_eq!(&exec(scenario), "undefined");
}
#[test]
fn spread_getters_in_object() {
let scenario = r#"
var a = { x: 42 };
var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } };
"#;
assert_eq!(&exec(scenario), "\"Error\": \"not thrown yet\"");
}
#[test]
fn spread_setters() {
let scenario = r#"
var z = { set x(nexX) { throw new Error() }, ... { x: 1 } };
"#;
assert_eq!(&exec(scenario), "undefined");
}
#[test]
fn spread_null_and_undefined_ignored() {
let scenario = r#"
var a = { ...null, ...undefined };
var count = 0;
for (key in a) { count++; }
count === 0
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
let other = {
c: 10,
};
let inst = {
val: 5,
b: "hello world",
nested: {
a: 5,
b: 6,
},
...other,
say_hi: function() {
console.log("hello!");
},
get a() {
return this.val + 1;
},
set a(new_value) {
this.val = new_value;
},
say_hello(msg) {
console.log("hello " + msg);
},
};
inst.a = 20;
inst.a;
inst.say_hello("humans");
"#,
);
}

475
boa_engine/src/syntax/ast/expression/operator/assign/mod.rs

@ -1,475 +0,0 @@
//! Assignment expression nodes, as defined by the [spec].
//!
//! An [assignment operator][mdn] assigns a value to its left operand based on the value of its right
//! operand. Almost any [`LeftHandSideExpression`][lhs] Parse Node can be the target of a simple
//! assignment expression (`=`). However, the compound assignment operations such as `%=` or `??=`
//! only allow ["simple"][simple] left hand side expressions as an assignment target.
//!
//! [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
//! [lhs]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
//! [simple]: https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, Sym, ToInternedString};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::{
ast::{
expression::{
access::PropertyAccess,
identifier::Identifier,
literal::{ArrayLiteral, ObjectLiteral},
Expression,
},
pattern::{
ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern,
},
property::{PropertyDefinition, PropertyName},
ContainsSymbol,
},
parser::RESERVED_IDENTIFIERS_STRICT,
};
use crate::try_break;
/// An assignment operator expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub struct Assign {
op: AssignOp,
lhs: Box<AssignTarget>,
rhs: Box<Expression>,
}
impl Assign {
/// Creates an `Assign` AST Expression.
pub(in crate::syntax) fn new(op: AssignOp, lhs: AssignTarget, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
/// Gets the operator of the assignment operation.
#[inline]
pub fn op(&self) -> AssignOp {
self.op
}
/// Gets the left hand side of the assignment operation.
#[inline]
pub fn lhs(&self) -> &AssignTarget {
&self.lhs
}
/// Gets the right hand side of the assignment operation.
#[inline]
pub fn rhs(&self) -> &Expression {
&self.rhs
}
#[inline]
pub(crate) fn contains_arguments(&self) -> bool {
(match &*self.lhs {
AssignTarget::Identifier(ident) => *ident == Sym::ARGUMENTS,
AssignTarget::Access(access) => access.contains_arguments(),
AssignTarget::Pattern(pattern) => pattern.contains_arguments(),
} || self.rhs.contains_arguments())
}
#[inline]
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
(match &*self.lhs {
AssignTarget::Identifier(_) => false,
AssignTarget::Access(access) => access.contains(symbol),
AssignTarget::Pattern(pattern) => pattern.contains(symbol),
} || self.rhs.contains(symbol))
}
}
impl ToInternedString for Assign {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {} {}",
self.lhs.to_interned_string(interner),
self.op,
self.rhs.to_interned_string(interner)
)
}
}
impl From<Assign> for Expression {
#[inline]
fn from(op: Assign) -> Self {
Self::Assign(op)
}
}
impl VisitWith for Assign {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_assign_target(&self.lhs));
visitor.visit_expression(&self.rhs)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_assign_target_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}
/// The valid left-hand-side expressions of an assignment operator. Also called
/// [`LeftHandSideExpression`][spec] in the spec.
///
/// [spec]: hhttps://tc39.es/ecma262/#prod-LeftHandSideExpression
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum AssignTarget {
/// A simple identifier, such as `a`.
Identifier(Identifier),
/// A property access, such as `a.prop`.
Access(PropertyAccess),
/// A pattern assignment, such as `{a, b, ...c}`.
Pattern(Pattern),
}
impl AssignTarget {
/// Converts the left-hand-side Expression of an assignment expression into an [`AssignTarget`].
/// Returns `None` if the given Expression is an invalid left-hand-side for a assignment expression.
pub(crate) fn from_expression(
expression: &Expression,
strict: bool,
destructure: bool,
) -> Option<Self> {
match expression {
Expression::Identifier(id) => Some(Self::Identifier(*id)),
Expression::PropertyAccess(access) => Some(Self::Access(access.clone())),
Expression::ObjectLiteral(object) if destructure => {
let pattern = object_decl_to_declaration_pattern(object, strict)?;
Some(Self::Pattern(pattern.into()))
}
Expression::ArrayLiteral(array) if destructure => {
let pattern = array_decl_to_declaration_pattern(array, strict)?;
Some(Self::Pattern(pattern.into()))
}
_ => None,
}
}
}
impl ToInternedString for AssignTarget {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
AssignTarget::Identifier(id) => id.to_interned_string(interner),
AssignTarget::Access(access) => access.to_interned_string(interner),
AssignTarget::Pattern(pattern) => pattern.to_interned_string(interner),
}
}
}
impl From<Identifier> for AssignTarget {
#[inline]
fn from(target: Identifier) -> Self {
Self::Identifier(target)
}
}
/// Converts an object literal into an object declaration pattern.
pub(crate) fn object_decl_to_declaration_pattern(
object: &ObjectLiteral,
strict: bool,
) -> Option<ObjectPattern> {
let mut bindings = Vec::new();
let mut excluded_keys = Vec::new();
for (i, property) in object.properties().iter().enumerate() {
match property {
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => {
return None
}
PropertyDefinition::IdentifierReference(ident) => {
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: None,
});
}
PropertyDefinition::Property(name, expr) => match (name, expr) {
(PropertyName::Literal(name), Expression::Identifier(ident)) if *name == *ident => {
if strict && *name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ObjectLiteral(object)) => {
let pattern = object_decl_to_declaration_pattern(object, strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ArrayLiteral(array)) => {
let pattern = array_decl_to_declaration_pattern(array, strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(_, Expression::Assign(assign)) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
if let Some(name) = name.literal() {
if name == *ident {
if strict && name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(name),
default_init: Some(assign.rhs().clone()),
});
} else {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(name),
default_init: Some(assign.rhs().clone()),
});
}
} else {
return None;
}
}
AssignTarget::Pattern(pattern) => {
bindings.push(ObjectPatternElement::Pattern {
name: name.clone(),
pattern: pattern.clone(),
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: Some(assign.rhs().clone()),
});
}
},
(_, Expression::PropertyAccess(access)) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: None,
});
}
(PropertyName::Computed(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Computed(name.clone()),
default_init: None,
});
}
_ => return None,
},
PropertyDefinition::SpreadObject(spread) => {
match spread {
Expression::Identifier(ident) => {
bindings.push(ObjectPatternElement::RestProperty {
ident: *ident,
excluded_keys: excluded_keys.clone(),
});
}
Expression::PropertyAccess(access) => {
bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess {
access: access.clone(),
excluded_keys: excluded_keys.clone(),
});
}
_ => return None,
}
if i + 1 != object.properties().len() {
return None;
}
}
PropertyDefinition::MethodDefinition(_, _) => return None,
PropertyDefinition::CoverInitializedName(ident, expr) => {
if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) {
return None;
}
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: Some(expr.clone()),
});
}
}
}
Some(ObjectPattern::new(bindings.into()))
}
/// Converts an array declaration into an array declaration pattern.
pub(crate) fn array_decl_to_declaration_pattern(
array: &ArrayLiteral,
strict: bool,
) -> Option<ArrayPattern> {
if array.has_trailing_comma_spread() {
return None;
}
let mut bindings = Vec::new();
for (i, expr) in array.as_ref().iter().enumerate() {
let expr = if let Some(expr) = expr {
expr
} else {
bindings.push(ArrayPatternElement::Elision);
continue;
};
match expr {
Expression::Identifier(ident) => {
if strict && *ident == Sym::ARGUMENTS {
return None;
}
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: None,
});
}
Expression::Spread(spread) => {
match spread.target() {
Expression::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleNameRest { ident: *ident });
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccessRest {
access: access.clone(),
});
}
Expression::ArrayLiteral(array) => {
let pattern = array_decl_to_declaration_pattern(array, strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
Expression::ObjectLiteral(object) => {
let pattern = object_decl_to_declaration_pattern(object, strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
_ => return None,
}
if i + 1 != array.as_ref().len() {
return None;
}
}
Expression::Assign(assign) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
AssignTarget::Pattern(pattern) => match pattern {
Pattern::Object(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Object(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
Pattern::Array(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Array(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
},
},
Expression::ArrayLiteral(array) => {
let pattern = array_decl_to_declaration_pattern(array, strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::ObjectLiteral(object) => {
let pattern = object_decl_to_declaration_pattern(object, strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
_ => return None,
}
}
Some(ArrayPattern::new(bindings.into()))
}
impl VisitWith for AssignTarget {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
AssignTarget::Identifier(id) => visitor.visit_identifier(id),
AssignTarget::Access(pa) => visitor.visit_property_access(pa),
AssignTarget::Pattern(pat) => visitor.visit_pattern(pat),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
AssignTarget::Identifier(id) => visitor.visit_identifier_mut(id),
AssignTarget::Access(pa) => visitor.visit_property_access_mut(pa),
AssignTarget::Pattern(pat) => visitor.visit_pattern_mut(pat),
}
}
}

140
boa_engine/src/syntax/ast/expression/operator/tests.rs

@ -1,140 +0,0 @@
use crate::exec;
#[test]
fn assignmentoperator_lhs_not_defined() {
let scenario = r#"
try {
a += 5
} catch (err) {
err.toString()
}
"#;
assert_eq!(&exec(scenario), "\"ReferenceError: a is not defined\"");
}
#[test]
fn assignmentoperator_rhs_throws_error() {
let scenario = r#"
try {
let a;
a += b
} catch (err) {
err.toString()
}
"#;
assert_eq!(&exec(scenario), "\"ReferenceError: b is not defined\"");
}
#[test]
fn instanceofoperator_rhs_not_object() {
let scenario = r#"
try {
let s = new String();
s instanceof 1
} catch (err) {
err.toString()
}
"#;
assert_eq!(
&exec(scenario),
"\"TypeError: right-hand side of 'instanceof' should be an object, got number\""
);
}
#[test]
fn instanceofoperator_rhs_not_callable() {
let scenario = r#"
try {
let s = new String();
s instanceof {}
} catch (err) {
err.toString()
}
"#;
assert_eq!(
&exec(scenario),
"\"TypeError: right-hand side of 'instanceof' is not callable\""
);
}
#[test]
fn logical_nullish_assignment() {
let scenario = r#"
let a = undefined;
a ??= 10;
a;
"#;
assert_eq!(&exec(scenario), "10");
let scenario = r#"
let a = 20;
a ??= 10;
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn logical_assignment() {
let scenario = r#"
let a = false;
a &&= 10;
a;
"#;
assert_eq!(&exec(scenario), "false");
let scenario = r#"
let a = 20;
a &&= 10;
a;
"#;
assert_eq!(&exec(scenario), "10");
let scenario = r#"
let a = null;
a ||= 10;
a;
"#;
assert_eq!(&exec(scenario), "10");
let scenario = r#"
let a = 20;
a ||= 10;
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
let a = 20;
a += 10;
a -= 10;
a *= 10;
a **= 10;
a /= 10;
a %= 10;
a &= 10;
a |= 10;
a ^= 10;
a <<= 10;
a >>= 10;
a >>>= 10;
a &&= 10;
a ||= 10;
a ??= 10;
a;
"#,
);
}

558
boa_engine/src/syntax/ast/statement/iteration/tests.rs

@ -1,558 +0,0 @@
use crate::{check_output, exec, syntax::ast::test_formatting, TestAction};
#[test]
fn while_loop_late_break() {
// Ordering with statement before the break.
let scenario = r#"
let a = 1;
while (a < 5) {
a++;
if (a == 3) {
break;
}
}
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn while_loop_early_break() {
// Ordering with statements after the break.
let scenario = r#"
let a = 1;
while (a < 5) {
if (a == 3) {
break;
}
a++;
}
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn for_loop_break() {
let scenario = r#"
let a = 1;
for (; a < 5; a++) {
if (a == 3) {
break;
}
}
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn for_loop_return() {
let scenario = r#"
function foo() {
for (let a = 1; a < 5; a++) {
if (a == 3) {
return a;
}
}
}
foo();
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn do_loop_late_break() {
// Ordering with statement before the break.
let scenario = r#"
let a = 1;
do {
a++;
if (a == 3) {
break;
}
} while (a < 5);
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn do_loop_early_break() {
// Ordering with statements after the break.
let scenario = r#"
let a = 1;
do {
if (a == 3) {
break;
}
a++;
} while (a < 5);
a;
"#;
assert_eq!(&exec(scenario), "3");
}
#[test]
fn break_out_of_inner_loop() {
let scenario = r#"
var a = 0, b = 0;
for (let i = 0; i < 2; i++) {
a++;
for (let j = 0; j < 10; j++) {
b++;
if (j == 3)
break;
}
a++;
}
[a, b]
"#;
assert_eq!(&exec(scenario), "[ 4, 8 ]");
}
#[test]
fn continue_inner_loop() {
let scenario = r#"
var a = 0, b = 0;
for (let i = 0; i < 2; i++) {
a++;
for (let j = 0; j < 10; j++) {
if (j < 3)
continue;
b++;
}
a++;
}
[a, b]
"#;
assert_eq!(&exec(scenario), "[ 4, 14 ]");
}
#[test]
fn for_loop_continue_out_of_switch() {
let scenario = r#"
var a = 0, b = 0, c = 0;
for (let i = 0; i < 3; i++) {
a++;
switch (i) {
case 0:
continue;
c++;
case 1:
continue;
case 5:
c++;
}
b++;
}
[a, b, c]
"#;
assert_eq!(&exec(scenario), "[ 3, 1, 0 ]");
}
#[test]
fn while_loop_continue() {
let scenario = r#"
var i = 0, a = 0, b = 0;
while (i < 3) {
i++;
if (i < 2) {
a++;
continue;
}
b++;
}
[a, b]
"#;
assert_eq!(&exec(scenario), "[ 1, 2 ]");
}
#[test]
fn do_while_loop_continue() {
let scenario = r#"
var i = 0, a = 0, b = 0;
do {
i++;
if (i < 2) {
a++;
continue;
}
b++;
} while (i < 3)
[a, b]
"#;
assert_eq!(&exec(scenario), "[ 1, 2 ]");
}
#[test]
fn for_of_loop_declaration() {
let scenario = r#"
var result = 0;
for (i of [1, 2, 3]) {
result = i;
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "3"),
TestAction::TestEq("i", "3"),
]);
}
#[test]
fn for_of_loop_var() {
let scenario = r#"
var result = 0;
for (var i of [1, 2, 3]) {
result = i;
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "3"),
TestAction::TestEq("i", "3"),
]);
}
#[test]
fn for_of_loop_let() {
let scenario = r#"
var result = 0;
for (let i of [1, 2, 3]) {
result = i;
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "3"),
TestAction::TestEq(
r#"
try {
i
} catch(e) {
e.toString()
}
"#,
"\"ReferenceError: i is not defined\"",
),
]);
}
#[test]
fn for_of_loop_const() {
let scenario = r#"
var result = 0;
for (let i of [1, 2, 3]) {
result = i;
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "3"),
TestAction::TestEq(
r#"
try {
i
} catch(e) {
e.toString()
}
"#,
"\"ReferenceError: i is not defined\"",
),
]);
}
#[test]
fn for_of_loop_break() {
let scenario = r#"
var result = 0;
for (var i of [1, 2, 3]) {
if (i > 1)
break;
result = i
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "1"),
TestAction::TestEq("i", "2"),
]);
}
#[test]
fn for_of_loop_continue() {
let scenario = r#"
var result = 0;
for (var i of [1, 2, 3]) {
if (i == 3)
continue;
result = i
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("result", "2"),
TestAction::TestEq("i", "3"),
]);
}
#[test]
fn for_of_loop_return() {
let scenario = r#"
function foo() {
for (i of [1, 2, 3]) {
if (i > 1)
return i;
}
}
"#;
check_output(&[
TestAction::Execute(scenario),
TestAction::TestEq("foo()", "2"),
]);
}
#[test]
fn for_loop_break_label() {
let scenario = r#"
var str = "";
outer: for (let i = 0; i < 5; i++) {
inner: for (let b = 0; b < 5; b++) {
if (b === 2) {
break outer;
}
str = str + b;
}
str = str + i;
}
str
"#;
assert_eq!(&exec(scenario), "\"01\"");
}
#[test]
fn for_loop_continue_label() {
let scenario = r#"
var count = 0;
label: for (let x = 0; x < 10;) {
while (true) {
x++;
count++;
continue label;
}
}
count
"#;
assert_eq!(&exec(scenario), "10");
}
#[test]
fn for_in_declaration() {
let init = r#"
let result = [];
let obj = { a: "a", b: 2};
var i;
for (i in obj) {
result = result.concat([i]);
}
"#;
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(
"result.length === 2 && result.includes('a') && result.includes('b')",
"true",
),
]);
}
#[test]
fn for_in_var_object() {
let init = r#"
let result = [];
let obj = { a: "a", b: 2};
for (var i in obj) {
result = result.concat([i]);
}
"#;
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(
"result.length === 2 && result.includes('a') && result.includes('b')",
"true",
),
]);
}
#[test]
fn for_in_var_array() {
let init = r#"
let result = [];
let arr = ["a", "b"];
for (var i in arr) {
result = result.concat([i]);
}
"#;
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(
"result.length === 2 && result.includes('0') && result.includes('1')",
"true",
),
]);
}
#[test]
fn for_in_let_object() {
let init = r#"
let result = [];
let obj = { a: "a", b: 2};
for (let i in obj) {
result = result.concat([i]);
}
"#;
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(
"result.length === 2 && result.includes('a') && result.includes('b')",
"true",
),
]);
}
#[test]
fn for_in_const_array() {
let init = r#"
let result = [];
let arr = ["a", "b"];
for (const i in arr) {
result = result.concat([i]);
}
"#;
check_output(&[
TestAction::Execute(init),
TestAction::TestEq(
"result.length === 2 && result.includes('0') && result.includes('1')",
"true",
),
]);
}
#[test]
fn for_in_break_label() {
let scenario = r#"
var str = "";
outer: for (let i in [1, 2]) {
inner: for (let b in [2, 3, 4]) {
if (b === "1") {
break outer;
}
str = str + b;
}
str = str + i;
}
str
"#;
assert_eq!(&exec(scenario), "\"0\"");
}
#[test]
fn for_in_continue_label() {
let scenario = r#"
var str = "";
outer: for (let i in [1, 2]) {
inner: for (let b in [2, 3, 4]) {
if (b === "1") {
continue outer;
}
str = str + b;
}
str = str + i;
}
str
"#;
assert_eq!(&exec(scenario), "\"00\"");
}
#[test]
fn fmt() {
// Labeled and unlabeled for in loops
test_formatting(
r#"
var str = "";
outer: for (let i in [1, 2]) {
for (let b in [2, 3, 4]) {
if (b === "1") {
continue outer;
}
str = str + b;
}
str = str + i;
}
str;
"#,
);
// Labeled and unlabeled for loops
test_formatting(
r#"
var str = "";
outer: for (let i = 0; i < 10; ++i) {
for (let j = 3; j < 6; ++j) {
if (j === "1") {
continue outer;
}
str = str + j;
}
str = str + i;
}
str;
"#,
);
// Labeled and unlabeled for of loops
test_formatting(
r#"
for (i of [1, 2, 3]) {
if (false) {
break;
}
}
label: for (i of [1, 2, 3]) {
if (false) {
break label;
}
}
"#,
);
// Labeled and unlabeled do while loops
test_formatting(
r#"
do {
break;
} while (true);
label: do {
break label;
} while (true);
"#,
);
// Labeled and unlabeled while loops
test_formatting(
r#"
while (true) {
break;
}
label: while (true) {
break label;
}
"#,
);
}

243
boa_engine/src/syntax/ast/statement/switch/tests.rs

@ -1,243 +0,0 @@
use crate::exec;
#[test]
fn single_case_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 10:
a = 20;
break;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn no_cases_switch() {
let scenario = r#"
let a = 10;
switch (a) {
}
a;
"#;
assert_eq!(&exec(scenario), "10");
}
#[test]
fn no_true_case_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 5:
a = 15;
break;
}
a;
"#;
assert_eq!(&exec(scenario), "10");
}
#[test]
fn two_case_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 5:
a = 15;
break;
case 10:
a = 20;
break;
}
a;
"#;
assert_eq!(&exec(scenario), "20");
}
#[test]
fn two_case_no_break_switch() {
let scenario = r#"
let a = 10;
let b = 10;
switch (a) {
case 10:
a = 150;
case 20:
b = 150;
break;
}
a + b;
"#;
assert_eq!(&exec(scenario), "300");
}
#[test]
fn three_case_partial_fallthrough() {
let scenario = r#"
let a = 10;
let b = 10;
switch (a) {
case 10:
a = 150;
case 20:
b = 150;
break;
case 15:
b = 1000;
break;
}
a + b;
"#;
assert_eq!(&exec(scenario), "300");
}
#[test]
fn default_taken_switch() {
let scenario = r#"
let a = 10;
switch (a) {
case 5:
a = 150;
break;
default:
a = 70;
}
a;
"#;
assert_eq!(&exec(scenario), "70");
}
#[test]
fn default_not_taken_switch() {
let scenario = r#"
let a = 5;
switch (a) {
case 5:
a = 150;
break;
default:
a = 70;
}
a;
"#;
assert_eq!(&exec(scenario), "150");
}
#[test]
fn string_switch() {
let scenario = r#"
let a = "hello";
switch (a) {
case "hello":
a = "world";
break;
default:
a = "hi";
}
a;
"#;
assert_eq!(&exec(scenario), "\"world\"");
}
#[test]
fn bigger_switch_example() {
let expected = [
"\"Mon\"",
"\"Tue\"",
"\"Wed\"",
"\"Thurs\"",
"\"Fri\"",
"\"Sat\"",
"\"Sun\"",
];
for (i, val) in expected.iter().enumerate() {
let scenario = format!(
r#"
let a = {i};
let b = "unknown";
switch (a) {{
case 0:
b = "Mon";
break;
case 1:
b = "Tue";
break;
case 2:
b = "Wed";
break;
case 3:
b = "Thurs";
break;
case 4:
b = "Fri";
break;
case 5:
b = "Sat";
break;
case 6:
b = "Sun";
break;
}}
b;
"#,
);
assert_eq!(&exec(&scenario), val);
}
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
let a = 3;
let b = "unknown";
switch (a) {
case 0:
b = "Mon";
break;
case 1:
b = "Tue";
break;
case 2:
b = "Wed";
break;
case 3:
b = "Thurs";
break;
case 4:
b = "Fri";
break;
case 5:
b = "Sat";
break;
case 6:
b = "Sun";
break;
default:
b = "Unknown";
}
b;
"#,
);
}

147
boa_engine/src/syntax/ast/statement/try/tests.rs

@ -1,147 +0,0 @@
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_pattern_object() {
let scenario = r#"
let a = 10;
try {
throw {
n: 30,
};
} catch ({ n }) {
a = n;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}
#[test]
fn catch_binding_pattern_array() {
let scenario = r#"
let a = 10;
try {
throw [20, 30];
} catch ([, n]) {
a = n;
}
a;
"#;
assert_eq!(&exec(scenario), "30");
}
#[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");
}
#[test]
fn fmt() {
crate::syntax::ast::test_formatting(
r#"
try {
throw "hello";
} catch(e) {
console.log(e);
} finally {
console.log("things");
}
try {
throw "hello";
} catch {
console.log("something went wrong");
}
"#,
);
}

118
boa_engine/src/syntax/ast/statement_list/tests.rs

@ -1,118 +0,0 @@
use crate::exec;
#[test]
fn strict_mode_global() {
let scenario = r#"
'use strict';
let throws = false;
try {
delete Boolean.prototype;
} catch (e) {
throws = true;
}
throws
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn strict_mode_function() {
let scenario = r#"
let throws = false;
function t() {
'use strict';
try {
delete Boolean.prototype;
} catch (e) {
throws = true;
}
}
t()
throws
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn strict_mode_function_after() {
let scenario = r#"
function t() {
'use strict';
}
t()
let throws = false;
try {
delete Boolean.prototype;
} catch (e) {
throws = true;
}
throws
"#;
assert_eq!(&exec(scenario), "false");
}
#[test]
fn strict_mode_global_active_in_function() {
let scenario = r#"
'use strict'
let throws = false;
function a(){
try {
delete Boolean.prototype;
} catch (e) {
throws = true;
}
}
a();
throws
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn strict_mode_function_in_function() {
let scenario = r#"
let throws = false;
function a(){
try {
delete Boolean.prototype;
} catch (e) {
throws = true;
}
}
function b(){
'use strict';
a();
}
b();
throws
"#;
assert_eq!(&exec(scenario), "false");
}
#[test]
fn strict_mode_function_return() {
let scenario = r#"
let throws = false;
function a() {
'use strict';
return function () {
try {
delete Boolean.prototype;
} catch (e) {
throws = true;
}
}
}
a()();
throws
"#;
assert_eq!(&exec(scenario), "true");
}

6
boa_engine/src/syntax/lexer/comment.rs

@ -1,10 +1,8 @@
//! This module implements lexing for comments used in the JavaScript programing language.
use super::{Cursor, Error, Tokenizer};
use crate::syntax::{
ast::{Position, Span},
lexer::{Token, TokenKind},
};
use crate::syntax::lexer::{Token, TokenKind};
use boa_ast::{Position, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

2
boa_engine/src/syntax/lexer/cursor.rs

@ -1,5 +1,5 @@
//! Module implementing the lexer cursor. This is used for managing the input byte stream.
use crate::syntax::ast::Position;
use boa_ast::Position;
use boa_profiler::Profiler;
use std::io::{self, Bytes, Error, ErrorKind, Read};

2
boa_engine/src/syntax/lexer/error.rs

@ -5,7 +5,7 @@
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard
use super::Position;
use boa_ast::Position;
use std::{error::Error as StdError, fmt, io};
#[derive(Debug)]

6
boa_engine/src/syntax/lexer/identifier.rs

@ -1,10 +1,8 @@
//! This module implements lexing for identifiers (foo, myvar, etc.) used in the JavaScript programing language.
use super::{Cursor, Error, Tokenizer};
use crate::syntax::{
ast::{Keyword, Position, Span},
lexer::{StringLiteral, Token, TokenKind},
};
use crate::syntax::lexer::{StringLiteral, Token, TokenKind};
use boa_ast::{Keyword, Position, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use boa_unicode::UnicodeProperties;

2
boa_engine/src/syntax/lexer/mod.rs

@ -42,7 +42,7 @@ use self::{
string::StringLiteral,
template::TemplateLiteral,
};
use crate::syntax::ast::{Position, Punctuator, Span};
use boa_ast::{Position, Punctuator, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

6
boa_engine/src/syntax/lexer/number.rs

@ -1,12 +1,10 @@
//! This module implements lexing for number literals (123, 787) used in the JavaScript programing language.
use crate::{
syntax::{
ast::{Position, Span},
lexer::{token::Numeric, Cursor, Error, Token, TokenKind, Tokenizer},
},
syntax::lexer::{token::Numeric, Cursor, Error, Token, TokenKind, Tokenizer},
JsBigInt,
};
use boa_ast::{Position, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use num_bigint::BigInt;

6
boa_engine/src/syntax/lexer/operator.rs

@ -1,9 +1,7 @@
//! This module implements lexing for operators (+, - etc.) used in the JavaScript programing language.
use crate::syntax::{
ast::{Position, Punctuator, Span},
lexer::{Cursor, Error, Token, TokenKind, Tokenizer},
};
use crate::syntax::lexer::{Cursor, Error, Token, TokenKind, Tokenizer};
use boa_ast::{Position, Punctuator, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

6
boa_engine/src/syntax/lexer/private_identifier.rs

@ -1,10 +1,8 @@
//! This module implements lexing for private identifiers (#foo, #myvar, etc.) used in the JavaScript programing language.
use super::{identifier::Identifier, Cursor, Error, Tokenizer};
use crate::syntax::{
ast::{Position, Span},
lexer::{Token, TokenKind},
};
use crate::syntax::lexer::{Token, TokenKind};
use boa_ast::{Position, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

6
boa_engine/src/syntax/lexer/regex.rs

@ -1,11 +1,9 @@
//! This module implements lexing for regex literals used in the JavaScript programing language.
use super::{Cursor, Error, Span, Tokenizer};
use crate::syntax::{
ast::Position,
lexer::{Token, TokenKind},
};
use crate::syntax::lexer::{Token, TokenKind};
use bitflags::bitflags;
use boa_ast::Position;
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::{

6
boa_engine/src/syntax/lexer/spread.rs

@ -1,10 +1,8 @@
//! This module implements lexing for spread (...) literals used in the JavaScript programing language.
use super::{Cursor, Error, Tokenizer};
use crate::syntax::{
ast::{Position, Punctuator, Span},
lexer::Token,
};
use crate::syntax::lexer::Token;
use boa_ast::{Position, Punctuator, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

6
boa_engine/src/syntax/lexer/string.rs

@ -1,10 +1,8 @@
//! This module implements lexing for string literals used in the JavaScript programing language.
use super::{Cursor, Error, Tokenizer};
use crate::syntax::{
ast::{Position, Span},
lexer::{Token, TokenKind},
};
use crate::syntax::lexer::{Token, TokenKind};
use boa_ast::{Position, Span};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::{

15
boa_engine/src/syntax/lexer/template.rs

@ -1,21 +1,16 @@
//! This module implements lexing for template literals used in the JavaScript programing language.
use super::{Cursor, Error, Tokenizer};
use crate::{
syntax::lexer::string::{StringLiteral, UTF16CodeUnitsBuffer},
syntax::{
ast::{Position, Span},
lexer::{Token, TokenKind},
},
use crate::syntax::lexer::{
string::{StringLiteral, UTF16CodeUnitsBuffer},
Token, TokenKind,
};
use boa_ast::{Position, Span};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
use std::io::{self, ErrorKind, Read};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TemplateString {
/// The template string of template literal with argument `raw` true.

10
boa_engine/src/syntax/lexer/tests.rs

@ -1,16 +1,12 @@
//! Tests for the lexer.
#![allow(clippy::indexing_slicing)]
use boa_ast::Keyword;
use boa_interner::Sym;
// use super::regex::RegExpFlags;
use super::token::Numeric;
use super::*;
use super::{Error, Position};
use crate::{
string::utf16,
syntax::{ast::Keyword, lexer::template::TemplateString},
};
use super::{token::Numeric, Error, Position, *};
use crate::{string::utf16, syntax::lexer::template::TemplateString};
use std::str;
fn span(start: (u32, u32), end: (u32, u32)) -> Span {

14
boa_engine/src/syntax/lexer/token.rs

@ -5,14 +5,10 @@
//!
//! [spec]: https://tc39.es/ecma262/#sec-tokens
use crate::syntax::{
ast::{Keyword, Punctuator, Span},
lexer::template::TemplateString,
};
use crate::syntax::lexer::template::TemplateString;
use boa_ast::{Keyword, Punctuator, Span};
use boa_interner::{Interner, Sym};
use num_bigint::BigInt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
/// This represents the smallest individual words, phrases, or characters that JavaScript can understand.
///
@ -20,7 +16,7 @@ use serde::{Deserialize, Serialize};
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tokens
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
/// The token kind, which contains the actual data of the token.
@ -55,7 +51,7 @@ impl Token {
}
/// Represents the type different types of numeric literals.
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Debug)]
pub enum Numeric {
/// A floating point number
@ -90,7 +86,7 @@ impl From<BigInt> for Numeric {
}
/// Represents the type of Token and the data it has inside.
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Debug)]
pub enum TokenKind {
/// A boolean literal, which is either `true` or `false`.

3
boa_engine/src/syntax/mod.rs

@ -1,7 +1,6 @@
//! Syntactical analysis, such as Abstract Syntax Tree (AST), Parsing and Lexing
//! Syntactical analysis, such as Parsing and Lexing.
// syntax module has a lot of acronyms
pub mod ast;
pub mod lexer;
pub mod parser;

2
boa_engine/src/syntax/parser/cursor/buffered_lexer/mod.rs

@ -1,8 +1,8 @@
use crate::syntax::{
ast::Position,
lexer::{InputElement, Lexer, Token, TokenKind},
parser::error::ParseError,
};
use boa_ast::Position;
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

6
boa_engine/src/syntax/parser/cursor/mod.rs

@ -2,10 +2,8 @@
mod buffered_lexer;
use super::{statement::PrivateElement, ParseError};
use crate::syntax::{
ast::{Position, Punctuator},
lexer::{InputElement, Lexer, Token, TokenKind},
};
use crate::syntax::lexer::{InputElement, Lexer, Token, TokenKind};
use boa_ast::{Position, Punctuator};
use boa_interner::{Interner, Sym};
use buffered_lexer::BufferedLexer;
use rustc_hash::FxHashMap;

7
boa_engine/src/syntax/parser/error.rs

@ -1,9 +1,8 @@
//! Error and result implementation for the parser.
use crate::syntax::{
ast::{Position, Span},
lexer::Error as LexError,
};
use crate::syntax::lexer::Error as LexError;
use boa_ast::{Position, Span};
use std::fmt;
/// Result of a parsing operation.

84
boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs

@ -9,23 +9,23 @@
use super::AssignmentExpression;
use crate::syntax::{
ast::{
self,
declaration::Variable,
expression::Identifier,
function::{FormalParameter, FormalParameterList, FormalParameterListFlags},
statement::Return,
Expression, Punctuator, StatementList,
},
lexer::{Error as LexError, TokenKind},
parser::{
error::{ErrorContext, ParseError, ParseResult},
expression::BindingIdentifier,
function::{FormalParameters, FunctionBody},
AllowAwait, AllowIn, AllowYield, Cursor, TokenParser,
name_in_lexically_declared_names, AllowAwait, AllowIn, AllowYield, Cursor, TokenParser,
},
};
use boa_interner::{Interner, Sym};
use boa_ast::{
self as ast,
declaration::Variable,
expression::Identifier,
function::{FormalParameter, FormalParameterList},
statement::Return,
Expression, Punctuator, StatementList,
};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;
@ -78,40 +78,33 @@ where
let _timer = Profiler::global().start_event("ArrowFunction", "Parsing");
let next_token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let (params, params_start_position) =
if let TokenKind::Punctuator(Punctuator::OpenParen) = &next_token.kind() {
// CoverParenthesizedExpressionAndArrowParameterList
let params_start_position = cursor
.expect(Punctuator::OpenParen, "arrow function", interner)?
.span()
.end();
let params = FormalParameters::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(Punctuator::CloseParen, "arrow function", interner)?;
(params, params_start_position)
} else {
let params_start_position = next_token.span().start();
let param = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)
.context("arrow function")?;
let has_arguments = param == Sym::ARGUMENTS;
let mut flags = FormalParameterListFlags::IS_SIMPLE;
if has_arguments {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
(
FormalParameterList::new(
Box::new([FormalParameter::new(
Variable::from_identifier(param, None),
false,
)]),
flags,
1,
),
params_start_position,
)
};
let (params, params_start_position) = if let TokenKind::Punctuator(Punctuator::OpenParen) =
&next_token.kind()
{
// CoverParenthesizedExpressionAndArrowParameterList
let params_start_position = cursor
.expect(Punctuator::OpenParen, "arrow function", interner)?
.span()
.end();
let params = FormalParameters::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(Punctuator::CloseParen, "arrow function", interner)?;
(params, params_start_position)
} else {
let params_start_position = next_token.span().start();
let param = BindingIdentifier::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)
.context("arrow function")?;
(
FormalParameterList::try_from(FormalParameter::new(
Variable::from_identifier(param, None),
false,
))
.expect("a single binding identifier without init is always a valid param list"),
params_start_position,
)
};
cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?;
@ -144,7 +137,8 @@ where
// It is a Syntax Error if any element of the BoundNames of ArrowParameters
// also occurs in the LexicallyDeclaredNames of ConciseBody.
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
params.name_in_lexically_declared_names(
name_in_lexically_declared_names(
&params,
&body.lexically_declared_names_top_level(),
params_start_position,
)?;

8
boa_engine/src/syntax/parser/expression/assignment/conditional.rs

@ -8,16 +8,16 @@
//! [spec]: https://tc39.es/ecma262/#sec-conditional-operator
use crate::syntax::{
ast::{
expression::{operator::Conditional, Identifier},
Expression, Punctuator,
},
lexer::TokenKind,
parser::{
expression::{AssignmentExpression, ShortCircuitExpression},
AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser,
},
};
use boa_ast::{
expression::{operator::Conditional, Identifier},
Expression, Punctuator,
};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

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

@ -9,19 +9,19 @@
use super::ParseError;
use crate::syntax::{
ast::{
expression::{
operator::{binary::ArithmeticOp, Binary},
Identifier,
},
Expression, Keyword, Punctuator,
},
lexer::TokenKind,
parser::{
expression::{unary::UnaryExpression, update::UpdateExpression},
AllowAwait, AllowYield, Cursor, ParseResult, TokenParser,
},
};
use boa_ast::{
expression::{
operator::{binary::ArithmeticOp, Binary},
Identifier,
},
Expression, Keyword, Punctuator,
};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

28
boa_engine/src/syntax/parser/expression/assignment/mod.rs

@ -13,14 +13,6 @@ mod exponentiation;
mod r#yield;
use crate::syntax::{
ast::{
self,
expression::{
operator::assign::{Assign, AssignOp, AssignTarget},
Identifier,
},
Expression, Keyword, Punctuator,
},
lexer::{Error as LexError, InputElement, TokenKind},
parser::{
expression::assignment::{
@ -28,15 +20,26 @@ use crate::syntax::{
conditional::ConditionalExpression,
r#yield::YieldExpression,
},
AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
name_in_lexically_declared_names, AllowAwait, AllowIn, AllowYield, Cursor, ParseError,
ParseResult, TokenParser,
},
};
use boa_ast::{
self as ast,
expression::{
operator::assign::{Assign, AssignOp, AssignTarget},
Identifier,
},
Expression, Keyword, Punctuator,
};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;
pub(super) use exponentiation::ExponentiationExpression;
use super::check_strict_arguments_or_eval;
/// Assignment expression parsing.
///
/// This can be one of the following:
@ -188,7 +191,8 @@ where
// It is a Syntax Error if any element of the BoundNames of ArrowParameters
// also occurs in the LexicallyDeclaredNames of ConciseBody.
// https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors
parameters.name_in_lexically_declared_names(
name_in_lexically_declared_names(
&parameters,
&body.lexically_declared_names_top_level(),
position,
)?;
@ -202,7 +206,7 @@ where
TokenKind::Punctuator(Punctuator::Assign) => {
if cursor.strict_mode() {
if let Expression::Identifier(ident) = lhs {
ident.check_strict_arguments_or_eval(position)?;
check_strict_arguments_or_eval(ident, position)?;
}
}
@ -227,7 +231,7 @@ where
TokenKind::Punctuator(p) if p.as_assign_op().is_some() => {
if cursor.strict_mode() {
if let Expression::Identifier(ident) = lhs {
ident.check_strict_arguments_or_eval(position)?;
check_strict_arguments_or_eval(ident, position)?;
}
}

2
boa_engine/src/syntax/parser/expression/assignment/yield.rs

@ -9,10 +9,10 @@
use super::AssignmentExpression;
use crate::syntax::{
ast::{expression::Yield, Expression, Keyword, Punctuator},
lexer::TokenKind,
parser::{AllowAwait, AllowIn, Cursor, ParseError, ParseResult, TokenParser},
};
use boa_ast::{expression::Yield, Expression, Keyword, Punctuator};
use boa_interner::Interner;
use boa_profiler::Profiler;
use std::io::Read;

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

Loading…
Cancel
Save