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

摘要

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值