摘要
上一篇里我们构建了语法树,但他并不能执行,还要把它转换成可执行的代码。语法树是抽象的,可以把它转换到各种平台的执行文件,但我们现在只关注它是如何生成一个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里的表达式树才只是循环和分支等结构,咱现在都提前实现了。
代码下载地址如下