Overview
Full Moon provides two visitor traits for traversing AST nodes:
Visitor - For read-only traversal and analysis
VisitorMut - For modifying the AST during traversal
Visitors automatically recurse through the entire AST, calling your methods for each node type.
Read-Only Visitors
Use the Visitor trait when you want to analyze code without modifying it:
use full_moon :: ast ::* ;
use full_moon :: visitors :: Visitor ;
#[derive( Default )]
struct LocalVariableVisitor {
names : Vec < String >,
}
impl Visitor for LocalVariableVisitor {
fn visit_local_assignment ( & mut self , local : & LocalAssignment ) {
// Collect all local variable names
self . names . extend (
local . names () . iter () . map ( | name | name . token () . to_string ())
);
}
}
Using a Visitor
use full_moon :: {parse, visitors :: Visitor };
let ast = parse ( "local x = 1; local y, z = 2, 3" ) ? ;
let mut visitor = LocalVariableVisitor :: default ();
visitor . visit_ast ( & ast );
assert_eq! ( visitor . names, vec! [ "x" , "y" , "z" ]);
Visitor Methods
Every AST node type has two visitor methods:
impl Visitor for MyVisitor {
// Called when entering the node
fn visit_if ( & mut self , if_stmt : & If ) {
println! ( "Found if statement" );
}
// Called when exiting the node (after children)
fn visit_if_end ( & mut self , if_stmt : & If ) {
println! ( "Finished if statement" );
}
}
The _end methods are called after all child nodes have been visited, useful for scope tracking or cleanup.
Analyzing Code Patterns
Counting Function Calls
use full_moon :: ast ::* ;
use full_moon :: visitors :: Visitor ;
#[derive( Default )]
struct FunctionCallCounter {
count : usize ,
}
impl Visitor for FunctionCallCounter {
fn visit_function_call ( & mut self , _call : & FunctionCall ) {
self . count += 1 ;
}
}
Finding Specific Patterns
use full_moon :: ast ::* ;
use full_moon :: visitors :: Visitor ;
struct GlobalAssignmentFinder {
globals : Vec < String >,
}
impl Visitor for GlobalAssignmentFinder {
fn visit_assignment ( & mut self , assignment : & Assignment ) {
for var in assignment . variables () {
if let Var :: Name ( name ) = var {
self . globals . push ( name . token () . to_string ());
}
}
}
}
Tracking Scope
Use _end methods to maintain scope:
use full_moon :: ast ::* ;
use full_moon :: visitors :: Visitor ;
struct ScopeTracker {
depth : usize ,
max_depth : usize ,
}
impl Visitor for ScopeTracker {
fn visit_block ( & mut self , _block : & Block ) {
self . depth += 1 ;
self . max_depth = self . max_depth . max ( self . depth);
}
fn visit_block_end ( & mut self , _block : & Block ) {
self . depth -= 1 ;
}
}
Mutable Visitors
Use VisitorMut to modify the AST during traversal:
use full_moon :: ast ::* ;
use full_moon :: visitors :: VisitorMut ;
struct VariableRenamer {
from : String ,
to : String ,
}
impl VisitorMut for VariableRenamer {
fn visit_token_reference ( & mut self , token : TokenReference ) -> TokenReference {
if token . token () . to_string () == self . from {
TokenReference :: new (
token . leading_trivia () . cloned () . collect (),
Token :: new ( TokenType :: Identifier {
identifier : self . to . clone () . into ()
}),
token . trailing_trivia () . cloned () . collect (),
)
} else {
token
}
}
}
Using a Mutable Visitor
use full_moon :: {parse, visitors :: VisitorMut };
let ast = parse ( "local x = 1; print(x)" ) ? ;
let mut renamer = VariableRenamer {
from : "x" . to_string (),
to : "y" . to_string (),
};
let modified_ast = renamer . visit_ast ( ast );
assert_eq! ( modified_ast . to_string (), "local y = 1; print(y)" );
Advanced Patterns
use full_moon :: ast ::* ;
use full_moon :: visitors :: VisitorMut ;
struct NumberDoubler ;
impl VisitorMut for NumberDoubler {
fn visit_expression ( & mut self , expr : Expression ) -> Expression {
match expr {
Expression :: Number ( token ) => {
// Parse the number, double it
if let Ok ( num ) = token . token () . to_string () . parse :: < i64 >() {
Expression :: Number (
TokenReference :: new (
token . leading_trivia () . cloned () . collect (),
Token :: new ( TokenType :: Number {
text : ( num * 2 ) . to_string () . into ()
}),
token . trailing_trivia () . cloned () . collect (),
)
)
} else {
Expression :: Number ( token )
}
}
_ => expr ,
}
}
}
Building Context During Traversal
use full_moon :: ast ::* ;
use full_moon :: visitors :: VisitorMut ;
use std :: collections :: HashSet ;
struct UnusedRemover {
defined : HashSet < String >,
used : HashSet < String >,
}
impl VisitorMut for UnusedRemover {
fn visit_local_assignment ( & mut self , local : LocalAssignment ) -> LocalAssignment {
// Track defined variables
for name in local . names () {
self . defined . insert ( name . token () . to_string ());
}
local
}
fn visit_var ( & mut self , var : Var ) -> Var {
// Track used variables
if let Var :: Name ( name ) = & var {
self . used . insert ( name . token () . to_string ());
}
var
}
}
Working with Tokens
Visitors can also hook into individual token types:
use full_moon :: tokenizer :: Token ;
use full_moon :: visitors :: Visitor ;
struct CommentCollector {
comments : Vec < String >,
}
impl Visitor for CommentCollector {
fn visit_single_line_comment ( & mut self , token : & Token ) {
self . comments . push ( token . to_string ());
}
fn visit_multi_line_comment ( & mut self , token : & Token ) {
self . comments . push ( token . to_string ());
}
}
Visitor Trait Methods
Full Moon generates visitor methods for all AST node types:
visit_stmt / visit_stmt_end
visit_assignment / visit_assignment_end
visit_local_assignment / visit_local_assignment_end
visit_function_declaration / visit_function_declaration_end
visit_if / visit_if_end
visit_while / visit_while_end
visit_repeat / visit_repeat_end
visit_numeric_for / visit_numeric_for_end
visit_generic_for / visit_generic_for_end
visit_expression / visit_expression_end
visit_function_call / visit_function_call_end
visit_table_constructor / visit_table_constructor_end
visit_binary_op / visit_binary_op_end
visit_unary_op / visit_unary_op_end
visit_token
visit_identifier
visit_number
visit_string_literal
visit_whitespace
visit_single_line_comment
visit_multi_line_comment
Luau-Specific Visitors
With the luau feature enabled, additional visitors are available:
#[cfg(feature = "luau" )]
impl Visitor for MyVisitor {
fn visit_type_declaration ( & mut self , type_decl : & TypeDeclaration ) {
println! ( "Found type: {}" , type_decl . type_name ());
}
fn visit_if_expression ( & mut self , if_expr : & IfExpression ) {
println! ( "Found if expression" );
}
fn visit_interpolated_string ( & mut self , interp : & InterpolatedString ) {
println! ( "Found interpolated string" );
}
}
Selective Visiting Only implement visitor methods for nodes you care about - others will be traversed automatically.
Avoid Cloning In Visitor (read-only), nodes are passed by reference. Avoid unnecessary cloning.
Batch Modifications For complex transformations, collect changes in first pass, apply in second pass.
Early Exit Use custom flags to stop traversal early when you’ve found what you need.
Common Use Cases
Linting
use full_moon :: visitors :: Visitor ;
struct LintVisitor {
errors : Vec < String >,
}
impl Visitor for LintVisitor {
fn visit_if ( & mut self , if_stmt : & If ) {
// Check for empty if blocks
if if_stmt . block () . stmts () . next () . is_none () {
self . errors . push ( "Empty if block" . to_string ());
}
}
}
Code Metrics
use full_moon :: visitors :: Visitor ;
#[derive( Default )]
struct ComplexityCalculator {
complexity : usize ,
}
impl Visitor for ComplexityCalculator {
fn visit_if ( & mut self , _ : & If ) {
self . complexity += 1 ;
}
fn visit_while ( & mut self , _ : & While ) {
self . complexity += 1 ;
}
fn visit_generic_for ( & mut self , _ : & GenericFor ) {
self . complexity += 1 ;
}
}
Next Steps
Static Analysis Example Build a complete static analysis tool
AST API Reference Full visitor trait documentation