天天看點

編譯抽象文法樹

編譯抽象文法樹

對大多數開發人員來說,編譯就意謂着産生本地代碼,給人感覺就是一個字,難。但是,并不一定要産生本地代碼,對于 DSL,通常産生其他更加通用的程式設計語言。.NET 架構提供幾個把抽象文法樹編譯成程式的功能。

技術的選擇取決于幾個因素。例如,如果語言針對的是開發人員,那麼,生成文本檔案就足夠了,内容可以是 F#,也可以是其他語言,或者是編譯過的程式集,能在程式中使用;然而,如果針對的是最終使用者,那麼幾乎可以肯定,必須編譯然後動态[ on the fly ]執行。表12-1 彙總了各種可用選項。

表12-1 .NET 的代碼生成技術

技術

描述

Microsoft.CSharp

.CSharpCodeProvider

這個類支援動态産生的 C# 編譯檔案,既可以通過簡單地字元串連接配接,也可以通過使用 System.CodeDom 命名空間。一旦代碼編譯成程式集,就可以動态加載到記憶體,通過反映執行。這個操作相對來說是昂貴的,因為它要求寫到磁盤,并使用反映去執行方法。

System.CodeDom

這是一組類,目标是在不同語言的可用操作之間進行抽象。其思想是,用在這個命名空間下可用的類來描述操作,然後,用提供程式把它編譯成選擇的語言。.NET 為 C# 和 Visual Basic 釋出了提供程式。

FSharp.Compiler.CodeDom.dll

這是随 F# 發行的 CodeDom 提供程式,它可以用于動态編譯 F#,方法與 C# 的 CodeDom 提供程式相似。

System.Reflection.Emit

有了這個命名空間,可以用中間語言建立程式集。由于中間語言提供了比 F#、C#,System.CodeDom 更多的功能,是以,更具靈活性。然而,由于是低級語言,就需要更多的耐心,為了獲得正确的結果,要花更多的時間。

Mono.Cecil

這個庫廣泛用于 Mono 架構,既可以解析程式集,也可以動态建立程式集。

我們System.Reflection.Emit.DynamicMethod 類,并不是因為我們特别需要中間語言的靈活性,而是因為中間語言内置了浮點運算的指令,可以很适合實作我們的小語言。DynamicMethod 還提供了快捷、容易的方法可以調用産生的程式。

createDynamicMethod 方法才真正編譯抽象文法樹,通過掃描抽象文法樹和生成代碼。首先,建立DynamicMethod 類的執行個體,來操作這個中間語言,是定義來表示這個方法的:

let temp = new DynamicMethod("",(type float), paramsTypes, meth.Module)

然後,createDynamicMethod 開始周遊樹。遇到辨別符,輸出的代碼是加載動态方法的參數:

| Ident name ->

   il.Emit(OpCodes.Ldarg, paramNames.IndexOf(name))

當遇到文字時,輸出的中間語言代碼是加載這個文字的值:

| Val x ->il.Emit(OpCodes.Ldc_R8, x)

當遇到操作時,必須遞歸評估計算表達式的兩項,然後輸出的是表示要求操作的指令:

| Multi (e1 , e2) ->

 generateIlInner e1

  generateIlInner e2

  il.Emit(OpCodes.Mul)

注意,如何運算是最後輸出的,在表達式的兩項遞歸評估之後。這樣做原因是中間語言基于堆棧,是以,來自其他運算的資料,在評估運算符之前,必須壓棧。

清單 12-7 是完整的編譯器。

清單 12-7 編譯從指令行輸入生成的抽象文法樹

openSystem.Collections.Generic

open System.Reflection

open System.Reflection.Emit

openStrangelights.ExpressionParser

// get a list of all the parameter names

let rec getParamList e =

    let

recgetParamListInner e names =

        match e

with

        | Identname ->

            if not (List.exists (funs

-> s = name) names) then

               name :: names

            else

               names

        | Multi(e1 , e2) ->

           names

           |> getParamListInner e1

           |> getParamListInner e2

        | Div(e1 , e2) ->

            |> getParamListInner e1

        | Plus(e1 , e2) ->

        | Minus(e1 , e2) ->

        | _ -> names

   getParamListInner e []

// create the dynamic method

letcreateDynamicMethod e (paramNames: string list) =

    let generateIl e (il : ILGenerator) =

        let

recgenerateIlInner e =

            match e

            |Ident name ->

               let index = List.find_index (fun s

-> s = name)paramNames

               il.Emit(OpCodes.Ldarg, index)

            |Val x -> il.Emit(OpCodes.Ldc_R8, x)

            |Multi (e1 , e2) ->

               generateIlInner e1

               generateIlInner e2

               il.Emit(OpCodes.Mul)

            |Div (e1 , e2) ->

               il.Emit(OpCodes.Div)

            | Plus (e1 , e2) ->

               il.Emit(OpCodes.Add)

            |Minus (e1 , e2) ->

               il.Emit(OpCodes.Sub)

       generateIlInner e

       il.Emit(OpCodes.Ret)

    let paramsTypes = Array.create paramNames.Length (typeof<float>)

    let meth = MethodInfo.GetCurrentMethod()

    let temp =

newDynamicMethod("", (typeof<float>), paramsTypes, meth.Module)

    let il = temp.GetILGenerator()

    generateIle il

    temp

// function to read the arguments fromthe command line

let collectArgs(paramNames : string list) =

    paramNames

    |> Seq.map

        (fun n

->

           printf "%s: " n

            box(float(Console.ReadLine())))

    |>Array.ofSeq

// the expression to be interpreted

let e = Multi(Val 2.,Plus(Val 2., Ident "a"))

// get a list of all the parameters fromthe expression

let paramNames =getParamList e

// compile the tree to a dynamic method

let dm =createDynamicMethod e paramNames

// print collect arguments from the user

let args =collectArgs paramNames

// execute and print out final result

printf "result:%O" (dm.Invoke(null, args))

運作結果如下:

編譯清單 12-7 的代碼産生下面的結果:

a: 4

result: 32