Skip to main content

Overview

The LuaSyntaxWalker class automatically traverses an entire Lua syntax tree in depth-first order, visiting each node, token, and optionally trivia. Unlike LuaSyntaxVisitor<TResult>, it automatically visits child nodes, making it ideal for operations that need to process the entire tree.

Namespace

Loretta.CodeAnalysis.Lua

Syntax

public abstract class LuaSyntaxWalker : LuaSyntaxVisitor

When to Use

Use LuaSyntaxWalker when you need to:
  • Traverse the entire syntax tree automatically
  • Collect information from multiple nodes
  • Perform operations at different tree depths (nodes, tokens, trivia)
  • Avoid manually implementing child node traversal
Use LuaSyntaxVisitor (generic) instead when you:
  • Need to return values from visits
  • Want manual control over traversal
  • Only need to visit specific node types

Constructor

protected LuaSyntaxWalker(SyntaxWalkerDepth depth = SyntaxWalkerDepth.Node)
Parameters:
  • depth: The depth to which the walker should descend

SyntaxWalkerDepth Options

The SyntaxWalkerDepth enum controls how deep the walker descends into the syntax tree:

Node (Default)

SyntaxWalkerDepth.Node
Visits only syntax nodes. This is the most common option.

Token

SyntaxWalkerDepth.Token
Visits nodes and tokens. Use when you need to examine keywords, operators, identifiers, and literals.

Trivia

SyntaxWalkerDepth.Trivia
Visits nodes, tokens, and trivia (whitespace, comments, etc.). Use when processing formatting or comments.

StructuredTrivia

SyntaxWalkerDepth.StructuredTrivia
Visits nodes, tokens, trivia, and structured trivia nodes. Rarely needed.

Key Properties

Depth

protected SyntaxWalkerDepth Depth { get; }
Gets the depth to which the walker descends.

Key Methods

Visit

public override void Visit(SyntaxNode? node)
Visits the specified node and automatically visits its children. Includes stack overflow protection.

DefaultVisit

public override void DefaultVisit(SyntaxNode node)
Called for each node. Automatically visits all child nodes and tokens based on the walker’s depth.

VisitToken

public virtual void VisitToken(SyntaxToken token)
Called when visiting a token (if Depth >= Token). Override to handle tokens.

VisitTrivia

public virtual void VisitTrivia(SyntaxTrivia trivia)
Called when visiting trivia (if Depth >= Trivia). Override to handle whitespace and comments.

VisitLeadingTrivia

public virtual void VisitLeadingTrivia(SyntaxToken token)
Called to visit the leading trivia of a token.

VisitTrailingTrivia

public virtual void VisitTrailingTrivia(SyntaxToken token)
Called to visit the trailing trivia of a token.

Visit Methods

The walker inherits all Visit methods from LuaSyntaxVisitor (see LuaSyntaxVisitor for the complete list). The key difference is that you typically don’t need to manually visit children - the walker does it automatically. Common methods to override:
  • VisitFunctionCallExpression
  • VisitIdentifierName
  • VisitLocalVariableDeclarationStatement
  • VisitAssignmentStatement
  • VisitBinaryExpression
  • And any other node type you’re interested in

Examples

Example 1: Collect All Identifiers

using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Syntax;
using System.Collections.Generic;

public class IdentifierCollector : LuaSyntaxWalker
{
    public List<string> Identifiers { get; } = new List<string>();

    // Constructor with depth
    public IdentifierCollector() : base(SyntaxWalkerDepth.Node)
    {
    }

    public override void VisitIdentifierName(IdentifierNameSyntax node)
    {
        Identifiers.Add(node.Name);
        
        // Call base to continue walking
        base.VisitIdentifierName(node);
    }
}

// Usage
var code = @"
local x = 10
local y = x + 20
print(x, y)
";

var tree = LuaSyntaxTree.ParseText(code, new LuaParseOptions(LuaSyntaxOptions.All));
var collector = new IdentifierCollector();
collector.Visit(tree.GetRoot());

foreach (var identifier in collector.Identifiers)
{
    Console.WriteLine(identifier);
}
// Output:
// x
// x
// y
// print
// x
// y

Example 2: Find All Function Declarations

public class FunctionFinder : LuaSyntaxWalker
{
    public List<string> FunctionNames { get; } = new List<string>();

    public override void VisitFunctionDeclarationStatement(FunctionDeclarationStatementSyntax node)
    {
        // Get the function name
        var name = node.Name.Name.Text;
        FunctionNames.Add(name);
        
        // Continue walking to find nested functions
        base.VisitFunctionDeclarationStatement(node);
    }

    public override void VisitLocalFunctionDeclarationStatement(LocalFunctionDeclarationStatementSyntax node)
    {
        // Get the local function name
        var name = node.Name.Text;
        FunctionNames.Add($"local {name}");
        
        // Continue walking
        base.VisitLocalFunctionDeclarationStatement(node);
    }
}

// Usage
var code = @"
function outer()
    local function inner()
        print('nested')
    end
    inner()
end

function another()
end
";

var tree = LuaSyntaxTree.ParseText(code, new LuaParseOptions(LuaSyntaxOptions.All));
var finder = new FunctionFinder();
finder.Visit(tree.GetRoot());

foreach (var name in finder.FunctionNames)
{
    Console.WriteLine(name);
}
// Output:
// outer
// local inner
// another

Example 3: Count Tokens and Comments

public class TokenAndCommentCounter : LuaSyntaxWalker
{
    public int TokenCount { get; private set; }
    public int CommentCount { get; private set; }

    // Use Token depth to visit tokens
    public TokenAndCommentCounter() : base(SyntaxWalkerDepth.Trivia)
    {
    }

    public override void VisitToken(SyntaxToken token)
    {
        if (token.Kind() != SyntaxKind.None)
        {
            TokenCount++;
        }
        
        // Continue to visit trivia
        base.VisitToken(token);
    }

    public override void VisitTrivia(SyntaxTrivia trivia)
    {
        if (trivia.Kind() == SyntaxKind.SingleLineCommentTrivia ||
            trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
        {
            CommentCount++;
        }
        
        base.VisitTrivia(trivia);
    }
}

// Usage
var code = @"
-- This is a comment
local x = 10 -- inline comment
/*
  Multi-line comment
*/
print(x)
";

var tree = LuaSyntaxTree.ParseText(code, new LuaParseOptions(LuaSyntaxOptions.All));
var counter = new TokenAndCommentCounter();
counter.Visit(tree.GetRoot());

Console.WriteLine($"Tokens: {counter.TokenCount}");
Console.WriteLine($"Comments: {counter.CommentCount}");

Example 4: Validate Variable Usage

public class VariableValidator : LuaSyntaxWalker
{
    private readonly HashSet<string> _declaredVariables = new HashSet<string>();
    public List<string> UndeclaredVariables { get; } = new List<string>();

    public override void VisitLocalVariableDeclarationStatement(LocalVariableDeclarationStatementSyntax node)
    {
        // Track declared variables
        foreach (var name in node.Names)
        {
            if (name.Name is IdentifierNameSyntax id)
            {
                _declaredVariables.Add(id.Name);
            }
        }
        
        base.VisitLocalVariableDeclarationStatement(node);
    }

    public override void VisitIdentifierName(IdentifierNameSyntax node)
    {
        // Check if variable is declared
        if (!_declaredVariables.Contains(node.Name) && 
            !IsKnownGlobal(node.Name))
        {
            UndeclaredVariables.Add(node.Name);
        }
        
        base.VisitIdentifierName(node);
    }

    private bool IsKnownGlobal(string name)
    {
        var globals = new[] { "print", "error", "assert", "type", "tonumber", "tostring" };
        return globals.Contains(name);
    }
}

// Usage
var code = @"
local x = 10
print(x)
print(y) -- y is undeclared
";

var tree = LuaSyntaxTree.ParseText(code, new LuaParseOptions(LuaSyntaxOptions.All));
var validator = new VariableValidator();
validator.Visit(tree.GetRoot());

if (validator.UndeclaredVariables.Any())
{
    Console.WriteLine("Undeclared variables:");
    foreach (var v in validator.UndeclaredVariables.Distinct())
    {
        Console.WriteLine($"  {v}");
    }
}
// Output:
// Undeclared variables:
//   y

Important Notes

Always Call Base Methods

When overriding Visit methods, always call the base method to ensure child nodes are visited:
public override void VisitFunctionCallExpression(FunctionCallExpressionSyntax node)
{
    // Your custom logic
    DoSomething(node);
    
    // IMPORTANT: Call base to continue walking
    base.VisitFunctionCallExpression(node);
}
If you don’t call the base method, the walker won’t descend into child nodes.

Stack Overflow Protection

The walker includes automatic stack overflow protection for deeply nested trees. The framework monitors recursion depth and throws an exception if it becomes too deep.

Differences from LuaSyntaxVisitor

FeatureLuaSyntaxWalkerLuaSyntaxVisitor (generic)
Auto-traverses childrenYesNo
Returns valuesNoYes
Depth controlYesNo
Override patternOverride Visit methods + call baseOverride Visit methods + manually visit children
Default behaviorVisits all childrenReturns default(TResult)
Common use caseCollect/analyze tree dataExtract/compute values
Visit tokens/triviaYes (with depth setting)No (nodes only)

See Also

Build docs developers (and LLMs) love