前面的文章裏,我們已經知道Antlr可以解析簡單的程式碼,並生成文法樹。
它的流程是
LEXER 與PARSER,
LEXER把TOKEN一段一段切出來,而與PARSER是把句子的文法樹構建出來。
那如果真的要把計算式的值算出來,要怎麼做呢?
在文法檔(.g4)裏,有一個LABEL(視別標籤)的功能,它可以讓設計者把每個句子,每個關鍵字視別出來,
在生成CODE時,產生方法,示範如下:
grammar LabeledExpr;
import CommonLexerRules;
prog : stat+ ;
stat : expr NEWLINE # printExpr
| ID '=' expr NEWLINE # assign
| NEWLINE # blank
;
expr : expr op=('*'|'/') expr # Muldiv
| expr op=('+'|'-') expr # AddSub
| INT # int
| ID # id
| '(' expr ')' # parens
;
MUL : '*';
DIV : '/';
ADD : '+';
SUB : '-';
在文法檔裏,# printExpr ,# assign,# blank,# Muldiv,# AddSub,
猛一看以為是註解,它就是所謂的LABEL(視別標籤),這是Antlr的一個特別功能,
它在產生程式碼時,會把這些標籤納入程式碼中,
配合Antlr的visitor設計模式,就可以去visit(尋訪)被解析的程式中的內容(context),
例如:
將上述內容存成LabeledExpr.g4這個文法檔。
然後,
$ antlr4 -no-listener -visitor LabeledExpr.g4
Antlr會幫你產生尋訪被解析程式的代碼,
打開LabeledExprVistior.java, 裏面有,
public interface LabeledExprVisitor<T> extends ParseTreeVisitor<T> {
/**
* Visit a parse tree produced by {@link LabeledExprParser#prog}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitProg(LabeledExprParser.ProgContext ctx);
/**
* Visit a parse tree produced by the {@code printExpr}
* labeled alternative in {@link LabeledExprParser#stat}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitPrintExpr(LabeledExprParser.PrintExprContext ctx);
/**
* Visit a parse tree produced by the {@code assign}
* labeled alternative in {@link LabeledExprParser#stat}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitAssign(LabeledExprParser.AssignContext ctx);
/**
* Visit a parse tree produced by the {@code blank}
* labeled alternative in {@link LabeledExprParser#stat}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitBlank(LabeledExprParser.BlankContext ctx);
/**
* Visit a parse tree produced by the {@code parens}
* labeled alternative in {@link LabeledExprParser#expr}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitParens(LabeledExprParser.ParensContext ctx);
/**
* Visit a parse tree produced by the {@code Muldiv}
* labeled alternative in {@link LabeledExprParser#expr}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitMuldiv(LabeledExprParser.MuldivContext ctx);
/**
* Visit a parse tree produced by the {@code AddSub}
* labeled alternative in {@link LabeledExprParser#expr}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitAddSub(LabeledExprParser.AddSubContext ctx);
/**
* Visit a parse tree produced by the {@code id}
* labeled alternative in {@link LabeledExprParser#expr}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitId(LabeledExprParser.IdContext ctx);
/**
* Visit a parse tree produced by the {@code int}
* labeled alternative in {@link LabeledExprParser#expr}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitInt(LabeledExprParser.IntContext ctx);
}
文法檔裏的標籤(如#int )就會產生visitInt的方法。#id 產生visitId。它的規則是visit標籤,
因為是計算機的用途,目前是用於計算整數,我們就實作這個interface,把integer 代入型別T.
public class EvalVisitor extends LabeledExprBaseVisitor<Integer>{
Map<String,Integer>memory = new HashMap<String,Integer>();
..........
@Override
public Integer visitMuldiv(LabeledExprParser.MuldivContext ctx){
//LabeledExprParser.MuldivContext ctx
int left = visit(ctx.expr(0));
int right = visit(ctx.expr(1));
if (ctx.op.getType()== LabeledExprParser.MUL) return left*right;
return left/right;
}
這裏示範乘法及除法 運算式時,要求出值的做法。
對照文法檔(.g4)裏的
對照的文法。expr : expr op=('*'|'/') expr # Muldiv
expr在程式裏出現,左邊是expr(0),右邊是expr(1),op 也出現了,而MUL,出現在MUL : '*';
Antlr 這套程式語言產生器,會根據你的文法,
一一產生想對應的方法(visitXXXX),句子內容(ctx),內容裏的組成元素( expr,op),
簡化你寫對應的程式。
小結: 本次內容,描述Antlr的 visitor設計模式,用以尋訪每一句子,及句中的元素,方便使用寫出對應的程式碼。