首页 > 代码库 > 编译抽象语法树
编译抽象语法树
编译抽象语法树
对大多数开发人员来说,编译就意谓着产生本地代码,给人感觉就是一个字,难。但是,并不一定要产生本地代码,对于 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) ->
names
|> getParamListInner e1
|> getParamListInner e2
| Plus(e1 , e2) ->
names
|> getParamListInner e1
|> getParamListInner e2
| Minus(e1 , e2) ->
names
|> getParamListInner e1
|> getParamListInner e2
| _ -> names
getParamListInner e []
// create the dynamic method
letcreateDynamicMethod e (paramNames: string list) =
let generateIl e (il : ILGenerator) =
let recgenerateIlInner e =
match e with
|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) ->
generateIlInner e1
generateIlInner e2
il.Emit(OpCodes.Div)
| Plus (e1 , e2) ->
generateIlInner e1
generateIlInner e2
il.Emit(OpCodes.Add)
|Minus (e1 , e2) ->
generateIlInner e1
generateIlInner 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