zoukankan      html  css  js  c++  java
  • 蛙蛙推荐:蛙蛙教你发明一种新语言之二代码生成

    摘要

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

  • 相关阅读:
    hbase过滤器(1)
    公司jar包提交到集群的方法
    hbase Hfile处理原因
    oracle pl/sql远程连接过程
    mapreduce join操作
    HTML不熟悉方法总结
    Ajax详解
    getElementById和querySelector区别
    Session
    ES6模块化
  • 原文地址:https://www.cnblogs.com/onlytiancai/p/wawasharp2.html
Copyright © 2011-2022 走看看