好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

写一个编译器

写一个编译器

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

?

BOM : '\uFEFF'   { _seeBom = true ; }

 

     ;

  

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

?

prog

     : BOM ? feature+

     ;

  

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

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

?

fragment ID_START

: '_'

| 'A' .. 'Z'

| 'a'   .. 'z'

...

| '\u02C6'   .. '\u02D1'

  

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

?

feature

     : FEATURE_DEF   feature_content? -> ^( FEATURE   FEATURE_DEF   feature_content?)

     ;

  

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

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

?

feature

     : ^( FEATURE   f= FEATURE_DEF   c=feature_content?)

         -> class (name = {removeKeyword( $f .getText(), "功能" )},

                  methods = { $c .scenarios})

     ;

  

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

?

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源码:

?

功能: 具有缩进编写方式的功能

       场景: 这是一个缩进后的场景

            *   这是一个步骤

            *   打算不用 "*" 字符来识别步骤了

  

生成下面的junit格式代码:

?

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

则会生成下面的代码:

?

#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 Source ,  GPL

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于写一个编译器的详细内容...

  阅读:40次