日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

都灵JVM编程语言:使用ANTLR构建高级词法分析器

發布時間:2023/12/3 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 都灵JVM编程语言:使用ANTLR构建高级词法分析器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

正如我在上一篇文章中所寫的那樣,我最近開始研究一種名為Turin的新編程語言。 可以在GitHub上找到適用于languag初始版本的編譯器。 我目前正在改進語言,并正在開發Maven和IntelliJ插件。 在這里和下一篇文章中,我將介紹編譯器和相關工具的不同組件。

編譯器的結構

編譯器需要做幾件事:

  • 獲取源代碼并生成抽象語法樹(AST)
  • 通過不同階段轉換AST以簡化處理。 我們基本上希望從非常接近語法的表示形式過渡到更易于處理的表示形式。 例如,我們可以對語言進行“去糖化”,將幾種(顯然)不同的結構表示為同一結構的變體。 一個例子? Java編譯器將字符串連接轉換為對StringBuffer.append的調用
  • 執行語義檢查。 例如,我們要檢查所有表達式是否都使用可接受的類型(我們不想對字符求和,對嗎?)
  • 產生字節碼
  • 第一步需要構建兩個組件:詞法分析器和解析器。 詞法分析器對文本進行操作并生成標記序列,而解析器將標記組合到用于創建AST的構造(類型聲明,語句,表達式等)中。 為了編寫詞法分析器和解析器,我使用了ANTLR。

    在本文的其余部分,我們將研究詞法分析器。 解析器和編譯器的其他組件將在以后的文章中討論。

    為什么要使用ANTLR?

    ANTLR是用于編寫詞法分析器和解析器的非常成熟的工具。 它可以生成多種語言的代碼,并具有良好的性能。 它維護良好,我確信它具有處理可能遇到的所有極端情況所需的所有功能。 除此之外,ANTLR 4可以編寫簡單的語法,因為它可以為您解決左遞歸定義。 因此,您不必編寫許多中間節點類型即可為表達式指定優先級規則。 我們將在分析器中對此進行更多介紹。

    Xtext使用了ANTLR(我已經使用了很多),并且在為.NET平臺 (一種用于.NET的EMF)構建模型驅動的開發框架時 ,我使用了ANTLR。 因此,我知道并信任ANTLR,因此沒有理由尋找其他選擇。

    當前的詞法分析器語法

    這是詞法分析器語法的當前版本。

    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)!='{' }?;

    我已經做了一些選擇:

    • 有兩種不同類型的ID: VALUE_ID和TYPE_ID。 由于可以輕松地區分值和類型,因此語法上的歧義性較小。 在Java中,當遇到(foo)時,我們不知道它是表達式(對括號之間foo表示的值的引用)還是類型foo的強制轉換。 我們需要看下面的內容才能理解它。 我認為這很愚蠢,因為實際上每個人都只對類型使用大寫的標識符,但是由于這不是由語言強制執行的,因此編譯器無法從中受益
    • 換行符與都靈相關,因此我們有針對它們的標記,我們基本上希望語句以換行符終止,但我們在逗號后接受可選的換行符
    • 空格(但換行符)和注釋是在它們自己的通道中捕獲的,因此我們可以在解析器語法中忽略它們,但可以在需要時檢索它們。 例如,我們需要它們來突出顯示語法,并且通常需要IntelliJ插件,因為它需要為源文件中的每個單個字符定義標記,而沒有空格
    • 最棘手的部分是在Ruby中解析字符串插值,例如“我的名字是#{user.name}”。 我們使用模式:遇到字符串開始(“)時,我們切換到詞法分析器模式IN_STRING。 在IN_STRING模式下,如果遇到插值(#{)的開始,我們將移至詞法分析器模式IN_INTERPOLATION。 在IN_INTERPOLATION模式下,我們需要接受表達式中使用的大多數標記(可悲的是,這意味著我們的詞法分析器語法有很多重復)。
    • 我不得不將關系運算符折疊為一種令牌類型,以使生成的詞法分析器的狀態數不會太大。 這意味著我將不得不查看RELOP令牌的文本,以確定需要執行哪個操作。 沒什么可怕的,但是您必須知道如何解決此類問題。

    測試詞法分析器

    我寫了很多針對詞法分析器的測試。 特別是,我測試了最復雜的部分:有關字符串插值的部分。

    一些測試的示例:

    @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);}

    如您所見,我只是在字符串上測試令牌,并驗證它是否生成了正確的令牌列表。 簡單而直接。

    結論

    我在ANTLR上使用該語言的經驗并不完美:存在問題和局限性。 必須在單個令牌類型中折疊多個運算符并不好。 必須為不同的詞法分析器模式重復幾個標記定義是不好的。 但是,ANTLR被證明是在實踐中可用的工具:它可以完成它需要做的所有事情,并且對于每個問題都有一個可接受的解決方案。 解決方案可能不是理想的,也許不是理想的解決方案,但是有一個解決方案。 因此,我可以使用它并繼續進行編譯器中更有趣的部分。

    翻譯自: https://www.javacodegeeks.com/2015/09/turin-programming-language-for-the-jvm-building-advanced-lexers-with-antlr.html

    總結

    以上是生活随笔為你收集整理的都灵JVM编程语言:使用ANTLR构建高级词法分析器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。