Repository navigation
Architecture
TinyTokenizer uses a two-level tokenization architecture designed for high performance and flexibility.
Source Text
│
▼
┌─────────────────────────────────────────────┐
│ Level 1: Lexer │
│ - Stateless character classifier │
│ - Produces SimpleToken structs │
│ - SIMD-optimized with SearchValues<char> │
│ - Never backtracks, never fails │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Level 2: TokenParser │
│ - Combines simple tokens into semantic │
│ Token records │
│ - Handles blocks, strings, comments │
│ - Recursive block parsing │
│ - Error recovery with ErrorToken │
└─────────────────────────────────────────────┘
│
▼
Semantic Tokens (Token records)
The Lexer class performs pure character classification:
- Stateless — No context between characters
- Never fails — Every character produces a token
-
SIMD-optimized — Uses
SearchValues<char>for vectorized matching -
Outputs
SimpleToken— Lightweight struct with position tracking
var lexer = new Lexer();
IEnumerable<SimpleToken> simpleTokens = lexer.Lex("foo(x)");
// Produces: Ident, OpenParen, Ident, CloseParenThe TokenParser class combines simple tokens into semantic tokens:
-
Combines tokens — Merges
OpenParen+ content +CloseParenintoBlockToken - Handles strings — Recognizes escape sequences
- Parses comments — Single-line and multi-line
- Matches operators — Greedy multi-character operator matching
-
Error recovery — Produces
ErrorTokenfor malformed input
var parser = new TokenParser(options);
IEnumerable<Token> tokens = parser.Parse(simpleTokens);
// Produces: IdentToken, BlockToken (with children)TinyTokenizer minimizes heap allocations through several techniques:
Token content references the original source string without copying:
public abstract record Token(ReadOnlyMemory<char> Content, TokenType Type, long Position);
// Access spans for processing without allocation
ReadOnlySpan<char> span = token.ContentSpan;Level 1 tokens are value types to avoid heap allocations during lexing:
public readonly record struct SimpleToken(
SimpleTokenType Type,
ReadOnlyMemory<char> Content,
int Position);The parser uses pooled buffers from CommunityToolkit.HighPerformance:
using var contentBuilder = new ArrayPoolBufferWriter<char>();
// Build token content without allocating new arraysMulti-character operators are matched using a trie data structure for O(k) lookup where k is operator length:
// Configured operators: ==, ===, =>, =
// Input: "==="
// Trie performs greedy matching:
// 1. Match '=' → found "="
// 2. Match '=' → found "=="
// 3. Match '=' → found "===" ← longest match winsThis ensures operators like === are matched before == without backtracking.
TinyAst uses the red-green tree pattern (similar to Roslyn):
- Immutable — Never modified after creation
- Shared — Identical subtrees share the same green node instance
- Position-agnostic — Don't store absolute positions
-
Cached —
GreenNodeCacheinterns common patterns
- Navigable — Have parent references
- Position-aware — Know their absolute position in source
- Lazy — Created on-demand as you traverse
- Cheap to create — Thin wrappers around green nodes
// Red nodes are what you work with
SyntaxNode root = tree.Root;
SyntaxNode parent = node.Parent;
int position = node.Position;
// Green nodes are implementation details
// (not directly exposed in public API)- Memory efficient — Shared green nodes for repeated patterns
- Fast edits — Only rebuild affected subtree paths
- Undo/redo — Simply swap the root green node
- Thread-safe — Immutable green layer is safe to share
The lexer uses SearchValues<char> for vectorized character classification:
// Pre-built character sets
private static readonly SearchValues<char> SingleCharTokens =
SearchValues.Create("{}[]()'\"\\/.*#@=+-<>|&%^~?!:,;");
// Vectorized contains check (uses SIMD when available)
if (SingleCharTokens.Contains(c))
{
// Fast path for known token characters
}This provides significant speedup when processing large files on modern CPUs with SIMD support.
- Token Types — SimpleToken vs Token types
- Configuration — TokenizerOptions and operator configuration
- TinyAst Guide — Working with syntax trees