-
Notifications
You must be signed in to change notification settings - Fork 0
Syntax Nodes
David Sisco edited this page Jan 9, 2026
·
4 revisions
Syntax nodes provide pattern-based AST matching to create typed wrapper objects for structural constructs.
using TinyTokenizer.Ast;
// Parse with schema (required for syntax node matching)
var tree = SyntaxTree.Parse("foo(x) + bar.baz", Schema.Default);
// Find all function calls
var funcCalls = tree.Match<FunctionCallSyntax>().ToList(); // [FunctionCallSyntax { Name="foo" }]
// Find all property accesses
var props = tree.Match<PropertyAccessSyntax>().ToList(); // [PropertyAccessSyntax { Object="bar", Property="baz" }]| Type | Pattern | Matches | Properties |
|---|---|---|---|
FunctionCallSyntax |
Ident + ParenBlock | foo(x) |
Name, Arguments
|
ArrayAccessSyntax |
Ident + BracketBlock | arr[0] |
Name, Index
|
PropertyAccessSyntax |
Ident + . + Ident |
obj.prop |
Object, Property
|
MethodCallSyntax |
Ident + . + Ident + ParenBlock |
obj.method(x) |
Object, Method, Arguments
|
var tree = SyntaxTree.Parse("obj.method(x, y)", Schema.Default);
var methods = tree.Match<MethodCallSyntax>().ToList(); // [MethodCallSyntax { Object="obj", Method="method" }]
foreach (var call in methods)
{
Console.WriteLine($"{call.Object}.{call.Method}(...)");
}public sealed class LambdaSyntax : SyntaxNode
{
// Constructor receives opaque CreationContext - pass to base
protected LambdaSyntax(CreationContext context)
: base(context) { }
// Access children by index (order matches pattern)
public SyntaxBlock Parameters => GetTypedChild<SyntaxBlock>(0);
public SyntaxToken Arrow => GetTypedChild<SyntaxToken>(1);
public SyntaxBlock Body => GetTypedChild<SyntaxBlock>(2);
// Convenience properties
public string ArrowText => Arrow.Text;
}var lambdaDef = Syntax.Define<LambdaSyntax>("Lambda")
.Match(
Query.ParenBlock, // Parameters (index 0)
Query.Operator("=>"), // Arrow (index 1)
Query.BraceBlock // Body (index 2)
)
.WithPriority(15) // Optional: higher = matched first
.Build();var schema = Schema.Create()
.WithOperators(CommonOperators.JavaScript)
.Define(lambdaDef)
.Build();var tree = SyntaxTree.Parse("(x) => { return x; }", schema);
var lambdas = tree.Match<LambdaSyntax>().ToList(); // [LambdaSyntax { Arrow="=>" }]
foreach (var lambda in lambdas)
{
Console.WriteLine($"Lambda with body: {lambda.Body.ToFullString()}");
}Implement INamedNode to enable querying by name:
public sealed class FunctionSyntax : SyntaxNode, INamedNode
{
protected FunctionSyntax(CreationContext context) : base(context) { }
// Expose the name for querying
public string Name => GetTypedChild<SyntaxToken>(1).Text;
public SyntaxToken Keyword => GetTypedChild<SyntaxToken>(0); // "function"
public SyntaxBlock Parameters => GetTypedChild<SyntaxBlock>(2); // (...)
public SyntaxBlock Body => GetTypedChild<SyntaxBlock>(3); // {...}
}var tree = SyntaxTree.Parse("function main() { } function helper() { }", schema);
// Find functions by name
var mainFunc = tree.Select(
Query.Syntax<FunctionSyntax>().Named("main") // Matches function named "main"
).FirstOrDefault();
// Use with editor
tree.CreateEditor()
.InsertBefore(Query.Syntax<FunctionSyntax>().Named("foo"), "// doc\n")
.Commit();Implement IBlockContainerNode to expose named blocks for injection:
public sealed class FunctionSyntax : SyntaxNode, INamedNode, IBlockContainerNode
{
protected FunctionSyntax(CreationContext context) : base(context) { }
public string Name => GetTypedChild<SyntaxToken>(1).Text;
// IBlockContainerNode - expose available block names
public IReadOnlyList<string> BlockNames => ["body", "params"];
public SyntaxBlock GetBlock(string? name = null) => name switch
{
null or "body" => GetTypedChild<SyntaxBlock>(3), // Default: body
"params" => GetTypedChild<SyntaxBlock>(2),
_ => throw new ArgumentException($"Unknown block: {name}")
};
}var mainQuery = Query.Syntax<FunctionSyntax>().Named("main");
tree.CreateEditor()
.InsertAfter(mainQuery.InnerStart("body"), "\n console.log('enter');")
.InsertBefore(mainQuery.InnerEnd("body"), "\n console.log('exit');")
.InsertAfter(mainQuery.InnerStart("params"), "int x")
.Commit();When multiple patterns could match, higher priority wins:
// MethodCall should match before FunctionCall
var methodDef = Syntax.Define<MethodCallSyntax>("MethodCall")
.Match(
Query.AnyIdent,
Query.Symbol("."),
Query.AnyIdent,
Query.ParenBlock
)
.WithPriority(20) // Higher priority
.Build();
var funcDef = Syntax.Define<FunctionCallSyntax>("FunctionCall")
.Match(Query.AnyIdent, Query.ParenBlock)
.WithPriority(10) // Lower priority
.Build();Use GetTypedChild<T>(index) based on pattern order:
// Pattern: .Match(Query.AnyIdent, Query.Symbol("."), Query.AnyIdent)
// Index: 0 1 2
public sealed class PropertyAccessSyntax : SyntaxNode
{
public SyntaxToken Object => GetTypedChild<SyntaxToken>(0); // First ident
public SyntaxToken Dot => GetTypedChild<SyntaxToken>(1); // The dot
public SyntaxToken Property => GetTypedChild<SyntaxToken>(2); // Second ident
}// 1. Node class
public sealed class IfStatementSyntax : SyntaxNode, IBlockContainerNode
{
protected IfStatementSyntax(CreationContext context) : base(context) { }
public SyntaxToken IfKeyword => GetTypedChild<SyntaxToken>(0);
public SyntaxBlock Condition => GetTypedChild<SyntaxBlock>(1);
public SyntaxBlock Body => GetTypedChild<SyntaxBlock>(2);
public IReadOnlyList<string> BlockNames => ["condition", "body"];
public SyntaxBlock GetBlock(string? name = null) => name switch
{
null or "body" => Body,
"condition" => Condition,
_ => throw new ArgumentException($"Unknown block: {name}")
};
}
// 2. Definition
var ifDef = Syntax.Define<IfStatementSyntax>("IfStatement")
.Match(
Query.Ident("if"),
Query.ParenBlock,
Query.BraceBlock
)
.Build();
// 3. Schema
var schema = Schema.Create()
.Define(ifDef)
.Build();
// 4. Usage
var tree = SyntaxTree.Parse("if (x) { doSomething(); }", schema);
var ifs = tree.Match<IfStatementSyntax>();
tree.CreateEditor()
.InsertAfter(Query.Syntax<IfStatementSyntax>().First().InnerStart("body"),
"\n console.log('condition was true');")
.Commit();- Schema — Adding definitions to schema
- Query API — Building match patterns
- SyntaxEditor — Editing with syntax queries