mirror of https://github.com/boa-dev/boa.git
Browse Source
This Pull Request closes no specific issue, but allows for analysis and post-processing passes by both internal and external developers. It changes the following: - Adds a Visitor trait, to be implemented by visitors of a particular node type. - Adds `Type`Visitor traits which offer access to private members of a node. - Adds an example which demonstrates the use of Visitor traits by walking over an AST and printing its contents. At this time, the PR is more of a demonstration of intent rather than a full PR. Once it's in a satisfactory state, I'll mark it as not a draft. Co-authored-by: Addison Crump <addison.crump@cispa.de>pull/2402/head
Addison Crump
2 years ago
53 changed files with 2521 additions and 4 deletions
@ -0,0 +1,276 @@
|
||||
//! Javascript Abstract Syntax Tree visitors.
|
||||
//!
|
||||
//! This module contains visitors which can be used to inspect or modify AST nodes. This allows for
|
||||
//! fine-grained manipulation of ASTs for analysis, rewriting, or instrumentation.
|
||||
|
||||
/// `Try`-like conditional unwrapping of `ControlFlow`.
|
||||
#[macro_export] |
||||
macro_rules! try_break { |
||||
($expr:expr) => { |
||||
match $expr { |
||||
core::ops::ControlFlow::Continue(c) => c, |
||||
core::ops::ControlFlow::Break(b) => return core::ops::ControlFlow::Break(b), |
||||
} |
||||
}; |
||||
} |
||||
|
||||
use crate::syntax::ast::declaration::{ |
||||
Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList, |
||||
}; |
||||
use crate::syntax::ast::expression::access::{ |
||||
PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess, |
||||
SuperPropertyAccess, |
||||
}; |
||||
use crate::syntax::ast::expression::literal::{ |
||||
ArrayLiteral, Literal, ObjectLiteral, TemplateElement, TemplateLiteral, |
||||
}; |
||||
use crate::syntax::ast::expression::operator::assign::{Assign, AssignTarget}; |
||||
use crate::syntax::ast::expression::operator::{Binary, Conditional, Unary}; |
||||
use crate::syntax::ast::expression::{ |
||||
Await, Call, Expression, Identifier, New, Optional, OptionalOperation, OptionalOperationKind, |
||||
Spread, SuperCall, TaggedTemplate, Yield, |
||||
}; |
||||
use crate::syntax::ast::function::{ |
||||
ArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameter, |
||||
FormalParameterList, Function, Generator, |
||||
}; |
||||
use crate::syntax::ast::pattern::{ |
||||
ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern, |
||||
}; |
||||
use crate::syntax::ast::property::{MethodDefinition, PropertyDefinition, PropertyName}; |
||||
use crate::syntax::ast::statement::iteration::{ |
||||
Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop, |
||||
IterableLoopInitializer, WhileLoop, |
||||
}; |
||||
use crate::syntax::ast::statement::{ |
||||
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try, |
||||
}; |
||||
use crate::syntax::ast::{StatementList, StatementListItem}; |
||||
use boa_interner::Sym; |
||||
|
||||
/// Creates the default visit function implementation for a particular type
|
||||
macro_rules! define_visit { |
||||
($fn_name:ident, $type_name:ident) => { |
||||
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor")] |
||||
#[must_use] |
||||
fn $fn_name(&mut self, node: &'ast $type_name) -> core::ops::ControlFlow<Self::BreakTy> { |
||||
node.visit_with(self) |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/// Creates the default mutable visit function implementation for a particular type
|
||||
macro_rules! define_visit_mut { |
||||
($fn_name:ident, $type_name:ident) => { |
||||
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor, mutably")] |
||||
#[must_use] |
||||
fn $fn_name( |
||||
&mut self, |
||||
node: &'ast mut $type_name, |
||||
) -> core::ops::ControlFlow<Self::BreakTy> { |
||||
node.visit_with_mut(self) |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/// Represents an AST visitor.
|
||||
///
|
||||
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
|
||||
/// visitor pattern.
|
||||
#[allow(unused_variables)] |
||||
pub trait Visitor<'ast>: Sized { |
||||
/// Type which will be propagated from the visitor if completing early.
|
||||
type BreakTy; |
||||
|
||||
define_visit!(visit_statement_list, StatementList); |
||||
define_visit!(visit_statement_list_item, StatementListItem); |
||||
define_visit!(visit_statement, Statement); |
||||
define_visit!(visit_declaration, Declaration); |
||||
define_visit!(visit_function, Function); |
||||
define_visit!(visit_generator, Generator); |
||||
define_visit!(visit_async_function, AsyncFunction); |
||||
define_visit!(visit_async_generator, AsyncGenerator); |
||||
define_visit!(visit_class, Class); |
||||
define_visit!(visit_lexical_declaration, LexicalDeclaration); |
||||
define_visit!(visit_block, Block); |
||||
define_visit!(visit_var_declaration, VarDeclaration); |
||||
define_visit!(visit_expression, Expression); |
||||
define_visit!(visit_if, If); |
||||
define_visit!(visit_do_while_loop, DoWhileLoop); |
||||
define_visit!(visit_while_loop, WhileLoop); |
||||
define_visit!(visit_for_loop, ForLoop); |
||||
define_visit!(visit_for_in_loop, ForInLoop); |
||||
define_visit!(visit_for_of_loop, ForOfLoop); |
||||
define_visit!(visit_switch, Switch); |
||||
define_visit!(visit_continue, Continue); |
||||
define_visit!(visit_break, Break); |
||||
define_visit!(visit_return, Return); |
||||
define_visit!(visit_labelled, Labelled); |
||||
define_visit!(visit_throw, Throw); |
||||
define_visit!(visit_try, Try); |
||||
define_visit!(visit_identifier, Identifier); |
||||
define_visit!(visit_formal_parameter_list, FormalParameterList); |
||||
define_visit!(visit_class_element, ClassElement); |
||||
define_visit!(visit_variable_list, VariableList); |
||||
define_visit!(visit_variable, Variable); |
||||
define_visit!(visit_binding, Binding); |
||||
define_visit!(visit_pattern, Pattern); |
||||
define_visit!(visit_literal, Literal); |
||||
define_visit!(visit_array_literal, ArrayLiteral); |
||||
define_visit!(visit_object_literal, ObjectLiteral); |
||||
define_visit!(visit_spread, Spread); |
||||
define_visit!(visit_arrow_function, ArrowFunction); |
||||
define_visit!(visit_template_literal, TemplateLiteral); |
||||
define_visit!(visit_property_access, PropertyAccess); |
||||
define_visit!(visit_new, New); |
||||
define_visit!(visit_call, Call); |
||||
define_visit!(visit_super_call, SuperCall); |
||||
define_visit!(visit_optional, Optional); |
||||
define_visit!(visit_tagged_template, TaggedTemplate); |
||||
define_visit!(visit_assign, Assign); |
||||
define_visit!(visit_unary, Unary); |
||||
define_visit!(visit_binary, Binary); |
||||
define_visit!(visit_conditional, Conditional); |
||||
define_visit!(visit_await, Await); |
||||
define_visit!(visit_yield, Yield); |
||||
define_visit!(visit_for_loop_initializer, ForLoopInitializer); |
||||
define_visit!(visit_iterable_loop_initializer, IterableLoopInitializer); |
||||
define_visit!(visit_case, Case); |
||||
define_visit!(visit_sym, Sym); |
||||
define_visit!(visit_labelled_item, LabelledItem); |
||||
define_visit!(visit_catch, Catch); |
||||
define_visit!(visit_finally, Finally); |
||||
define_visit!(visit_formal_parameter, FormalParameter); |
||||
define_visit!(visit_property_name, PropertyName); |
||||
define_visit!(visit_method_definition, MethodDefinition); |
||||
define_visit!(visit_object_pattern, ObjectPattern); |
||||
define_visit!(visit_array_pattern, ArrayPattern); |
||||
define_visit!(visit_property_definition, PropertyDefinition); |
||||
define_visit!(visit_template_element, TemplateElement); |
||||
define_visit!(visit_simple_property_access, SimplePropertyAccess); |
||||
define_visit!(visit_private_property_access, PrivatePropertyAccess); |
||||
define_visit!(visit_super_property_access, SuperPropertyAccess); |
||||
define_visit!(visit_optional_operation, OptionalOperation); |
||||
define_visit!(visit_assign_target, AssignTarget); |
||||
define_visit!(visit_object_pattern_element, ObjectPatternElement); |
||||
define_visit!(visit_array_pattern_element, ArrayPatternElement); |
||||
define_visit!(visit_property_access_field, PropertyAccessField); |
||||
define_visit!(visit_optional_operation_kind, OptionalOperationKind); |
||||
} |
||||
|
||||
/// Represents an AST visitor which can modify AST content.
|
||||
///
|
||||
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
|
||||
/// visitor pattern.
|
||||
#[allow(unused_variables)] |
||||
pub trait VisitorMut<'ast>: Sized { |
||||
/// Type which will be propagated from the visitor if completing early.
|
||||
type BreakTy; |
||||
|
||||
define_visit_mut!(visit_statement_list_mut, StatementList); |
||||
define_visit_mut!(visit_statement_list_item_mut, StatementListItem); |
||||
define_visit_mut!(visit_statement_mut, Statement); |
||||
define_visit_mut!(visit_declaration_mut, Declaration); |
||||
define_visit_mut!(visit_function_mut, Function); |
||||
define_visit_mut!(visit_generator_mut, Generator); |
||||
define_visit_mut!(visit_async_function_mut, AsyncFunction); |
||||
define_visit_mut!(visit_async_generator_mut, AsyncGenerator); |
||||
define_visit_mut!(visit_class_mut, Class); |
||||
define_visit_mut!(visit_lexical_declaration_mut, LexicalDeclaration); |
||||
define_visit_mut!(visit_block_mut, Block); |
||||
define_visit_mut!(visit_var_declaration_mut, VarDeclaration); |
||||
define_visit_mut!(visit_expression_mut, Expression); |
||||
define_visit_mut!(visit_if_mut, If); |
||||
define_visit_mut!(visit_do_while_loop_mut, DoWhileLoop); |
||||
define_visit_mut!(visit_while_loop_mut, WhileLoop); |
||||
define_visit_mut!(visit_for_loop_mut, ForLoop); |
||||
define_visit_mut!(visit_for_in_loop_mut, ForInLoop); |
||||
define_visit_mut!(visit_for_of_loop_mut, ForOfLoop); |
||||
define_visit_mut!(visit_switch_mut, Switch); |
||||
define_visit_mut!(visit_continue_mut, Continue); |
||||
define_visit_mut!(visit_break_mut, Break); |
||||
define_visit_mut!(visit_return_mut, Return); |
||||
define_visit_mut!(visit_labelled_mut, Labelled); |
||||
define_visit_mut!(visit_throw_mut, Throw); |
||||
define_visit_mut!(visit_try_mut, Try); |
||||
define_visit_mut!(visit_identifier_mut, Identifier); |
||||
define_visit_mut!(visit_formal_parameter_list_mut, FormalParameterList); |
||||
define_visit_mut!(visit_class_element_mut, ClassElement); |
||||
define_visit_mut!(visit_variable_list_mut, VariableList); |
||||
define_visit_mut!(visit_variable_mut, Variable); |
||||
define_visit_mut!(visit_binding_mut, Binding); |
||||
define_visit_mut!(visit_pattern_mut, Pattern); |
||||
define_visit_mut!(visit_literal_mut, Literal); |
||||
define_visit_mut!(visit_array_literal_mut, ArrayLiteral); |
||||
define_visit_mut!(visit_object_literal_mut, ObjectLiteral); |
||||
define_visit_mut!(visit_spread_mut, Spread); |
||||
define_visit_mut!(visit_arrow_function_mut, ArrowFunction); |
||||
define_visit_mut!(visit_template_literal_mut, TemplateLiteral); |
||||
define_visit_mut!(visit_property_access_mut, PropertyAccess); |
||||
define_visit_mut!(visit_new_mut, New); |
||||
define_visit_mut!(visit_call_mut, Call); |
||||
define_visit_mut!(visit_super_call_mut, SuperCall); |
||||
define_visit_mut!(visit_optional_mut, Optional); |
||||
define_visit_mut!(visit_tagged_template_mut, TaggedTemplate); |
||||
define_visit_mut!(visit_assign_mut, Assign); |
||||
define_visit_mut!(visit_unary_mut, Unary); |
||||
define_visit_mut!(visit_binary_mut, Binary); |
||||
define_visit_mut!(visit_conditional_mut, Conditional); |
||||
define_visit_mut!(visit_await_mut, Await); |
||||
define_visit_mut!(visit_yield_mut, Yield); |
||||
define_visit_mut!(visit_for_loop_initializer_mut, ForLoopInitializer); |
||||
define_visit_mut!(visit_iterable_loop_initializer_mut, IterableLoopInitializer); |
||||
define_visit_mut!(visit_case_mut, Case); |
||||
define_visit_mut!(visit_sym_mut, Sym); |
||||
define_visit_mut!(visit_labelled_item_mut, LabelledItem); |
||||
define_visit_mut!(visit_catch_mut, Catch); |
||||
define_visit_mut!(visit_finally_mut, Finally); |
||||
define_visit_mut!(visit_formal_parameter_mut, FormalParameter); |
||||
define_visit_mut!(visit_property_name_mut, PropertyName); |
||||
define_visit_mut!(visit_method_definition_mut, MethodDefinition); |
||||
define_visit_mut!(visit_object_pattern_mut, ObjectPattern); |
||||
define_visit_mut!(visit_array_pattern_mut, ArrayPattern); |
||||
define_visit_mut!(visit_property_definition_mut, PropertyDefinition); |
||||
define_visit_mut!(visit_template_element_mut, TemplateElement); |
||||
define_visit_mut!(visit_simple_property_access_mut, SimplePropertyAccess); |
||||
define_visit_mut!(visit_private_property_access_mut, PrivatePropertyAccess); |
||||
define_visit_mut!(visit_super_property_access_mut, SuperPropertyAccess); |
||||
define_visit_mut!(visit_optional_operation_mut, OptionalOperation); |
||||
define_visit_mut!(visit_assign_target_mut, AssignTarget); |
||||
define_visit_mut!(visit_object_pattern_element_mut, ObjectPatternElement); |
||||
define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement); |
||||
define_visit_mut!(visit_property_access_field_mut, PropertyAccessField); |
||||
define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind); |
||||
} |
||||
|
||||
/// Denotes that a type may be visited, providing a method which allows a visitor to traverse its
|
||||
/// private fields.
|
||||
pub trait VisitWith { |
||||
/// Visit this node with the provided visitor.
|
||||
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>; |
||||
|
||||
/// Visit this node with the provided visitor mutably, allowing the visitor to modify private
|
||||
/// fields.
|
||||
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>; |
||||
} |
||||
|
||||
// implementation for Sym as it is out-of-crate
|
||||
impl VisitWith for Sym { |
||||
fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy> |
||||
where |
||||
V: Visitor<'a>, |
||||
{ |
||||
core::ops::ControlFlow::Continue(()) |
||||
} |
||||
|
||||
fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy> |
||||
where |
||||
V: VisitorMut<'a>, |
||||
{ |
||||
core::ops::ControlFlow::Continue(()) |
||||
} |
||||
} |
@ -0,0 +1,84 @@
|
||||
// This example demonstrates how to use visitors to modify an AST. Namely, the visitors shown here
|
||||
// are used to swap the operands of commutable arithmetic operations. For an example which simply
|
||||
// inspects the AST without modifying it, see symbol_visitor.rs.
|
||||
|
||||
use boa_engine::syntax::ast::expression::operator::binary::{ArithmeticOp, BinaryOp}; |
||||
use boa_engine::syntax::ast::expression::operator::Binary; |
||||
use boa_engine::syntax::ast::visitor::{VisitWith, VisitorMut}; |
||||
use boa_engine::syntax::ast::Expression; |
||||
use boa_engine::syntax::Parser; |
||||
use boa_engine::Context; |
||||
use boa_interner::ToInternedString; |
||||
use core::ops::ControlFlow; |
||||
use std::convert::Infallible; |
||||
use std::fs::File; |
||||
use std::io::BufReader; |
||||
|
||||
/// Visitor which, when applied to a binary expression, will swap the operands. Use in other
|
||||
/// circumstances is undefined.
|
||||
#[derive(Default)] |
||||
struct OpExchanger<'ast> { |
||||
lhs: Option<&'ast mut Expression>, |
||||
} |
||||
|
||||
impl<'ast> VisitorMut<'ast> for OpExchanger<'ast> { |
||||
type BreakTy = (); |
||||
|
||||
fn visit_expression_mut(&mut self, node: &'ast mut Expression) -> ControlFlow<Self::BreakTy> { |
||||
if let Some(lhs) = self.lhs.take() { |
||||
core::mem::swap(lhs, node); |
||||
ControlFlow::Break(()) |
||||
} else { |
||||
self.lhs = Some(node); |
||||
// we do not traverse into the expression; we are only to be used with a binary op
|
||||
ControlFlow::Continue(()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Visitor which walks the AST and swaps the operands of commutable arithmetic binary expressions.
|
||||
#[derive(Default)] |
||||
struct CommutorVisitor {} |
||||
|
||||
impl<'ast> VisitorMut<'ast> for CommutorVisitor { |
||||
type BreakTy = Infallible; |
||||
|
||||
fn visit_binary_mut(&mut self, node: &'ast mut Binary) -> ControlFlow<Self::BreakTy> { |
||||
match node.op() { |
||||
BinaryOp::Arithmetic(op) => { |
||||
match op { |
||||
ArithmeticOp::Add | ArithmeticOp::Mul => { |
||||
// set up the exchanger and swap lhs and rhs
|
||||
let mut exchanger = OpExchanger::default(); |
||||
assert!(matches!( |
||||
exchanger.visit_binary_mut(node), |
||||
ControlFlow::Break(_) |
||||
)); |
||||
} |
||||
_ => {} |
||||
} |
||||
} |
||||
_ => {} |
||||
} |
||||
// traverse further in; there may nested binary operations
|
||||
node.visit_with_mut(self) |
||||
} |
||||
} |
||||
|
||||
fn main() { |
||||
let mut parser = Parser::new(BufReader::new( |
||||
File::open("boa_examples/scripts/calc.js").unwrap(), |
||||
)); |
||||
let mut ctx = Context::default(); |
||||
|
||||
let mut statements = parser.parse_all(&mut ctx).unwrap(); |
||||
|
||||
let mut visitor = CommutorVisitor::default(); |
||||
|
||||
assert!(matches!( |
||||
visitor.visit_statement_list_mut(&mut statements), |
||||
ControlFlow::Continue(_) |
||||
)); |
||||
|
||||
println!("{}", statements.to_interned_string(ctx.interner())); |
||||
} |
@ -0,0 +1,51 @@
|
||||
// This example demonstrates how to use a visitor to perform simple operations over the Javascript
|
||||
// AST, namely: finding all the `Sym`s present in a script. See commuter_visitor.rs for an example
|
||||
// which mutates the AST.
|
||||
|
||||
use boa_engine::syntax::ast::visitor::Visitor; |
||||
use boa_engine::syntax::Parser; |
||||
use boa_engine::Context; |
||||
use boa_interner::Sym; |
||||
use core::ops::ControlFlow; |
||||
use std::collections::HashSet; |
||||
use std::convert::Infallible; |
||||
use std::fs::File; |
||||
use std::io::BufReader; |
||||
|
||||
#[derive(Debug, Clone, Default)] |
||||
struct SymbolVisitor { |
||||
observed: HashSet<Sym>, |
||||
} |
||||
|
||||
impl<'ast> Visitor<'ast> for SymbolVisitor { |
||||
type BreakTy = Infallible; |
||||
|
||||
fn visit_sym(&mut self, node: &'ast Sym) -> ControlFlow<Self::BreakTy> { |
||||
self.observed.insert(*node); |
||||
ControlFlow::Continue(()) |
||||
} |
||||
} |
||||
|
||||
fn main() { |
||||
let mut parser = Parser::new(BufReader::new( |
||||
File::open("boa_examples/scripts/calc.js").unwrap(), |
||||
)); |
||||
let mut ctx = Context::default(); |
||||
|
||||
let statements = parser.parse_all(&mut ctx).unwrap(); |
||||
|
||||
let mut visitor = SymbolVisitor::default(); |
||||
|
||||
assert!(matches!( |
||||
visitor.visit_statement_list(&statements), |
||||
ControlFlow::Continue(_) |
||||
)); |
||||
|
||||
println!( |
||||
"Observed {} unique strings/symbols:", |
||||
visitor.observed.len() |
||||
); |
||||
for sym in visitor.observed { |
||||
println!(" - {}", ctx.interner().resolve(sym).unwrap()); |
||||
} |
||||
} |
Loading…
Reference in new issue