Browse Source

Implement AST Visitor pattern (attempt #3) (#2392)

This Pull Request closes no specific issue, but allows for analysis and post-processing passes by both internal and external developers.

It changes the following:

- Adds a Visitor trait, to be implemented by visitors of a particular node type.
- Adds `Type`Visitor traits which offer access to private members of a node.
- Adds an example which demonstrates the use of Visitor traits by walking over an AST and printing its contents.

At this time, the PR is more of a demonstration of intent rather than a full PR. Once it's in a satisfactory state, I'll mark it as not a draft.


Co-authored-by: Addison Crump <addison.crump@cispa.de>
pull/2402/head
Addison Crump 2 years ago
parent
commit
b5b8cdfa18
  1. 1
      Cargo.lock
  2. 32
      boa_engine/src/syntax/ast/declaration/mod.rs
  3. 111
      boa_engine/src/syntax/ast/declaration/variable.rs
  4. 101
      boa_engine/src/syntax/ast/expression/access.rs
  5. 18
      boa_engine/src/syntax/ast/expression/await.rs
  6. 49
      boa_engine/src/syntax/ast/expression/call.rs
  7. 18
      boa_engine/src/syntax/ast/expression/identifier.rs
  8. 25
      boa_engine/src/syntax/ast/expression/literal/array.rs
  9. 26
      boa_engine/src/syntax/ast/expression/literal/mod.rs
  10. 25
      boa_engine/src/syntax/ast/expression/literal/object/mod.rs
  11. 47
      boa_engine/src/syntax/ast/expression/literal/template.rs
  12. 78
      boa_engine/src/syntax/ast/expression/mod.rs
  13. 18
      boa_engine/src/syntax/ast/expression/new.rs
  14. 46
      boa_engine/src/syntax/ast/expression/operator/assign/mod.rs
  15. 22
      boa_engine/src/syntax/ast/expression/operator/binary/mod.rs
  16. 23
      boa_engine/src/syntax/ast/expression/operator/conditional.rs
  17. 18
      boa_engine/src/syntax/ast/expression/operator/unary/mod.rs
  18. 81
      boa_engine/src/syntax/ast/expression/optional.rs
  19. 18
      boa_engine/src/syntax/ast/expression/spread.rs
  20. 39
      boa_engine/src/syntax/ast/expression/tagged_template.rs
  21. 26
      boa_engine/src/syntax/ast/expression/yield.rs
  22. 27
      boa_engine/src/syntax/ast/function/arrow_function.rs
  23. 27
      boa_engine/src/syntax/ast/function/async_function.rs
  24. 27
      boa_engine/src/syntax/ast/function/async_generator.rs
  25. 123
      boa_engine/src/syntax/ast/function/class.rs
  26. 27
      boa_engine/src/syntax/ast/function/generator.rs
  27. 27
      boa_engine/src/syntax/ast/function/mod.rs
  28. 42
      boa_engine/src/syntax/ast/function/parameters.rs
  29. 1
      boa_engine/src/syntax/ast/mod.rs
  30. 287
      boa_engine/src/syntax/ast/pattern.rs
  31. 101
      boa_engine/src/syntax/ast/property.rs
  32. 18
      boa_engine/src/syntax/ast/statement/block.rs
  33. 29
      boa_engine/src/syntax/ast/statement/if.rs
  34. 27
      boa_engine/src/syntax/ast/statement/iteration/break.rs
  35. 27
      boa_engine/src/syntax/ast/statement/iteration/continue.rs
  36. 21
      boa_engine/src/syntax/ast/statement/iteration/do_while_loop.rs
  37. 23
      boa_engine/src/syntax/ast/statement/iteration/for_in_loop.rs
  38. 61
      boa_engine/src/syntax/ast/statement/iteration/for_loop.rs
  39. 23
      boa_engine/src/syntax/ast/statement/iteration/for_of_loop.rs
  40. 32
      boa_engine/src/syntax/ast/statement/iteration/mod.rs
  41. 21
      boa_engine/src/syntax/ast/statement/iteration/while_loop.rs
  42. 43
      boa_engine/src/syntax/ast/statement/labelled.rs
  43. 60
      boa_engine/src/syntax/ast/statement/mod.rs
  44. 26
      boa_engine/src/syntax/ast/statement/return.rs
  45. 51
      boa_engine/src/syntax/ast/statement/switch/mod.rs
  46. 18
      boa_engine/src/syntax/ast/statement/throw.rs
  47. 71
      boa_engine/src/syntax/ast/statement/try/mod.rs
  48. 49
      boa_engine/src/syntax/ast/statement_list/mod.rs
  49. 276
      boa_engine/src/syntax/ast/visitor.rs
  50. 2
      boa_engine/src/value/mod.rs
  51. 1
      boa_examples/Cargo.toml
  52. 84
      boa_examples/src/bin/commuter_visitor.rs
  53. 51
      boa_examples/src/bin/symbol_visitor.rs

1
Cargo.lock generated

@ -119,6 +119,7 @@ version = "0.16.0"
dependencies = [
"boa_engine",
"boa_gc",
"boa_interner",
"gc",
]

32
boa_engine/src/syntax/ast/declaration/mod.rs

@ -20,10 +20,12 @@ use super::{
ContainsSymbol,
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use tap::Tap;
mod variable;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
pub use variable::*;
/// The `Declaration` Parse Node.
@ -165,3 +167,33 @@ impl ToIndentedString for Declaration {
}
}
}
impl VisitWith for Declaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Declaration::Function(f) => visitor.visit_function(f),
Declaration::Generator(g) => visitor.visit_generator(g),
Declaration::AsyncFunction(af) => visitor.visit_async_function(af),
Declaration::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Declaration::Class(c) => visitor.visit_class(c),
Declaration::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Declaration::Function(f) => visitor.visit_function_mut(f),
Declaration::Generator(g) => visitor.visit_generator_mut(g),
Declaration::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Declaration::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Declaration::Class(c) => visitor.visit_class_mut(c),
Declaration::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}

111
boa_engine/src/syntax/ast/declaration/variable.rs

@ -1,13 +1,16 @@
//! Variable related declarations.
use core::ops::ControlFlow;
use std::convert::TryFrom;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
expression::{Expression, Identifier},
join_nodes,
pattern::Pattern,
ContainsSymbol, Statement,
};
use crate::try_break;
use boa_interner::{Interner, ToInternedString};
use super::Declaration;
@ -68,6 +71,22 @@ impl ToInternedString for VarDeclaration {
}
}
impl VisitWith for VarDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable_list(&self.0)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_list_mut(&mut self.0)
}
}
/// A **[lexical declaration]** defines variables that are scoped to the lexical environment of
/// the variable declaration.
///
@ -141,6 +160,30 @@ impl ToInternedString for LexicalDeclaration {
}
}
impl VisitWith for LexicalDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
LexicalDeclaration::Const(vars) | LexicalDeclaration::Let(vars) => {
visitor.visit_variable_list(vars)
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
LexicalDeclaration::Const(vars) | LexicalDeclaration::Let(vars) => {
visitor.visit_variable_list_mut(vars)
}
}
}
}
/// List of variables in a variable declaration.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
@ -171,6 +214,28 @@ impl ToInternedString for VariableList {
}
}
impl VisitWith for VariableList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for variable in self.list.iter() {
try_break!(visitor.visit_variable(variable));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for variable in self.list.iter_mut() {
try_break!(visitor.visit_variable_mut(variable));
}
ControlFlow::Continue(())
}
}
/// The error returned by the [`VariableList::try_from`] function.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromVariableListError(());
@ -288,6 +353,30 @@ impl Variable {
}
}
impl VisitWith for Variable {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_binding(&self.binding));
if let Some(init) = &self.init {
try_break!(visitor.visit_expression(init));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_binding_mut(&mut self.binding));
if let Some(init) = &mut self.init {
try_break!(visitor.visit_expression_mut(init));
}
ControlFlow::Continue(())
}
}
/// Binding represents either an individual binding or a binding pattern.
///
/// More information:
@ -348,6 +437,28 @@ impl ToInternedString for Binding {
}
}
impl VisitWith for Binding {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Binding::Identifier(id) => visitor.visit_identifier(id),
Binding::Pattern(pattern) => visitor.visit_pattern(pattern),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Binding::Identifier(id) => visitor.visit_identifier_mut(id),
Binding::Pattern(pattern) => visitor.visit_pattern_mut(pattern),
}
}
}
#[cfg(test)]
mod tests {
#[test]

101
boa_engine/src/syntax/ast/expression/access.rs

@ -14,8 +14,11 @@
//! [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 boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// A property access field.
///
@ -59,6 +62,28 @@ impl From<Expression> for PropertyAccessField {
}
}
impl VisitWith for PropertyAccessField {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
PropertyAccessField::Const(sym) => visitor.visit_sym(sym),
PropertyAccessField::Expr(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
PropertyAccessField::Const(sym) => visitor.visit_sym_mut(sym),
PropertyAccessField::Expr(expr) => visitor.visit_expression_mut(&mut *expr),
}
}
}
/// A property access expression.
///
/// See the [module level documentation][self] for more information.
@ -111,6 +136,30 @@ impl From<PropertyAccess> for Expression {
}
}
impl VisitWith for PropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
PropertyAccess::Simple(spa) => visitor.visit_simple_property_access(spa),
PropertyAccess::Private(ppa) => visitor.visit_private_property_access(ppa),
PropertyAccess::Super(supa) => visitor.visit_super_property_access(supa),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
PropertyAccess::Simple(spa) => visitor.visit_simple_property_access_mut(spa),
PropertyAccess::Private(ppa) => visitor.visit_private_property_access_mut(ppa),
PropertyAccess::Super(supa) => visitor.visit_super_property_access_mut(supa),
}
}
}
/// A simple property access, where the target object is an [`Expression`].
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
@ -175,6 +224,24 @@ impl From<SimplePropertyAccess> for PropertyAccess {
}
}
impl VisitWith for SimplePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_property_access_field(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_property_access_field_mut(&mut self.field)
}
}
/// An access expression to a class object's [private fields][mdn].
///
/// Private property accesses differ slightly from plain property accesses, since the accessed
@ -243,6 +310,24 @@ impl From<PrivatePropertyAccess> for PropertyAccess {
}
}
impl VisitWith for PrivatePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_sym(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_sym_mut(&mut self.field)
}
}
/// A property access of an object's parent, as defined by the [spec].
///
/// A `SuperPropertyAccess` is much like a regular [`PropertyAccess`], but where its `target` object
@ -298,3 +383,19 @@ impl From<SuperPropertyAccess> for PropertyAccess {
Self::Super(access)
}
}
impl VisitWith for SuperPropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_property_access_field(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_property_access_field_mut(&mut self.field)
}
}

18
boa_engine/src/syntax/ast/expression/await.rs

@ -1,8 +1,10 @@
//! Await expression Expression.
use crate::syntax::ast::ContainsSymbol;
use core::ops::ControlFlow;
use super::Expression;
use crate::syntax::ast::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
@ -62,6 +64,22 @@ impl From<Await> for Expression {
}
}
impl VisitWith for Await {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}
#[cfg(test)]
mod tests {
#[test]

49
boa_engine/src/syntax/ast/expression/call.rs

@ -1,5 +1,8 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{join_nodes, ContainsSymbol};
use crate::try_break;
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
@ -75,6 +78,30 @@ impl From<Call> for Expression {
}
}
impl VisitWith for Call {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.function));
for expr in self.args.iter() {
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>,
{
try_break!(visitor.visit_expression_mut(&mut self.function));
for expr in self.args.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}
/// The `super` keyword is used to access and call functions on an object's parent.
///
/// More information:
@ -128,6 +155,28 @@ impl From<SuperCall> for Expression {
}
}
impl VisitWith for SuperCall {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.args.iter() {
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.args.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]

18
boa_engine/src/syntax/ast/expression/identifier.rs

@ -1,10 +1,12 @@
//! Local identifier Expression.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
string::ToStringEscaped,
syntax::{ast::Position, parser::ParseError},
};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
@ -105,3 +107,19 @@ impl From<Identifier> for Expression {
Self::Identifier(local)
}
}
impl VisitWith for Identifier {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_sym(&self.ident)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_sym_mut(&mut self.ident)
}
}

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

@ -1,7 +1,10 @@
//! 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).
@ -105,6 +108,28 @@ impl From<ArrayLiteral> for Expression {
}
}
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]

26
boa_engine/src/syntax/ast/expression/literal/mod.rs

@ -12,9 +12,11 @@ mod object;
mod template;
pub use array::ArrayLiteral;
use core::ops::ControlFlow;
pub use object::ObjectLiteral;
pub use template::{TemplateElement, TemplateLiteral};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use num_bigint::BigInt;
@ -184,3 +186,27 @@ impl ToInternedString for Literal {
}
}
}
impl VisitWith for Literal {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Literal::String(sym) = self {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Literal::String(sym) = self {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

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

@ -3,6 +3,7 @@
#[cfg(test)]
mod tests;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
block_to_string,
expression::Expression,
@ -10,7 +11,9 @@ use crate::syntax::ast::{
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.
@ -153,3 +156,25 @@ impl From<ObjectLiteral> for Expression {
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

@ -1,12 +1,15 @@
//! Template literal Expression.
use core::ops::ControlFlow;
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},
try_break,
};
/// Template literals are string literals allowing embedded expressions.
@ -96,6 +99,50 @@ impl ToInternedString for TemplateLiteral {
}
}
impl VisitWith for TemplateLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for element in self.elements.iter() {
try_break!(visitor.visit_template_element(element));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for element in self.elements.iter_mut() {
try_break!(visitor.visit_template_element_mut(element));
}
ControlFlow::Continue(())
}
}
impl VisitWith for TemplateElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
TemplateElement::String(sym) => visitor.visit_sym(sym),
TemplateElement::Expr(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
TemplateElement::String(sym) => visitor.visit_sym_mut(sym),
TemplateElement::Expr(expr) => visitor.visit_expression_mut(expr),
}
}
}
#[cfg(test)]
mod tests {
use crate::exec;

78
boa_engine/src/syntax/ast/expression/mod.rs

@ -10,6 +10,7 @@
//! [lhs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#left-hand-side_expressions
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use self::{
access::PropertyAccess,
@ -32,6 +33,7 @@ mod spread;
mod tagged_template;
mod r#yield;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
pub use call::{Call, SuperCall};
pub use identifier::Identifier;
pub use new::New;
@ -286,3 +288,79 @@ impl ToIndentedString for Expression {
self.to_no_indent_string(interner, indentation)
}
}
impl VisitWith for Expression {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Expression::Identifier(id) => visitor.visit_identifier(id),
Expression::Literal(lit) => visitor.visit_literal(lit),
Expression::ArrayLiteral(arlit) => visitor.visit_array_literal(arlit),
Expression::ObjectLiteral(olit) => visitor.visit_object_literal(olit),
Expression::Spread(sp) => visitor.visit_spread(sp),
Expression::Function(f) => visitor.visit_function(f),
Expression::ArrowFunction(af) => visitor.visit_arrow_function(af),
Expression::Generator(g) => visitor.visit_generator(g),
Expression::AsyncFunction(af) => visitor.visit_async_function(af),
Expression::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Expression::Class(c) => visitor.visit_class(c),
Expression::TemplateLiteral(tlit) => visitor.visit_template_literal(tlit),
Expression::PropertyAccess(pa) => visitor.visit_property_access(pa),
Expression::New(n) => visitor.visit_new(n),
Expression::Call(c) => visitor.visit_call(c),
Expression::SuperCall(sc) => visitor.visit_super_call(sc),
Expression::Optional(opt) => visitor.visit_optional(opt),
Expression::TaggedTemplate(tt) => visitor.visit_tagged_template(tt),
Expression::Assign(a) => visitor.visit_assign(a),
Expression::Unary(u) => visitor.visit_unary(u),
Expression::Binary(b) => visitor.visit_binary(b),
Expression::Conditional(c) => visitor.visit_conditional(c),
Expression::Await(a) => visitor.visit_await(a),
Expression::Yield(y) => visitor.visit_yield(y),
Expression::FormalParameterList(fpl) => visitor.visit_formal_parameter_list(fpl),
Expression::This | Expression::NewTarget => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Expression::Identifier(id) => visitor.visit_identifier_mut(id),
Expression::Literal(lit) => visitor.visit_literal_mut(lit),
Expression::ArrayLiteral(arlit) => visitor.visit_array_literal_mut(arlit),
Expression::ObjectLiteral(olit) => visitor.visit_object_literal_mut(olit),
Expression::Spread(sp) => visitor.visit_spread_mut(sp),
Expression::Function(f) => visitor.visit_function_mut(f),
Expression::ArrowFunction(af) => visitor.visit_arrow_function_mut(af),
Expression::Generator(g) => visitor.visit_generator_mut(g),
Expression::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Expression::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Expression::Class(c) => visitor.visit_class_mut(c),
Expression::TemplateLiteral(tlit) => visitor.visit_template_literal_mut(tlit),
Expression::PropertyAccess(pa) => visitor.visit_property_access_mut(pa),
Expression::New(n) => visitor.visit_new_mut(n),
Expression::Call(c) => visitor.visit_call_mut(c),
Expression::SuperCall(sc) => visitor.visit_super_call_mut(sc),
Expression::Optional(opt) => visitor.visit_optional_mut(opt),
Expression::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt),
Expression::Assign(a) => visitor.visit_assign_mut(a),
Expression::Unary(u) => visitor.visit_unary_mut(u),
Expression::Binary(b) => visitor.visit_binary_mut(b),
Expression::Conditional(c) => visitor.visit_conditional_mut(c),
Expression::Await(a) => visitor.visit_await_mut(a),
Expression::Yield(y) => visitor.visit_yield_mut(y),
Expression::FormalParameterList(fpl) => visitor.visit_formal_parameter_list_mut(fpl),
Expression::This | Expression::NewTarget => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
}
}
}

18
boa_engine/src/syntax/ast/expression/new.rs

@ -1,5 +1,7 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Call, ContainsSymbol};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
@ -74,6 +76,22 @@ impl From<New> for Expression {
}
}
impl VisitWith for New {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_call(&self.call)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_call_mut(&mut self.call)
}
}
#[cfg(test)]
mod tests {
#[test]

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

@ -12,10 +12,12 @@
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::{
@ -32,6 +34,8 @@ use crate::syntax::{
},
parser::RESERVED_IDENTIFIERS_STRICT,
};
use crate::try_break;
/// An assignment operator expression.
///
/// See the [module level documentation][self] for more information.
@ -109,6 +113,24 @@ impl From<Assign> for Expression {
}
}
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.
///
@ -427,3 +449,27 @@ pub(crate) fn array_decl_to_declaration_pattern(
}
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),
}
}
}

22
boa_engine/src/syntax/ast/expression/operator/binary/mod.rs

@ -15,11 +15,15 @@
//! [comma]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
mod op;
use core::ops::ControlFlow;
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;
/// Binary operations require two operands, one before the operator and one after the operator.
///
@ -89,3 +93,21 @@ impl From<Binary> for Expression {
Self::Binary(op)
}
}
impl VisitWith for Binary {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&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_expression_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}

23
boa_engine/src/syntax/ast/expression/operator/conditional.rs

@ -1,5 +1,8 @@
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;
/// The `conditional` (ternary) operation is the only JavaScript operation that takes three
/// operands.
@ -85,3 +88,23 @@ impl From<Conditional> for Expression {
Self::Conditional(cond_op)
}
}
impl VisitWith for Conditional {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
try_break!(visitor.visit_expression(&self.if_true));
visitor.visit_expression(&self.if_false)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
try_break!(visitor.visit_expression_mut(&mut self.if_true));
visitor.visit_expression_mut(&mut self.if_false)
}
}

18
boa_engine/src/syntax/ast/expression/operator/unary/mod.rs

@ -13,10 +13,12 @@
//! [not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, ContainsSymbol};
/// A unary expression is an operation with only one operand.
@ -87,3 +89,19 @@ impl From<Unary> for Expression {
Self::Unary(op)
}
}
impl VisitWith for Unary {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

81
boa_engine/src/syntax/ast/expression/optional.rs

@ -1,6 +1,9 @@
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 super::{access::PropertyAccessField, Expression};
@ -44,6 +47,44 @@ impl OptionalOperationKind {
}
}
impl VisitWith for OptionalOperationKind {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
OptionalOperationKind::SimplePropertyAccess { field } => {
visitor.visit_property_access_field(field)
}
OptionalOperationKind::PrivatePropertyAccess { field } => visitor.visit_sym(field),
OptionalOperationKind::Call { args } => {
for arg in args.iter() {
try_break!(visitor.visit_expression(arg));
}
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
OptionalOperationKind::SimplePropertyAccess { field } => {
visitor.visit_property_access_field_mut(field)
}
OptionalOperationKind::PrivatePropertyAccess { field } => visitor.visit_sym_mut(field),
OptionalOperationKind::Call { args } => {
for arg in args.iter_mut() {
try_break!(visitor.visit_expression_mut(arg));
}
ControlFlow::Continue(())
}
}
}
}
/// Operation within an [`Optional`] chain.
///
/// An operation within an `Optional` chain can be either shorted or non-shorted. A shorted operation
@ -121,6 +162,22 @@ impl ToInternedString for OptionalOperation {
}
}
impl VisitWith for OptionalOperation {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_optional_operation_kind(&self.kind)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_optional_operation_kind_mut(&mut self.kind)
}
}
/// An optional chain expression, as defined by the [spec].
///
/// [Optional chaining][mdn] allows for short-circuiting property accesses and function calls, which
@ -150,6 +207,30 @@ pub struct Optional {
chain: Box<[OptionalOperation]>,
}
impl VisitWith for Optional {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
for op in self.chain.iter() {
try_break!(visitor.visit_optional_operation(op));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
for op in self.chain.iter_mut() {
try_break!(visitor.visit_optional_operation_mut(op));
}
ControlFlow::Continue(())
}
}
impl Optional {
/// Creates a new `Optional` expression.
#[inline]

18
boa_engine/src/syntax/ast/expression/spread.rs

@ -1,5 +1,7 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::ContainsSymbol;
use super::Expression;
@ -67,6 +69,22 @@ impl From<Spread> for Expression {
}
}
impl VisitWith for Spread {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}
#[cfg(test)]
mod tests {
use crate::exec;

39
boa_engine/src/syntax/ast/expression/tagged_template.rs

@ -1,6 +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 super::Expression;
@ -93,6 +96,42 @@ impl From<TaggedTemplate> for Expression {
}
}
impl VisitWith for TaggedTemplate {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.tag));
for raw in self.raws.iter() {
try_break!(visitor.visit_sym(raw));
}
for cooked in self.cookeds.iter().flatten() {
try_break!(visitor.visit_sym(cooked));
}
for expr in self.exprs.iter() {
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>,
{
try_break!(visitor.visit_expression_mut(&mut self.tag));
for raw in self.raws.iter_mut() {
try_break!(visitor.visit_sym_mut(raw));
}
for cooked in self.cookeds.iter_mut().flatten() {
try_break!(visitor.visit_sym_mut(cooked));
}
for expr in self.exprs.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]

26
boa_engine/src/syntax/ast/expression/yield.rs

@ -1,5 +1,7 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::ContainsSymbol;
use super::Expression;
@ -70,3 +72,27 @@ impl ToInternedString for Yield {
}
}
}
impl VisitWith for Yield {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(expr) = &self.target {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(expr) = &mut self.target {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}

27
boa_engine/src/syntax/ast/function/arrow_function.rs

@ -1,8 +1,11 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
expression::{Expression, Identifier},
join_nodes, ContainsSymbol, StatementList,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
@ -105,6 +108,30 @@ impl From<ArrowFunction> for Expression {
}
}
impl VisitWith for ArrowFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg(test)]
mod tests {
#[test]

27
boa_engine/src/syntax/ast/function/async_function.rs

@ -1,10 +1,13 @@
//! Async Function Expression.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
@ -95,6 +98,30 @@ impl From<AsyncFunction> for Declaration {
}
}
impl VisitWith for AsyncFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg(test)]
mod tests {
#[test]

27
boa_engine/src/syntax/ast/function/async_generator.rs

@ -1,10 +1,13 @@
//! Async Generator Expression
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
@ -86,3 +89,27 @@ impl From<AsyncGenerator> for Declaration {
Self::AsyncGenerator(f)
}
}
impl VisitWith for AsyncGenerator {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

123
boa_engine/src/syntax/ast/function/class.rs

@ -1,5 +1,7 @@
use core::ops::ControlFlow;
use std::borrow::Cow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
string::ToStringEscaped,
syntax::ast::{
@ -9,6 +11,7 @@ use crate::{
property::{MethodDefinition, PropertyName},
ContainsSymbol, Declaration, StatementList, StatementListItem,
},
try_break,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
@ -107,7 +110,7 @@ impl ToIndentedString for Class {
if let Some(sup) = &self.super_ref {
format!(" extends {}", sup.to_interned_string(interner))
} else {
"".to_string()
String::new()
}
);
}
@ -117,7 +120,7 @@ impl ToIndentedString for Class {
if let Some(sup) = &self.super_ref {
format!("extends {}", sup.to_interned_string(interner))
} else {
"".to_string()
String::new()
}
);
if let Some(expr) = &self.constructor {
@ -377,6 +380,46 @@ impl From<Class> for Declaration {
}
}
impl VisitWith for Class {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
if let Some(expr) = &self.super_ref {
try_break!(visitor.visit_expression(expr));
}
if let Some(func) = &self.constructor {
try_break!(visitor.visit_function(func));
}
for elem in self.elements.iter() {
try_break!(visitor.visit_class_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
if let Some(expr) = &mut self.super_ref {
try_break!(visitor.visit_expression_mut(expr));
}
if let Some(func) = &mut self.constructor {
try_break!(visitor.visit_function_mut(func));
}
for elem in self.elements.iter_mut() {
try_break!(visitor.visit_class_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// An element that can be within a [`Class`], as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElement
@ -446,6 +489,82 @@ impl ClassElement {
}
}
impl VisitWith for ClassElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
ClassElement::MethodDefinition(pn, md)
| ClassElement::StaticMethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_method_definition(md)
}
ClassElement::FieldDefinition(pn, maybe_expr)
| ClassElement::StaticFieldDefinition(pn, maybe_expr) => {
try_break!(visitor.visit_property_name(pn));
if let Some(expr) = maybe_expr {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
ClassElement::PrivateMethodDefinition(sym, md)
| ClassElement::PrivateStaticMethodDefinition(sym, md) => {
try_break!(visitor.visit_sym(sym));
visitor.visit_method_definition(md)
}
ClassElement::PrivateFieldDefinition(sym, maybe_expr)
| ClassElement::PrivateStaticFieldDefinition(sym, maybe_expr) => {
try_break!(visitor.visit_sym(sym));
if let Some(expr) = maybe_expr {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
ClassElement::StaticBlock(sl) => visitor.visit_statement_list(sl),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
ClassElement::MethodDefinition(pn, md)
| ClassElement::StaticMethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_method_definition_mut(md)
}
ClassElement::FieldDefinition(pn, maybe_expr)
| ClassElement::StaticFieldDefinition(pn, maybe_expr) => {
try_break!(visitor.visit_property_name_mut(pn));
if let Some(expr) = maybe_expr {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
ClassElement::PrivateMethodDefinition(sym, md)
| ClassElement::PrivateStaticMethodDefinition(sym, md) => {
try_break!(visitor.visit_sym_mut(sym));
visitor.visit_method_definition_mut(md)
}
ClassElement::PrivateFieldDefinition(sym, maybe_expr)
| ClassElement::PrivateStaticFieldDefinition(sym, maybe_expr) => {
try_break!(visitor.visit_sym_mut(sym));
if let Some(expr) = maybe_expr {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
ClassElement::StaticBlock(sl) => visitor.visit_statement_list_mut(sl),
}
}
}
#[cfg(test)]
mod tests {
use crate::syntax::ast::test_formatting;

27
boa_engine/src/syntax/ast/function/generator.rs

@ -3,7 +3,10 @@ use crate::syntax::ast::{
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString};
use super::FormalParameterList;
@ -88,3 +91,27 @@ impl From<Generator> for Declaration {
Self::Generator(f)
}
}
impl VisitWith for Generator {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

27
boa_engine/src/syntax/ast/function/mod.rs

@ -31,12 +31,15 @@ pub use arrow_function::ArrowFunction;
pub use async_function::AsyncFunction;
pub use async_generator::AsyncGenerator;
pub use class::{Class, ClassElement};
use core::ops::ControlFlow;
pub use generator::Generator;
pub use parameters::{FormalParameter, FormalParameterList};
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 boa_interner::{Interner, ToIndentedString};
use super::expression::{Expression, Identifier};
@ -165,6 +168,30 @@ pub(crate) fn has_direct_super(body: &StatementList, parameters: &FormalParamete
false
}
impl VisitWith for Function {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}
#[cfg(test)]
mod tests {

42
boa_engine/src/syntax/ast/function/parameters.rs

@ -1,3 +1,4 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::{
ast::{
declaration::{Binding, Variable},
@ -7,8 +8,10 @@ use crate::syntax::{
},
parser::ParseError,
};
use crate::try_break;
use bitflags::bitflags;
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use rustc_hash::FxHashSet;
/// A list of `FormalParameter`s that describes the parameters of a function, as defined by the [spec].
@ -211,6 +214,29 @@ impl From<FormalParameter> for FormalParameterList {
}
}
impl VisitWith for FormalParameterList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for parameter in self.parameters.iter() {
try_break!(visitor.visit_formal_parameter(parameter));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for parameter in self.parameters.iter_mut() {
try_break!(visitor.visit_formal_parameter_mut(parameter));
}
// TODO recompute flags
ControlFlow::Continue(())
}
}
bitflags! {
/// Flags for a [`FormalParameterList`].
#[allow(clippy::unsafe_derive_deserialize)]
@ -317,3 +343,19 @@ impl ToInternedString for FormalParameter {
buf
}
}
impl VisitWith for FormalParameter {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable(&self.variable)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_mut(&mut self.variable)
}
}

1
boa_engine/src/syntax/ast/mod.rs

@ -24,6 +24,7 @@ pub mod keyword;
pub mod pattern;
pub mod property;
pub mod statement;
pub mod visitor;
use boa_interner::{Interner, ToIndentedString, ToInternedString};

287
boa_engine/src/syntax/ast/pattern.rs

@ -22,7 +22,10 @@
//! [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 boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::{
expression::{access::PropertyAccess, Identifier},
@ -115,6 +118,28 @@ impl Pattern {
}
}
impl VisitWith for Pattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Pattern::Object(op) => visitor.visit_object_pattern(op),
Pattern::Array(ap) => visitor.visit_array_pattern(ap),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Pattern::Object(op) => visitor.visit_object_pattern_mut(op),
Pattern::Array(ap) => visitor.visit_array_pattern_mut(ap),
}
}
}
/// An object binding or assignment pattern.
///
/// Corresponds to the [`ObjectBindingPattern`][spec1] and the [`ObjectAssignmentPattern`][spec2]
@ -197,6 +222,28 @@ impl ObjectPattern {
}
}
impl VisitWith for ObjectPattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for elem in self.0.iter() {
try_break!(visitor.visit_object_pattern_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for elem in self.0.iter_mut() {
try_break!(visitor.visit_object_pattern_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// An array binding or assignment pattern.
///
/// Corresponds to the [`ArrayBindingPattern`][spec1] and the [`ArrayAssignmentPattern`][spec2]
@ -268,6 +315,28 @@ impl ArrayPattern {
}
}
impl VisitWith for ArrayPattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for elem in self.0.iter() {
try_break!(visitor.visit_array_pattern_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for elem in self.0.iter_mut() {
try_break!(visitor.visit_array_pattern_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// The different types of bindings that an [`ObjectPattern`] may contain.
///
/// Corresponds to the [`BindingProperty`][spec1] and the [`AssignmentProperty`][spec2] nodes.
@ -541,6 +610,142 @@ impl ToInternedString for ObjectPatternElement {
}
}
impl VisitWith for ObjectPatternElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
ObjectPatternElement::SingleName {
name,
ident,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_identifier(ident));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
ObjectPatternElement::RestProperty {
ident,
excluded_keys,
} => {
try_break!(visitor.visit_identifier(ident));
for key in excluded_keys {
try_break!(visitor.visit_identifier(key));
}
ControlFlow::Continue(())
}
ObjectPatternElement::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_property_access(access));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
ObjectPatternElement::AssignmentRestPropertyAccess {
access,
excluded_keys,
} => {
try_break!(visitor.visit_property_access(access));
for key in excluded_keys {
try_break!(visitor.visit_identifier(key));
}
ControlFlow::Continue(())
}
ObjectPatternElement::Pattern {
name,
pattern,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_pattern(pattern));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
ObjectPatternElement::SingleName {
name,
ident,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_identifier_mut(ident));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
ObjectPatternElement::RestProperty {
ident,
excluded_keys,
} => {
try_break!(visitor.visit_identifier_mut(ident));
for key in excluded_keys {
try_break!(visitor.visit_identifier_mut(key));
}
ControlFlow::Continue(())
}
ObjectPatternElement::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_property_access_mut(access));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
ObjectPatternElement::AssignmentRestPropertyAccess {
access,
excluded_keys,
} => {
try_break!(visitor.visit_property_access_mut(access));
for key in excluded_keys {
try_break!(visitor.visit_identifier_mut(key));
}
ControlFlow::Continue(())
}
ObjectPatternElement::Pattern {
name,
pattern,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_pattern_mut(pattern));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}
}
}
/// The different types of bindings that an array binding pattern may contain.
///
/// Corresponds to the [`BindingElement`][spec1] and the [`AssignmentElement`][spec2] nodes.
@ -752,3 +957,85 @@ impl ToInternedString for ArrayPatternElement {
}
}
}
impl VisitWith for ArrayPatternElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
ArrayPatternElement::SingleName {
ident,
default_init,
} => {
try_break!(visitor.visit_identifier(ident));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
ArrayPatternElement::PropertyAccess { access }
| ArrayPatternElement::PropertyAccessRest { access } => {
visitor.visit_property_access(access)
}
ArrayPatternElement::Pattern {
pattern,
default_init,
} => {
try_break!(visitor.visit_pattern(pattern));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
ArrayPatternElement::SingleNameRest { ident } => visitor.visit_identifier(ident),
ArrayPatternElement::PatternRest { pattern } => visitor.visit_pattern(pattern),
ArrayPatternElement::Elision => {
// special case to be handled by user
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
ArrayPatternElement::SingleName {
ident,
default_init,
} => {
try_break!(visitor.visit_identifier_mut(ident));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
ArrayPatternElement::PropertyAccess { access }
| ArrayPatternElement::PropertyAccessRest { access } => {
visitor.visit_property_access_mut(access)
}
ArrayPatternElement::Pattern {
pattern,
default_init,
} => {
try_break!(visitor.visit_pattern_mut(pattern));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
ArrayPatternElement::SingleNameRest { ident } => visitor.visit_identifier_mut(ident),
ArrayPatternElement::PatternRest { pattern } => visitor.visit_pattern_mut(pattern),
ArrayPatternElement::Elision => {
// special case to be handled by user
ControlFlow::Continue(())
}
}
}
}

101
boa_engine/src/syntax/ast/property.rs

@ -1,6 +1,9 @@
//! Property definition related types, used in object literals and class definitions.
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::try_break;
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::{
expression::{literal::Literal, Identifier},
@ -113,6 +116,52 @@ impl PropertyDefinition {
}
}
impl VisitWith for PropertyDefinition {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
PropertyDefinition::IdentifierReference(id) => visitor.visit_identifier(id),
PropertyDefinition::Property(pn, expr) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_expression(expr)
}
PropertyDefinition::MethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_method_definition(md)
}
PropertyDefinition::SpreadObject(expr) => visitor.visit_expression(expr),
PropertyDefinition::CoverInitializedName(id, expr) => {
try_break!(visitor.visit_identifier(id));
visitor.visit_expression(expr)
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
PropertyDefinition::IdentifierReference(id) => visitor.visit_identifier_mut(id),
PropertyDefinition::Property(pn, expr) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_expression_mut(expr)
}
PropertyDefinition::MethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_method_definition_mut(md)
}
PropertyDefinition::SpreadObject(expr) => visitor.visit_expression_mut(expr),
PropertyDefinition::CoverInitializedName(id, expr) => {
try_break!(visitor.visit_identifier_mut(id));
visitor.visit_expression_mut(expr)
}
}
}
}
/// Method definition.
///
/// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced.
@ -225,6 +274,36 @@ impl MethodDefinition {
}
}
impl VisitWith for MethodDefinition {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => {
visitor.visit_function(f)
}
MethodDefinition::Generator(g) => visitor.visit_generator(g),
MethodDefinition::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
MethodDefinition::Async(af) => visitor.visit_async_function(af),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => {
visitor.visit_function_mut(f)
}
MethodDefinition::Generator(g) => visitor.visit_generator_mut(g),
MethodDefinition::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
MethodDefinition::Async(af) => visitor.visit_async_function_mut(af),
}
}
}
/// `PropertyName` can be either a literal or computed.
///
/// More information:
@ -317,6 +396,28 @@ impl From<Expression> for PropertyName {
}
}
impl VisitWith for PropertyName {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
PropertyName::Literal(sym) => visitor.visit_sym(sym),
PropertyName::Computed(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
PropertyName::Literal(sym) => visitor.visit_sym_mut(sym),
PropertyName::Computed(expr) => visitor.visit_expression_mut(expr),
}
}
}
/// `ClassElementName` can be either a property name or a private identifier.
///
/// More information:

18
boa_engine/src/syntax/ast/statement/block.rs

@ -1,8 +1,10 @@
//! Block AST node.
use super::Statement;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Identifier, ContainsSymbol, StatementList};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
/// A `block` statement (or compound statement in other languages) is used to group zero or
/// more statements.
@ -80,6 +82,22 @@ impl From<Block> for Statement {
}
}
impl VisitWith for Block {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_statement_list(&self.statements)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_statement_list_mut(&mut self.statements)
}
}
#[cfg(test)]
mod tests {
#[test]

29
boa_engine/src/syntax/ast/statement/if.rs

@ -1,7 +1,10 @@
//! If statement
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `if` statement executes a statement if a specified condition is [`truthy`][truthy]. If
/// the condition is [`falsy`][falsy], another statement can be executed.
@ -95,6 +98,32 @@ impl From<If> for Statement {
}
}
impl VisitWith for If {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
try_break!(visitor.visit_statement(&self.body));
if let Some(stmt) = &self.else_node {
try_break!(visitor.visit_statement(stmt));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
try_break!(visitor.visit_statement_mut(&mut self.body));
if let Some(stmt) = &mut self.else_node {
try_break!(visitor.visit_statement_mut(stmt));
}
ControlFlow::Continue(())
}
}
#[cfg(test)]
mod tests {
#[test]

27
boa_engine/src/syntax/ast/statement/iteration/break.rs

@ -1,6 +1,9 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::Statement;
/// The `break` statement terminates the current loop, switch, or label statement and transfers
/// program control to the statement following the terminated statement.
///
@ -54,6 +57,30 @@ impl From<Break> for Statement {
}
}
impl VisitWith for Break {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(sym) = &self.label {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(sym) = &mut self.label {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}
#[cfg(test)]
mod tests {
#[test]

27
boa_engine/src/syntax/ast/statement/iteration/continue.rs

@ -1,5 +1,8 @@
use crate::syntax::ast::statement::Statement;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// The `continue` statement terminates execution of the statements in the current iteration of
/// the current or labeled loop, and continues execution of the loop with the next iteration.
///
@ -51,3 +54,27 @@ impl From<Continue> for Statement {
Self::Continue(cont)
}
}
impl VisitWith for Continue {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(sym) = &self.label {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(sym) = &mut self.label {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

21
boa_engine/src/syntax/ast/statement/iteration/do_while_loop.rs

@ -1,5 +1,8 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `do...while` statement creates a loop that executes a specified statement until the
/// test condition evaluates to false.
@ -67,3 +70,21 @@ impl From<DoWhileLoop> for Statement {
Self::DoWhileLoop(do_while)
}
}
impl VisitWith for DoWhileLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_statement(&self.body));
visitor.visit_expression(&self.condition)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_statement_mut(&mut self.body));
visitor.visit_expression_mut(&mut self.condition)
}
}

23
boa_engine/src/syntax/ast/statement/iteration/for_in_loop.rs

@ -1,9 +1,12 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
ContainsSymbol,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A `for...in` loop statement, as defined by the [spec].
///
@ -83,3 +86,23 @@ impl From<ForInLoop> for Statement {
Self::ForInLoop(for_in)
}
}
impl VisitWith for ForInLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer(&self.initializer));
try_break!(visitor.visit_expression(&self.target));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer_mut(&mut self.initializer));
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_statement_mut(&mut self.body)
}
}

61
boa_engine/src/syntax/ast/statement/iteration/for_loop.rs

@ -1,10 +1,13 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
declaration::{LexicalDeclaration, VarDeclaration, Variable},
expression::Identifier,
statement::Statement,
ContainsSymbol, Expression,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `for` statement creates a loop that consists of three optional expressions.
///
@ -110,6 +113,40 @@ impl From<ForLoop> for Statement {
}
}
impl VisitWith for ForLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(fli) = &self.inner.init {
try_break!(visitor.visit_for_loop_initializer(fli));
}
if let Some(expr) = &self.inner.condition {
try_break!(visitor.visit_expression(expr));
}
if let Some(expr) = &self.inner.final_expr {
try_break!(visitor.visit_expression(expr));
}
visitor.visit_statement(&self.inner.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(fli) = &mut self.inner.init {
try_break!(visitor.visit_for_loop_initializer_mut(fli));
}
if let Some(expr) = &mut self.inner.condition {
try_break!(visitor.visit_expression_mut(expr));
}
if let Some(expr) = &mut self.inner.final_expr {
try_break!(visitor.visit_expression_mut(expr));
}
visitor.visit_statement_mut(&mut self.inner.body)
}
}
/// Inner structure to avoid multiple indirections in the heap.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
@ -248,3 +285,27 @@ impl From<VarDeclaration> for ForLoopInitializer {
ForLoopInitializer::Var(list)
}
}
impl VisitWith for ForLoopInitializer {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
ForLoopInitializer::Expression(expr) => visitor.visit_expression(expr),
ForLoopInitializer::Var(vd) => visitor.visit_var_declaration(vd),
ForLoopInitializer::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
ForLoopInitializer::Expression(expr) => visitor.visit_expression_mut(expr),
ForLoopInitializer::Var(vd) => visitor.visit_var_declaration_mut(vd),
ForLoopInitializer::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}

23
boa_engine/src/syntax/ast/statement/iteration/for_of_loop.rs

@ -1,9 +1,12 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
ContainsSymbol,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A `for...of` loop statement, as defined by the [spec].
///
@ -97,3 +100,23 @@ impl From<ForOfLoop> for Statement {
Self::ForOfLoop(for_of)
}
}
impl VisitWith for ForOfLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer(&self.init));
try_break!(visitor.visit_expression(&self.iterable));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer_mut(&mut self.init));
try_break!(visitor.visit_expression_mut(&mut self.iterable));
visitor.visit_statement_mut(&mut self.body)
}
}

32
boa_engine/src/syntax/ast/statement/iteration/mod.rs

@ -13,6 +13,7 @@ use crate::syntax::ast::{
expression::{access::PropertyAccess, Identifier},
pattern::Pattern,
};
use core::ops::ControlFlow;
pub use self::{
do_while_loop::DoWhileLoop,
@ -23,6 +24,7 @@ pub use self::{
r#continue::Continue,
while_loop::WhileLoop,
};
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use super::ContainsSymbol;
@ -107,3 +109,33 @@ impl ToInternedString for IterableLoopInitializer {
format!("{pre} {}", binding.to_interned_string(interner))
}
}
impl VisitWith for IterableLoopInitializer {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
IterableLoopInitializer::Identifier(id) => visitor.visit_identifier(id),
IterableLoopInitializer::Access(pa) => visitor.visit_property_access(pa),
IterableLoopInitializer::Var(b)
| IterableLoopInitializer::Let(b)
| IterableLoopInitializer::Const(b) => visitor.visit_binding(b),
IterableLoopInitializer::Pattern(p) => visitor.visit_pattern(p),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
IterableLoopInitializer::Identifier(id) => visitor.visit_identifier_mut(id),
IterableLoopInitializer::Access(pa) => visitor.visit_property_access_mut(pa),
IterableLoopInitializer::Var(b)
| IterableLoopInitializer::Let(b)
| IterableLoopInitializer::Const(b) => visitor.visit_binding_mut(b),
IterableLoopInitializer::Pattern(p) => visitor.visit_pattern_mut(p),
}
}
}

21
boa_engine/src/syntax/ast/statement/iteration/while_loop.rs

@ -1,5 +1,8 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `while` statement creates a loop that executes a specified statement as long as the
/// test condition evaluates to `true`.
@ -68,3 +71,21 @@ impl From<WhileLoop> for Statement {
Self::WhileLoop(while_loop)
}
}
impl VisitWith for WhileLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
visitor.visit_statement_mut(&mut self.body)
}
}

43
boa_engine/src/syntax/ast/statement/labelled.rs

@ -1,6 +1,9 @@
use super::Statement;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{function::Function, ContainsSymbol};
use crate::try_break;
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The set of Parse Nodes that can be preceded by a label, as defined by the [spec].
///
@ -63,6 +66,28 @@ impl From<Statement> for LabelledItem {
}
}
impl VisitWith for LabelledItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
LabelledItem::Function(f) => visitor.visit_function(f),
LabelledItem::Statement(s) => visitor.visit_statement(s),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
LabelledItem::Function(f) => visitor.visit_function_mut(f),
LabelledItem::Statement(s) => visitor.visit_statement_mut(s),
}
}
}
/// Labelled statement nodes, as defined by the [spec].
///
/// The method [`Labelled::item`] doesn't return a [`Statement`] for compatibility reasons.
@ -125,3 +150,21 @@ impl From<Labelled> for Statement {
Self::Labelled(labelled)
}
}
impl VisitWith for Labelled {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_labelled_item(&self.item));
visitor.visit_sym(&self.label)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_labelled_item_mut(&mut self.item));
visitor.visit_sym_mut(&mut self.label)
}
}

60
boa_engine/src/syntax/ast/statement/mod.rs

@ -28,7 +28,9 @@ pub use self::{
switch::{Case, Switch},
throw::Throw,
};
use core::ops::ControlFlow;
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use rustc_hash::FxHashSet;
use tap::Tap;
@ -313,3 +315,61 @@ impl ToIndentedString for Statement {
buf
}
}
impl VisitWith for Statement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Statement::Block(b) => visitor.visit_block(b),
Statement::Var(v) => visitor.visit_var_declaration(v),
Statement::Empty => {
// do nothing; there is nothing to visit here
ControlFlow::Continue(())
}
Statement::Expression(e) => visitor.visit_expression(e),
Statement::If(i) => visitor.visit_if(i),
Statement::DoWhileLoop(dw) => visitor.visit_do_while_loop(dw),
Statement::WhileLoop(w) => visitor.visit_while_loop(w),
Statement::ForLoop(f) => visitor.visit_for_loop(f),
Statement::ForInLoop(fi) => visitor.visit_for_in_loop(fi),
Statement::ForOfLoop(fo) => visitor.visit_for_of_loop(fo),
Statement::Switch(s) => visitor.visit_switch(s),
Statement::Continue(c) => visitor.visit_continue(c),
Statement::Break(b) => visitor.visit_break(b),
Statement::Return(r) => visitor.visit_return(r),
Statement::Labelled(l) => visitor.visit_labelled(l),
Statement::Throw(th) => visitor.visit_throw(th),
Statement::Try(tr) => visitor.visit_try(tr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Statement::Block(b) => visitor.visit_block_mut(b),
Statement::Var(v) => visitor.visit_var_declaration_mut(v),
Statement::Empty => {
// do nothing; there is nothing to visit here
ControlFlow::Continue(())
}
Statement::Expression(e) => visitor.visit_expression_mut(e),
Statement::If(i) => visitor.visit_if_mut(i),
Statement::DoWhileLoop(dw) => visitor.visit_do_while_loop_mut(dw),
Statement::WhileLoop(w) => visitor.visit_while_loop_mut(w),
Statement::ForLoop(f) => visitor.visit_for_loop_mut(f),
Statement::ForInLoop(fi) => visitor.visit_for_in_loop_mut(fi),
Statement::ForOfLoop(fo) => visitor.visit_for_of_loop_mut(fo),
Statement::Switch(s) => visitor.visit_switch_mut(s),
Statement::Continue(c) => visitor.visit_continue_mut(c),
Statement::Break(b) => visitor.visit_break_mut(b),
Statement::Return(r) => visitor.visit_return_mut(r),
Statement::Labelled(l) => visitor.visit_labelled_mut(l),
Statement::Throw(th) => visitor.visit_throw_mut(th),
Statement::Try(tr) => visitor.visit_try_mut(tr),
}
}
}

26
boa_engine/src/syntax/ast/statement/return.rs

@ -1,5 +1,7 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, ContainsSymbol};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `return` statement ends function execution and specifies a value to be returned to the
/// function caller.
@ -60,6 +62,30 @@ impl ToInternedString for Return {
}
}
impl VisitWith for Return {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(expr) = &self.target {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(expr) = &mut self.target {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}
#[cfg(test)]
mod tests {
#[test]

51
boa_engine/src/syntax/ast/statement/switch/mod.rs

@ -1,7 +1,10 @@
//! Switch node.
//!
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{expression::Expression, statement::Statement, StatementList};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use super::ContainsSymbol;
@ -58,6 +61,24 @@ impl Case {
}
}
impl VisitWith for Case {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
visitor.visit_statement_list_mut(&mut self.body)
}
}
/// The `switch` statement evaluates an expression, matching the expression's value to a case
/// clause, and executes statements associated with that case, as well as statements in cases
/// that follow the matching case.
@ -155,3 +176,33 @@ impl From<Switch> for Statement {
Self::Switch(switch)
}
}
impl VisitWith for Switch {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.val));
for case in self.cases.iter() {
try_break!(visitor.visit_case(case));
}
if let Some(sl) = &self.default {
try_break!(visitor.visit_statement_list(sl));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.val));
for case in self.cases.iter_mut() {
try_break!(visitor.visit_case_mut(case));
}
if let Some(sl) = &mut self.default {
try_break!(visitor.visit_statement_list_mut(sl));
}
ControlFlow::Continue(())
}
}

18
boa_engine/src/syntax/ast/statement/throw.rs

@ -1,5 +1,7 @@
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{statement::Statement, ContainsSymbol, Expression};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `throw` statement throws a user-defined exception.
///
@ -55,6 +57,22 @@ impl From<Throw> for Statement {
}
}
impl VisitWith for Throw {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}
#[cfg(test)]
mod tests {
#[test]

71
boa_engine/src/syntax/ast/statement/try/mod.rs

@ -1,11 +1,14 @@
//! Error handling statements
use crate::syntax::ast::visitor::{VisitWith, Visitor, VisitorMut};
use crate::syntax::ast::{
declaration::Binding,
statement::{Block, Statement},
StatementListItem,
};
use crate::try_break;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use super::ContainsSymbol;
@ -112,6 +115,36 @@ impl From<Try> for Statement {
}
}
impl VisitWith for Try {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_block(&self.block));
if let Some(catch) = &self.catch {
try_break!(visitor.visit_catch(catch));
}
if let Some(finally) = &self.finally {
try_break!(visitor.visit_finally(finally));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
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));
}
ControlFlow::Continue(())
}
}
/// Catch block.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
@ -173,6 +206,28 @@ impl ToIndentedString for Catch {
}
}
impl VisitWith for Catch {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(binding) = &self.parameter {
try_break!(visitor.visit_binding(binding));
}
visitor.visit_block(&self.block)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(binding) = &mut self.parameter {
try_break!(visitor.visit_binding_mut(binding));
}
visitor.visit_block_mut(&mut self.block)
}
}
/// Finally block.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
@ -221,3 +276,19 @@ impl From<Block> for Finally {
Self { block }
}
}
impl VisitWith for Finally {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_block(&self.block)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_block_mut(&mut self.block)
}
}

49
boa_engine/src/syntax/ast/statement_list/mod.rs

@ -1,8 +1,11 @@
//! 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 boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use rustc_hash::FxHashSet;
use std::cmp::Ordering;
@ -117,6 +120,30 @@ impl From<Declaration> for StatementListItem {
}
}
impl VisitWith for StatementListItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
StatementListItem::Statement(statement) => visitor.visit_statement(statement),
StatementListItem::Declaration(declaration) => visitor.visit_declaration(declaration),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
StatementListItem::Statement(statement) => visitor.visit_statement_mut(statement),
StatementListItem::Declaration(declaration) => {
visitor.visit_declaration_mut(declaration)
}
}
}
}
/// List of statements.
///
/// More information:
@ -278,3 +305,25 @@ impl ToIndentedString for StatementList {
buf
}
}
impl VisitWith for StatementList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for statement in self.statements.iter() {
try_break!(visitor.visit_statement_list_item(statement));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for statement in self.statements.iter_mut() {
try_break!(visitor.visit_statement_list_item_mut(statement));
}
ControlFlow::Continue(())
}
}

276
boa_engine/src/syntax/ast/visitor.rs

@ -0,0 +1,276 @@
//! Javascript Abstract Syntax Tree visitors.
//!
//! This module contains visitors which can be used to inspect or modify AST nodes. This allows for
//! fine-grained manipulation of ASTs for analysis, rewriting, or instrumentation.
/// `Try`-like conditional unwrapping of `ControlFlow`.
#[macro_export]
macro_rules! try_break {
($expr:expr) => {
match $expr {
core::ops::ControlFlow::Continue(c) => c,
core::ops::ControlFlow::Break(b) => return core::ops::ControlFlow::Break(b),
}
};
}
use crate::syntax::ast::declaration::{
Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList,
};
use crate::syntax::ast::expression::access::{
PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess,
SuperPropertyAccess,
};
use crate::syntax::ast::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::{
Await, Call, Expression, Identifier, New, Optional, OptionalOperation, OptionalOperationKind,
Spread, SuperCall, TaggedTemplate, Yield,
};
use crate::syntax::ast::function::{
ArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameter,
FormalParameterList, Function, Generator,
};
use crate::syntax::ast::pattern::{
ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern,
};
use crate::syntax::ast::property::{MethodDefinition, PropertyDefinition, PropertyName};
use crate::syntax::ast::statement::iteration::{
Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop,
IterableLoopInitializer, WhileLoop,
};
use crate::syntax::ast::statement::{
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try,
};
use crate::syntax::ast::{StatementList, StatementListItem};
use boa_interner::Sym;
/// Creates the default visit function implementation for a particular type
macro_rules! define_visit {
($fn_name:ident, $type_name:ident) => {
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor")]
#[must_use]
fn $fn_name(&mut self, node: &'ast $type_name) -> core::ops::ControlFlow<Self::BreakTy> {
node.visit_with(self)
}
};
}
/// Creates the default mutable visit function implementation for a particular type
macro_rules! define_visit_mut {
($fn_name:ident, $type_name:ident) => {
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor, mutably")]
#[must_use]
fn $fn_name(
&mut self,
node: &'ast mut $type_name,
) -> core::ops::ControlFlow<Self::BreakTy> {
node.visit_with_mut(self)
}
};
}
/// Represents an AST visitor.
///
/// 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;
define_visit!(visit_statement_list, StatementList);
define_visit!(visit_statement_list_item, StatementListItem);
define_visit!(visit_statement, Statement);
define_visit!(visit_declaration, Declaration);
define_visit!(visit_function, Function);
define_visit!(visit_generator, Generator);
define_visit!(visit_async_function, AsyncFunction);
define_visit!(visit_async_generator, AsyncGenerator);
define_visit!(visit_class, Class);
define_visit!(visit_lexical_declaration, LexicalDeclaration);
define_visit!(visit_block, Block);
define_visit!(visit_var_declaration, VarDeclaration);
define_visit!(visit_expression, Expression);
define_visit!(visit_if, If);
define_visit!(visit_do_while_loop, DoWhileLoop);
define_visit!(visit_while_loop, WhileLoop);
define_visit!(visit_for_loop, ForLoop);
define_visit!(visit_for_in_loop, ForInLoop);
define_visit!(visit_for_of_loop, ForOfLoop);
define_visit!(visit_switch, Switch);
define_visit!(visit_continue, Continue);
define_visit!(visit_break, Break);
define_visit!(visit_return, Return);
define_visit!(visit_labelled, Labelled);
define_visit!(visit_throw, Throw);
define_visit!(visit_try, Try);
define_visit!(visit_identifier, Identifier);
define_visit!(visit_formal_parameter_list, FormalParameterList);
define_visit!(visit_class_element, ClassElement);
define_visit!(visit_variable_list, VariableList);
define_visit!(visit_variable, Variable);
define_visit!(visit_binding, Binding);
define_visit!(visit_pattern, Pattern);
define_visit!(visit_literal, Literal);
define_visit!(visit_array_literal, ArrayLiteral);
define_visit!(visit_object_literal, ObjectLiteral);
define_visit!(visit_spread, Spread);
define_visit!(visit_arrow_function, ArrowFunction);
define_visit!(visit_template_literal, TemplateLiteral);
define_visit!(visit_property_access, PropertyAccess);
define_visit!(visit_new, New);
define_visit!(visit_call, Call);
define_visit!(visit_super_call, SuperCall);
define_visit!(visit_optional, Optional);
define_visit!(visit_tagged_template, TaggedTemplate);
define_visit!(visit_assign, Assign);
define_visit!(visit_unary, Unary);
define_visit!(visit_binary, Binary);
define_visit!(visit_conditional, Conditional);
define_visit!(visit_await, Await);
define_visit!(visit_yield, Yield);
define_visit!(visit_for_loop_initializer, ForLoopInitializer);
define_visit!(visit_iterable_loop_initializer, IterableLoopInitializer);
define_visit!(visit_case, Case);
define_visit!(visit_sym, Sym);
define_visit!(visit_labelled_item, LabelledItem);
define_visit!(visit_catch, Catch);
define_visit!(visit_finally, Finally);
define_visit!(visit_formal_parameter, FormalParameter);
define_visit!(visit_property_name, PropertyName);
define_visit!(visit_method_definition, MethodDefinition);
define_visit!(visit_object_pattern, ObjectPattern);
define_visit!(visit_array_pattern, ArrayPattern);
define_visit!(visit_property_definition, PropertyDefinition);
define_visit!(visit_template_element, TemplateElement);
define_visit!(visit_simple_property_access, SimplePropertyAccess);
define_visit!(visit_private_property_access, PrivatePropertyAccess);
define_visit!(visit_super_property_access, SuperPropertyAccess);
define_visit!(visit_optional_operation, OptionalOperation);
define_visit!(visit_assign_target, AssignTarget);
define_visit!(visit_object_pattern_element, ObjectPatternElement);
define_visit!(visit_array_pattern_element, ArrayPatternElement);
define_visit!(visit_property_access_field, PropertyAccessField);
define_visit!(visit_optional_operation_kind, OptionalOperationKind);
}
/// Represents an AST visitor which can modify AST content.
///
/// 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;
define_visit_mut!(visit_statement_list_mut, StatementList);
define_visit_mut!(visit_statement_list_item_mut, StatementListItem);
define_visit_mut!(visit_statement_mut, Statement);
define_visit_mut!(visit_declaration_mut, Declaration);
define_visit_mut!(visit_function_mut, Function);
define_visit_mut!(visit_generator_mut, Generator);
define_visit_mut!(visit_async_function_mut, AsyncFunction);
define_visit_mut!(visit_async_generator_mut, AsyncGenerator);
define_visit_mut!(visit_class_mut, Class);
define_visit_mut!(visit_lexical_declaration_mut, LexicalDeclaration);
define_visit_mut!(visit_block_mut, Block);
define_visit_mut!(visit_var_declaration_mut, VarDeclaration);
define_visit_mut!(visit_expression_mut, Expression);
define_visit_mut!(visit_if_mut, If);
define_visit_mut!(visit_do_while_loop_mut, DoWhileLoop);
define_visit_mut!(visit_while_loop_mut, WhileLoop);
define_visit_mut!(visit_for_loop_mut, ForLoop);
define_visit_mut!(visit_for_in_loop_mut, ForInLoop);
define_visit_mut!(visit_for_of_loop_mut, ForOfLoop);
define_visit_mut!(visit_switch_mut, Switch);
define_visit_mut!(visit_continue_mut, Continue);
define_visit_mut!(visit_break_mut, Break);
define_visit_mut!(visit_return_mut, Return);
define_visit_mut!(visit_labelled_mut, Labelled);
define_visit_mut!(visit_throw_mut, Throw);
define_visit_mut!(visit_try_mut, Try);
define_visit_mut!(visit_identifier_mut, Identifier);
define_visit_mut!(visit_formal_parameter_list_mut, FormalParameterList);
define_visit_mut!(visit_class_element_mut, ClassElement);
define_visit_mut!(visit_variable_list_mut, VariableList);
define_visit_mut!(visit_variable_mut, Variable);
define_visit_mut!(visit_binding_mut, Binding);
define_visit_mut!(visit_pattern_mut, Pattern);
define_visit_mut!(visit_literal_mut, Literal);
define_visit_mut!(visit_array_literal_mut, ArrayLiteral);
define_visit_mut!(visit_object_literal_mut, ObjectLiteral);
define_visit_mut!(visit_spread_mut, Spread);
define_visit_mut!(visit_arrow_function_mut, ArrowFunction);
define_visit_mut!(visit_template_literal_mut, TemplateLiteral);
define_visit_mut!(visit_property_access_mut, PropertyAccess);
define_visit_mut!(visit_new_mut, New);
define_visit_mut!(visit_call_mut, Call);
define_visit_mut!(visit_super_call_mut, SuperCall);
define_visit_mut!(visit_optional_mut, Optional);
define_visit_mut!(visit_tagged_template_mut, TaggedTemplate);
define_visit_mut!(visit_assign_mut, Assign);
define_visit_mut!(visit_unary_mut, Unary);
define_visit_mut!(visit_binary_mut, Binary);
define_visit_mut!(visit_conditional_mut, Conditional);
define_visit_mut!(visit_await_mut, Await);
define_visit_mut!(visit_yield_mut, Yield);
define_visit_mut!(visit_for_loop_initializer_mut, ForLoopInitializer);
define_visit_mut!(visit_iterable_loop_initializer_mut, IterableLoopInitializer);
define_visit_mut!(visit_case_mut, Case);
define_visit_mut!(visit_sym_mut, Sym);
define_visit_mut!(visit_labelled_item_mut, LabelledItem);
define_visit_mut!(visit_catch_mut, Catch);
define_visit_mut!(visit_finally_mut, Finally);
define_visit_mut!(visit_formal_parameter_mut, FormalParameter);
define_visit_mut!(visit_property_name_mut, PropertyName);
define_visit_mut!(visit_method_definition_mut, MethodDefinition);
define_visit_mut!(visit_object_pattern_mut, ObjectPattern);
define_visit_mut!(visit_array_pattern_mut, ArrayPattern);
define_visit_mut!(visit_property_definition_mut, PropertyDefinition);
define_visit_mut!(visit_template_element_mut, TemplateElement);
define_visit_mut!(visit_simple_property_access_mut, SimplePropertyAccess);
define_visit_mut!(visit_private_property_access_mut, PrivatePropertyAccess);
define_visit_mut!(visit_super_property_access_mut, SuperPropertyAccess);
define_visit_mut!(visit_optional_operation_mut, OptionalOperation);
define_visit_mut!(visit_assign_target_mut, AssignTarget);
define_visit_mut!(visit_object_pattern_element_mut, ObjectPatternElement);
define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement);
define_visit_mut!(visit_property_access_field_mut, PropertyAccessField);
define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind);
}
/// Denotes that a type may be visited, providing a method which allows a visitor to traverse its
/// private fields.
pub trait VisitWith {
/// Visit this node with the provided visitor.
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy>
where
V: Visitor<'a>;
/// Visit this node with the provided visitor mutably, allowing the visitor to modify private
/// fields.
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>;
}
// implementation for Sym as it is out-of-crate
impl VisitWith for Sym {
fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
core::ops::ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
core::ops::ControlFlow::Continue(())
}
}

2
boa_engine/src/value/mod.rs

@ -40,8 +40,6 @@ mod r#type;
pub use conversions::*;
pub use display::ValueDisplay;
pub use equality::*;
pub use hash::*;
pub use integer::IntegerOrInfinity;
pub use operations::*;
pub use r#type::Type;

1
boa_examples/Cargo.toml

@ -13,5 +13,6 @@ rust-version.workspace = true
[dependencies]
boa_engine = { workspace = true, features = ["console"] }
boa_interner.workspace = true
boa_gc.workspace = true
gc = "0.4.1"

84
boa_examples/src/bin/commuter_visitor.rs

@ -0,0 +1,84 @@
// This example demonstrates how to use visitors to modify an AST. Namely, the visitors shown here
// are used to swap the operands of commutable arithmetic operations. For an example which simply
// inspects the AST without modifying it, see symbol_visitor.rs.
use boa_engine::syntax::ast::expression::operator::binary::{ArithmeticOp, BinaryOp};
use boa_engine::syntax::ast::expression::operator::Binary;
use boa_engine::syntax::ast::visitor::{VisitWith, VisitorMut};
use boa_engine::syntax::ast::Expression;
use boa_engine::syntax::Parser;
use boa_engine::Context;
use boa_interner::ToInternedString;
use core::ops::ControlFlow;
use std::convert::Infallible;
use std::fs::File;
use std::io::BufReader;
/// Visitor which, when applied to a binary expression, will swap the operands. Use in other
/// circumstances is undefined.
#[derive(Default)]
struct OpExchanger<'ast> {
lhs: Option<&'ast mut Expression>,
}
impl<'ast> VisitorMut<'ast> for OpExchanger<'ast> {
type BreakTy = ();
fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> {
if let Some(lhs) = self.lhs.take() {
core::mem::swap(lhs, node);
ControlFlow::Break(())
} else {
self.lhs = Some(node);
// we do not traverse into the expression; we are only to be used with a binary op
ControlFlow::Continue(())
}
}
}
/// Visitor which walks the AST and swaps the operands of commutable arithmetic binary expressions.
#[derive(Default)]
struct CommutorVisitor {}
impl<'ast> VisitorMut<'ast> for CommutorVisitor {
type BreakTy = Infallible;
fn visit_binary_mut(&mut self, node: &'ast mut Binary) -> ControlFlow<Self::BreakTy> {
match node.op() {
BinaryOp::Arithmetic(op) => {
match op {
ArithmeticOp::Add | ArithmeticOp::Mul => {
// set up the exchanger and swap lhs and rhs
let mut exchanger = OpExchanger::default();
assert!(matches!(
exchanger.visit_binary_mut(node),
ControlFlow::Break(_)
));
}
_ => {}
}
}
_ => {}
}
// traverse further in; there may nested binary operations
node.visit_with_mut(self)
}
}
fn main() {
let mut parser = Parser::new(BufReader::new(
File::open("boa_examples/scripts/calc.js").unwrap(),
));
let mut ctx = Context::default();
let mut statements = parser.parse_all(&mut ctx).unwrap();
let mut visitor = CommutorVisitor::default();
assert!(matches!(
visitor.visit_statement_list_mut(&mut statements),
ControlFlow::Continue(_)
));
println!("{}", statements.to_interned_string(ctx.interner()));
}

51
boa_examples/src/bin/symbol_visitor.rs

@ -0,0 +1,51 @@
// This example demonstrates how to use a visitor to perform simple operations over the Javascript
// AST, namely: finding all the `Sym`s present in a script. See commuter_visitor.rs for an example
// which mutates the AST.
use boa_engine::syntax::ast::visitor::Visitor;
use boa_engine::syntax::Parser;
use boa_engine::Context;
use boa_interner::Sym;
use core::ops::ControlFlow;
use std::collections::HashSet;
use std::convert::Infallible;
use std::fs::File;
use std::io::BufReader;
#[derive(Debug, Clone, Default)]
struct SymbolVisitor {
observed: HashSet<Sym>,
}
impl<'ast> Visitor<'ast> for SymbolVisitor {
type BreakTy = Infallible;
fn visit_sym(&mut self, node: &'ast Sym) -> ControlFlow<Self::BreakTy> {
self.observed.insert(*node);
ControlFlow::Continue(())
}
}
fn main() {
let mut parser = Parser::new(BufReader::new(
File::open("boa_examples/scripts/calc.js").unwrap(),
));
let mut ctx = Context::default();
let statements = parser.parse_all(&mut ctx).unwrap();
let mut visitor = SymbolVisitor::default();
assert!(matches!(
visitor.visit_statement_list(&statements),
ControlFlow::Continue(_)
));
println!(
"Observed {} unique strings/symbols:",
visitor.observed.len()
);
for sym in visitor.observed {
println!(" - {}", ctx.interner().resolve(sym).unwrap());
}
}
Loading…
Cancel
Save