一个简单的语言的语法(二):ANTLR的重写规则
生活随笔
收集整理的這篇文章主要介紹了
一个简单的语言的语法(二):ANTLR的重写规则
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
們使用ANTLR來(lái)描述了Jerry語(yǔ)言的基本語(yǔ)法,并通過(guò)ANTLRWorks來(lái)實(shí)驗(yàn)該語(yǔ)法對(duì)樣本代碼生成的解析樹(shù)。但如同上一篇最后所述,這樣得到的解析樹(shù)中有太多對(duì)后續(xù)處理來(lái)說(shuō)無(wú)用的冗余信息。我們需要消除這些冗余信息,得到抽象語(yǔ)法樹(shù)(AST)。
本篇將以之前做的語(yǔ)法為基礎(chǔ),通過(guò)添加樹(shù)重寫(xiě)規(guī)則來(lái)將ANTLR默認(rèn)生成的解析樹(shù)簡(jiǎn)化整理為抽象語(yǔ)法樹(shù)。
本文涉及的源碼和運(yùn)行時(shí)庫(kù)打包在附件里了,懶得復(fù)制粘貼的話就直接下載附件的版本,用ANTLRWorks來(lái)查看和編輯語(yǔ)法文件吧~
修改后的語(yǔ)法文件如下:
Jerry.g(ANTLR 3.1語(yǔ)法文件,以Java為生成目標(biāo)語(yǔ)言)
Java代碼 ?grammar?Jerry;?? ?? options?{?? ????language?=?Java;?? ????output?=?AST;?? ????ASTLabelType?=?CommonTree;?? }?? ?? tokens?{?? ????//?imaginary?tokens?? ????VAR_DECL;?? ????SIMPLE_TYPE;?? ????ARRAY_TYPE;?? ????ARRAY_LITERAL;?? ????SIMPLE_VAR_ACCESS;?? ????ARRAY_VAR_ACCESS;?? ????UNARY_MINUS;?? ????BLOCK;?? ????EXPR_STMT;?? }?? ?? //?parser?rules?? ?? program?:???statementList?EOF!?? ????????{?? ????????????System.out.println(?? ????????????????null?==?$statementList.tree???? ????????????????"null"?:?? ????????????????$statementList.tree.toStringTree());?? ????????}?? ????;?? ?? statementList?? ????:???statement*?? ????;?? ?? statement?? ????:???expressionStatement?? ????|???variableDeclaration?? ????|???blockStatement?? ????|???ifStatement?? ????|???whileStatement?? ????|???breakStatement?? ????|???readStatement?? ????|???writeStatement?? ????;?? ?? expressionStatement?? ????:???expression?SEMICOLON?? ????????????->?^(?EXPR_STMT?expression?)?? ????;?? ?? variableDeclaration?? ????:???typeSpecifier?? ????????????(?Identifier?? ????????????????(???->?^(?VAR_DECL?^(?SIMPLE_TYPE?typeSpecifier?)?Identifier?)?? ????????????????|?(?LBRACK?Integer?RBRACK?)+?? ????????????????????->?^(?VAR_DECL?^(?ARRAY_TYPE?typeSpecifier?Integer+?)?Identifier?)?? ????????????????|?EQ?expression?? ????????????????????->?^(?VAR_DECL?^(?SIMPLE_TYPE?typeSpecifier?)?Identifier?expression?)?? ????????????????|?(?LBRACK?Integer?RBRACK?)+?EQ?arrayLiteral?? ????????????????????->?^(?VAR_DECL?^(?ARRAY_TYPE?typeSpecifier?Integer+?)?Identifier?arrayLiteral?)?? ????????????????)?? ????????????)?? ????????????(?COMMA?id=Identifier?? ????????????????(???->?$variableDeclaration?^(?VAR_DECL?^(?SIMPLE_TYPE?typeSpecifier?)?$id?)?? ????????????????|?(?LBRACK?dim1+=Integer?RBRACK?)+?? ????????????????????->?$variableDeclaration?^(?VAR_DECL?^(?ARRAY_TYPE?typeSpecifier?$dim1+?)?$id?)?? ????????????????|?EQ?exp=expression?? ????????????????????->?$variableDeclaration?^(?VAR_DECL?^(?SIMPLE_TYPE?typeSpecifier?)?$id?$exp?)?? ????????????????|?(?LBRACK?dim2+=Integer?RBRACK?)+?EQ?al=arrayLiteral?? ????????????????????->?$variableDeclaration?^(?VAR_DECL?^(?ARRAY_TYPE?typeSpecifier?$dim2+?)?$id?$al?)?? ????????????????)?? ????????????????{?if?(null?!=?$dim1)?$dim1.clear();?if?(null?!=?$dim2)?$dim2.clear();?}?? ????????????)*?? ????????SEMICOLON?? ????;?? ?? typeSpecifier?? ????:???INT?|?REAL?? ????;?? ?? arrayLiteral?? ????:???LBRACE?? ????????????arrayLiteralElement?(?COMMA?arrayLiteralElement?)*?? ????????RBRACE?? ????????????->?^(?ARRAY_LITERAL?arrayLiteralElement+?)?? ????;?? ?? arrayLiteralElement?? ????:???expression?? ????|???arrayLiteral?? ????;?? ?? blockStatement?? ????:???LBRACE?statementList?RBRACE?? ????????????->?^(?BLOCK?statementList?)?? ????;?? ?? ifStatement?? ????:???IF^?LPAREN!?expression?RPAREN!?statement?(?ELSE!?statement?)??? ????;?? ?? whileStatement?? ????:???WHILE^?LPAREN!?expression?RPAREN!?statement?? ????;?? ?? breakStatement?? ????:???BREAK?SEMICOLON!?? ????;?? ?? readStatement?? ????:???READ^?variableAccess?SEMICOLON!?? ????;?? ?? writeStatement?? ????:???WRITE^?expression?SEMICOLON!?? ????;?? ?? variableAccess?? ????:???Identifier?? ????????(???->?^(?SIMPLE_VAR_ACCESS?Identifier?)?? ????????|?(?LBRACK?Integer?RBRACK?)+?? ????????????->?^(?ARRAY_VAR_ACCESS?Identifier?Integer+?)?? ????????)?? ????;?? ?? expression?? ????:???assignmentExpression?? ????|???logicalOrExpression?? ????;?? ?? assignmentExpression?? ????:???variableAccess?EQ^?expression?? ????;?? ?? logicalOrExpression?? ????:???logicalAndExpression?(?OROR^?logicalAndExpression?)*?? ????;?? ?? logicalAndExpression?? ????:???relationalExpression?(?ANDAND^?relationalExpression?)*?? ????;?? ?? relationalExpression?? ????:???additiveExpression?(?relationalOperator^?additiveExpression?)??? ????|???BANG^?relationalExpression?? ????;?? ?? additiveExpression?? ????:???multiplicativeExpression?(?additiveOperator^?multiplicativeExpression?)*?? ????;?? ???? multiplicativeExpression?? ????:???primaryExpression?(?multiplicativeOperator^?primaryExpression?)*?? ????;?? ?? primaryExpression?? ????:???variableAccess?? ????|???Integer?? ????|???RealNumber?? ????|???LPAREN!?expression?RPAREN!?? ????|???MINUS?primaryExpression?? ????????????->?^(?UNARY_MINUS?primaryExpression?)?? ????;?? ?? relationalOperator????? ????:???LT?|?GT?|?EQEQ?|?LE?|?GE?|?NE?? ????;?? ?? additiveOperator?? ????:???PLUS?|?MINUS?? ????;?? ?? multiplicativeOperator?? ????:???MUL?|?DIV?? ????;?? ?? //?lexer?rules?? ?? LPAREN??:???'('?? ????;?? ?? RPAREN??:???')'?? ????;?? ?? LBRACK??:???'['?? ????;?? ?? RBRACK??:???']'?? ????;?? ?? LBRACE??:???'{'?? ????;?? ?? RBRACE??:???'}'?? ????;?? ?? COMMA???:???','?? ????;?? ?? SEMICOLON?? ????:???';'?? ????;?? ?? PLUS????:???'+'?? ????;?? ?? MINUS???:???'-'?? ????;?? ?? MUL?:???'*'?? ????;?? ?? DIV?:???'/'?? ????;?? ?? EQEQ????:???'=='?? ????;?? ?? NE??:???'!='?? ????;?? ?? LT??:???'<'?? ????;?? ?? LE??:???'<='?? ????;?? ?? GT??:???'>'?? ????;?? ?? GE??:???'>='?? ????;?? ?? BANG????:???'!'?? ????;?? ?? ANDAND??:???'&&'?? ????;?? ?? OROR????:???'||'?? ????;?? ?? EQ??:???'='?? ????;?? ?? IF??:???'if'?? ????;?? ?? ELSE????:???'else'?? ????;?? ?? WHILE???:???'while'?? ????;?? ?? BREAK???:???'break'?? ????;?? ?? READ????:???'read'?? ????;?? ?? WRITE???:???'write'?? ????;?? ?? INT?:???'int'?? ????;?? ?? REAL????:???'real'?? ????;?? ?? Identifier?? ????:???LetterOrUnderscore?(?LetterOrUnderscore?|?Digit?)*?? ????;?? ?? Integer?:???Digit+?? ????;?? ?? RealNumber?? ????:???Digit+?'.'?Digit+?? ????;?? ?? fragment?? Digit???:???'0'..'9'?? ????;?? ?? fragment?? LetterOrUnderscore?? ????:???Letter?|?'_'?? ????;?? ?? fragment?? Letter??:???(?'a'..'z'?|?'A'..'Z'?)?? ????;?? ?? WS??:???(?'?'?|?'\t'?|?'\r'?|?'\n'?)+?{?$channel?=?HIDDEN;?}????? ????;?? ?? Comment?? ????:???'/*'?(?options?{?greedy?=?false;?}?:?.?)*?'*/'?{?$channel?=?HIDDEN;?}?? ????;?? ?? LineComment?? ????:???'//'?~('\n'|'\r')*?'\r'??'\n'?{?$channel?=?HIDDEN;?}?? ????;??
稍微說(shuō)明一下修改點(diǎn)。應(yīng)該觀察到lexer rules部分是完全沒(méi)有改變的,修改的主要是一些選項(xiàng)和parser rules。
首先,在文件的開(kāi)頭添加了一組選項(xiàng):
Java代碼 ?options?{?? ????language?=?Java;?? ????output?=?AST;?? ????ASTLabelType?=?CommonTree;?? }??
ANTLR會(huì)知道應(yīng)該使用生成AST的模式,以CommonTree作為AST的節(jié)點(diǎn)類型,并以Java作為生成的解析器源碼的語(yǔ)言。上一篇是在 ANTLRWorks里編輯和實(shí)驗(yàn)語(yǔ)法的,這次我們需要生成實(shí)際能運(yùn)行的解析器,所以需要指定這些選項(xiàng)(默認(rèn)就是生成Java源碼,不過(guò)后續(xù)文章中我應(yīng)該 會(huì)換用CSharp2目標(biāo)。這個(gè)以后再說(shuō))。
接下來(lái),可以看到除了原本在lexer rules里定義的實(shí)際存在的token類型之外,這次我們?cè)谡Z(yǔ)法文件的開(kāi)頭還增加了一組虛擬的token類型。這些token類型是為了讓生成出來(lái)的抽象語(yǔ)法樹(shù)易于解析而添加的。
例如,觀察VAR_DECL這個(gè)token類型。在原本的語(yǔ)法中,沒(méi)有任何關(guān)鍵字能清楚的標(biāo)識(shí)出當(dāng)前處理的內(nèi)容是一個(gè)變量聲明。為了方便后續(xù)分析,我們可以“制造”出一個(gè)虛構(gòu)的token作為一個(gè)變量聲明語(yǔ)句的根元素,然后以變量的類型、標(biāo)識(shí)符和初始值為子元素。
然后就是最重要的部分,樹(shù)重寫(xiě)規(guī)則了。有兩種形式來(lái)表述樹(shù)重寫(xiě)規(guī)則:一是直接在原本的語(yǔ)法規(guī)則上添加樹(shù)生成用的運(yùn)算符(^和!),二是在原本的語(yǔ)法規(guī)則后添加一個(gè)箭頭("->"),并在箭頭后顯式指定需要生成的節(jié)點(diǎn)的結(jié)構(gòu)。
看兩個(gè)例子:
while語(yǔ)句。原本的語(yǔ)法是:
Java代碼 ?whileStatement?:?'while'?'('?expression?')'?statement?;??
這里我們想讓生成出來(lái)的子樹(shù)以'while'為根節(jié)點(diǎn),以expression和statement為子節(jié)點(diǎn)。
可以直接在該語(yǔ)法上添加樹(shù)生成運(yùn)算符:在某個(gè)元素后加上帽子符號(hào)('^')來(lái)表示它是生成的子樹(shù)的根節(jié)點(diǎn),在某個(gè)元素后加上嘆號(hào)('!')來(lái)表示生成的子樹(shù)中應(yīng)該忽略該元素。于是修改得到的語(yǔ)法是:
Java代碼 ?whileStatement?:?'while'^?'('!?expression?')'!?statement?;??
也可以顯式指定樹(shù)重寫(xiě)規(guī)則。一棵子樹(shù)用這種方式來(lái)表示:
Java代碼 ?^(?root?element1?element2?...?)??
這里我們要的就是:
Java代碼 ?whileStatement?:?'while'?'('?expression?')'?statement?? ????->?^(?'while'?expression?statement?)?? ??;??
這種形式我們能一目了然看到最終生成的子樹(shù)的結(jié)構(gòu)。
兩種形式是等價(jià)的,可以根據(jù)具體情況來(lái)選擇能簡(jiǎn)單而清晰的表示出樹(shù)改寫(xiě)規(guī)則的版本。
對(duì)表達(dá)式相關(guān)的語(yǔ)法規(guī)則,我們幾乎都是用添加運(yùn)算符的形式來(lái)表示樹(shù)改寫(xiě)規(guī)則,因?yàn)閷?duì)左結(jié)合的雙目運(yùn)算符,這樣是最簡(jiǎn)潔的。
ANTLR生成的解析器使用LL(*)算法;與一般的LL解析器一樣,ANTLR不支持左遞歸的語(yǔ)法規(guī)則。這使得書(shū)寫(xiě)左結(jié)合的雙目運(yùn)算符時(shí),一般得寫(xiě)成這樣的形式:
Java代碼 ?exprWithHigherPrecedence?? ??:?exprWithLowerPrecedence?(?op?exprWithLowerPrecedence?)*?? ??;??
而不能以左遞歸來(lái)指定左結(jié)合。(但右結(jié)合還是可以用右遞歸來(lái)指定的。)
那么在表示樹(shù)改寫(xiě)規(guī)則的時(shí)候,使用運(yùn)算符來(lái)修飾語(yǔ)法就是這樣:
Java代碼 ?exprWithHigherPrecedence?? ??:?exprWithLowerPrecedence?(?op^?exprWithLowerPrecedence?)*?? ??;??
只是在op的后面添加了一個(gè)帽子符號(hào)('^'),表明在沒(méi)有匹配到op運(yùn)算符時(shí)就直接返回exprWithLowerPrecedence規(guī)則所 生成的樹(shù);而如果匹配到了op運(yùn)算符,則每匹配到一次就生成一個(gè)新的以op為根節(jié)點(diǎn)的、前后兩個(gè)較低優(yōu)先級(jí)的表達(dá)式節(jié)點(diǎn)為子節(jié)點(diǎn)的樹(shù)。
這個(gè)樹(shù)改寫(xiě)規(guī)則如果要顯式指定,就得寫(xiě)成:
Java代碼 ?exprWithHigherPrecedence?? ??:?exprWithLowerPrecedence?? ??????(?op?exp=exprWithLowerPrecedence?? ??????????->?^(?op?$exprWithHigherPrecedence?$exp?)?? ??????)*?? ??;??
后者相比之下麻煩多了,所以一般都會(huì)使用前者。
可惜C風(fēng)格的變量聲明語(yǔ)句的語(yǔ)法很麻煩,結(jié)果variableDeclaration在修改后膨脹了好多 T T
最不爽的地方就是C風(fēng)格的數(shù)組變量聲明是把數(shù)組的維度寫(xiě)在變量名后面的。這就使得語(yǔ)句開(kāi)頭的類型(例如int、char等)可能只是變量的實(shí)際類型的一部分,而另一部分要在變量名的之前(例如表示指針的星號(hào)('*'))或之后(例如表示數(shù)組的方括號(hào)('[' ']'))。
就不能把整個(gè)類型寫(xiě)在一起么……T T 于是衍生出來(lái)的Java和C#明顯都吸取了這個(gè)教訓(xùn)。
在語(yǔ)法的program規(guī)則中,我們添加了一條嵌入語(yǔ)法動(dòng)作,讓生成的解析器在匹配完program規(guī)則后將其對(duì)應(yīng)的抽象語(yǔ)法樹(shù)以字符串的形式輸出出來(lái)。
如果是在ANTLRWorks里編輯該語(yǔ)法文件,可以在菜單里選擇Generate -> Generate Code來(lái)生成出解析器的源碼。這里例子中我們會(huì)得到JerryLexer.java和JerryParser.java。
要運(yùn)行這個(gè)解析器,還需要寫(xiě)一個(gè)簡(jiǎn)單的啟動(dòng)程序來(lái)調(diào)用生成出來(lái)的JerryLexer和JerryParser。源碼如下:
TestJerry.java
Java代碼 ?import?org.antlr.runtime.*;?? ?? public?class?TestJerry?{?? ????public?static?void?main(String[]?args)?throws?Exception?{?? ????????//?Create?an?input?character?stream?from?standard?in?? ????????ANTLRInputStream?input?=?new?ANTLRInputStream(System.in);?? ????????//?Create?an?JerryLexer?that?feeds?from?that?stream?? ????????JerryLexer?lexer?=?new?JerryLexer(input);?? ????????//?Create?a?stream?of?tokens?fed?by?the?lexer?? ????????CommonTokenStream?tokens?=?new?CommonTokenStream(lexer);?? ????????//?Create?a?parser?that?feeds?off?the?token?stream?? ????????JerryParser?parser?=?new?JerryParser(tokens);?? ????????//?Begin?parsing?at?rule?prog?? ????????parser.program();?? ????}?? }??
它指定從標(biāo)準(zhǔn)輸入流得到要解析的Jerry代碼,然后通過(guò)JerryLexer將代碼解析成token流,再將token流交給JerryParser進(jìn)行句法分析。
將JerryLexer.java、JerryParser.java和TestJerry.java放在跟ANTLRWorks同一目錄下,然后編譯它們:
引用javac -Xlint:unchecked -cp antlrworks-1.2.2.jar JerryLexer.java JerryParser.java TestJerry.java
(因?yàn)锳NTLRWorks里含有ANTLR的運(yùn)行時(shí)庫(kù),而我正好又是用ANTLRWorks來(lái)編輯語(yǔ)法文件的,所以直接用ANTLRWorks 的JAR包放在classpath里來(lái)得到需要的ANTLR運(yùn)行時(shí)類。實(shí)際開(kāi)發(fā)的話可以從ANTLR官網(wǎng)獲得只含有ANTLR運(yùn)行時(shí)庫(kù)的JAR包并在編譯 和運(yùn)行的時(shí)候?qū)⑵涮砑拥絚lasspath里。)
上一篇的最后有這樣的一段Jerry例子:
C代碼 ?//?line?comment?? //?declare?variables?with/without?initializers?? int?i?=?1,?j;?? int?x?=?i?+?2?*?3?-?4?/?(?6?-?-?7?);?? int?array[2][3]?=?{?? ??{?0,?1,?2?},?? ??{?3,?4,?6?}?? };?? ?? /*? ??block?comment? */?? ?? while?(i?<?10)?i?=?i?+?1;?? while?(!x?>?0?&&?i?<?10)?{?? ??x?=?x?-?1;?? ??if?(i?<?5)?break;?? ??else?read?i;?? }?? ?? write?x?-?j;??
(語(yǔ)法是符合要求的,至于代碼的意義就別追究了,只是用來(lái)演示各種語(yǔ)法結(jié)構(gòu)隨便寫(xiě)的)
用本篇的ANTLR語(yǔ)法文件生成的解析器,我們可以解析這個(gè)例子,得到對(duì)應(yīng)的抽象語(yǔ)法樹(shù)的字符串表示。表示方法是:
Java代碼 ?(root?element1?element2?...)??
跟LISP的S-expression非常類似。
于是執(zhí)行測(cè)試程序。將要解析的代碼保存到JerrySample.txt中,然后執(zhí)行下面的命令:
引用java -cp ".;antlrworks-1.2.2.jar" TestJerry < JerrySample.txt
得到輸出:
Java代碼 ?(VAR_DECL?(SIMPLE_TYPE?int)?i?1)?(VAR_DECL?(SIMPLE_TYPE?int)?j)?(VAR_DECL?(SIMPLE_TYPE?int)?x?(-?(+?(SIMPLE_VAR_ACCESS?i)?(*?2?3))?(/?4?(-?6?(UNARY_MINUS?7)))))?(VAR_DECL?(ARRAY_TYPE?int?2?3)?array?(ARRAY_LITERAL?(ARRAY_LITERAL?0?1?2)?(ARRAY_LITERAL?3?4?6)))?(while?(<?(SIMPLE_VAR_ACCESS?i)?10)?(=?(SIMPLE_VAR_ACCESS?i)?(+?(SIMPLE_VAR_ACCESS?i)?1)))?(while?(&&?(!?(>?(SIMPLE_VAR_ACCESS?x)?0))?(<?(SIMPLE_VAR_ACCESS?i)?10))?(BLOCK?(=?(SIMPLE_VAR_ACCESS?x)?(-?(SIMPLE_VAR_ACCESS?x)?1))?(if?(<?(SIMPLE_VAR_ACCESS?i)?5)?break?(read?(SIMPLE_VAR_ACCESS?i)))))?(write?(-?(SIMPLE_VAR_ACCESS?x)?(SIMPLE_VAR_ACCESS?j)))??
這樣太亂了看不清楚。將其格式稍微整理一下得到:
Java代碼 ?(VAR_DECL?? ??(SIMPLE_TYPE?int)?? ??i?? ??1?? )?? (VAR_DECL?? ??(SIMPLE_TYPE?int)?? ??j?? )?? (VAR_DECL?? ??(SIMPLE_TYPE?int)?? ??x?? ??(-?? ????(+?(SIMPLE_VAR_ACCESS?i)?(*?2?3))?? ????(/?4?(-?6?(UNARY_MINUS?7)))?? ??)?? )?? (VAR_DECL?? ??(ARRAY_TYPE?? ????int?? ????2?? ????3?? ??)?? ??array?? ??(ARRAY_LITERAL?? ????(ARRAY_LITERAL?0?1?2)?? ????(ARRAY_LITERAL?3?4?6)?? ??)?? )?? ?? (while?? ??(<?(SIMPLE_VAR_ACCESS?i)?10)?? ??(=?(SIMPLE_VAR_ACCESS?i)?(+?(SIMPLE_VAR_ACCESS?i)?1))?? )?? (while?? ??(&&?? ????(!?(>?(SIMPLE_VAR_ACCESS?x)?0))?? ????(<?(SIMPLE_VAR_ACCESS?i)?10)?? ??)?? ??(BLOCK?? ????(=?(SIMPLE_VAR_ACCESS?x)?(-?(SIMPLE_VAR_ACCESS?x)?1))?? ????(if?? ??????(<?(SIMPLE_VAR_ACCESS?i)?5)?? ??????break?? ??????(read?(SIMPLE_VAR_ACCESS?i))?? ????)?? ??)?? )?? (write?? ??(-?(SIMPLE_VAR_ACCESS?x)?(SIMPLE_VAR_ACCESS?j)))??
可以跟原本的代碼對(duì)比一下,看看是否保持了原本的結(jié)構(gòu)。
得到這棵抽象語(yǔ)法樹(shù)之后,接下來(lái)就可以對(duì)樹(shù)來(lái)做匹配和分析了。由于樹(shù)本身已經(jīng)有了結(jié)構(gòu),下面就可以用更干凈的描述方式來(lái)表述我們要對(duì)樹(shù)做的處理。
本篇將以之前做的語(yǔ)法為基礎(chǔ),通過(guò)添加樹(shù)重寫(xiě)規(guī)則來(lái)將ANTLR默認(rèn)生成的解析樹(shù)簡(jiǎn)化整理為抽象語(yǔ)法樹(shù)。
本文涉及的源碼和運(yùn)行時(shí)庫(kù)打包在附件里了,懶得復(fù)制粘貼的話就直接下載附件的版本,用ANTLRWorks來(lái)查看和編輯語(yǔ)法文件吧~
修改后的語(yǔ)法文件如下:
Jerry.g(ANTLR 3.1語(yǔ)法文件,以Java為生成目標(biāo)語(yǔ)言)
Java代碼 ?
稍微說(shuō)明一下修改點(diǎn)。應(yīng)該觀察到lexer rules部分是完全沒(méi)有改變的,修改的主要是一些選項(xiàng)和parser rules。
首先,在文件的開(kāi)頭添加了一組選項(xiàng):
Java代碼 ?
ANTLR會(huì)知道應(yīng)該使用生成AST的模式,以CommonTree作為AST的節(jié)點(diǎn)類型,并以Java作為生成的解析器源碼的語(yǔ)言。上一篇是在 ANTLRWorks里編輯和實(shí)驗(yàn)語(yǔ)法的,這次我們需要生成實(shí)際能運(yùn)行的解析器,所以需要指定這些選項(xiàng)(默認(rèn)就是生成Java源碼,不過(guò)后續(xù)文章中我應(yīng)該 會(huì)換用CSharp2目標(biāo)。這個(gè)以后再說(shuō))。
接下來(lái),可以看到除了原本在lexer rules里定義的實(shí)際存在的token類型之外,這次我們?cè)谡Z(yǔ)法文件的開(kāi)頭還增加了一組虛擬的token類型。這些token類型是為了讓生成出來(lái)的抽象語(yǔ)法樹(shù)易于解析而添加的。
例如,觀察VAR_DECL這個(gè)token類型。在原本的語(yǔ)法中,沒(méi)有任何關(guān)鍵字能清楚的標(biāo)識(shí)出當(dāng)前處理的內(nèi)容是一個(gè)變量聲明。為了方便后續(xù)分析,我們可以“制造”出一個(gè)虛構(gòu)的token作為一個(gè)變量聲明語(yǔ)句的根元素,然后以變量的類型、標(biāo)識(shí)符和初始值為子元素。
然后就是最重要的部分,樹(shù)重寫(xiě)規(guī)則了。有兩種形式來(lái)表述樹(shù)重寫(xiě)規(guī)則:一是直接在原本的語(yǔ)法規(guī)則上添加樹(shù)生成用的運(yùn)算符(^和!),二是在原本的語(yǔ)法規(guī)則后添加一個(gè)箭頭("->"),并在箭頭后顯式指定需要生成的節(jié)點(diǎn)的結(jié)構(gòu)。
看兩個(gè)例子:
while語(yǔ)句。原本的語(yǔ)法是:
Java代碼 ?
這里我們想讓生成出來(lái)的子樹(shù)以'while'為根節(jié)點(diǎn),以expression和statement為子節(jié)點(diǎn)。
可以直接在該語(yǔ)法上添加樹(shù)生成運(yùn)算符:在某個(gè)元素后加上帽子符號(hào)('^')來(lái)表示它是生成的子樹(shù)的根節(jié)點(diǎn),在某個(gè)元素后加上嘆號(hào)('!')來(lái)表示生成的子樹(shù)中應(yīng)該忽略該元素。于是修改得到的語(yǔ)法是:
Java代碼 ?
也可以顯式指定樹(shù)重寫(xiě)規(guī)則。一棵子樹(shù)用這種方式來(lái)表示:
Java代碼 ?
這里我們要的就是:
Java代碼 ?
這種形式我們能一目了然看到最終生成的子樹(shù)的結(jié)構(gòu)。
兩種形式是等價(jià)的,可以根據(jù)具體情況來(lái)選擇能簡(jiǎn)單而清晰的表示出樹(shù)改寫(xiě)規(guī)則的版本。
對(duì)表達(dá)式相關(guān)的語(yǔ)法規(guī)則,我們幾乎都是用添加運(yùn)算符的形式來(lái)表示樹(shù)改寫(xiě)規(guī)則,因?yàn)閷?duì)左結(jié)合的雙目運(yùn)算符,這樣是最簡(jiǎn)潔的。
ANTLR生成的解析器使用LL(*)算法;與一般的LL解析器一樣,ANTLR不支持左遞歸的語(yǔ)法規(guī)則。這使得書(shū)寫(xiě)左結(jié)合的雙目運(yùn)算符時(shí),一般得寫(xiě)成這樣的形式:
Java代碼 ?
而不能以左遞歸來(lái)指定左結(jié)合。(但右結(jié)合還是可以用右遞歸來(lái)指定的。)
那么在表示樹(shù)改寫(xiě)規(guī)則的時(shí)候,使用運(yùn)算符來(lái)修飾語(yǔ)法就是這樣:
Java代碼 ?
只是在op的后面添加了一個(gè)帽子符號(hào)('^'),表明在沒(méi)有匹配到op運(yùn)算符時(shí)就直接返回exprWithLowerPrecedence規(guī)則所 生成的樹(shù);而如果匹配到了op運(yùn)算符,則每匹配到一次就生成一個(gè)新的以op為根節(jié)點(diǎn)的、前后兩個(gè)較低優(yōu)先級(jí)的表達(dá)式節(jié)點(diǎn)為子節(jié)點(diǎn)的樹(shù)。
這個(gè)樹(shù)改寫(xiě)規(guī)則如果要顯式指定,就得寫(xiě)成:
Java代碼 ?
后者相比之下麻煩多了,所以一般都會(huì)使用前者。
可惜C風(fēng)格的變量聲明語(yǔ)句的語(yǔ)法很麻煩,結(jié)果variableDeclaration在修改后膨脹了好多 T T
最不爽的地方就是C風(fēng)格的數(shù)組變量聲明是把數(shù)組的維度寫(xiě)在變量名后面的。這就使得語(yǔ)句開(kāi)頭的類型(例如int、char等)可能只是變量的實(shí)際類型的一部分,而另一部分要在變量名的之前(例如表示指針的星號(hào)('*'))或之后(例如表示數(shù)組的方括號(hào)('[' ']'))。
就不能把整個(gè)類型寫(xiě)在一起么……T T 于是衍生出來(lái)的Java和C#明顯都吸取了這個(gè)教訓(xùn)。
在語(yǔ)法的program規(guī)則中,我們添加了一條嵌入語(yǔ)法動(dòng)作,讓生成的解析器在匹配完program規(guī)則后將其對(duì)應(yīng)的抽象語(yǔ)法樹(shù)以字符串的形式輸出出來(lái)。
如果是在ANTLRWorks里編輯該語(yǔ)法文件,可以在菜單里選擇Generate -> Generate Code來(lái)生成出解析器的源碼。這里例子中我們會(huì)得到JerryLexer.java和JerryParser.java。
要運(yùn)行這個(gè)解析器,還需要寫(xiě)一個(gè)簡(jiǎn)單的啟動(dòng)程序來(lái)調(diào)用生成出來(lái)的JerryLexer和JerryParser。源碼如下:
TestJerry.java
Java代碼 ?
它指定從標(biāo)準(zhǔn)輸入流得到要解析的Jerry代碼,然后通過(guò)JerryLexer將代碼解析成token流,再將token流交給JerryParser進(jìn)行句法分析。
將JerryLexer.java、JerryParser.java和TestJerry.java放在跟ANTLRWorks同一目錄下,然后編譯它們:
引用javac -Xlint:unchecked -cp antlrworks-1.2.2.jar JerryLexer.java JerryParser.java TestJerry.java
(因?yàn)锳NTLRWorks里含有ANTLR的運(yùn)行時(shí)庫(kù),而我正好又是用ANTLRWorks來(lái)編輯語(yǔ)法文件的,所以直接用ANTLRWorks 的JAR包放在classpath里來(lái)得到需要的ANTLR運(yùn)行時(shí)類。實(shí)際開(kāi)發(fā)的話可以從ANTLR官網(wǎng)獲得只含有ANTLR運(yùn)行時(shí)庫(kù)的JAR包并在編譯 和運(yùn)行的時(shí)候?qū)⑵涮砑拥絚lasspath里。)
上一篇的最后有這樣的一段Jerry例子:
C代碼 ?
(語(yǔ)法是符合要求的,至于代碼的意義就別追究了,只是用來(lái)演示各種語(yǔ)法結(jié)構(gòu)隨便寫(xiě)的)
用本篇的ANTLR語(yǔ)法文件生成的解析器,我們可以解析這個(gè)例子,得到對(duì)應(yīng)的抽象語(yǔ)法樹(shù)的字符串表示。表示方法是:
Java代碼 ?
跟LISP的S-expression非常類似。
于是執(zhí)行測(cè)試程序。將要解析的代碼保存到JerrySample.txt中,然后執(zhí)行下面的命令:
引用java -cp ".;antlrworks-1.2.2.jar" TestJerry < JerrySample.txt
得到輸出:
Java代碼 ?
這樣太亂了看不清楚。將其格式稍微整理一下得到:
Java代碼 ?
可以跟原本的代碼對(duì)比一下,看看是否保持了原本的結(jié)構(gòu)。
得到這棵抽象語(yǔ)法樹(shù)之后,接下來(lái)就可以對(duì)樹(shù)來(lái)做匹配和分析了。由于樹(shù)本身已經(jīng)有了結(jié)構(gòu),下面就可以用更干凈的描述方式來(lái)表述我們要對(duì)樹(shù)做的處理。
轉(zhuǎn)載于:https://www.cnblogs.com/shihao/archive/2012/06/02/2532218.html
總結(jié)
以上是生活随笔為你收集整理的一个简单的语言的语法(二):ANTLR的重写规则的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C/C++ static和const关
- 下一篇: windows7 设置 Local Se