Skip to content

Commit

Permalink
Implement AST Visitor pattern (attempt #3) (#2392)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
addisoncrump and addisoncrump committed Nov 2, 2022
1 parent c72e4c2 commit b5b8cdf
Show file tree
Hide file tree
Showing 53 changed files with 2,521 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions boa_engine/src/syntax/ast/declaration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -165,3 +167,33 @@ impl ToIndentedString for Declaration {
}
}
}

impl VisitWith for Declaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Declaration::Function(f) => visitor.visit_function(f),
Declaration::Generator(g) => visitor.visit_generator(g),
Declaration::AsyncFunction(af) => visitor.visit_async_function(af),
Declaration::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Declaration::Class(c) => visitor.visit_class(c),
Declaration::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Declaration::Function(f) => visitor.visit_function_mut(f),
Declaration::Generator(g) => visitor.visit_generator_mut(g),
Declaration::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Declaration::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Declaration::Class(c) => visitor.visit_class_mut(c),
Declaration::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}
111 changes: 111 additions & 0 deletions boa_engine/src/syntax/ast/declaration/variable.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -68,6 +71,22 @@ impl ToInternedString for VarDeclaration {
}
}

impl VisitWith for VarDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable_list(&self.0)
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_list_mut(&mut self.0)
}
}

/// A **[lexical declaration]** defines variables that are scoped to the lexical environment of
/// the variable declaration.
///
Expand Down Expand Up @@ -141,6 +160,30 @@ impl ToInternedString for LexicalDeclaration {
}
}

impl VisitWith for LexicalDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
LexicalDeclaration::Const(vars) | LexicalDeclaration::Let(vars) => {
visitor.visit_variable_list(vars)
}
}
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
LexicalDeclaration::Const(vars) | LexicalDeclaration::Let(vars) => {
visitor.visit_variable_list_mut(vars)
}
}
}
}

/// List of variables in a variable declaration.
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -171,6 +214,28 @@ impl ToInternedString for VariableList {
}
}

impl VisitWith for VariableList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for variable in self.list.iter() {
try_break!(visitor.visit_variable(variable));
}
ControlFlow::Continue(())
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for variable in self.list.iter_mut() {
try_break!(visitor.visit_variable_mut(variable));
}
ControlFlow::Continue(())
}
}

/// The error returned by the [`VariableList::try_from`] function.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromVariableListError(());
Expand Down Expand Up @@ -288,6 +353,30 @@ impl Variable {
}
}

impl VisitWith for Variable {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_binding(&self.binding));
if let Some(init) = &self.init {
try_break!(visitor.visit_expression(init));
}
ControlFlow::Continue(())
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_binding_mut(&mut self.binding));
if let Some(init) = &mut self.init {
try_break!(visitor.visit_expression_mut(init));
}
ControlFlow::Continue(())
}
}

/// Binding represents either an individual binding or a binding pattern.
///
/// More information:
Expand Down Expand Up @@ -348,6 +437,28 @@ impl ToInternedString for Binding {
}
}

impl VisitWith for Binding {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Binding::Identifier(id) => visitor.visit_identifier(id),
Binding::Pattern(pattern) => visitor.visit_pattern(pattern),
}
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Binding::Identifier(id) => visitor.visit_identifier_mut(id),
Binding::Pattern(pattern) => visitor.visit_pattern_mut(pattern),
}
}
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
101 changes: 101 additions & 0 deletions boa_engine/src/syntax/ast/expression/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -59,6 +62,28 @@ impl From<Expression> for PropertyAccessField {
}
}

impl VisitWith for PropertyAccessField {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
PropertyAccessField::Const(sym) => visitor.visit_sym(sym),
PropertyAccessField::Expr(expr) => visitor.visit_expression(expr),
}
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
PropertyAccessField::Const(sym) => visitor.visit_sym_mut(sym),
PropertyAccessField::Expr(expr) => visitor.visit_expression_mut(&mut *expr),
}
}
}

/// A property access expression.
///
/// See the [module level documentation][self] for more information.
Expand Down Expand Up @@ -111,6 +136,30 @@ impl From<PropertyAccess> for Expression {
}
}

impl VisitWith for PropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
PropertyAccess::Simple(spa) => visitor.visit_simple_property_access(spa),
PropertyAccess::Private(ppa) => visitor.visit_private_property_access(ppa),
PropertyAccess::Super(supa) => visitor.visit_super_property_access(supa),
}
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
PropertyAccess::Simple(spa) => visitor.visit_simple_property_access_mut(spa),
PropertyAccess::Private(ppa) => visitor.visit_private_property_access_mut(ppa),
PropertyAccess::Super(supa) => visitor.visit_super_property_access_mut(supa),
}
}
}

/// A simple property access, where the target object is an [`Expression`].
#[cfg_attr(feature = "deser", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -175,6 +224,24 @@ impl From<SimplePropertyAccess> for PropertyAccess {
}
}

impl VisitWith for SimplePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_property_access_field(&self.field)
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_property_access_field_mut(&mut self.field)
}
}

/// An access expression to a class object's [private fields][mdn].
///
/// Private property accesses differ slightly from plain property accesses, since the accessed
Expand Down Expand Up @@ -243,6 +310,24 @@ impl From<PrivatePropertyAccess> for PropertyAccess {
}
}

impl VisitWith for PrivatePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_sym(&self.field)
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_sym_mut(&mut self.field)
}
}

/// A property access of an object's parent, as defined by the [spec].
///
/// A `SuperPropertyAccess` is much like a regular [`PropertyAccess`], but where its `target` object
Expand Down Expand Up @@ -298,3 +383,19 @@ impl From<SuperPropertyAccess> for PropertyAccess {
Self::Super(access)
}
}

impl VisitWith for SuperPropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_property_access_field(&self.field)
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_property_access_field_mut(&mut self.field)
}
}
18 changes: 18 additions & 0 deletions boa_engine/src/syntax/ast/expression/await.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -62,6 +64,22 @@ impl From<Await> for Expression {
}
}

impl VisitWith for Await {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}

fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
Loading

0 comments on commit b5b8cdf

Please sign in to comment.