写一个编译器

本文介绍前一段时间开发的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

你可能感兴趣的:(source,open,GPL)