Repository navigation
TinyAst Guide
David Sisco edited this page Jan 6, 2026
·
4 revisions
TinyAst provides a syntax tree API for efficient AST manipulation with fluent queries and undo/redo support.
Create a syntax tree from source text:
using TinyTokenizer.Ast;
// Simple parse
var tree = SyntaxTree.Parse("function foo() { return 1; }");
// Parse with schema (enables syntax node matching)
var tree = SyntaxTree.Parse(source, Schema.Default);
// Parse with custom options
var schema = Schema.Create()
.WithOperators(CommonOperators.JavaScript)
.Build();
var tree = SyntaxTree.Parse(source, schema);The public API uses "red" nodes — navigable wrappers with parent references:
SyntaxNode (abstract base)
├── SyntaxBlock — Contains children ({ }, [ ], ( ))
├── SyntaxToken — Terminal tokens (identifiers, operators, etc.)
└── SyntaxList — Root token list
var tree = SyntaxTree.Parse("foo(x)");
SyntaxNode root = tree.Root;
// Root is always a SyntaxList
if (root is SyntaxList list)
{
Console.WriteLine($"Root has {list.Children.Count()} children");
}var tree = SyntaxTree.Parse("a + b");
foreach (var child in tree.Root.Children)
{
Console.WriteLine($"{child.Kind} at {child.Position}");
}
// Output:
// Ident at 0
// Whitespace at 1
// Operator at 2
// Whitespace at 3
// Ident at 4var tree = SyntaxTree.Parse("foo(bar)");
// Find a nested node
var bar = tree.Leaves.First(l => l.Text == "bar");
// Navigate up
SyntaxNode? parent = bar.Parent; // The paren block
SyntaxNode? grandparent = parent?.Parent; // The rootEvery node has a Kind indicating its type:
| Kind | Node Type | Description |
|---|---|---|
Ident |
SyntaxToken |
Identifiers |
Numeric |
SyntaxToken |
Number literals |
String |
SyntaxToken |
Quoted strings |
Operator |
SyntaxToken |
Multi-char operators (=>, ==, etc.) |
Symbol |
SyntaxToken |
Single characters (., ,, ;) |
TaggedIdent |
SyntaxToken |
Prefixed identifiers (#define, @attr) |
Comment |
SyntaxToken |
Comments |
Error |
SyntaxToken |
Parse errors |
BraceBlock |
SyntaxBlock |
{ } |
BracketBlock |
SyntaxBlock |
[ ] |
ParenBlock |
SyntaxBlock |
( ) |
| 500-1999 | SyntaxToken |
Schema-defined keywords |
| 2000+ | varies | Schema-defined syntax nodes |
Keywords receive unique NodeKind values when defined via Schema.DefineKeywords(). Use kind.IsKeyword() to check.
foreach (var node in tree.Root.Children)
{
switch (node.Kind)
{
case NodeKind.Ident:
Console.WriteLine($"Identifier: {((SyntaxToken)node).Text}");
break;
case NodeKind.BraceBlock:
Console.WriteLine($"Block with {((SyntaxBlock)node).Children.Count()} children");
break;
default:
if (node.Kind.IsKeyword())
Console.WriteLine($"Keyword: {((SyntaxToken)node).Text}");
break;
}
}Get all leaf (terminal) nodes:
var tree = SyntaxTree.Parse("foo(x, y)");
// All leaves (including whitespace)
foreach (var leaf in tree.Leaves)
{
Console.WriteLine($"{leaf.Kind}: '{leaf.Text}'");
}
// Filter to specific kinds
var idents = tree.Leaves.Where(l => l.Kind == NodeKind.Ident); // [Leaf("foo"), Leaf("x"), Leaf("y")]Use the Query API for powerful CSS-like selectors:
var tree = SyntaxTree.Parse("foo(x) + bar(y)");
// Find specific identifiers
var foos = tree.Select(Query.Ident("foo")); // [Ident("foo")]
// Find all identifiers
var allIdents = tree.Select(Query.AnyIdent); // [Ident("foo"), Ident("x"), Ident("bar"), Ident("y")]
// Find function calls (ident followed by parens)
var funcCalls = tree.Select(Query.AnyIdent.FollowedBy(Query.ParenBlock)); // [Ident("foo"), Ident("bar")]Use SyntaxEditor for batched mutations:
tree.CreateEditor()
.Replace(Query.Ident("foo"), "bar")
.InsertAfter(Query.BraceBlock.First().Start(), "// entry\n")
.Commit();All edits support undo/redo:
var tree = SyntaxTree.Parse("hello");
tree.CreateEditor()
.Replace(Query.Ident("hello"), "goodbye")
.Commit();
Console.WriteLine(tree.Root.ToFullString()); // "goodbye"
tree.Undo();
Console.WriteLine(tree.Root.ToFullString()); // "hello"
tree.Redo();
Console.WriteLine(tree.Root.ToFullString()); // "goodbye"Every node tracks its position in the source:
var tree = SyntaxTree.Parse("foo bar");
var bar = tree.Leaves.First(l => l.Text == "bar");
Console.WriteLine($"Position: {bar.Position}"); // 4
Console.WriteLine($"Width: {bar.Width}"); // 3
Console.WriteLine($"End: {bar.End}"); // 7var tree = SyntaxTree.Parse("foo(x)");
// Full text including all whitespace
string full = tree.Root.ToFullString();
// Individual leaf text
foreach (var leaf in tree.Leaves)
{
Console.WriteLine(leaf.Text);
}TinyAst uses red-green trees (similar to Roslyn):
- Green nodes (internal) — Immutable, shared, position-agnostic
- Red nodes (public) — Navigable wrappers with parent references
This design enables:
- Memory efficiency — Shared subtrees
- Fast edits — Only rebuild affected paths
- Undo/redo — Swap root green node
See Architecture for details.
- Query API — CSS-like node selectors
- SyntaxEditor — Batch mutations
- TreeWalker — DOM-style traversal
- Trivia — Whitespace and comments
- Schema — Configuring parsing