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