寫一個編譯器


本文介紹前一段時間開發的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();
    }
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM