用Java解析:您可以使用的所有工具和库
如果需要從Java解析語言或文檔,則從根本上講有三種方法可以解決問題:
- 使用支持該特定語言的現(xiàn)有庫:例如用于解析XML的庫
- 手動構(gòu)建自己的自定義解析器
- 生成解析器的工具或庫:例如ANTLR,可用于構(gòu)建任何語言的解析器
使用現(xiàn)有庫
第一種選擇最適合眾所周知和受支持的語言,例如XML或HTML 。 一個好的庫通常還包括API,以編程方式構(gòu)建和修改該語言的文檔。 這通常是您從基本解析器獲得的更多信息。 問題在于這樣的庫不是很常見,它們僅支持最常見的語言。 在其他情況下,您不走運。
手動構(gòu)建自己的自定義解析器
如果您有特殊需要,可能需要選擇第二種方法。 從某種意義上說,您需要解析的語言無法用傳統(tǒng)的解析器生成器解析,或者您有使用典型解析器生成器無法滿足的特定要求。 例如,因為您需要最佳的性能或不同組件之間的深度集成。
生成解析器的工具或庫
在所有其他情況下,第三個選項應(yīng)為默認選項,因為這是最靈活的且開發(fā)時間較短的選項。 這就是為什么在本文中我們將重點放在與該選項相對應(yīng)的工具和庫上的原因。
創(chuàng)建解析器的工具
我們將看到:
- 可以生成可從Java(以及可能從其他語言)使用的解析器的工具
- Java庫來構(gòu)建解析器
可用于生成解析器代碼的工具稱為解析器生成器或編譯器編譯器 。 創(chuàng)建解析器的庫稱為解析器組合器 。
解析器生成器(或解析器組合器)并不簡單:您需要一些時間來學(xué)習(xí)如何使用它們,并且并非所有類型的解析器生成器都適用于所有類型的語言。 這就是為什么我們準備了其中最知名的清單,并對每個清單進行了簡短介紹。 我們還專注于一種目標語言:Java。 這也意味著(通常)解析器本身將用Java編寫。
列出所有語言的所有可能的工具和庫解析器會很有趣,但沒那么有用。 那是因為會有太多簡單的選擇,而我們都會迷失其中。 通過專注于一種編程語言,我們可以進行逐個比較,并幫助您為項目選擇一個選項。
有關(guān)解析器的有用信息
為了確保所有程序員都可以訪問這些列表,我們?yōu)樗阉髡Z法分析器可能遇到的術(shù)語和概念作了簡短說明。 我們不是在給您正式的解釋,而是實際的解釋。
解析器的結(jié)構(gòu)
解析器通常由兩部分組成: lexer (也稱為掃描器或令牌生成器 )和適當?shù)慕馕銎鳌?并非所有解析器都采用這種兩步模式:某些解析器不依賴詞法分析器。 它們稱為無掃描程序解析器 。
一個詞法分析器和一個解析器按順序工作:詞法分析器掃描輸入并生成匹配的令牌,解析器掃描令牌并生成解析結(jié)果。
讓我們看下面的示例,并想象我們正在嘗試解析數(shù)學(xué)運算。
437 + 734詞法分析器掃描文本,然后找到“ 4”,“ 3”,“ 7”,然后找到空格“”。 詞法分析器的工作是識別第一個字符構(gòu)成NUM類型的一個標記。 然后,詞法分析器找到一個'+'符號,它對應(yīng)于PLUS類型的第二個令牌,最后找到另一個NUM類型的令牌。
解析器通常將組合詞法分析器生成的令牌并將其分組。
詞法分析器或解析器使用的定義稱為規(guī)則或產(chǎn)生式 。 詞法分析器規(guī)則將指定數(shù)字序列與NUM類型的令牌相對應(yīng),而分析器規(guī)則將指定數(shù)字序列NUM,PLUS,NUM類型的令牌與表達式相對應(yīng)。
無掃描程序的解析器是不同的,因為它們直接處理原始文本,而不是處理由詞法分析器生成的令牌列表。
現(xiàn)在通常會找到可以生成詞法分析器和解析器的套件。 過去,更常見的是將兩種不同的工具結(jié)合在一起:一種用于生成詞法分析器,另一種用于生成解析器。 例如,古老的lex&yacc夫婦就是這種情況:lex生成了lexer,而yacc生成了解析器。
解析樹和抽象語法樹
有兩個相關(guān)的術(shù)語,有時可以互換使用:解析樹和抽象語法樹(AST)。
從概念上講,它們非常相似:
- 它們都是樹 :有一個表示整個已解析代碼的根。 然后有較小的子樹表示代碼的一部分,這些子樹變得越來越小,直到樹中出現(xiàn)單個令牌為止
- 區(qū)別在于抽象級別:分析樹包含程序中出現(xiàn)的所有標記以及可能的一組中間規(guī)則。 AST而是解析樹的完善版本,其中刪除了可能衍生或?qū)τ诶斫獯a段不重要的信息
在AST中,某些信息會丟失,例如,注釋和分組符號(括號)未顯示。 對于程序來說,諸如注釋之類的東西是多余的,而分組符號則由樹的結(jié)構(gòu)隱式定義。
解析樹是更接近具體語法的代碼表示。 它顯示了解析器實現(xiàn)的許多細節(jié)。 例如,規(guī)則通常對應(yīng)于節(jié)點的類型。 通常,在解析器生成器的幫助下,用戶通常將其轉(zhuǎn)換為AST。
AST的圖形表示如下所示。
有時您可能想開始生成一個解析樹,然后從中派生AST。 這是有道理的,因為解析樹更易于為解析器生成(它是解析過程的直接表示),但是AST更簡單且易于通過以下步驟進行處理。 通過執(zhí)行以下步驟,我們表示您可能希望在樹上執(zhí)行的所有操作:代碼驗證,解釋,編譯等。
語法
語法是對語言的形式描述,可用于識別其結(jié)構(gòu)。
簡而言之,是一列定義如何構(gòu)造每個構(gòu)造的規(guī)則列表。 例如,if語句的規(guī)則可以指定它必須以“ if”關(guān)鍵字開頭,后跟左括號,表達式,右括號和語句。
規(guī)則可以引用其他規(guī)則或令牌類型。 在if語句的示例中,關(guān)鍵字“ if”,左括號和右括號是標記類型,而expression和statement是對其他規(guī)則的引用。
描述語法最常用的格式是Backus-Naur形式(BNF) ,它也有很多變體,包括Extended Backus-Naur形式 。 Extented變體的優(yōu)點是包括一種表示重復(fù)的簡單方法。 Backus-Naur語法中的典型規(guī)則如下所示:
<symbol> ::= __expression__<simbol>通常是非終結(jié)符,這意味著可以用右邊的元素組__expression__ 。 元素__expression__可以包含其他非終止符號或終止符號。 終端符號只是在語法中的任何地方都不會顯示為<symbol> 。 終端符號的典型示例是一串字符,例如“ class”。
左遞歸規(guī)則
在解析器的上下文中,一個重要功能是對左遞歸規(guī)則的支持。 這意味著規(guī)則可以從對自身的引用開始。 此引用也可以是間接的。
考慮例如算術(shù)運算。 一個加法可以描述為兩個用加號(+)分隔的表達式,但是一個表達式也可以包含其他加法。
addition ::= expression '+' expression multiplication ::= expression '*' expression // an expression could be an addition or a multiplication or a number expression ::= addition | multiplication |// a number此描述還匹配5 + 4 + 3之類的多個加法項。這是因為它可以解釋為表達式(5)('+')expression(4 + 3)。 然后4 + 3本身可以分為兩個部分。
問題是這種規(guī)則可能無法與某些解析器生成器一起使用。 另一種選擇是一長串表達式,它還要注意運算符的優(yōu)先級。
一些解析器生成器支持直接的左遞歸規(guī)則,但不支持間接的規(guī)則。
語言和文法的類型
我們主要關(guān)心的是可以使用解析器生成器解析的兩種類型的語言: 常規(guī)語言和無上下文語言 。 我們可以根據(jù)喬姆斯基語言的層次結(jié)構(gòu)為您提供正式的定義,但是它沒有那么有用。 讓我們來看一些實際的方面。
常規(guī)語言可以由一系列常規(guī)表達式定義,而無上下文的語言則需要更多。 一個簡單的經(jīng)驗法則是,如果一種語言的語法具有遞歸元素,則它不是常規(guī)語言。 例如,正如我們在其他地方所說, HTML不是常規(guī)語言 。 實際上,大多數(shù)編程語言都是上下文無關(guān)的語言。
通常對一種語言對應(yīng)一種語法。 也就是說,存在分別對應(yīng)于常規(guī)和無上下文語言的常規(guī)語法和無上下文語法。 但是使事情變得復(fù)雜的是,有一種相對較新的語法(創(chuàng)建于2004年),稱為解析表達語法(PEG)。 這些語法與無上下文語法一樣強大,但是根據(jù)他們的作者,他們描述了更自然的編程語言。
PEG和CFG的區(qū)別
PEG和CFG之間的主要區(qū)別在于,選擇的順序在PEG中有意義,而在CFG中沒有意義。 如果有許多可能的有效方法來解析輸入,則CFG會模棱兩可,因此是錯誤的。 取而代之的是使用PEG,將選擇第一個適用的選擇,這將自動解決一些歧義。
另一個區(qū)別是PEG使用無掃描器解析器:它們不需要單獨的詞法分析器或詞法分析階段。
傳統(tǒng)上,PEG和某些CFG都無法處理左遞歸規(guī)則,但是一些工具已找到解決方法。 通過修改基本的解析算法,或者使該工具以非遞歸方式自動重寫左遞歸規(guī)則。 這兩種方法都有缺點:要么使生成的解析器難以理解,要么使其性能變差。 但是,實際上,更容易,更快的開發(fā)的優(yōu)點勝于缺點。
解析器生成器
解析器生成器工具的基本工作流程非常簡單:編寫定義語言或文檔的語法,然后運行該工具以生成可從Java代碼使用的解析器。
解析器可能會生成AST,您可能必須遍歷自己,也可以使用其他現(xiàn)成的類(例如Listeners或Visitors)來遍歷。 相反,有些工具提供了將代碼嵌入語法中的機會,以便在每次匹配特定規(guī)則時都執(zhí)行該代碼。
通常,您需要運行時庫和/或程序才能使用生成的解析器。
常規(guī)(詞法分析器)
分析常規(guī)語言的工具通常是詞法分析器。
JFlex
JFlex是基于確定性有限自動機(DFA)的詞法分析器(lexer)生成器。 JFlex詞法分析器根據(jù)定義的語法(稱為規(guī)范)匹配輸入,并執(zhí)行相應(yīng)的操作(嵌入在語法中)。
它可以用作獨立工具,但作為詞法分析器生成器旨在與解析器生成器一起使用:通常與CUP或BYacc / J一起使用。 它也可以與ANTLR一起使用。
典型的語法(規(guī)范)分為三部分,以'%%'分隔:
JFlex規(guī)范文件
// taken from the documentation /* JFlex example: partial Java language lexer specification */ import java_cup.runtime.*;%%// second section%class Lexer%unicode%cup[..]LineTerminator = \r|\n|\r\n%%// third section/* keywords */ <YYINITIAL> "abstract" { return symbol(sym.ABSTRACT); } <YYINITIAL> "boolean" { return symbol(sym.BOOLEAN); } <YYINITIAL> "break" { return symbol(sym.BREAK); }<STRING> {\" { yybegin(YYINITIAL);return symbol(sym.STRING_LITERAL,string.toString()); }[..] }/* error fallback */ [^] { throw new Error("Illegal character <"+yytext()+">"); }上下文無關(guān)
讓我們看看生成上下文無關(guān)解析器的工具。
ANTLR可能是Java最常用的解析器生成器。 ANTLR基于作者開發(fā)的新LL算法,并在本文中進行了描述: 自適應(yīng)LL(*)解析:動態(tài)分析的能力(PDF) 。
它可以輸出多種語言的解析器。 但是廣大社區(qū)的真正附加值是大量可用的語法 。 版本4支持直接的左遞歸規(guī)則。
它提供了兩種遍歷AST的方式,而不是將動作嵌入語法中:訪問者和聽眾。 第一個適用于您必須操縱樹的元素或與之交互的情況,而第二個適用于在規(guī)則匹配時只需要做一些事情的情況。
典型的語法分為兩部分:詞法分析器規(guī)則和解析器規(guī)則。 該劃分是隱式的,因為所有以大寫字母開頭的規(guī)則都是詞法分析器規(guī)則,而以小寫字母開頭的規(guī)則是解析器規(guī)則。 另外,可以在單獨的文件中定義詞法分析器和解析器語法。
一個非常簡單的ANTLR語法
grammar simple;basic : NAME ':' NAME ;NAME : [a-zA-Z]* ;COMMENT : '/*' .*? '*/' -> skip ;如果您對ANTLR感興趣,可以查看我們編寫的這個龐大的ANTLR教程 。
APG
APG是一種遞歸下降解析器,它使用了增強BNF的變體,他們稱之為超集增強BNF。 ABNF是BNF的特定變體,旨在更好地支持雙向通信協(xié)議。 APG還支持其他運算符,例如語法謂詞和自定義用戶定義的匹配函數(shù)。
它可以使用C / C ++,Java e JavaScript生成解析器。 對最后一種語言的支持似乎更好,并且是最新的:它具有更多功能,并且似乎已更新。 實際上,文檔說它的設(shè)計具有JavaScript RegExp的外觀。
因為它是基于ABNF的,所以它特別適合解析許多Internet技術(shù)規(guī)范的語言,并且實際上是許多大型電信公司的首選解析器。
APG語法非常簡潔易懂。
APG語法
// example from a tutorial of the author of the tool available here // https://www.sitepoint.com/alternative-to-regular-expressions/ phone-number = ["("] area-code sep office-code sep subscriber area-code = 3digit ; 3 digits office-code = 3digit ; 3 digits subscriber = 4digit ; 4 digits sep = *3(%d32-47 / %d58-126 / %d9) ; 0-3 ASCII non-digits digit = %d48-57 ; 0-9BYACC / J
BYACC是生成Java代碼的Yacc。 這就是整個想法,它定義了它的優(yōu)點和缺點。 眾所周知,它可以更輕松地將Yacc和C程序轉(zhuǎn)換為Java程序。 盡管顯然您仍然需要將語義動作中嵌入的所有C代碼轉(zhuǎn)換為Java代碼。 另一個優(yōu)點是您不需要單獨的運行時,生成的解析器便是您所需要的。
另一方面,它已經(jīng)很老了,解析世界已經(jīng)取得了很多進步。 如果您是經(jīng)驗豐富的Yacc開發(fā)人員,并且具有要升級的代碼庫,那么這是一個不錯的選擇,否則,您應(yīng)該考慮使用更多更現(xiàn)代的替代方法。
典型的語法分為三部分,以'%%'分隔:聲明,操作和代碼。 第二個包含語法規(guī)則,第三個包含自定義用戶代碼。
BYacc語法
// from the documentation %{import java.lang.Math; import java.io.*; import java.util.StringTokenizer; %}/* YACC Declarations */ %token NUM%left '-' '+'%left '*' '/'%left NEG /* negation--unary minus */%right '^' /* exponentiation *//* Grammar follows */ %%input: /* empty string */| input line;line: '\n'| exp '\n' { System.out.println(" " + $1.dval + " "); }; %%public static void main(String args[]) {Parser par = new Parser(false);[..] }可可/ R
Coco / R是一個編譯器生成器,它采用屬性語法并生成掃描器和遞歸下降解析器。 屬性語法意味著可以多種方式對以EBNF變體編寫的規(guī)則進行注釋,以更改生成的解析器的方法。
掃描程序包括處理諸如編譯指示之類的編譯指示的支持。 解析器可以忽略它們,而自定義代碼可以處理它們。 掃描儀也可以被壓制,并用手動構(gòu)建的掃描儀代替。
從技術(shù)上講,所有語法都必須為LL(1),也就是說,解析器必須能夠僅在前面看一個符號的情況下選擇正確的規(guī)則。 但是Coco / R提供了幾種繞過此限制的方法,包括語義檢查,這些檢查基本上是必須返回布爾值的自定義函數(shù)。 該手冊還提供一些建議,以重構(gòu)您的代碼以遵守此限制。
Coco / R語法如下所示。
Coco / R語法
[Imports] // ident is the name of the grammar "COMPILER" ident // this includes arbitrary fields and method in the target language (eg. Java) [GlobalFieldsAndMethods] // ScannerSpecification CHARACTERS[..]zero = '0'.zeroToThree = zero + "123" .octalDigit = zero + "1234567" . nonZeroDigit = "123456789".digit = '0' + nonZeroDigit .[..]TOKENSident = letter { letter | digit }. [..] // ParserSpecification PRODUCTIONS // just a rule is shown IdentList = ident <out int x> (. int n = 1; .) {',' ident (. n++; .) } (. Console.WriteLine("n = " + n); .) . // end "END" ident '.'Coco / R有一個很好的文檔,并提供了一些示例語法。 它支持多種語言,包括Java,C#和C ++。
CookCC
CookCC是用Java編寫的LALR(1)解析器生成器。 可以用三種不同的方式指定語法:
- 以Yacc格式:它可以讀取為Yacc定義的語法
- 以自己的XML格式
- 通過使用特定注釋在Java代碼中
一個獨特的功能是它還可以輸出Yacc語法。 如果您需要與支持Yacc語法的工具進行交互,這將非常有用。 就像一些舊的C程序,您必須與之保持兼容性。
它需要Java 7來生成解析器,但是它可以在早期版本上運行。
使用注釋定義的典型解析器將如下所示。
CookCC解析器
// required import import org.yuanheng.cookcc.*;@CookCCOption (lexerTable = "compressed", parserTable = "compressed") // the generated parser class will be a parent of the one you define // in this case it will be "Parser" public class Calculator extends Parser {// code// a lexer rule@Shortcuts ( shortcuts = {@Shortcut (name="nonws", pattern="[^ \\t\\n]"),@Shortcut (name="ws", pattern="[ \\t]")})@Lex (pattern="{nonws}+", state="INITIAL")void matchWord (){m_cc += yyLength ();++m_wc;}// a typical parser rules@Rule (lhs = "stmt", rhs = "SEMICOLON")protected Node parseStmt (){return new SemiColonNode ();} }對于解析器生成器的標準,使用Java批注是一個奇特的選擇。 與諸如ANTLR之類的替代方法相比,語法和動作之間的劃分肯定不夠清晰。 這可能會使解析器更難以維護復(fù)雜的語言。 另外移植到另一種語言可能需要完全重寫。
另一方面,這種方法允許將語法規(guī)則與匹配規(guī)則時要執(zhí)行的動作混合在一起。 此外,由于它只是Java代碼,因此具有集成到您選擇的IDE中的優(yōu)勢。
杯子
CUP是“有用的解析器構(gòu)造”的縮寫,它是Java的LALR解析器生成器。 它只是生成正確的解析器部分,但非常適合與JFlex一起使用。 盡管顯然您也可以手動構(gòu)建詞法分析器以與CUP一起使用。 語法具有類似于Yacc的語法,并且允許為每個規(guī)則嵌入代碼。
它可以自動生成一個分析樹,但不能自動生成一個AST。
它還具有一個Eclipse插件來幫助您創(chuàng)建語法,因此有效地具有其自己的IDE。
典型的語法類似于YACC。
CUP語法
// example from the documentation // CUP specification for a simple expression evaluator (w/ actions)import java_cup.runtime.*;/* Preliminaries to set up and use the scanner. */ init with {: scanner.init(); :}; scan with {: return scanner.next_token(); :};/* Terminals (tokens returned by the scanner). */ terminal SEMI, PLUS, MINUS, TIMES, DIVIDE, MOD; terminal UMINUS, LPAREN, RPAREN; terminal Integer NUMBER;/* Non-terminals */ non terminal expr_list, expr_part; non terminal Integer expr;/* Precedences */ precedence left PLUS, MINUS; precedence left TIMES, DIVIDE, MOD; precedence left UMINUS;/* The grammar */ expr_list ::= expr_list expr_part|expr_part;expr_part ::= expr:e{: System.out.println("= " + e); :}SEMI ; [..]語法學(xué)
Grammatica是C#和Java解析器生成器(編譯器編譯器)。 它讀取語法文件(采用EBNF格式),并為解析器創(chuàng)建注釋清晰且易于閱讀的C#或Java源代碼。 它支持LL(k)語法,自動錯誤恢復(fù),可讀錯誤消息以及語法和源代碼之間的清晰分隔。
Grammatica網(wǎng)站上的描述本身就是Grammatica的一個很好的代表:簡單易用,文檔完善,功能豐富。 您可以通過對生成的類進行子類化來構(gòu)建偵聽器,但不能對訪問者進行子類化。 有很好的參考,但例子并不多。
Grammatica的典型語法分為三個部分:標頭,標記和產(chǎn)生式。 它也很干凈,幾乎和ANTLR一樣。 盡管格式略有不同,但它也基于類似的擴展BNF。
語法語法
%header%GRAMMARTYPE = "LL"[..]%tokens%ADD = "+" SUB = "-" [..] NUMBER = <<[0-9]+>> WHITESPACE = <<[ \t\n\r]+>> %ignore%%productions%Expression = Term [ExpressionTail] ;ExpressionTail = "+" Expression| "-" Expression ;Term = Factor [TermTail] ;[..]Atom = NUMBER| IDENTIFIER ;雅克
Jacc與BYACC / J相似,不同之處在于Jacc是用Java編寫的,因此它可以在程序可以運行的任何地方運行。 根據(jù)經(jīng)驗,它是作為Yacc的更新版本開發(fā)的。 作者介紹了在錯誤消息,模塊化和調(diào)試支持等方面的小改進。
如果您知道Yacc并且沒有任何代碼庫可升級,那么它可能是一個不錯的選擇。
JavaCC
JavaCC是另一種廣泛使用的Java解析器生成器。 語法文件包含動作和解析器所需的所有自定義代碼。
與ANTLR相比,語法文件不那么干凈,并且包含許多Java源代碼。
JavaCC語法
javacc_options // "PARSER_BEGIN" "(" <IDENTIFIER> ")" PARSER_BEGIN(SimpleParser) public final class SimpleParser { // Standard parser class setup...public static void main(String args[]) {SimpleParser parser;java.io.InputStream input;} PARSER_END(SimpleParser)// the rules of the grammar // token rules TOKEN : {< #DIGIT : ["0"-"9"] > | < #LETTER : ["A"-"Z","a"-"z"] > | < IDENT : <LETTER> (<LETTER> | <DIGIT>)* > [..] }SKIP : { " " | "\t" | "\n" | "\r" }// parser rules[..]void IdentDef() : {} {<IDENT> ("*" | "-")? }由于其悠久的歷史,它被用于JavaParser等重要項目。 這在文檔和用法上有一些古怪之處。 例如,從技術(shù)上講,JavaCC本身并不構(gòu)建AST,但是它附帶了一個可實現(xiàn)AST的工具JTree,因此它確實可以構(gòu)建。
有一個語法存儲庫 ,但其中沒有很多語法。 它需要Java 5或更高版本。
型號CC
ModelCC是基于模型的解析器生成器,可將語言規(guī)范與語言處理[..]分離。 ModelCC接收概念模型作為輸入,以及對其進行注釋的約束。
實際上,您可以使用注釋定義語言的模型,該模型在Java中用作語法。 然后,將創(chuàng)建的模型提供給ModelCC,以獲取解析器。
使用ModelCC,您可以獨立于所使用的解析算法來定義語言。 相反,它應(yīng)該是語言的最佳概念表示。 盡管它在后臺使用了傳統(tǒng)的解析算法。 因此,語法本身使用獨立于任何解析算法的形式,但是ModelCC并不使用魔術(shù),而是生成普通的解析器。
對于工具作者的意圖有明確的描述,但是文檔有限。 盡管如此,還是有可用的示例,包括下面部分顯示的計算器模型。
public abstract class Expression implements IModel {public abstract double eval(); }[..]public abstract class UnaryOperator implements IModel {public abstract double eval(Expression e); }[..]@Pattern(regExp="-") public class MinusOperator extends UnaryOperator implements IModel {@Override public double eval(Expression e) { return -e.eval(); } }@Associativity(AssociativityType.LEFT_TO_RIGHT) public abstract class BinaryOperator implements IModel {public abstract double eval(Expression e1,Expression e2); }[..]@Priority(value=2) @Pattern(regExp="-") public class SubtractionOperator extends BinaryOperator implements IModel {@Override public double eval(Expression e1,Expression e2) { return e1.eval()-e2.eval(); } }[..]SableCC
SableCC是為論文而創(chuàng)建的解析器生成器,目的是易于使用并在語法和Java代碼之間提供清晰的分隔。 第3版還應(yīng)該提供一種包括在內(nèi)的現(xiàn)成的使用訪客走AST的方式。 但這只是理論上的全部,因為實際上沒有文檔,而且我們也不知道如何使用這些東西。
此外,第4版于2015年開始發(fā)布,顯然已被放棄。
UrchinCC
Urchin(CC)是一種解析器生成器,可用于定義稱為Urchin解析器定義的語法。 然后,您從中生成一個Java解析器。 Urchin還從UPD吸引了一位訪客。
這里有詳盡的教程,還可以用來解釋Urchin的工作原理及其局限性,但是手冊內(nèi)容有限。
UPD分為三個部分:終端,令牌和規(guī)則。
UPD文件
terminals {Letters ::= 'a'..'z', 'A'..'Z';Digits ::= '0'..'9'; }token {Space ::= [' ', #8, #9]*1;EOLN ::= [#10, #13];EOF ::= [#65535];[..]Identifier ::= [Letters] [Letters, Digits]*; }rules {Variable ::= "var", Identifier;Element ::= Number | Identifier;PlusExpression ::= Element, '+', Expression;[..] }聚乙二醇
在CFG解析器之后,是時候來看看Java中可用的PEG解析器了。
天篷
Canopy是針對Java,JavaScript,Python和Ruby的解析器編譯器。 它獲取一個描述解析表達式語法的文件,并將其編譯為目標語言的解析器模塊。 生成的解析器對Canopy本身沒有運行時依賴性。
它還提供了對解析樹節(jié)點的輕松訪問。
Canopy語法具有使用動作注釋在解析器中使用自定義代碼的簡潔功能。 實際上。 您只需在規(guī)則旁邊寫一個函數(shù)的名稱,然后在源代碼中實現(xiàn)該函數(shù)。
帶動作的冠層語法
// the actions are prepended by % grammar Mapsmap <- "{" string ":" value "}" %make_mapstring <- "'" [^']* "'" %make_stringvalue <- list / numberlist <- "[" value ("," value)* "]" %make_listnumber <- [0-9]+ %make_number包含操作代碼的Java文件。
[..] import maps.Actions; [..]class MapsActions implements Actions {public Pair make_map(String input, int start, int end, List<TreeNode> elements) {Text string = (Text)elements.get(1);Array array = (Array)elements.get(3);return new Pair(string.string, array.list);}[..] }拉賈
Laja是一個兩階段的無掃描器,自頂向下,回溯解析器生成器,支持運行時語法規(guī)則。
Laja是代碼生成器和解析器生成器,主要用于創(chuàng)建外部DSL。 這意味著它具有一些獨特的功能。 使用Laja,您不僅必須指定數(shù)據(jù)的結(jié)構(gòu),還必須指定如何將數(shù)據(jù)映射到Java結(jié)構(gòu)中。 這種結(jié)構(gòu)通常是層次結(jié)構(gòu)或平面組織中的對象。 簡而言之,它使解析數(shù)據(jù)文件變得非常容易,但是不太適合通用編程語言。
Laja選項(例如輸出目錄或輸入文件)在配置文件中設(shè)置。
Laja語法分為規(guī)則部分和數(shù)據(jù)映射部分。 看起來像這樣。
Laja語法
// this example is from the documentation grammar example {s = [" "]+;newline = "\r\n" | "\n";letter = "a".."z";digit = "0".."9";label = letter [digit|letter]+;row = label ":" s [!(newline|END)+]:value [newline];example = row+;Row row.setLabel(String label);row.setValue(String value);Example example.addRow(Row row); }老鼠
鼠標是將PEG轉(zhuǎn)錄為用Java編寫的可執(zhí)行解析器的工具。
它不使用packrat,因此它比典型的PEG解析器使用更少的內(nèi)存(該手冊將Mouse和Rats進行了顯式比較!)。
它沒有語法存儲庫,但是有適用于Java 6-8和C的語法。
鼠標語法很干凈。 要包含自定義代碼(一種稱為語義謂詞)的功能,您需要執(zhí)行與Canopy中相似的操作。 您在語法中包含一個名稱,然后在Java文件中實際編寫自定義代碼。
鼠標語法
// example from the manual // http://mousepeg.sourceforge.net/Manual.pdf // the semantics are between {} Sum = Space Sign Number (AddOp Number)* !_ {sum} ; Number = Digits Space {number} ; Sign = ("-" Space)? ; AddOp = [-+] Space ; Digits = [0-9]+ ; Space = " "* ;老鼠!
老鼠! 是xtc(eXTensible編譯器)的解析器生成器部分。 它基于PEG,但是使用“生成實際解析器所必需的附加表達式和運算符”。 它支持左遞歸生產(chǎn)。 它可以自動生成AST。
它需要Java 6或更高版本。
語法可能很簡潔,但是您可以在每次制作后嵌入自定義代碼。
老鼠! 語法
// example from Introduction to the Rats! Parser Generator // http://cs.nyu.edu/courses/fall11/CSCI-GA.2130-001/rats-intro.pdf /* module intro */ module Simple; option parser(SimpleParser);/* productions for syntax analysis */ public String program = e:expr EOF { yyValue = e; } ; String expr = t:term r:rest { yyValue = t + r; } ; String rest = PLUS t:term r:rest { yyValue = t + "+" + r; }/ MINUS t:term r:rest { yyValue = t + "-" + r; }/ /*empty*/ { yyValue = ""; } ; String term = d:DIGIT { yyValue = d; } ;/* productions for lexical analysis */ void PLUS = "+"; void MINUS = "-"; String DIGIT = [0-9]; void EOF = ! ;解析器組合器
通過組合與語法規(guī)則等效的不同模式匹配功能,它們使您可以簡單地用Java代碼創(chuàng)建解析器。 通常認為它們適合于更簡單的解析需求。 由于它們只是Java庫,因此您可以輕松地將它們引入項目中:您不需要任何特定的生成步驟,并且可以在自己喜歡的Java編輯器中編寫所有代碼。 它們的主要優(yōu)點是可以集成到傳統(tǒng)工作流程和IDE中。
實際上,這意味著它們對于您發(fā)現(xiàn)的所有小解析問題都非常有用。 如果典型的開發(fā)人員遇到問題(對于簡單的正則表達式而言過于復(fù)雜),則通常可以使用這些庫。 簡而言之,如果您需要構(gòu)建解析器,但實際上并不需要,解析器組合器可能是您的最佳選擇。
Jparsec
Jparsec是Haskell的parsec庫的端口。
解析器組合器通常在一個階段中使用,也就是說它們沒有詞法分析器。 這僅僅是因為它很快變得太復(fù)雜而無法直接在代碼中管理所有組合器鏈。 話雖這么說,jparsec有一個特殊的類來支持詞法分析。
它不支持左遞歸規(guī)則,但是它為最常見的用例提供了一個特殊的類:管理運算符的優(yōu)先級。
用jparsec編寫的典型解析器與此類似。
使用Jparsec的計算器解析器
// from the documentation public class Calculator {static final Parser<Double> NUMBER =Terminals.DecimalLiteral.PARSER.map(Double::valueOf);private static final Terminals OPERATORS =Terminals.operators("+", "-", "*", "/", "(", ")");[..]static final Parser<?> TOKENIZER =Parsers.or(Terminals.DecimalLiteral.TOKENIZER, OPERATORS.tokenizer());[..]static Parser<Double> calculator(Parser<Double> atom) {Parser.Reference<Double> ref = Parser.newReference();Parser<Double> unit = ref.lazy().between(term("("), term(")")).or(atom);Parser<Double> parser = new OperatorTable<Double>().infixl(op("+", (l, r) -> l + r), 10).infixl(op("-", (l, r) -> l - r), 10).infixl(Parsers.or(term("*"), WHITESPACE_MUL).retn((l, r) -> l * r), 20).infixl(op("/", (l, r) -> l / r), 20).prefix(op("-", v -> -v), 30).build(unit);ref.set(parser);return parser;}public static final Parser<Double> CALCULATOR =calculator(NUMBER).from(TOKENIZER, IGNORED); }煮熟的
Parboiled提供了遞歸下降PEG解析器實現(xiàn),該實現(xiàn)可對您指定的PEG規(guī)則進行操作。
煮熟的目的是提供一種易于使用和理解的方式來用Java創(chuàng)建小型DSL。 它把自己放在一堆簡單的正則表達式和一個工業(yè)強度的解析器生成器(如ANTLR)之間的空間中。 簡要語法可以包括帶有自定義代碼的動作,這些動作可以直接包含在語法代碼中,也可以通過接口包含。
煮熟的解析器示例
// example parser from the parboiled repository // CalculatorParser4.javapackage org.parboiled.examples.calculators;[..]@BuildParseTree public class CalculatorParser4 extends CalculatorParser<CalcNode> {@Overridepublic Rule InputLine() {return Sequence(Expression(), EOI);}public Rule Expression() {return OperatorRule(Term(), FirstOf("+ ", "- "));}[..]public Rule OperatorRule(Rule subRule, Rule operatorRule) {Var<Character> op = new Var<Character>();return Sequence(subRule,ZeroOrMore(operatorRule, op.set(matchedChar()),subRule,push(new CalcNode(op.get(), pop(1), pop()))));}[..]public Rule Number() {return Sequence(Sequence(Optional(Ch('-')),OneOrMore(Digit()),Optional(Ch('.'), OneOrMore(Digit()))),// the action uses a default string in case it is run during error recovery (resynchronization)push(new CalcNode(Double.parseDouble(matchOrDefault("0")))),WhiteSpace());}//**************** MAIN ****************public static void main(String[] args) {main(CalculatorParser4.class);} }它不會為您構(gòu)建AST,但會提供一個解析樹和一些類來簡化構(gòu)建它。
該文檔非常好,它解釋了功能,顯示了示例,并將精簡的想法與其他選項進行了比較。 存儲庫中有一些示例語法,其中一個用于Java。
它被多個項目使用,包括像neo4j這樣的重要項目。
PetitParser
PetitParser結(jié)合了無掃描程序解析,解析器組合器,解析表達式語法和packrat解析器的思想,將語法和解析器建模為可以動態(tài)重新配置的對象。
PetitParser是解析器組合器和傳統(tǒng)解析器生成器之間的交叉。 所有信息都寫在源代碼中,但是源代碼分為兩個文件。 在一個文件中,您定義了語法,而在另一個文件中,您定義了與各種元素相對應(yīng)的動作。 這個想法是,它應(yīng)該允許您動態(tài)地重新定義語法。 雖然設(shè)計精巧,但如果設(shè)計精巧,也值得商bat。 您可以看到示例JSON語法的冗長超出了人們的預(yù)期。
JSON的示例語法文件的摘錄。
示例PetitParser語法
package org.petitparser.grammar.json;[..]public class JsonGrammarDefinition extends GrammarDefinition {// setup code not shownpublic JsonGrammarDefinition() {def("start", ref("value").end());def("array", of('[').trim().seq(ref("elements").optional()).seq(of(']').trim()));def("elements", ref("value").separatedBy(of(',').trim()));def("members", ref("pair").separatedBy(of(',').trim()));[..]def("trueToken", of("true").flatten().trim());def("falseToken", of("false").flatten().trim());def("nullToken", of("null").flatten().trim());def("stringToken", ref("stringPrimitive").flatten().trim());def("numberToken", ref("numberPrimitive").flatten().trim());[..]} }JSON的示例解析器定義文件(定義了規(guī)則的操作)的摘錄。
PetitParser的解析器定義文件
package org.petitparser.grammar.json;import org.petitparser.utils.Functions;public class JsonParserDefinition extends JsonGrammarDefinition {public JsonParserDefinition() {action("elements", Functions.withoutSeparators());action("members", Functions.withoutSeparators());action("array", new Function<List<List<?>>, List<?>>() {@Overridepublic List<?> apply(List<List<?>> input) {return input.get(1) != null ? input.get(1) : new ArrayList<>();}}); [..]} }有一個用Java編寫的版本,但也有Smalltalk,Dart,PHP和TypeScript的版本。
缺少文檔,但是有示例語法可用。
解析Java的Java庫:JavaParser
有一種特殊情況需要更多注釋:要在Java中解析Java代碼的情況。 在這種情況下,我們必須建議使用一個名為JavaParser的庫。 順便說一下,我們?yōu)镴avaParser做出了巨大貢獻,但這并不是我們建議這樣做的唯一原因。 事實是JavaParser是一個有數(shù)十個貢獻者和數(shù)千個用戶的項目,因此它非常健壯。
功能快速列表:
- 它支持1到9的所有Java版本
- 它支持詞法保留和精美打印:這意味著您可以解析Java代碼,對其進行修改并以原始格式或精美打印將其打印回
- 它可以與JavaSymbolSolver一起使用,從而為您提供符號解析。 即,它了解哪些方法被調(diào)用,引用引用鏈接到哪些聲明,計算表達式的類型等。
說服了嗎 您是否仍想為Java編寫自己的Java解析器?
摘要
用Java進行解析是一個廣泛的話題,而解析器的領(lǐng)域與通常的程序員領(lǐng)域有些不同。 您會發(fā)現(xiàn)直接來自學(xué)術(shù)界的最佳工具,而軟件通常并非如此。 已經(jīng)為論文或研究項目啟動了一些工具和庫。 好處是工具傾向于易于免費使用。 不利的一面是,有些作者更愿意對工具的作用原理進行很好的解釋,而不是對如何使用它們有很好的文檔。 而且,隨著原始作者完成其碩士或博士學(xué)位,一些工具最終被放棄了。
我們傾向于大量使用解析器生成器:ANTLR是我們最喜歡的解析器生成器,并且在JavaParser的工作中我們廣泛使用JavaCC。 我們不是非常使用解析器組合器。 不是因為它們不好,而是因為它們有用途,實際上我們在C#中寫了一篇有關(guān)它的文章 。 但是,對于我們處理的問題,它們通常導(dǎo)致難以維護的代碼。 但是,從一開始它們可能會更容易,因此您可能需要考慮這些。 尤其是直到現(xiàn)在,您已經(jīng)使用正則表達式和手工編寫的半解析器來破解了一些可怕的東西。
我們絕對不能真正地對您說您應(yīng)該使用什么軟件。 對用戶來說最好的可能對其他人不是最好的。 我們都知道,技術(shù)上最正確的解決方案在所有限制條件下可能都不是現(xiàn)實生活中的理想選擇。 但是我們在工作中搜索并嘗試了許多類似的工具,因此類似本文的內(nèi)容可以幫助我們節(jié)省一些時間。 因此,我們想分享我們在Java最佳解析選項方面學(xué)到的知識。
翻譯自: https://www.javacodegeeks.com/2017/06/parsing-java-tools-libraries-can-use.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的用Java解析:您可以使用的所有工具和库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开发商承认游戏《真人快打 1》上市时不支
- 下一篇: 新建区房管局备案查询官网(新建区房管局备