都灵JVM编程语言:使用ANTLR构建高级词法分析器
正如我在上一篇文章中所寫(xiě)的那樣,我最近開(kāi)始研究一種名為T(mén)urin的新編程語(yǔ)言。 可以在GitHub上找到適用于languag初始版本的編譯器。 我目前正在改進(jìn)語(yǔ)言,并正在開(kāi)發(fā)Maven和IntelliJ插件。 在這里和下一篇文章中,我將介紹編譯器和相關(guān)工具的不同組件。
編譯器的結(jié)構(gòu)
編譯器需要做幾件事:
第一步需要構(gòu)建兩個(gè)組件:詞法分析器和解析器。 詞法分析器對(duì)文本進(jìn)行操作并生成標(biāo)記序列,而解析器將標(biāo)記組合到用于創(chuàng)建AST的構(gòu)造(類(lèi)型聲明,語(yǔ)句,表達(dá)式等)中。 為了編寫(xiě)詞法分析器和解析器,我使用了ANTLR。
在本文的其余部分,我們將研究詞法分析器。 解析器和編譯器的其他組件將在以后的文章中討論。
為什么要使用ANTLR?
ANTLR是用于編寫(xiě)詞法分析器和解析器的非常成熟的工具。 它可以生成多種語(yǔ)言的代碼,并具有良好的性能。 它維護(hù)良好,我確信它具有處理可能遇到的所有極端情況所需的所有功能。 除此之外,ANTLR 4可以編寫(xiě)簡(jiǎn)單的語(yǔ)法,因?yàn)樗梢詾槟鉀Q左遞歸定義。 因此,您不必編寫(xiě)許多中間節(jié)點(diǎn)類(lèi)型即可為表達(dá)式指定優(yōu)先級(jí)規(guī)則。 我們將在分析器中對(duì)此進(jìn)行更多介紹。
Xtext使用了ANTLR(我已經(jīng)使用了很多),并且在為.NET平臺(tái) (一種用于.NET的EMF)構(gòu)建模型驅(qū)動(dòng)的開(kāi)發(fā)框架時(shí) ,我使用了ANTLR。 因此,我知道并信任ANTLR,因此沒(méi)有理由尋找其他選擇。
當(dāng)前的詞法分析器語(yǔ)法
這是詞法分析器語(yǔ)法的當(dāng)前版本。
lexer grammar TurinLexer;@header {}@lexer::members {public static final int WHITESPACE = 1;public static final int COMMENTS = 2; }// It is suggested to define the token types reused in different mode. // See mode in-interpolation below tokens { VALUE_ID, TYPE_ID, INT, LPAREN, RPAREN, COMMA, RELOP, AND_KW, OR_KW, NOT_KW }// Of course keywords has to be defined before the rules for identifiers NAMESPACE_KW : 'namespace'; PROGRAM_KW : 'program'; PROPERTY_KW : 'property'; TYPE_KW : 'type'; VAL_KW : 'val'; HAS_KW : 'has'; ABSTRACT_KW : 'abstract'; SHARED_KW : 'shared'; IMPORT_KW : 'import'; AS_KW : 'as'; VOID_KW : 'Void'; RETURN_KW : 'return'; FALSE_KW : 'false'; TRUE_KW : 'true'; IF_KW : 'if'; ELIF_KW : 'elif'; ELSE_KW : 'else';// For definitions reused in mode in-interpolation we define and refer to fragments AND_KW : F_AND; OR_KW : F_OR; NOT_KW : F_NOT;LPAREN : '('; RPAREN : ')'; LBRACKET : '{'; RBRACKET : '}'; LSQUARE : '['; RSQUARE : ']'; COMMA : ','; POINT : '.'; COLON : ':'; // We use just one token type to reduce the number of states (and not crash Antlr...) // https://github.com/antlr/antlr4/issues/840 EQUAL : '==' -> type(RELOP); DIFFERENT : '!=' -> type(RELOP); LESSEQ : '<=' -> type(RELOP); LESS : '<' -> type(RELOP); MOREEQ : '>=' -> type(RELOP); MORE : '>' -> type(RELOP); // ASSIGNMENT has to comes after EQUAL ASSIGNMENT : '='; // Mathematical operators cannot be merged in one token type because // they have different precedences ASTERISK : '*'; SLASH : '/'; PLUS : '+'; MINUS : '-';PRIMITIVE_TYPE : F_PRIMITIVE_TYPE; BASIC_TYPE : F_BASIC_TYPE;VALUE_ID : F_VALUE_ID; // Only for types TYPE_ID : F_TYPE_ID; INT : F_INT;// Let's switch to another mode here STRING_START : '"' -> pushMode(IN_STRING);WS : (' ' | '\t')+ -> channel(WHITESPACE); NL : '\r'? '\n';COMMENT : '/*' .*? '*/' -> channel(COMMENTS);LINE_COMMENT : '//' ~[\r\n]* -> channel(COMMENTS);mode IN_STRING;STRING_STOP : '"' -> popMode; STRING_CONTENT : (~["\\#]|ESCAPE_SEQUENCE|SHARP)+; INTERPOLATION_START : '#{' -> pushMode(IN_INTERPOLATION);mode IN_INTERPOLATION;INTERPOLATION_END : '}' -> popMode; I_PRIMITIVE_TYPE : F_PRIMITIVE_TYPE -> type(PRIMITIVE_TYPE); I_BASIC_TYPE : F_BASIC_TYPE -> type(BASIC_TYPE); I_FALSE_KW : 'false' -> type(FALSE_KW); I_TRUE_KW : 'true' -> type(TRUE_KW); I_AND_KW : F_AND -> type(AND_KW); I_OR_KW : F_OR -> type(OR_KW); I_NOT_KW : F_NOT -> type(NOT_KW); I_IF_KW : 'if' -> type(IF_KW); I_ELSE_KW : 'else' -> type(ELSE_KW); I_VALUE_ID : F_VALUE_ID -> type(VALUE_ID); I_TYPE_ID : F_TYPE_ID -> type(TYPE_ID); I_INT : F_INT -> type(INT); I_COMMA : ',' -> type(COMMA); I_LPAREN : '(' -> type(LPAREN); I_RPAREN : ')' -> type(RPAREN); I_LSQUARE : '[' -> type(LSQUARE); I_RSQUARE : ']' -> type(RSQUARE);I_ASTERISK : '*' -> type(ASTERISK); I_SLASH : '/' -> type(SLASH); I_PLUS : '+' -> type(PLUS); I_MINUS : '-' -> type(MINUS);I_POINT : '.' -> type(POINT); I_EQUAL : '==' -> type(RELOP); I_DIFFERENT : '!=' -> type(RELOP); I_LESSEQ : '<=' -> type(RELOP); I_LESS : '<' -> type(RELOP); I_MOREEQ : '>=' -> type(RELOP); I_MORE : '>' -> type(RELOP); I_STRING_START : '"' -> type(STRING_START), pushMode(IN_STRING); I_WS : (' ' | '\t')+ -> type(WS), channel(WHITESPACE);fragment F_AND : 'and'; fragment F_OR : 'or'; fragment F_NOT : 'not'; fragment F_VALUE_ID : ('_')*'a'..'z' ('A'..'Z' | 'a'..'z' | '0'..'9' | '_')*; // Only for types fragment F_TYPE_ID : ('_')*'A'..'Z' ('A'..'Z' | 'a'..'z' | '0'..'9' | '_')*; fragment F_INT : '0'|(('1'..'9')('0'..'9')*); fragment F_PRIMITIVE_TYPE : 'Byte'|'Int'|'Long'|'Boolean'|'Char'|'Float'|'Double'|'Short'; fragment F_BASIC_TYPE : 'UInt';fragment ESCAPE_SEQUENCE : '\\r'|'\\n'|'\\t'|'\\"'|'\\\\'; fragment SHARP : '#'{ _input.LA(1)!='{' }?;我已經(jīng)做了一些選擇:
- 有兩種不同類(lèi)型的ID: VALUE_ID和TYPE_ID。 由于可以輕松地區(qū)分值和類(lèi)型,因此語(yǔ)法上的歧義性較小。 在Java中,當(dāng)遇到(foo)時(shí),我們不知道它是表達(dá)式(對(duì)括號(hào)之間foo表示的值的引用)還是類(lèi)型foo的強(qiáng)制轉(zhuǎn)換。 我們需要看下面的內(nèi)容才能理解它。 我認(rèn)為這很愚蠢,因?yàn)閷?shí)際上每個(gè)人都只對(duì)類(lèi)型使用大寫(xiě)的標(biāo)識(shí)符,但是由于這不是由語(yǔ)言強(qiáng)制執(zhí)行的,因此編譯器無(wú)法從中受益
- 換行符與都靈相關(guān),因此我們有針對(duì)它們的標(biāo)記,我們基本上希望語(yǔ)句以換行符終止,但我們?cè)诙禾?hào)后接受可選的換行符
- 空格(但換行符)和注釋是在它們自己的通道中捕獲的,因此我們可以在解析器語(yǔ)法中忽略它們,但可以在需要時(shí)檢索它們。 例如,我們需要它們來(lái)突出顯示語(yǔ)法,并且通常需要IntelliJ插件,因?yàn)樗枰獮樵次募械拿總€(gè)單個(gè)字符定義標(biāo)記,而沒(méi)有空格
- 最棘手的部分是在Ruby中解析字符串插值,例如“我的名字是#{user.name}”。 我們使用模式:遇到字符串開(kāi)始(“)時(shí),我們切換到詞法分析器模式IN_STRING。 在IN_STRING模式下,如果遇到插值(#{)的開(kāi)始,我們將移至詞法分析器模式IN_INTERPOLATION。 在IN_INTERPOLATION模式下,我們需要接受表達(dá)式中使用的大多數(shù)標(biāo)記(可悲的是,這意味著我們的詞法分析器語(yǔ)法有很多重復(fù))。
- 我不得不將關(guān)系運(yùn)算符折疊為一種令牌類(lèi)型,以使生成的詞法分析器的狀態(tài)數(shù)不會(huì)太大。 這意味著我將不得不查看RELOP令牌的文本,以確定需要執(zhí)行哪個(gè)操作。 沒(méi)什么可怕的,但是您必須知道如何解決此類(lèi)問(wèn)題。
測(cè)試詞法分析器
我寫(xiě)了很多針對(duì)詞法分析器的測(cè)試。 特別是,我測(cè)試了最復(fù)雜的部分:有關(guān)字符串插值的部分。
一些測(cè)試的示例:
@Testpublic void parseStringWithEmptyInterpolation() throws IOException {String code = "\"Hel#{}lo!\"";verify(code, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.INTERPOLATION_START, TurinLexer.INTERPOLATION_END, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}@Testpublic void parseStringWithInterpolationContainingID() throws IOException {String code = "\"Hel#{foo}lo!\"";verify(code, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.INTERPOLATION_START,TurinLexer.VALUE_ID,TurinLexer.INTERPOLATION_END, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}@Testpublic void parseStringWithSharpSymbol() throws IOException {String code = "\"Hel#lo!\"";verify(code, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}@Testpublic void parseMethodDefinitionWithExpressionBody() throws IOException {String code = "Void toString() = \"foo\"";verify(code, TurinLexer.VOID_KW, TurinLexer.VALUE_ID, TurinLexer.LPAREN, TurinLexer.RPAREN, TurinLexer.ASSIGNMENT, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}如您所見(jiàn),我只是在字符串上測(cè)試令牌,并驗(yàn)證它是否生成了正確的令牌列表。 簡(jiǎn)單而直接。
結(jié)論
我在ANTLR上使用該語(yǔ)言的經(jīng)驗(yàn)并不完美:存在問(wèn)題和局限性。 必須在單個(gè)令牌類(lèi)型中折疊多個(gè)運(yùn)算符并不好。 必須為不同的詞法分析器模式重復(fù)幾個(gè)標(biāo)記定義是不好的。 但是,ANTLR被證明是在實(shí)踐中可用的工具:它可以完成它需要做的所有事情,并且對(duì)于每個(gè)問(wèn)題都有一個(gè)可接受的解決方案。 解決方案可能不是理想的,也許不是理想的解決方案,但是有一個(gè)解決方案。 因此,我可以使用它并繼續(xù)進(jìn)行編譯器中更有趣的部分。
翻譯自: https://www.javacodegeeks.com/2015/09/turin-programming-language-for-the-jvm-building-advanced-lexers-with-antlr.html
總結(jié)
以上是生活随笔為你收集整理的都灵JVM编程语言:使用ANTLR构建高级词法分析器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 安卓新特性设备管理(安卓新特性)
- 下一篇: jboss 发布web_JBoss模块示