蛙蛙推荐:蛙蛙教你发明一种新语言之二--代码生成

摘要

上一篇里我们构建了语法树,但他并不能执行,还要把它转换成可执行的代码。语法树是抽象的,可以把它转换到各种平台的执行文件,但我们现在只关注它是如何生成一个CLR的可执行文件。

 

IL指令介绍
书接上文,话说曹操来到了景阳岗。。。,汗,串频道了。。。
有人说,编译原理中语法分析是核心,有人却说语法分析没啥新花样,都是死的东西,代码生成才是最关键的,确实,你要到了那种水平,啥都觉得简单。我们要把语法树翻译成.NET中间语言,所以我们得学一些常用的IL指令,常识性的东西就不介绍了,比如CLR的大致运行原理呀,反射,Emit基础应用等都不细说了。把IL运行环境想象成一个堆栈,我们要把参数压入到堆栈里,然后执行各种执行,我们会用到的指令罗列如下
ldstr 加载一个字符串到栈上
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后问题解决,服了,这就是基础差和基础好的差距。


辅助方法

IL里除了栈、指令,还有两个东西,一个是Lebel,一个是本地变量,就是临时变量,有了这些所有的东西,顺序,分支,循环都可以实现了,我是先写C#代码,然后反射看il代码,然后翻译成Emit的c#代码,这么一个过程来学的。代码生成分几个方法
private void GenStmt(Stmt stmt)
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方法用来获取一个表达式的类型,我们先把后两个简单的方法实现看下。
private System.Type TypeOfExpr(Expr expr) {
    
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);
    }
}

大致意思就是写死的,固定的表达式类型返回固定的类型,如果是一个变量名的话,就返回这个变量名指向的本地变量的类型。
private void Store(string name, System.Type type) {
    
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指令来吧栈顶的对象保存成一个命名的本地变量,没别的,就是查看了下符号表里有没有声明过这个变量以及抛出了一些异常。

生成表达式

再看一个稍微简单的,生成表达式的部分,比如Match表达式,它有两个参数,两个参数都是string类型,第一个input,第二个是Pattern,c#代码的写法是new Regex(pattern).Matches(input).GetEnumerator();
而要转换成il的话,就要先把pattern用ldstr指令加载到栈上,然后用newobj创建一个Regex对象,然后用ldstr把input压入栈,然后用callvrit来调用Regex的Matches方法,把一个IEnumerable对象呀入栈,最后来掉一次IEnumerable的GetEnumerator()方法

大致应该如下
L_0006: ldstr "\\d+|" //pattern
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的代码应该如下
if (expr is Match) {
    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的代码生成        
if (expr is StrLen) {
      StrLen len 
= (StrLen)expr;
      GenExpr(len.Input, 
typeof(string));
      il.Emit(OpCodes.Callvirt, 
typeof(string).GetMethod("get_Length"));
      deliveredType 
= typeof(int);

}

生成语句

表达式的生成大概就是这样了,没有比这再复杂的了,再复杂也不会了,有了上面的基础,我们看一个生成foreach语句的吧。
逻辑上应该是这样,foreach语句应该翻译成一个while语句如下的WawaSharp语句
for item in arr do
    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起来使用。


if (stmt is Foreach) {
        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语句的生成就不说了,看代码吧。

检查结果

完了看下,我们示例代码中生成的IL代码吧
.entrypoint
.maxstack 6
.locals init (
    [
0string str,
    [
1class [mscorlib]System.Collections.IEnumerator enumerator,
    [
2class [mscorlib]System.Text.StringBuilder builder,
    [
3object obj2,
    [
4int32 num,
    [
5class [mscorlib]System.Collections.IEnumerator enumerator2,
    [
6object 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#代码如下
string input = "11|222|33|44|55";
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());

执行输出如下
E:\CompilerWriting\bin\Debug>TEST
11

222

33

44

55


1
1
3
3
4
4
5
5


总结

一个小型的语言系统WawaSharp已经实现了,它可以做简单的字符串处理,你可以在此基础上扩展更全的字符串处理函数,甚至xml处理函数,这样做一些简单的XML和字符串处理小工具,用这种语言就足以应付了,否则还得打开vs.net写c#。据说.net 4.0里的表达式树才只是循环和分支等结构,咱现在都提前实现了。

代码下载地址如下

wawasharp.zip 

posted @ 2009-11-17 00:24  蛙蛙王子  Views(2186)  Comments(6Edit  收藏  举报