写一个编译器
本文介紹前一段時間開發的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();} }
轉載于:https://www.cnblogs.com/vowei/archive/2012/12/29/2839286.html
總結
- 上一篇: erl的启动参数分析
- 下一篇: ASP.NET-第一天-HTML基础