前六篇詳細介紹詞法分析器相關演算法 (正則表達式 → NFA → DFA → 最小化 → 多規則/上下文/Reject)。本文從整體架構角度,說明 Cyjb.Compilers 類庫的使用指南與自訂詞法分析器實作要點。
Token<T> 泛型,T 為列舉型別,提供編譯期檢查、語義清晰、可轉整數。Cyjb.Compilers、Cyjb.Compilers.Lexers。LexerRule<T> 介面更直觀。
public enum CalcToken
{
Number, Plus, Minus, Multiply, Divide,
LParen, RParen, Identifier, Assign, EndOfFile
}
EndOfFile = -1 由 Token<T>.EndOfFile 內建處理。
var grammar = new Grammar();
grammar.DefineContext("STRING", ContextType.Exclusive); // 排除型:僅啟用 STRING 規則
grammar.DefineContext("COMMENT", ContextType.Inclusive); // 包含型:啟用 COMMENT + 無上下文規則
預設上下文為 "Initial"。
grammar.DefineRegex("digit", "[0-9]+");
grammar.DefineRegex("ident", "[a-zA-Z_][a-zA-Z0-9_]*");
grammar.DefineSymbol(CalcToken.Number, "{digit}"); // 簡單:自動回傳 Token
grammar.DefineSymbol(CalcToken.Identifier, "{ident}", ctx =>
{
ctx.Token.Value = double.Parse(ctx.Text); // 自訂動作:解析數值存入 Token.Value
});
grammar.DefineSymbol(CalcToken.Plus, @"\+");
grammar.DefineSymbol(CalcToken.Minus, @"-");
grammar.DefineSymbol(CalcToken.Multiply, @"\*");
grammar.DefineSymbol(CalcToken.Divide, @"/");
grammar.DefineSymbol(CalcToken.LParen, @"\(");
grammar.DefineSymbol(CalcToken.RParen, @"\)");
grammar.DefineSymbol(CalcToken.Assign, @"=");
// 空白忽略:定義無標識符、動作為空
grammar.DefineSymbol(null, @"\s+", ctx => { /* 拋棄 */ });
Accept():接受匹配,回傳 Token。More():保留本次文字,併入下次匹配 (如字串換行)。Reject():拒絕匹配,嘗試次優規則。BeginContext/PushContext/PopContext:上下文切換。
var lexer = grammar.ToLexer(); // 或 ToLexer(TextReader), ToLexer(string), ToLexer(Stream)
foreach (var token in lexer)
Console.WriteLine(token);
從 grammar.LexerRule 取得 LexerRule<T>,包含:
CharClass[65536]:每個字元對應的字元類別索引。Contexts:上下文標籤 → DFA 起始狀態索引 字典。Symbols:終結符列表 (SymbolData<T>:標識符、動作、向前看資訊)。States:DFA 狀態列表 (StateData:轉移表、對應終結符索引)。每上下文 2 個 DFA (行首/非行首)。此資料結構不依賴 Grammar<T>,可序列化為 JSON、寫入 .cs 檔、甚至移植至其他語言 (Java、Go、Rust) 實作高效能詞法分析器。
switch 或查表替代多型。Stream 分塊讀取,配合 More() 處理跨塊 Token。Reject 回退機制,支援關鍵字/識別符衝突解決。詞法分析器是編譯器前端的基石。Cyjb.Compilers 提供從定義到 DFA 構建的完整管線,開發者可專注於語言文法設計,亦可深入底層自訂高效能掃描器。源碼與文檔請參考 GitHub 專案。