摘要
上一篇里我们构建了语法树,但他并不能执行,还要把它转换成可执行的代码。语法树是抽象的,可以把它转换到各种平台的执行文件,但我们现在只关注它是如何生成一个CLR的可执行文件。
IL指令介绍书接上文,话说曹操来到了景阳岗。。。,汗,串频道了。。。
有人说,编译原理中语法分析是核心,有人却说语法分析没啥新花样,都是死的东西,代码生成才是最关键的,确实,你要到了那种水平,啥都觉得简单。我们要把语法树翻译成.NET中间语言,所以我们得学一些常用的IL指令,常识性的东西就不介绍了,比如CLR的大致运行原理呀,反射,Emit基础应用等都不细说了。把IL运行环境想象成一个堆栈,我们要把参数压入到堆栈里,然后执行各种执行,我们会用到的指令罗列如下
stloc 把栈顶的对象保存到本地变量里
ldloc 把本地变量加载到栈顶上
newobj 创建一个对象,并入栈,后面跟一个构造函数
callvirt 调用实例方法,后面跟methodinfo
br 无条件跳转到某标签
castclass 强制类型转换,后面跟要转换成的类型
call 调用静态方法
ceq 比较是否相等,如果相当就把1压入栈,否则把0压入栈
brtrue 如果栈顶元素是true或者非0,就跳转到指定标签
ret 表示返回栈顶对象,方法结束
就这么几个,简单吧,可你不懂还不行,周日我没弄明白brtrue和brtrue_s的区别,折腾了我一下午,一晚上,整整六七个小时,if语句里多加一条语句就出错,去掉就可以了,等今天跟脑袋一说,脑袋一眼就看出是长指令,短指令的问题,我汗,就是说brtrue_s跳转的标签只能是一个字节长度,所以if的body里语句多了就跳不过去了,我把brtrue_s都换成brtrue后问题解决,服了,这就是基础差和基础好的差距。
辅助方法
private void GenExpr(Expr expr, System.Type expectedType)
private void Store(string name, System.Type type)
private System.Type TypeOfExpr(Expr expr)
前两个方法顾名思义,一个用来生成语句,一个用来生成表达式,其中GenExpr第二个参数表示你预想的表达式返回的数据类型,Store是保存一个本地变量,TypeOfExpr方法用来获取一个表达式的类型,我们先把后两个简单的方法实现看下。
if (expr is StringLiteral) {
return typeof(string);
}
else if (expr is IntLiteral) {
return typeof(int);
}
else if (expr is Match) {
return typeof(System.Collections.IEnumerator);
}
else if (expr is StrLen) {
return typeof(int);
}
else if (expr is BinExpr && ((BinExpr)expr).Op == BinOp.Eq) {
return typeof(bool);
}
else if (expr is Builder) {
return typeof(System.Text.StringBuilder);
}
else if (expr is Variable) {
Variable var = (Variable)expr;
if (this.symbolTable.ContainsKey(var.Ident)) {
Emit.LocalBuilder locb = this.symbolTable[var.Ident];
return locb.LocalType;
}
else {
throw new System.Exception("undeclared variable '" + var.Ident + "'");
}
}
else {
throw new System.Exception("don't know how to calculate the type of " + expr.GetType().Name);
}
}
大致意思就是写死的,固定的表达式类型返回固定的类型,如果是一个变量名的话,就返回这个变量名指向的本地变量的类型。
if (this.symbolTable.ContainsKey(name)) {
Emit.LocalBuilder locb = this.symbolTable[name];
if (locb.LocalType == type) {
this.il.Emit(Emit.OpCodes.Stloc, this.symbolTable[name]);
}
else {
throw new System.Exception("'" + name + "' is of type " + locb.LocalType.Name +
" but attempted to store value of type " + type.Name);
}
}
else {
throw new System.Exception("undeclared variable '" + name + "'");
}
}
这个方法是用Stloc指令来吧栈顶的对象保存成一个命名的本地变量,没别的,就是查看了下符号表里有没有声明过这个变量以及抛出了一些异常。
生成表达式
而要转换成il的话,就要先把pattern用ldstr指令加载到栈上,然后用newobj创建一个Regex对象,然后用ldstr把input压入栈,然后用callvrit来调用Regex的Matches方法,把一个IEnumerable对象呀入栈,最后来掉一次IEnumerable的GetEnumerator()方法
大致应该如下
L_000b: newobj instance void [System]System.Text.RegularExpressions.Regex::.ctor(string)
L_0010: ldloc.0 //input
L_0011: callvirt instance class [System]System.Text.RegularExpressions.MatchCollection
[System]System.Text.RegularExpressions.Regex::Matches(string) //call Matches
L_0016: callvirt instance class [mscorlib]System.Collections.IEnumerator
[System]System.Text.RegularExpressions.MatchCollection::GetEnumerator() //call GetEnumerator
而我们根据AST生成IL的代码应该如下
Match m = (Match)expr;
GenExpr(m.Pattern, typeof(string));
il.Emit(OpCodes.Newobj, typeof(Regex).GetConstructor(new[] { typeof(string) }));
GenExpr(m.Input, typeof(string));
il.Emit(OpCodes.Callvirt, typeof(Regex).GetMethod("Matches", new[] { typeof(string) }));
il.Emit(OpCodes.Callvirt, typeof(MatchCollection).GetMethod("GetEnumerator"));
deliveredType = typeof(System.Collections.IEnumerator);
}
再看一个StrLen的代码生成
StrLen len = (StrLen)expr;
GenExpr(len.Input, typeof(string));
il.Emit(OpCodes.Callvirt, typeof(string).GetMethod("get_Length"));
deliveredType = typeof(int);
}
生成语句
逻辑上应该是这样,foreach语句应该翻译成一个while语句如下的WawaSharp语句
print item;
end;
对应的c#语句是
foeach(object item in arr)
{
Console.Write(item);
}
我们先转换成如下
while(arr.MoveNext())
{
object o = arr.Current;
Console.Write(o);
}
再想办法弄成IL指令序列,这里有两个label,一个是arr.MoveNext这里,这里测试返回值是否为true,一个是循环体,执行while里的实际执行语句,我们一个定义为test,一个定义为body,刚开始我先用Br指令无提交跳转到test标签,test标签里,生成IEnumerable表达式,用Callvirt调用MoveNext,当返回true时,用Brtrue指令跳转到body标签,body标签里先得到IEnumerable,这里又用了次GenExpr,这里不会重新调用GetEnumerator方法的,因为each.IEnumerable只是一个变量arr,执行GenExpr(each.IEnumerable, typeof(System.Collections.IEnumerator))只不过是ldloc arr而已,所以不用担心性能问题。然后调用get_Current,访问Current树形,再强转成System.Text.RegularExpressions.Match类型,并调用其父类Capture的Value属性,最后把item本地变量存起来,供body语句里ldloc起来使用。
Foreach each = (Foreach)stmt;
Label body = il.DefineLabel();
Label test = il.DefineLabel();
il.Emit(OpCodes.Br, test);
il.MarkLabel(body);
GenExpr(each.IEnumerable, typeof(System.Collections.IEnumerator));
il.Emit(OpCodes.Callvirt, typeof(System.Collections.IEnumerator).GetMethod("get_Current"));
il.Emit(OpCodes.Castclass, typeof(System.Text.RegularExpressions.Match));
il.Emit(OpCodes.Callvirt, typeof(Capture).GetMethod("get_Value"));
symbolTable[each.Ident] = il.DeclareLocal(typeof(object));
Store(each.Ident, typeof(object));
GenStmt(each.Body);
il.MarkLabel(test);
GenExpr(each.IEnumerable, typeof(System.Collections.IEnumerator));
il.Emit(OpCodes.Callvirt, typeof(System.Collections.IEnumerator).GetMethod("MoveNext"));
il.Emit(OpCodes.Brtrue, body);
}
累了,就举这一个例子算了,if语句的生成就不说了,看代码吧。
检查结果
.maxstack 6
.locals init (
[0] string str,
[1] class [mscorlib]System.Collections.IEnumerator enumerator,
[2] class [mscorlib]System.Text.StringBuilder builder,
[3] object obj2,
[4] int32 num,
[5] class [mscorlib]System.Collections.IEnumerator enumerator2,
[6] object obj3)
L_0000: ldstr "11|222|33|44|55"
L_0005: stloc.0
L_0006: ldstr "\\d+|"
L_000b: newobj instance void [System]System.Text.RegularExpressions.Regex::.ctor(string)
L_0010: ldloc.0
L_0011: callvirt instance class [System]System.Text.RegularExpressions.MatchCollection
[System]System.Text.RegularExpressions.Regex::Matches(string)
L_0016: callvirt instance class [mscorlib]System.Collections.IEnumerator
[System]System.Text.RegularExpressions.MatchCollection::GetEnumerator()
L_001b: stloc.1
L_001c: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0021: stloc.2
L_0022: br L_00bb
L_0027: ldloc.1
L_0028: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
L_002d: castclass [System]System.Text.RegularExpressions.Match
L_0032: callvirt instance string [System]System.Text.RegularExpressions.Capture::get_Value()
L_0037: stloc.3
L_0038: ldloc.3
L_0039: callvirt instance string [mscorlib]System.Object::ToString()
L_003e: call void [mscorlib]System.Console::WriteLine(object)
L_0043: ldloc.3
L_0044: callvirt instance string [mscorlib]System.Object::ToString()
L_0049: callvirt instance int32 [mscorlib]System.String::get_Length()
L_004e: stloc.s num
L_0050: ldloc.s num
L_0052: ldc.i4 2
L_0057: ceq
L_0059: ldc.i4.0
L_005a: ceq
L_005c: brtrue L_00bb
L_0061: ldstr "\\d"
L_0066: newobj instance void [System]System.Text.RegularExpressions.Regex::.ctor(string)
L_006b: ldloc.3
L_006c: callvirt instance string [mscorlib]System.Object::ToString()
L_0071: callvirt instance class [System]System.Text.RegularExpressions.MatchCollection
[System]System.Text.RegularExpressions.Regex::Matches(string)
L_0076: callvirt instance class [mscorlib]System.Collections.IEnumerator
[System]System.Text.RegularExpressions.MatchCollection::GetEnumerator()
L_007b: stloc.s enumerator2
L_007d: br L_00af
L_0082: ldloc.s enumerator2
L_0084: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
L_0089: castclass [System]System.Text.RegularExpressions.Match
L_008e: callvirt instance string [System]System.Text.RegularExpressions.Capture::get_Value()
L_0093: stloc.s obj3
L_0095: ldloc.2
L_0096: ldstr "\r\n"
L_009b: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
L_00a0: pop
L_00a1: ldloc.2
L_00a2: ldloc.s obj3
L_00a4: callvirt instance string [mscorlib]System.Object::ToString()
L_00a9: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
L_00ae: pop
L_00af: ldloc.s enumerator2
L_00b1: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_00b6: brtrue L_0082
L_00bb: ldloc.1
L_00bc: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_00c1: brtrue L_0027
L_00c6: ldloc.2
L_00c7: callvirt instance string [mscorlib]System.Object::ToString()
L_00cc: call void [mscorlib]System.Console::WriteLine(object)
L_00d1: ret
用refleter转换成c#代码如下
IEnumerator enumerator = new Regex(@"\d+|").Matches(input).GetEnumerator();
StringBuilder builder = new StringBuilder();
while (enumerator.MoveNext())
{
object obj2 = ((Match) enumerator.Current).Value;
Console.WriteLine(obj2.ToString());
if (obj2.ToString().Length == 2)
{
IEnumerator enumerator2 = new Regex(@"\d").Matches(obj2.ToString()).GetEnumerator();
while (enumerator2.MoveNext())
{
object obj3 = ((Match) enumerator2.Current).Value;
builder.Append("\r\n");
builder.Append(obj3.ToString());
}
}
}
Console.WriteLine(builder.ToString());
执行输出如下
11
222
33
44
55
1
1
3
3
4
4
5
5
总结
一个小型的语言系统WawaSharp已经实现了,它可以做简单的字符串处理,你可以在此基础上扩展更全的字符串处理函数,甚至xml处理函数,这样做一些简单的XML和字符串处理小工具,用这种语言就足以应付了,否则还得打开vs.net写c#。据说.net 4.0里的表达式树才只是循环和分支等结构,咱现在都提前实现了。
代码下载地址如下