From b5b8cdfa18e511287c78cbe88852696f3b7b648f Mon Sep 17 00:00:00 2001 From: Addison Crump Date: Wed, 2 Nov 2022 15:45:20 +0000 Subject: [PATCH] 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 --- Cargo.lock | 1 + boa_engine/src/syntax/ast/declaration/mod.rs | 32 ++ .../src/syntax/ast/declaration/variable.rs | 111 +++++++ .../src/syntax/ast/expression/access.rs | 101 ++++++ boa_engine/src/syntax/ast/expression/await.rs | 18 ++ boa_engine/src/syntax/ast/expression/call.rs | 49 +++ .../src/syntax/ast/expression/identifier.rs | 18 ++ .../syntax/ast/expression/literal/array.rs | 25 ++ .../src/syntax/ast/expression/literal/mod.rs | 26 ++ .../ast/expression/literal/object/mod.rs | 25 ++ .../syntax/ast/expression/literal/template.rs | 47 +++ boa_engine/src/syntax/ast/expression/mod.rs | 78 +++++ boa_engine/src/syntax/ast/expression/new.rs | 18 ++ .../ast/expression/operator/assign/mod.rs | 46 +++ .../ast/expression/operator/binary/mod.rs | 22 ++ .../ast/expression/operator/conditional.rs | 23 ++ .../ast/expression/operator/unary/mod.rs | 18 ++ .../src/syntax/ast/expression/optional.rs | 81 +++++ .../src/syntax/ast/expression/spread.rs | 18 ++ .../syntax/ast/expression/tagged_template.rs | 39 +++ boa_engine/src/syntax/ast/expression/yield.rs | 26 ++ .../src/syntax/ast/function/arrow_function.rs | 27 ++ .../src/syntax/ast/function/async_function.rs | 27 ++ .../syntax/ast/function/async_generator.rs | 27 ++ boa_engine/src/syntax/ast/function/class.rs | 123 +++++++- .../src/syntax/ast/function/generator.rs | 27 ++ boa_engine/src/syntax/ast/function/mod.rs | 27 ++ .../src/syntax/ast/function/parameters.rs | 42 +++ boa_engine/src/syntax/ast/mod.rs | 1 + boa_engine/src/syntax/ast/pattern.rs | 287 ++++++++++++++++++ boa_engine/src/syntax/ast/property.rs | 101 ++++++ boa_engine/src/syntax/ast/statement/block.rs | 18 ++ boa_engine/src/syntax/ast/statement/if.rs | 29 ++ .../syntax/ast/statement/iteration/break.rs | 27 ++ .../ast/statement/iteration/continue.rs | 27 ++ .../ast/statement/iteration/do_while_loop.rs | 21 ++ .../ast/statement/iteration/for_in_loop.rs | 23 ++ .../ast/statement/iteration/for_loop.rs | 61 ++++ .../ast/statement/iteration/for_of_loop.rs | 23 ++ .../src/syntax/ast/statement/iteration/mod.rs | 32 ++ .../ast/statement/iteration/while_loop.rs | 21 ++ .../src/syntax/ast/statement/labelled.rs | 43 +++ boa_engine/src/syntax/ast/statement/mod.rs | 60 ++++ boa_engine/src/syntax/ast/statement/return.rs | 26 ++ .../src/syntax/ast/statement/switch/mod.rs | 51 ++++ boa_engine/src/syntax/ast/statement/throw.rs | 18 ++ .../src/syntax/ast/statement/try/mod.rs | 71 +++++ .../src/syntax/ast/statement_list/mod.rs | 49 +++ boa_engine/src/syntax/ast/visitor.rs | 276 +++++++++++++++++ boa_engine/src/value/mod.rs | 2 - boa_examples/Cargo.toml | 1 + boa_examples/src/bin/commuter_visitor.rs | 84 +++++ boa_examples/src/bin/symbol_visitor.rs | 51 ++++ 53 files changed, 2521 insertions(+), 4 deletions(-) create mode 100644 boa_engine/src/syntax/ast/visitor.rs create mode 100644 boa_examples/src/bin/commuter_visitor.rs create mode 100644 boa_examples/src/bin/symbol_visitor.rs diff --git a/Cargo.lock b/Cargo.lock index 7514563f10..00c71dbf3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,7 @@ version = "0.16.0" dependencies = [ "boa_engine", "boa_gc", + "boa_interner", "gc", ] diff --git a/boa_engine/src/syntax/ast/declaration/mod.rs b/boa_engine/src/syntax/ast/declaration/mod.rs index 1b9fb43b19..eca685b815 100644 --- a/boa_engine/src/syntax/ast/declaration/mod.rs +++ b/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 + 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 + 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), + } + } +} diff --git a/boa_engine/src/syntax/ast/declaration/variable.rs b/boa_engine/src/syntax/ast/declaration/variable.rs index 10fe4d4ad5..c2ea06b86e 100644 --- a/boa_engine/src/syntax/ast/declaration/variable.rs +++ b/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 + where + V: Visitor<'a>, + { + visitor.visit_variable_list(&self.0) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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] diff --git a/boa_engine/src/syntax/ast/expression/access.rs b/boa_engine/src/syntax/ast/expression/access.rs index 7559977535..2c4e2b3257 100644 --- a/boa_engine/src/syntax/ast/expression/access.rs +++ b/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 for PropertyAccessField { } } +impl VisitWith for PropertyAccessField { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for Expression { } } +impl VisitWith for PropertyAccess { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for PropertyAccess { } } +impl VisitWith for SimplePropertyAccess { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for PropertyAccess { } } +impl VisitWith for PrivatePropertyAccess { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for PropertyAccess { Self::Super(access) } } + +impl VisitWith for SuperPropertyAccess { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_property_access_field(&self.field) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_property_access_field_mut(&mut self.field) + } +} diff --git a/boa_engine/src/syntax/ast/expression/await.rs b/boa_engine/src/syntax/ast/expression/await.rs index 035b84533f..00154c3b78 100644 --- a/boa_engine/src/syntax/ast/expression/await.rs +++ b/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 for Expression { } } +impl VisitWith for Await { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_expression(&self.target) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_expression_mut(&mut self.target) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/boa_engine/src/syntax/ast/expression/call.rs b/boa_engine/src/syntax/ast/expression/call.rs index e003c47541..f99bc9956b 100644 --- a/boa_engine/src/syntax/ast/expression/call.rs +++ b/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 for Expression { } } +impl VisitWith for Call { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for Expression { } } +impl VisitWith for SuperCall { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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] diff --git a/boa_engine/src/syntax/ast/expression/identifier.rs b/boa_engine/src/syntax/ast/expression/identifier.rs index 716af6a962..c9b753bed3 100644 --- a/boa_engine/src/syntax/ast/expression/identifier.rs +++ b/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 for Expression { Self::Identifier(local) } } + +impl VisitWith for Identifier { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_sym(&self.ident) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_sym_mut(&mut self.ident) + } +} diff --git a/boa_engine/src/syntax/ast/expression/literal/array.rs b/boa_engine/src/syntax/ast/expression/literal/array.rs index 76c3977f3d..b072b72a40 100644 --- a/boa_engine/src/syntax/ast/expression/literal/array.rs +++ b/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 for Expression { } } +impl VisitWith for ArrayLiteral { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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] diff --git a/boa_engine/src/syntax/ast/expression/literal/mod.rs b/boa_engine/src/syntax/ast/expression/literal/mod.rs index a809b369e8..dac95a4e7f 100644 --- a/boa_engine/src/syntax/ast/expression/literal/mod.rs +++ b/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 + 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 + where + V: VisitorMut<'a>, + { + if let Literal::String(sym) = self { + visitor.visit_sym_mut(sym) + } else { + ControlFlow::Continue(()) + } + } +} diff --git a/boa_engine/src/syntax/ast/expression/literal/object/mod.rs b/boa_engine/src/syntax/ast/expression/literal/object/mod.rs index a736129edb..e3e410feac 100644 --- a/boa_engine/src/syntax/ast/expression/literal/object/mod.rs +++ b/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 for Expression { Self::ObjectLiteral(obj) } } + +impl VisitWith for ObjectLiteral { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + for pd in self.properties.iter_mut() { + try_break!(visitor.visit_property_definition_mut(pd)); + } + ControlFlow::Continue(()) + } +} diff --git a/boa_engine/src/syntax/ast/expression/literal/template.rs b/boa_engine/src/syntax/ast/expression/literal/template.rs index 1f9dfac648..cd1dc7c7ce 100644 --- a/boa_engine/src/syntax/ast/expression/literal/template.rs +++ b/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 + 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 + 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 + 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 + 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; diff --git a/boa_engine/src/syntax/ast/expression/mod.rs b/boa_engine/src/syntax/ast/expression/mod.rs index 5e833abfd3..75d74068a7 100644 --- a/boa_engine/src/syntax/ast/expression/mod.rs +++ b/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 + 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 + 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(()) + } + } + } +} diff --git a/boa_engine/src/syntax/ast/expression/new.rs b/boa_engine/src/syntax/ast/expression/new.rs index 5dc2731c2e..9b8c0a7088 100644 --- a/boa_engine/src/syntax/ast/expression/new.rs +++ b/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 for Expression { } } +impl VisitWith for New { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_call(&self.call) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_call_mut(&mut self.call) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/boa_engine/src/syntax/ast/expression/operator/assign/mod.rs b/boa_engine/src/syntax/ast/expression/operator/assign/mod.rs index f3ed6b31cf..bc6d339245 100644 --- a/boa_engine/src/syntax/ast/expression/operator/assign/mod.rs +++ b/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 for Expression { } } +impl VisitWith for Assign { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 + 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 + 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), + } + } +} diff --git a/boa_engine/src/syntax/ast/expression/operator/binary/mod.rs b/boa_engine/src/syntax/ast/expression/operator/binary/mod.rs index 6b3ecca78a..d46ea962d4 100644 --- a/boa_engine/src/syntax/ast/expression/operator/binary/mod.rs +++ b/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 for Expression { Self::Binary(op) } } + +impl VisitWith for Binary { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + try_break!(visitor.visit_expression_mut(&mut self.lhs)); + visitor.visit_expression_mut(&mut self.rhs) + } +} diff --git a/boa_engine/src/syntax/ast/expression/operator/conditional.rs b/boa_engine/src/syntax/ast/expression/operator/conditional.rs index 6233bd4f74..fc0f19edde 100644 --- a/boa_engine/src/syntax/ast/expression/operator/conditional.rs +++ b/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 for Expression { Self::Conditional(cond_op) } } + +impl VisitWith for Conditional { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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) + } +} diff --git a/boa_engine/src/syntax/ast/expression/operator/unary/mod.rs b/boa_engine/src/syntax/ast/expression/operator/unary/mod.rs index e5caf9869e..baa262f940 100644 --- a/boa_engine/src/syntax/ast/expression/operator/unary/mod.rs +++ b/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 for Expression { Self::Unary(op) } } + +impl VisitWith for Unary { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_expression(&self.target) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_expression_mut(&mut self.target) + } +} diff --git a/boa_engine/src/syntax/ast/expression/optional.rs b/boa_engine/src/syntax/ast/expression/optional.rs index 11a56b1e8f..4919578ac7 100644 --- a/boa_engine/src/syntax/ast/expression/optional.rs +++ b/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 + 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 + 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 + where + V: Visitor<'a>, + { + visitor.visit_optional_operation_kind(&self.kind) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + 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 + 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 + 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] diff --git a/boa_engine/src/syntax/ast/expression/spread.rs b/boa_engine/src/syntax/ast/expression/spread.rs index cf6ece318c..5dc32d36ba 100644 --- a/boa_engine/src/syntax/ast/expression/spread.rs +++ b/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 for Expression { } } +impl VisitWith for Spread { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_expression(&self.target) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_expression_mut(&mut self.target) + } +} + #[cfg(test)] mod tests { use crate::exec; diff --git a/boa_engine/src/syntax/ast/expression/tagged_template.rs b/boa_engine/src/syntax/ast/expression/tagged_template.rs index d1400a6028..78b1f8f45c 100644 --- a/boa_engine/src/syntax/ast/expression/tagged_template.rs +++ b/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 for Expression { } } +impl VisitWith for TaggedTemplate { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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] diff --git a/boa_engine/src/syntax/ast/expression/yield.rs b/boa_engine/src/syntax/ast/expression/yield.rs index 213ab133d1..6284f316fc 100644 --- a/boa_engine/src/syntax/ast/expression/yield.rs +++ b/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 + 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 + where + V: VisitorMut<'a>, + { + if let Some(expr) = &mut self.target { + visitor.visit_expression_mut(expr) + } else { + ControlFlow::Continue(()) + } + } +} diff --git a/boa_engine/src/syntax/ast/function/arrow_function.rs b/boa_engine/src/syntax/ast/function/arrow_function.rs index 930262b2f9..ad2003d7eb 100644 --- a/boa_engine/src/syntax/ast/function/arrow_function.rs +++ b/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 for Expression { } } +impl VisitWith for ArrowFunction { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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] diff --git a/boa_engine/src/syntax/ast/function/async_function.rs b/boa_engine/src/syntax/ast/function/async_function.rs index c399f9c1d4..867c8fb8da 100644 --- a/boa_engine/src/syntax/ast/function/async_function.rs +++ b/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 for Declaration { } } +impl VisitWith for AsyncFunction { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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] diff --git a/boa_engine/src/syntax/ast/function/async_generator.rs b/boa_engine/src/syntax/ast/function/async_generator.rs index 8c246f03ee..93db559e5b 100644 --- a/boa_engine/src/syntax/ast/function/async_generator.rs +++ b/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 for Declaration { Self::AsyncGenerator(f) } } + +impl VisitWith for AsyncGenerator { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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) + } +} diff --git a/boa_engine/src/syntax/ast/function/class.rs b/boa_engine/src/syntax/ast/function/class.rs index a9052778e3..302313f803 100644 --- a/boa_engine/src/syntax/ast/function/class.rs +++ b/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 for Declaration { } } +impl VisitWith for Class { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 + 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 + 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; diff --git a/boa_engine/src/syntax/ast/function/generator.rs b/boa_engine/src/syntax/ast/function/generator.rs index 3219d262fb..89f240d009 100644 --- a/boa_engine/src/syntax/ast/function/generator.rs +++ b/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 for Declaration { Self::Generator(f) } } + +impl VisitWith for Generator { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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) + } +} diff --git a/boa_engine/src/syntax/ast/function/mod.rs b/boa_engine/src/syntax/ast/function/mod.rs index 9eb6328a63..f9e6c2f90c 100644 --- a/boa_engine/src/syntax/ast/function/mod.rs +++ b/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 + 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 + 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 { diff --git a/boa_engine/src/syntax/ast/function/parameters.rs b/boa_engine/src/syntax/ast/function/parameters.rs index a2d6785c06..9386ec5bec 100644 --- a/boa_engine/src/syntax/ast/function/parameters.rs +++ b/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 for FormalParameterList { } } +impl VisitWith for FormalParameterList { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 + where + V: Visitor<'a>, + { + visitor.visit_variable(&self.variable) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_variable_mut(&mut self.variable) + } +} diff --git a/boa_engine/src/syntax/ast/mod.rs b/boa_engine/src/syntax/ast/mod.rs index 360f45a16d..6f4d3e87f4 100644 --- a/boa_engine/src/syntax/ast/mod.rs +++ b/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}; diff --git a/boa_engine/src/syntax/ast/pattern.rs b/boa_engine/src/syntax/ast/pattern.rs index d70a513ffa..080c8697f3 100644 --- a/boa_engine/src/syntax/ast/pattern.rs +++ b/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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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(()) + } + } + } +} diff --git a/boa_engine/src/syntax/ast/property.rs b/boa_engine/src/syntax/ast/property.rs index 6942187743..6a465dbb63 100644 --- a/boa_engine/src/syntax/ast/property.rs +++ b/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 + 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 + 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 + 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 + 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 for PropertyName { } } +impl VisitWith for PropertyName { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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: diff --git a/boa_engine/src/syntax/ast/statement/block.rs b/boa_engine/src/syntax/ast/statement/block.rs index 5922326905..c6a8d61ec4 100644 --- a/boa_engine/src/syntax/ast/statement/block.rs +++ b/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 for Statement { } } +impl VisitWith for Block { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_statement_list(&self.statements) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_statement_list_mut(&mut self.statements) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/boa_engine/src/syntax/ast/statement/if.rs b/boa_engine/src/syntax/ast/statement/if.rs index a81dc8f53e..adca66a855 100644 --- a/boa_engine/src/syntax/ast/statement/if.rs +++ b/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 for Statement { } } +impl VisitWith for If { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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] diff --git a/boa_engine/src/syntax/ast/statement/iteration/break.rs b/boa_engine/src/syntax/ast/statement/iteration/break.rs index 57dba8e8de..b05c47aeb1 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/break.rs +++ b/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 for Statement { } } +impl VisitWith for Break { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + if let Some(sym) = &mut self.label { + visitor.visit_sym_mut(sym) + } else { + ControlFlow::Continue(()) + } + } +} + #[cfg(test)] mod tests { #[test] diff --git a/boa_engine/src/syntax/ast/statement/iteration/continue.rs b/boa_engine/src/syntax/ast/statement/iteration/continue.rs index 61dffe7002..7c7f1f1f17 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/continue.rs +++ b/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 for Statement { Self::Continue(cont) } } + +impl VisitWith for Continue { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + if let Some(sym) = &mut self.label { + visitor.visit_sym_mut(sym) + } else { + ControlFlow::Continue(()) + } + } +} diff --git a/boa_engine/src/syntax/ast/statement/iteration/do_while_loop.rs b/boa_engine/src/syntax/ast/statement/iteration/do_while_loop.rs index 35ceca07a6..5ebd5de0b1 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/do_while_loop.rs +++ b/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 for Statement { Self::DoWhileLoop(do_while) } } + +impl VisitWith for DoWhileLoop { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + try_break!(visitor.visit_statement_mut(&mut self.body)); + visitor.visit_expression_mut(&mut self.condition) + } +} diff --git a/boa_engine/src/syntax/ast/statement/iteration/for_in_loop.rs b/boa_engine/src/syntax/ast/statement/iteration/for_in_loop.rs index f19fd95e95..0b04974dae 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/for_in_loop.rs +++ b/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 for Statement { Self::ForInLoop(for_in) } } + +impl VisitWith for ForInLoop { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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) + } +} diff --git a/boa_engine/src/syntax/ast/statement/iteration/for_loop.rs b/boa_engine/src/syntax/ast/statement/iteration/for_loop.rs index 8d7d242fc3..c697e2c7e3 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/for_loop.rs +++ b/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 for Statement { } } +impl VisitWith for ForLoop { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for ForLoopInitializer { ForLoopInitializer::Var(list) } } + +impl VisitWith for ForLoopInitializer { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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), + } + } +} diff --git a/boa_engine/src/syntax/ast/statement/iteration/for_of_loop.rs b/boa_engine/src/syntax/ast/statement/iteration/for_of_loop.rs index 45d4a1c729..cfcd1079c3 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/for_of_loop.rs +++ b/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 for Statement { Self::ForOfLoop(for_of) } } + +impl VisitWith for ForOfLoop { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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) + } +} diff --git a/boa_engine/src/syntax/ast/statement/iteration/mod.rs b/boa_engine/src/syntax/ast/statement/iteration/mod.rs index 54c4239ead..ff1ecd3037 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/mod.rs +++ b/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 + 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 + 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), + } + } +} diff --git a/boa_engine/src/syntax/ast/statement/iteration/while_loop.rs b/boa_engine/src/syntax/ast/statement/iteration/while_loop.rs index a59dd56c9b..6b2eeb396c 100644 --- a/boa_engine/src/syntax/ast/statement/iteration/while_loop.rs +++ b/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 for Statement { Self::WhileLoop(while_loop) } } + +impl VisitWith for WhileLoop { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + try_break!(visitor.visit_expression_mut(&mut self.condition)); + visitor.visit_statement_mut(&mut self.body) + } +} diff --git a/boa_engine/src/syntax/ast/statement/labelled.rs b/boa_engine/src/syntax/ast/statement/labelled.rs index d385e67872..477fdd0af0 100644 --- a/boa_engine/src/syntax/ast/statement/labelled.rs +++ b/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 for LabelledItem { } } +impl VisitWith for LabelledItem { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 for Statement { Self::Labelled(labelled) } } + +impl VisitWith for Labelled { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + where + V: VisitorMut<'a>, + { + try_break!(visitor.visit_labelled_item_mut(&mut self.item)); + visitor.visit_sym_mut(&mut self.label) + } +} diff --git a/boa_engine/src/syntax/ast/statement/mod.rs b/boa_engine/src/syntax/ast/statement/mod.rs index c097e04627..831d395651 100644 --- a/boa_engine/src/syntax/ast/statement/mod.rs +++ b/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 + 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 + 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), + } + } +} diff --git a/boa_engine/src/syntax/ast/statement/return.rs b/boa_engine/src/syntax/ast/statement/return.rs index a8fbd04578..4370f64aec 100644 --- a/boa_engine/src/syntax/ast/statement/return.rs +++ b/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 + 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 + where + V: VisitorMut<'a>, + { + if let Some(expr) = &mut self.target { + visitor.visit_expression_mut(expr) + } else { + ControlFlow::Continue(()) + } + } +} + #[cfg(test)] mod tests { #[test] diff --git a/boa_engine/src/syntax/ast/statement/switch/mod.rs b/boa_engine/src/syntax/ast/statement/switch/mod.rs index 4bda0249d5..44d85c44d5 100644 --- a/boa_engine/src/syntax/ast/statement/switch/mod.rs +++ b/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 + 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 + 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 for Statement { Self::Switch(switch) } } + +impl VisitWith for Switch { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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(()) + } +} diff --git a/boa_engine/src/syntax/ast/statement/throw.rs b/boa_engine/src/syntax/ast/statement/throw.rs index 552b0b4bd8..c1747c6f51 100644 --- a/boa_engine/src/syntax/ast/statement/throw.rs +++ b/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 for Statement { } } +impl VisitWith for Throw { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_expression(&self.target) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_expression_mut(&mut self.target) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/boa_engine/src/syntax/ast/statement/try/mod.rs b/boa_engine/src/syntax/ast/statement/try/mod.rs index 503f9c71df..0237162f85 100644 --- a/boa_engine/src/syntax/ast/statement/try/mod.rs +++ b/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 for Statement { } } +impl VisitWith for Try { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 + 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 + 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 for Finally { Self { block } } } + +impl VisitWith for Finally { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + where + V: Visitor<'a>, + { + visitor.visit_block(&self.block) + } + + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow + where + V: VisitorMut<'a>, + { + visitor.visit_block_mut(&mut self.block) + } +} diff --git a/boa_engine/src/syntax/ast/statement_list/mod.rs b/boa_engine/src/syntax/ast/statement_list/mod.rs index f9f0f5297c..c43bc3bd8c 100644 --- a/boa_engine/src/syntax/ast/statement_list/mod.rs +++ b/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 for StatementListItem { } } +impl VisitWith for StatementListItem { + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow + 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 + 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 + 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 + where + V: VisitorMut<'a>, + { + for statement in self.statements.iter_mut() { + try_break!(visitor.visit_statement_list_item_mut(statement)); + } + ControlFlow::Continue(()) + } +} diff --git a/boa_engine/src/syntax/ast/visitor.rs b/boa_engine/src/syntax/ast/visitor.rs new file mode 100644 index 0000000000..ea38a122ee --- /dev/null +++ b/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 { + 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 { + 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 + 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 + 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 + where + V: Visitor<'a>, + { + core::ops::ControlFlow::Continue(()) + } + + fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> core::ops::ControlFlow + where + V: VisitorMut<'a>, + { + core::ops::ControlFlow::Continue(()) + } +} diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 3b9ac4fcd3..9202fc5fb8 100644 --- a/boa_engine/src/value/mod.rs +++ b/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; diff --git a/boa_examples/Cargo.toml b/boa_examples/Cargo.toml index cfdf7dcdba..daf754f80b 100644 --- a/boa_examples/Cargo.toml +++ b/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" diff --git a/boa_examples/src/bin/commuter_visitor.rs b/boa_examples/src/bin/commuter_visitor.rs new file mode 100644 index 0000000000..eee08b50e4 --- /dev/null +++ b/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 { + 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 { + 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())); +} diff --git a/boa_examples/src/bin/symbol_visitor.rs b/boa_examples/src/bin/symbol_visitor.rs new file mode 100644 index 0000000000..d1cd169846 --- /dev/null +++ b/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, +} + +impl<'ast> Visitor<'ast> for SymbolVisitor { + type BreakTy = Infallible; + + fn visit_sym(&mut self, node: &'ast Sym) -> ControlFlow { + 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()); + } +}