首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 詞法分析器(六):構建完整的詞法分析器

     編輯:關於C#

前言

經過前幾篇文章的鋪墊,核心的 DFA(確定有限狀態自動機)已經成功構建。最後一步,就是基於這個 DFA 構建出功能完整、可實際投入使用的詞法分析器。雖然目前尚不支援像 Flex 那樣的詞法定義文件,仍需在代碼中定義規則,但基本功能已完全足夠應對大多數場景。

一、詞法規則的定義

所有規則都在 Grammar<T> 類中定義,泛型參數 T 代表詞法單元標識符的類型(必須為枚舉)。定義規則主要包含三個方法:

  • DefineContext:定義詞法分析器上下文
  • DefineRegex:定義可複用的正則表達式
  • DefineSymbol:定義終結符及其對應動作

1. 上下文

調用 DefineContext 定義的上下文由 LexerContext 類表示,包含索引、標籤和類型。上下文類型分為包含型和排除型,對應 Flex 的 %s 和 %x:

  • 排除型:僅激活當前上下文定義的規則,其他規則失效
  • 包含型:除當前上下文規則外,未指定上下文的規則也會被激活

默認上下文標籤為 "Initial"。

2. 正則表達式定義

DefineRegex 等價於 Flex 的定義段,可預定義常用模式簡化規則:

grammar.DefineRegex("digit", "[0-9]");

後續規則中即可使用 {digit} 引用。

3. 終結符與動作

DefineSymbol 對應 Flex 的規則段,定義終結符的正則與動作。動作使用 Action<ReaderController<T>>,由 ReaderController<T> 提供核心方法:

  • Accept:接受當前詞法單元,返回 Token
  • Reject:拒絕當前匹配,尋找次優規則(會導致所有匹配變慢,慎用)
  • More:通知下次匹配成功時追加文本而非替換

Accept 和 Reject 互斥,每次匹配只能調用其一。若兩者均未調用,視為匹配成功但不返回 Token,繼續匹配下一個單元。

二、詞法分析器的實現

2.1 基礎詞法分析器

採用與 Flex 相同的衝突解決規則:

  1. 優先選擇最長匹配
  2. 最長匹配對應多個正則時,選擇最先定義的

基礎分析器相當於 DFA 執行器,不斷讀取字符送入 DFA,記錄最後一次接受狀態。當進入死狀態時,最後記錄的接受狀態即為最長匹配。核心邏輯見 SimpleReader<T>:

int lastAccept = -1, lastIndex = Source.Index;
while (true) {
    int ch = Source.Read();
    if (ch == -1) break; // EOF
    state = base.LexerRule.Transitions[state, base.LexerRule.CharClass[ch]];
    if (state == LexerRule.DeadState) break;
    int[] symbolIndex = base.LexerRule.SymbolIndex[state];
    if (symbolIndex.Length > 0) {
        lastAccept = symbolIndex[0];
        lastIndex = Source.Index;
    }
}
if (lastAccept >= 0) {
    Source.Unget(Source.Index - lastIndex);
    DoAction(base.LexerRule.Actions[lastAccept], lastAccept, Source.Accept());
}

2.2 支援定長向前看

向前看規則形式為 r = s/t。若 s 或 t 可匹配字符串長度固定,稱為定長向前看。預先計算長度存入 int?[] Trailing 數組:

  • null:非向前看
  • 正數 n:前部 s 長度固定為 n
  • 負數 -n:後部 t 長度固定為 n
  • 0:長度均不固定

匹配後根據 Trailing 值截取實際匹配文本。實現見 FixedTrailingReader<T>。

2.3 支援變長向前看

變長向前看無法確定回退位置,需使用堆棧保存所有接受狀態,沿堆棧向下尋找包含 int.MaxValue - symbolIndex 的狀態(即向前看頭狀態)。注意:若 s 結尾與 t 開頭可匹配同樣字符,可能導致匹配偏差,應盡量避免此類正則。實現見 RejectableTrailingReader<T>:

int target = int.MaxValue - acceptState;
while (true) {
    astate = stateStack.Pop();
    if (ContainsTrailingHead(astate.SymbolIndex, target)) break;
}
bool ContainsTrailingHead(int[] symbolIndex, int target) {
    for (int i = symbolIndex.Length - 1; i >= 0; i--) {
        int idx = symbolIndex[i];
        if (idx < base.LexerRule.SymbolCount) break;
        if (idx == target) return true;
    }
    return false;
}

2.4 支援 Reject 動作

Reject 指示跳過當前規則,尋找同樣匹配輸入(或前綴)的次優規則。例如定義多個前綴規則並全部 Reject,最終會輸出所有匹配的前綴。實現需在匹配邏輯中引入回溯機制。

三、實際應用範例

3.1 計算器詞法分析

Grammar g = new Grammar();
g.DefineSymbol("[0-9]+");
g.DefineSymbol("+");
g.DefineSymbol("-");
g.DefineSymbol("*");
g.DefineSymbol("/");
g.DefineSymbol("^");
g.DefineSymbol("(");
g.DefineSymbol(")");
// 忽略空白
g.DefineSymbol("s", c => { });
LexerRule lexer = g.CreateLexer();
string text = "456 + (98 - 56) * 89 / -845 + 2^3";
TokenReader reader = lexer.GetReader(text);
while (true) {
    try {
        Token token = reader.ReadToken();
        if (token.Index == Token.EndOfFileIndex) break;
        Console.WriteLine(token);
    } catch (SourceException se) {
        Console.WriteLine(se.Message);
    }
}

輸出 Token 序列包含數字、運算符、括號等。

3.2 字符串字面量匹配

支援普通字符串與逐字字符串(@""):

g.DefineRegex("regular_string_character", @"[^""\\\n\r\u0085\u2028\u2029]|(\\.)");
g.DefineRegex("regular_string_literal", @"""{regular_string_character}*""");
g.DefineRegex("verbatim_string_characters", @"[^""]|""""");
g.DefineRegex("verbatim_string_literal", @"@""{verbatim_string_characters}*""");
g.DefineSymbol("{regular_string_literal}|{verbatim_string_literal}");

3.3 帶轉義處理的字符串

利用上下文機制實現轉義解析:

g.DefineContext("str");
g.DefineContext("vstr");
g.DefineSymbol(@""", c => { c.PushContext("str"); textBuilder.Clear(); });
g.DefineSymbol(@"""", c => { c.PushContext("vstr"); textBuilder.Clear(); });
g.DefineSymbol(@""", c => { c.PopContext(); c.Accept(0, textBuilder.ToString(), null); });
g.DefineSymbol(@"\\u[0-9]{4}", c => textBuilder.Append((char)int.Parse(c.Text.Substring(2), NumberStyles.HexNumber)));
// ... 其他轉義序列處理
g.DefineSymbol(@".", c => textBuilder.Append(c.Text));
// 逐字字符串處理類似

輸出即為轉義後的實際字符串內容。

總結

至此,完整的詞法分析器已構建完成。相關代碼可在項目倉庫中找到,基礎類(如輸入緩衝)亦已提供。本系列暫告一段落。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved