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

從零構建 .NET 語言編譯器:掃描、解析、AST 與 IL 代碼生成全解析

     編輯:關於.NET

本文帶領讀者完成一個名為「Good for Nothing」的玩具語言編譯器,涵蓋詞法分析、語法分析、AST 構建、IL 代碼生成全流程,揭示 .NET 編譯器開發的核心原理與 System.Reflection.Emit 實戰技巧。

語言定義 (EBNF)

<stmt> := var <ident> = <expr> | <ident> = <expr> | for <ident> = <expr> to <expr> do <stmt> end
         | read_int <ident> | print <expr> | <stmt> ; <stmt>
<expr> := <string> | <int> | <arith_expr> | <ident>
<arith_expr> := <expr> <arith_op> <expr>
<arith_op> := + | - | * | /
<ident> := <char> <ident_rest>*
<ident_rest> := <char> | <digit>

支援變量聲明、賦值、for 循環、控制台 I/O、算術表達式,類型系統含 int/string 映射至 System.Int32/System.String。

編譯器三階段架構

編譯器階段圖

  1. Scanner (掃描器):字符流 → Token 序列,過濾空白、識別關鍵字/標識符/字面量/運算符。
  2. Parser (解析器):Token 序列 → AST (抽象語法樹),採用 LL(1) 自上而下遞歸下降解析。
  3. CodeGen (代碼生成器):遍歷 AST → 發射 IL → 打包 .NET 程序集。

關鍵組件實現

1. 掃描器:字符流分詞

public Scanner(TextReader input) {
    while (input.Peek() != -1) {
        char ch = (char)input.Peek();
        if (char.IsWhiteSpace(ch)) { input.Read(); continue; }
        if (ch == '"') {  // 字符串字面量
            input.Read(); var sb = new StringBuilder();
            while ((ch = (char)input.Peek()) != '"') { sb.Append(ch); input.Read(); }
            input.Read(); tokens.Add(sb.ToString()); continue;
        }
        if (char.IsLetter(ch) || ch == '_') { /* 標識符/關鍵字 */ }
        if (char.IsDigit(ch)) { /* 數字字面量 */ }
        /* 運算符、分號等單字符 Token */
    }
}

2. AST 節點定義

public abstract class Stmt { }
public class DeclareVar : Stmt { public string Ident; public Expr Expr; }
public class Assign : Stmt { public string Ident; public Expr Expr; }
public class ForLoop : Stmt { public string Ident; public Expr From, To; public Stmt Body; }
public class Print : Stmt { public Expr Expr; }
public class ReadInt : Stmt { public string Ident; }
public class Sequence : Stmt { public Stmt First, Second; }

public abstract class Expr { }
public class IntLiteral : Expr { public int Value; }
public class StringLiteral : Expr { public string Value; }
public class Variable : Expr { public string Ident; }
public class ArithExpr : Expr { public Expr Left, Right; public ArithOp Op; }

3. 解析器核心邏輯

private Stmt ParseStmt() {
    if (tokens[index].Equals("print")) { index++; return new Print { Expr = ParseExpr() }; }
    if (tokens[index].Equals("var")) { index++; /* 解析聲明 */ }
    if (tokens[index].Equals("for")) { index++; /* 解析 for 循環 */ }
    if (tokens[index] is string ident) { /* 解析賦值 */ }
    /* 分號處理:構建 Sequence 節點串聯語句 */
}

AST 樹結構示例

4. IL 代碼生成器 (Reflection.Emit)

public CodeGen(Stmt stmt, string moduleName) {
    var name = new AssemblyName(Path.GetFileNameWithoutExtension(moduleName));
    var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Save);
    var mod = asm.DefineDynamicModule(moduleName);
    var type = mod.DefineType("Foo");
    var method = type.DefineMethod("Main", MethodAttributes.Static, typeof(void), Type.EmptyTypes);
    method.SetEntryPoint();  // 關鍵:設置入口點
    _il = method.GetILGenerator();
    _symbolTable = new Dictionary<string, LocalBuilder>();

    GenStmt(stmt);
    _il.Emit(OpCodes.Ret);
    type.CreateType();
    asm.Save(moduleName);
}

關鍵 IL 生成模式

AST 節點IL 指令序列
IntLiteralLdc_I4 value
StringLiteralLdstr "value"
VariableLdloc localBuilder
ArithExpr (+)Ldloc left; Ldloc right; Add
AssignGenExpr(expr); Stloc localBuilder
PrintGenExpr(expr); Call Console.WriteLine(string)
ForLoop初始化 → Br test → Body Label → 體 → 遞增 → Test Label → Ldloc; Ldc_I4 limit; Blt body

For 循環生成的 IL (對應圖 14)

// for x = 0 to 100 do print "hello" end
IL_0006: ldc.i4 0          // x = 0
IL_000b: stloc.0
IL_000c: br IL_0023        // 跳轉測試
IL_0011: ...               // 循環體
IL_001b: ldloc.0           // 遞增
IL_001c: ldc.i4 1
IL_0021: add
IL_0022: stloc.0
IL_0023: ldloc.0           // 測試 x < 100
IL_0024: ldc.i4 100
IL_0029: blt IL_0011

進階主題:動態調用與 DLR

  • 後期綁定:動態語言需運行時解析方法。傳統 Reflection 慢,DynamicMethod (LCG) 可生成樁代碼,將調用轉為快速 call 指令,首次 JIT 後性能接近靜態調用。
  • DLR (動態語言運行時):.NET 4.0 引入,提供統一的動態調用站點緩存、類型系統互操作、AST 共享、REPL 支持,是 IronPython/IronRuby/Roslyn 的基石。

驗證工具鏈

  • peverify.exe:驗證 IL 正確性(堆棧平衡、類型安全),定位「Expected numeric type on the stack」等錯誤。
  • ildasm.exe:反編譯程序集查看 IL,對標商業編譯器生成模式。
  • ilasm.exe:從 IL 文本構建程序集,快速原型驗證指令序列。

推薦進階閱讀

  • 《Compiling for the .NET Common Language Runtime》- John Gough
  • 《Inside Microsoft IL Assembler》- Serge Lidin
  • 《Compilers: Principles, Techniques, and Tools》- Dragon Book
  • 《Programming Language Pragmatics》- Michael L. Scott
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved