* 官網手冊比較精簡,建議買書。 * [[https://github.com/antlr/antlr4/blob/master/doc/index.md|ANTLR 4 Documentation]] * [[https://www.amazon.com/Definitive-ANTLR-4-Reference/dp/1934356999|The Definitive ANTLR 4 Reference]] * [[http://www.antlr.org/api/Java/index.html|ANTLR 4 Runtime 4.7 API]] * [[http://www.antlr.org/|ANTLR]] * ANTLR 除了在語法嵌入 action 之外,提供以下兩種方式。可以先使用,熟悉之後再回來看 design pattern。 * [[wp>Visitor pattern]] * [[wp>Observer pattern]] ====== 環境設定 ====== * [[https://github.com/antlr/antlr4/blob/master/doc/getting-started.md|Getting Started with ANTLR v4]] - 下載 Jar 檔,設定環境。 $ cd /usr/local/lib $ curl -O http://www.antlr.org/download/antlr-4.7-complete.jar $ export CLASSPATH=".:/usr/local/lib/antlr-4.7-complete.jar:$CLASSPATH" $ alias antlr4='java -jar /usr/local/lib/antlr-4.7-complete.jar' $ alias grun='java org.antlr.v4.gui.TestRig' - 下載範例。 # cd; mkdir antlr; cd antlr # curl -O http://media.pragprog.com/titles/tpantlr2/code/tpantlr2-code.tgz # tar xvf tpantlr2-code.tgz # cd code/install $ git clone https://github.com/azru0512/antlr.git $ cd install $ antlr4 Hello.g4 $ javac *.java # grun grammer_name start_rule $ grun Hello r -tokens $ grun Hello r -gui ====== Part I ====== * Ch3. A Starter ANTLR Project * 調用 ANTLR 產生的 parser 和 lexer 方式如下: // import ANTLR's runtime libraries import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.tree.*; public class Test { public static void main(String[] args) throws Exception { // create a CharStream that reads from standard input ANTLRInputStream input = new ANTLRInputStream(System.in); // create a lexer that feeds off of input CharStream ArrayInitLexer lexer = new ArrayInitLexer(input); // create a buffer of tokens pulled from the lexer CommonTokenStream tokens = new CommonTokenStream(lexer); // create a parser that feeds off the tokens buffer ArrayInitParser parser = new ArrayInitParser(tokens); ParseTree tree = parser.init(); // begin parsing at init rule // 處理 parse 得到的 AST,tree。 } } * 一般繼承 ANTLR 生成的 Listener 基類,覆寫自己所需要的 callback。 /** Convert short array inits like {1,2,3} to "\u0001\u0002\u0003" */ public class ShortToUnicodeString extends ArrayInitBaseListener { /** Translate { to " */ @Override public void enterInit(ArrayInitParser.InitContext ctx) { System.out.print('"'); } /** Translate } to " */ @Override public void exitInit(ArrayInitParser.InitContext ctx) { System.out.print('"'); } /** Translate integers to 4-digit hexadecimal strings prefixed with \\u */ @Override public void enterValue(ArrayInitParser.ValueContext ctx) { // Assumes no nested array initializers int value = Integer.valueOf(ctx.INT().getText()); System.out.printf("\\u%04x", value); } } * 當 ANTLR 的 AST walker 遍歷節點時 (enter 和 exit),會調用相應的 callback,同時傳入跟該節點相關的資訊 (ctx)。我們可以透過 ctx 取得該節點的值,或是該節點的子節點 ... 等等。 * 遍歷 AST 並調用 Listener 的代碼基本如下: // Create a generic parse tree walker that can trigger callbacks ParseTreeWalker walker = new ParseTreeWalker(); // Walk the tree created during the parse, trigger callbacks walker.walk(new ShortToUnicodeString(), tree); * Ch4. A Quick Tour * 基本範例。 $ antlr4 Expr.g4 $ javac Expr*.java $ grun Expr prog -gui t.expr * ''grammer'' 開頭。語法規則小寫字母開頭,語彙規則大寫字母開頭。語法和語彙規則可以寫在同一個檔案,或是寫在不同檔案,透過 ''import''  載入。 * Visitor $ antlr4 -no-listener -visitor LabeledExpr.g4 $ javac Calc.java LabeledExpr*.java $ java Calc t.expr * Expr.g4 需要稍做修改以得到 LabeledExpr.g4。 * 繼承 ANTLR 生成的 Visitor 基類,其中的型別參數代表每條規則的返回型別。使用方法如下: ParseTree tree = parser.prog(); // parse EvalVisitor eval = new EvalVisitor(); eval.visit(tree); * Listener $ antlr4 Java.g4 $ javac Java*.java Extract*.java $ java ExtractInterfaceTool Demo.java * 繼承 ANTLR 生成的 Listener 基類。使用方法如下: ParseTree tree = parser.compilationUnit(); // parse ParseTreeWalker walker = new ParseTreeWalker(); // create standard walker ExtractInterfaceListener extractor = new ExtractInterfaceListener(parser); walker.walk(extractor, tree); // initiate walk of tree with listener * 修改 ExtractInterfaceListener.java 加入以下代碼,可以印出 Demo.java 的 ''import'' 語句。 @Override public void enterImportDeclaration(JavaParser.ImportDeclarationContext ctx) { System.out.println("import "+parser.getTokenStream().getText(ctx)); } * Embed Action $ antlr4 -no-listener Rows.g4 $ javac Rows*.java Col.java $ java Col 1 < t.rows * [[https://github.com/antlr/antlr4/blob/master/doc/predicates.md|Semantic Predicates]] * 根據 parse 的內容決定下一步動作。 * Lexical * Island Grammars * 欲剖析的目標語言,其語彙規則可能有多組。比如: 程式和註解的規則不同。和 flex 的 [[http://westes.github.io/flex/manual/Start-Conditions.html#Start-Conditions|Start Conditions]] 一樣功能。 * TokenStreamRewriter * 可以在 Listener 內透過 TokenStreamRewriter 簡單改寫輸入文本。 * Channel * Token 可以經由不同的 channel 傳給 parser。 ====== Part II ====== * Ch5. Designing Grammers * [[https://github.com/antlr/grammars-v4|Grammars written for ANTLR v4]] * 語法基本有底下四種 pattern: * Sequence * ''exprList: expr (',' expr)* ;'' * Choice * ''type: 'float' | 'int' | 'void' ;'' * Token dependence * ''vector: '[' INT+ ']' ;'' * Nested phrase * ''expr: expr '+' expr ;'' * 運算子優先級和結合律。優先級由上而下,由高到低。Bison 使用 precedence declaration ([[https://www.gnu.org/software/bison/manual/bison.html#Precedence-Decl|3.7.3 Operator Precedence]]) 指定優先級和結合律。 * 語彙規則基本常用如下。ANTLR 的 fragment 和 Flex 的 name definition ([[http://westes.github.io/flex/manual/Definitions-Section.html#Definitions-Section|5.1 Format of the Definitions Section]]) 相同。 * Identifier * ''ID: [a-zA-Z]+ ;'' * Number * ''INT: [0-9]+' ;'' * String Literal: ''.*'' 匹配任意字元,但是這樣會連後面的 ''"'' 都被匹配。''.*?'' 是 nongreedy 規則,匹配任意字元直到 ''"''。 * ''STRING: '"' .*? '"' ;'' * Comment and White Space: ''skip'' 跟 Flex 的 empty action ([[http://westes.github.io/flex/manual/Actions.html#Actions|8 Actions]]) 相同。 * ''WS: [ \t\r\n]+ -> skip ;'' * 何者要放在語彙規則,或是語法規則? * parsing 不需要的東西,放在語彙規則,匹配並捨棄。如空白和註解。 * 常用的 token 放在語彙規則。如 identifier。 * parsing 不需要加以區分的,放在語彙規則。如: ''NUMBER: INT | FLOAT''。如果 parsing 不區分 int 和 float,統一交給語彙規則處理。 * Ch6. Exploring Some Real Grammars * comma-seperated value $ cd code/example $ antlr4 CSV.g4 $ javac CSV.*.java $ grun CSV file -tokens data.csv * nested elements $ antlr4 JSON.g4 $ javac JSON*.java $ grun JSON json -tokens [1, "\u0049", 1.3e9] * Ch7. Decoupling Grammars from Application-Specific Code * 最基本將 grammar 和 action 拆分的方式,是將 action 代碼包裝成函式,原本 action 代碼改成函式調用。對於 Bison 來說,一般做法是建立一個 Compiler 類,包含實現 action 的 method。透過 ''%parse-param {Compiler* compiler}'' 指示 ''yyparse'' 將會接受 Compiler 指針。爾後透過該 Compiler 指針調用 method 執行 action。Compiler 類除了 method 之外,所有編譯過程中所需要紀錄的資訊都可以當作 member 加以保存。 * Listener * 交由 ANTLR 遍歷 AST,再調用自定義的 listener。 ParseTree tree = parser.file(); // create a standard ANTLR parse tree walker ParseTreeWalker walker = new ParseTreeWalker(); // create listener then feed to walker PropertyFileLoader loader = new PropertyFileLoader(); walker.walk(loader, tree); // walk parse tree * $ cd code/listeners $ antlr4 LExpr.g4 $ javac LExpr*.java TestLEvaluator.java $ java TestLEvaluator 1+2*3 * 如果 listener 需要返回值,必須透過 stack 模擬。 * Visitor * 如果需要自己遍歷 AST (比如略過部分 AST 節點),採用 Visitor 模式。 ParseTree tree = parser.file(); PropertyFileVisitor loader = new PropertyFileVisitor(); loader.visit(tree); * $ cd code/listeners $ antlr4 -visitor LExpr.g4 $ javac LExpr*.java TestLEvalVisitor.java $ java TestLEvalVisitor 1+2*3 * Annotated Parse Tree * 將值存在 AST 節點。 * Ch8. Building Some Real Language Applications * Loading CSV Data $ cd code/listeners $ antlr4 CSV.g4 $ javac CSV*.java LoadCSV.java $ java LoadCSV t.csv * 想好要在哪個地方,以哪種形式保存自己所需的資料。 * Translating JSON to XML $ antlr4 JSON.g4 $ javac JSON*.java $ java JSON2XML t.json * ''ParseTreeProperty'' 每個節點可以儲存資料 T。 * Generating a Call Graph $ antlr4 Cymbol.g4 $ javac Cymbol*.java CallGraph.java $ java CallGraph t.cymbol * Validating Program Symbol Usage $ antlr4 Cymbol.g4 $ javac Cymbol*.java CheckSymbols.java *Phase.java *Scope.java *Symbol.java $ java CheckSymbols vars.cymbol * 使用 symbol table。第一次 walk tree 定義 (define) symbol,第二次 walk tree 解析 (resolve) symbol。 ParseTreeWalker walker = new ParseTreeWalker(); DefPhase def = new DefPhase(); walker.walk(def, tree); // create next phase and feed symbol table info from def to ref phase RefPhase ref = new RefPhase(def.globals, def.scopes); walker.walk(ref, tree); * 一般會記錄 global symbol table 和當前的 symbol table。 * 某些 symbol 同時也是 symbol table (scope)。例如: function 本身是 symbol,同時也是一個 scope,包含其後的參數列表。 * symbol table 和 ast node 會互指。例如: function ast node 會指向其對應的 function symbol table。 * symbol table 會指向上層的 symbol table。 ====== Part III ====== * Ch9. Error Reporting and Recovery * A Parade of Errors $ antlr4 Simple.g4 $ javac Simple*.java $ grun Simple prog class T { int i; } * 參考 [[https://pragprog.com/titles/tpantlr2/errata|Errata for The Definitive ANTLR 4 Reference]] 修正 Simple.g4。 * single-token deletion 和 insertion 是 Parser 忽略錯誤的 token 或是插入預期的 token,做簡單的 error recovery。 * Altering and Redirecting ANTLR Error Messages $ javac TestE_Listener.java TestE_Listener.java:10: error: cannot find symbol import org.antlr.v4.runtime.Nullable; ^ $ java TestE_Listener class T T { int i; } * 繼承 ''BaseErrorListener'',override ''syntaxError''。 * Automatic Error Recovery Strategy * 策略和 Bison ([[https://www.gnu.org/software/bison/manual/html_node/Error-Recovery.html|6 Error Recovery]]) 基本一致。尋找適當的同步點 (synchronization point)。 * [[http://www.antlr.org/api/Java/org/antlr/v4/runtime/DefaultErrorStrategy.html|DefaultErrorStrategy]]