zoukankan      html  css  js  c++  java
  • 写一个编译器

    本文介绍前一段时间开发的BDD语言iQA的编写以及设计过程,概要介绍词法分析、语法分析以及分析语法树生成代码的过程,由于iQA语言只是一个简单的代码生成工具,所以里面并没有使用到任何的语义分析的过程。

    iQA是开源的,其源码位置在:https://github.com/vowei/iqa

    要编译它,请从antlr的官网下载最新版本,放在src文件夹的lib目录里,然后按照READM.md文件逐步编译即可。

    关于antlr的词法、语法分析过程我在前面的文章里已经写过很多了,请读者参阅文章:
    编译器的词法分析简介:http://www.cnblogs.com/vowei/archive/2012/08/27/2658375.html

    编译器的语法分析简介:http://www.cnblogs.com/vowei/archive/2012/09/03/2668316.html

    编译器的语义分析简介:http://www.cnblogs.com/vowei/archive/2012/09/24/2700243.html

    编译器的语法错误处理简介:http://www.cnblogs.com/vowei/archive/2012/09/28/2707451.html

    对于iQA来说,词法分析方面还需要有亮点要说,与纯解析内存字符串的iquery不同,iQA需要读取源文件,而iQA本身是支持中文等国际化语言的,因此需要考虑编码的问题,特别是Unicode文件里的BOM字符(http://en.wikipedia.org/wiki/Byte_order_mark) - 简单来说,就是在Unicode文件里,会有一个特殊的字节表示文件的字节顺序,有的文件里会有这个字节,而有的文件却不一定有它,因此为了解决这个问题,在词法文件iQALexer.g里,我添加了一个符号BOM:

    1
    2
    3
    BOM: '\uFEFF' { _seeBom = true; }
     
        ;

      

    在语法文件iQAParser.g里,通过指定BOM是一个可选符号来适应这个问题。

    1
    2
    3
    prog
        : BOM? feature+
        ;

      

    另外,iQA支持类似python的缩进语法,因此在词法和语法文件里,针对缩进的空格都做了特殊处理,详情请参考:http://www.cnblogs.com/killmyday/archive/2012/08/19/2646719.html

    最后,为了支持中文等unicode变量以及关键字,词法文件iQALexer.g里通过定义ID_START符号来实现这种支持。

    1
    2
    3
    4
    5
    6
    fragment ID_START
    : '_'
    | 'A'.. 'Z'
    | 'a' .. 'z'
    ...
    | '\u02C6' .. '\u02D1'

      

    在iQAParser.g里将语法解析完毕后,其实可以直接在iQAParser.g里直接使用println的方式执行代码生成工作,但这样一来就限制我只能生成一种编程语言,为了实现生成多种编程语言的功能,在iQAParser.g里实际上是生成一个语法树,如:

    1
    2
    3
    feature
        : FEATURE_DEF feature_content? -> ^(FEATURE FEATURE_DEF feature_content?)
        ;

      

    就是在语法树里添加类似下图的节点:

    而iQATree.g就是解析这个语法树,使用StringTemplate来生成代码,如下面就是解析前面feature节点的代码:

    1
    2
    3
    4
    5
    feature
        : ^(FEATURE f=FEATURE_DEF c=feature_content?)
            -> class(name = {removeKeyword($f.getText(), "功能")},
                     methods = {$c.scenarios})
        ;

      

    antlr是通过StringTemplate来生成代码的,如上面的代码使用了class这个StringTemplate生成代码,可以通过替换class的实现方式来生成不同语言的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class(name, methods) ::= <<
    // <name>就是class这个StringTemplate的参数,在生成代码时,使用从语法树传入的值替换它
    public class <name> extends iQATestBase {
    // 此处省略代码 … …
       public <name>() throws Exception {
           super("cc.iqa.studio.demo.MainActivity", "cc.iqa.studio.demo");
       }
    // 此处省略代码 … …
    //
    // methods也是传入StringTemplate的参数,是一个数组;
    // 在生成代码时,由于iQATree.g传入的是$c.scenarios
    // 而$c.scenarios的值是针对iQATree.g的scenario节点生成的代码。
    //
       <methods; separator = "\n">
    // 此处省略代码 … …
    }
    >>

      

    这样一来,可以通过替换StringTemplate的方式来生成不同语言的代码,例如执行命令

    java -cp lib/antlr-3.4-complete.jar:. iQATest ../iqa.test/res/testParseStepBasic.txt cc/iqa/iQAMobileJUnit.stg

    就可以将下面的iQA源码:

    1
    2
    3
    4
    功能: 具有缩进编写方式的功能
          场景: 这是一个缩进后的场景
               * 这是一个步骤
               * 打算不用"*"字符来识别步骤了

      

    生成下面的junit格式代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package cc.iqa.studio.demo.test;
     
    import java.util.*;
    import com.jayway.android.robotium.solo.*;
    import cc.iqa.runtime.android.*;
    import cc.iqa.library.*;
    import cc.iqa.core.*;
    import com.google.gson.*;
     
    public class 具有缩进编写方式的功能 extends iQATestBase {
       private Solo _solo;
       
       private ControlNameResolver _resolver;
     
       public 具有缩进编写方式的功能() throws Exception {
           super("cc.iqa.studio.demo.MainActivity", "cc.iqa.studio.demo");
       }
     
       public void setUp() throws Exception
       {
           ControlNameMap map = new ControlNameMap();
           this._resolver = map.getResolver();
           AutomationContext context = new AutomationContext();
           this._solo = new Solo(this.getInstrumentation(), this.getActivity());
           context.put("solo", this._solo);
           this.getContainer().addComponent(context);
       }
     
       public void tearDown() throws Exception
       {
           this._solo.finishOpenedActivities();
           this.OnScenarioEnd();
       }
     
       public void test这是一个缩进后的场景() throws Exception
       {
           AutomationContext context = this.getContainer().getComponent(AutomationContext.class);
           Hashtable<String, Object> resolver = null;
           Hashtable<String, Object> variables = new Hashtable<String, Object>();
           this.S("这是一个步骤");
           this.S("打算不用\"*\"字符来识别步骤了");
       }
       
       public class ControlNameMap {       
           private ControlNameResolver _resolver;
        
           public ControlNameMap() throws Exception
           {
               Gson gson = new Gson();
               String json = "";
               this._resolver = gson.fromJson(json, ControlNameResolver.class);
           }       
     
           ControlNameResolver getResolver()
           {
               return this._resolver;
           }
       }
    }

      

    而如果换一个StringTemplate实现,如执行命令:
    java -cp lib/antlr-3.4-complete.jar:. iQATest ../iqa.test/res/testParseStepBasic.txt cc/iqa/iQAMobileApple.stg

    则会生成下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #import "lib.js"
     
    var testSuite = function() {
        var map = { /* need add control map here */ };
        var testRunner = new TestRunner(map);
        this.test这是一个缩进后的场景 = function() {
            var scenarioInfo = {
                "title": 这是一个缩进后的场景
            };
            testRunner.ScenarioSetup(scenarioInfo);
     
            testRunner.Step("这是一个步骤");
            testRunner.Step("打算不用\"*\"字符来识别步骤了");
            testRunner.ScenarioCleanup();
        }
     
        this.test缩进后的第二个场景 = function() {
            var scenarioInfo = {
                "title": 缩进后的第二个场景
            };
            testRunner.ScenarioSetup(scenarioInfo);
            testRunner.ScenarioCleanup();
        }
    }

      

     
    标签: Open SourceGPL
  • 相关阅读:
    建设是为“有” 共享是为“无”
    设计模式-命令模式
    设计模式-建造者模式
    设计模式-抽象工厂模式(升级版工厂方法模式)
    设计模式-原型模式
    设计模式-单例模式
    Java中的多维数组
    设计模式-装饰者
    设计模式-模板方法
    乐观锁与悲观锁
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2839641.html
Copyright © 2011-2022 走看看