Antlr(DSL)

Antlrhtml

Name:ANother Tool for language for Language Recognitionjava

Site:    node

https://github.com/antlr/git

https://theantlrguy.atlassian.net/wiki/display/ANTLR3/ANTLR+v3+documentationgithub

http://www.antlr3.org/grammar/list.html算法

http://www.crifan.com/files/doc/docbook/antlr_tutorial/release/pdf/antlr_tutorial.pdfexpress

 

做用:生成某種語言的Lexer, Parser, Tree Walker or Lexer&Parser的combinorapi

用例: Hibernate解析HQL閉包

           Spring解析 ELide

           Gemfire(or Geode)解析OQL

版本:3.3(3.3其實是用2.7依據Antlr.g grammar文件生成的parser)

(由這個parser來解析咱們的grammar 文件,而後由它的另外一個library StringTemplate 來生成咱們的parser 或者lexer)

 

輸入:特定語言A的文法文件  (.g文件)

輸出:特定語言A的解析程序(能夠是Java C# C++ 等等)

 

文法文件:通常包括header塊、options塊、文法分析器類(parser)及規則定義、詞法分掃描器類(lexer)及token定義。其中最爲重要的是規則和token的定義。  規則的定義形式和編譯理論中的擴展巴科斯範式(EBNF)極爲類似,包括規則名、規則體、一個用做結束標誌的分號和異常處理部分(可省略)

規則的名字必須是小寫字母開始,而token的名字則必須是大寫字母開始。

.g文件格式注意順序固定,而且rule在最後部分:

     optionSpec
     tokensSpec
     attributeScopes
     actions
     rule1 : ...  | ;
     rule2 :      | ;

 

基礎知識

ANTLR生成的解析器叫作遞歸降低解析器(recursive-descent parser),屬於自頂向下解析器(top-down parser)的一種。顧名思義,遞歸降低指的就是解析過程是從語法樹的根開始向葉子(token)遞歸,比較酷的是代碼的調用圖能與樹結點對應上。會針對每個rule 都會生成 一個 function , 若是是最終符號會調用 match() 方法。 

ANTLR爲每一個Rule都會生成一個Context對象,它會記錄識別時的全部信息。ANTLR提供了Listener和Visitor兩種遍歷機制。Listener是全自動化的,ANTLR會主導深度優先遍歷過程,咱們只需處理各類事件就能夠了。而Visitor則提供了可控的遍歷方式,咱們能夠自行決定是否顯示地調用子結點的visit方法。

 

LL(K)文法    

    LL文法是自上而下的分析法,從文法的開始符號出發,或是說從樹根開始,向下構造語法樹,知道創建每一個樹葉。也叫遞歸降低分析法。

 非肯定的自上而下

    本質上就是從特定的文法符號開始進行窮舉,直到找到匹配的字符串(合法輸入)或窮舉結束(不合法輸入)。每一步都是對當前句型的最左非終端(一般是表達始終的大寫字母),當有多個符號時,就只能逐個嘗試,在嘗試失敗時,固然會回溯到原先字符串,故稱帶回溯的分析方法。效率低。

 非肯定的下推自動機

    輸入字符串,讀頭,有窮狀態自動機,先進後出下推棧。本質是輸入後使得狀態機到達某個狀態

 文法如何消除左遞歸:

    1.用擴展的BNF巴科斯範式

        用{}表示出現0~n次
        用[]表示出現0或1次
        用()表示隔離公共因子A->x(y|w|..|z)

    2.直接改寫,消除左遞歸

        一句U  →  UxIy
        改成U  →  yU1
        U1 →  xU1|null

LL(k)文法是上下文無關文法的一個真子集

LL(k)文法也是容許採用肯定的從左至右掃描(輸入串)和自上而下分析技術的最大一類文法。    

LR文法是自下而上的分析法,從給定的輸入串開始,或是說從語法書的末端開始,向上規約,直至根節點。也叫算符優先分析法

在 Anltr 中,算法的優先級須要經過文法規則的嵌套定義來體現

在 Antlr 中語法定義和詞法定義經過規則的第一個字符來區別, 規定語法定義符號的第一個字母小寫,而詞法定義符號的第一個字母大寫。

Antlr 支持多種目標語言,能夠把生成的分析器生成爲 Java,C#,C,Python,JavaScript 等多種語言,默認目標語言爲 Java,經過 options {language=?;} 來改變目標語言。

ANTLR的語法文件使用擴展巴科斯範式EBNF描述,記得編譯原理的用起來很是簡單,須要進一步瞭解的是怎麼構造本身的recognizer和translator。不少的語法不須要從頭寫,一方面不少語言標準中基本都使用EBNF描述,另外一方面ANTLR網站http://www.antlr.org/grammar/list上有大量寫好的語法文件,能夠參考使用。

巴科斯範式擴展符號 EBNF

() : 產生式組合
? 
: 產生式出現0或1次
* 
: 0或屢次
+ 
: 1或屢次
.  
: 任意一個字符
~ 
: 不出現後面的字符
.. 
: 字符範圍
能夠參考http://www.cl.cam.ac.uk/~mgk25/iso-ebnf.html

整數定義:

integer
: (HEX_PREFIX | OCTAL_PREFIX)? DIGITS; //能夠有一個16進制或8進制前綴,沒有則爲10進制定義
HEX_PREFIX:
'0x'; //16進制前綴
OCTAL_PREFIX:
'0o'; //8進制前綴
DIGITS:
'1'..'9' '0'..'9'*; //第一個字符必須爲1-9,後面能夠是任意多個0-9字符

多行註釋符號/*和*/的定義:

ML_COMMENT : '/*' (
options {greedy=false;} : . )* '*/' ;
/*開頭,*/結束,(.)*表示中間能夠有任意多個字符,options
{greedy=false;}是一個謂詞選項,告訴分析器不要採用貪婪模式,即匹配到隨後出現的第一個*/就結束,而不是試圖去匹配輸入字符流中的最後一個*/。

 

左遞歸、右遞歸 left/right recursion

若是一個產生式在最左開始位置包含它本身,叫左遞歸,例如exp: A | exp ',' A。而exp: A ',' exp | A則是右遞歸。用實際例子來看。

1. 右遞歸

例如實現一個表達式: +8結果爲9;++8結果爲10;+++8結果爲11,以此類推。右遞歸語法爲:

expr: PLUS expr|INT;
INT: '1'..'9''0'..'9'*;
PLUS: '+';

用C#示例的方法來運行這個例子的語法文件內容以下:

left returns [int value] : e=expr { $value = $e.value; };
expr returns [int value] :
    PLUS e=expr { $value=$e.value+1; }
    | INT { $value=int.Parse( $INT.text ); };
INT: '1'..'9' '0'..'9'*;
PLUS: '+';

有個理解上容易產生歧義的地方,即expr應當解析成expr: (PLUS expr) | INT;仍是expr: PLUS (expr | INT);?應當是前面這種方式。

2. 左遞歸

例如實現表達式: 8+結果爲9;8++結果爲10;8+++結果爲11,以此類推。左遞歸語法爲:

expr: expr|INT PLUS;
INT: '1'..'9''0'..'9'*;
PLUS: '+';

ANTLR不支持左遞歸,上面的語法在生成時會報錯: error(210):  The following sets of rules are mutually left-recursive [expr]。

將這個語法改成下面這樣,就不是左遞歸了。

expr: INT PLUS*;
INT: '1'..'9''0'..'9'*;
PLUS: '+';

 

零散的概念

語法多義性: 語法設計最值得關注的問題。第一點是人的思惟對語法描述理解的歧義,與語法解釋器的實際結果不一致,例如上面提到
的expr的問題。另外就是語法描述自己邏輯上存在多義性,即對一樣的輸入能夠解釋成多種結果,它們都符合語法描述的規則。

lexer:詞法分析器,從輸入字符流解析出詞彙序列(tokens)。

parser:語法解析器,對詞彙進行語法分析,生成語法樹(抽象語法樹AST)。

EBNF的語法不區分詞法分析和語法分析,對應的只有終結符、非終結符,終結符描述輸入,非終結符描述輸入所表達的樹結構。ANTLR
使用詞法分析器識別終結符,使用語法分析器分析非終結符(生成的分析器代碼文件有兩個,一個是詞法分析器***Lexer,一個是語法
分析器***Parser),並要求詞法規則所有以大寫開始,語法規則所有以小寫開始。對每個語法規則,最終都必須以詞法規則結束,
不然是一個無效語法,生成時會報錯。

 

ANTLR生成的分析器代碼中,語法規則都會有一個同名的方法,而詞法規則的名稱則跟語法文件給出的不同。若是須要使用這些詞法規則,可取的方法之一是定義一個語法規則與之對應;另外就是定義一個符號表,例如tokens{...}。定義符號表的優勢是會優先匹配符號表,例如一些關鍵字等,能夠避免他們被其它規則匹配上。

關於規則的定義順序,語法文件中先出現的規則具備優先匹配的做用。

ANTLRv3.g 

grammarDef
      :   DOC_COMMENT?
    ('lexer'  {gtype=LEXER_GRAMMAR;}    // pure lexer
    |   'parser' {gtype=PARSER_GRAMMAR;}   // pure parser
    |   'tree'   {gtype=TREE_GRAMMAR;}     // a tree parser
    |     {gtype=COMBINED_GRAMMAR;} // merged parser/lexer
    )
    g='grammar' id ';' optionsSpec? tokensSpec? attrScope* action*
    rule+
    EOF
    -> ^( {adaptor.create(gtype,$g)}
               id DOC_COMMENT? optionsSpec? tokensSpec? attrScope* action* rule+
             );
)

 

Lexer: 文法分析器類。主要用於把讀入的字節流根據規則分段。既把長麪條根據你要的尺寸切成一段一段,並不對其做任何修改

類型名:匹配的具體規則

 

Parser: 解析器類。主要用於處理通過 Lexer 處理後的各段。

起始規則名: 
規則實例名:類型名或規則名 
{Java 語句……; }; 
……

起始規則名:任意。 

規則實例名:就象 Java 中「 String s ;」的 s 同樣。規則實例名用於在以後的 JAVA 語句中調用。 

類型名或規則名:能夠是在 Lexer 中定義的類型名,也能夠是 Parser 中定義的規則名。感受就像是 int 與 Integer 的區別。 

Java 語句:指當知足當前規則時所執行的語句。 Antlr 會自動嵌入生成的 java 類中。

 

 

實踐:

1)在此法分析中,好比要描述一個「>=」與」>」時,若是用 

BEQUAL:('>''=');  
BIGER:」>」;


當語法文件進行此法分析的時,當掃描到一個」>」形式時,不知道是將其當BEQUAL仍是當BIGER符號處理,即出現了衝突,那麼能夠採用如下這種形式定義: 
 

BEQUAL:('>''=')=>('>''=')|'>'{ $setType(BIGER); };//它的形式爲: (...)=>(...)|(...)。這至關於通常語言中的三元表達式:(1)?(2):(3)。若是式1爲真,則返回式2的值,不然返回式3的值。

 

2)在ANTLR中一個規則至關與JAVA語言中的一個函數,所以它能夠有傳參和返回值,例如: 

expr [HashMap hm] returns [String s]
//即至關於JAVA中的: 
public String expr(HashMap hm){…}

 

3) ANTLR中能夠內嵌生成的目標語言 

{import java.lang.Math;}

 

4)標點符號和關鍵字

符號   描述  
(...)  子規則  
(...)*  閉包子規則(零和多個)
(...)+  正閉包子規則(一個和多個)
(...)?  可選(零個和一個)
{...}  語義動做  [...]  規則參數
{...}?  語義謂詞 
(...)=>  語法謂詞  
|    可選符  
..  範圍符
~  非  
.  通配符  
=  賦值   
:  標號符, 規則開始 
;    規則結束  
<...>  元素選項  
class  語法類  
extends  指定語法基類  
returns  指定返回類型  
options  options 節  
tokens   tokens 節  
header  header 節  
tokens   token 定義節

!		do not include node or subtree (if referencing a rule) in subtree
^		make node root of subtree created for entire enclosing rule even if nested in a subrule

 

5)規則引用   以小寫字母開頭的標識符是爲ANTLR的語法規則。接下來的字符能夠是任意字母,數字或下劃線。詞法規則不能引用語法規則。詞法規則以大寫字母開頭。

6)動做.   在<>尖括號中的字符序列是語義動做(多是嵌套的)。在字符串和字符中的尖括號不是動做分隔符。

7)動做參數   在[ ]方括號中的字符序列是動做參數(多是嵌套的)。在字符串和字符中的方括號不是動做分隔符。在[]中的參數是用被生成的語言的語法定義的,而且用逗號分開。

codeBlock  
[int scope, String name] // input arguments 
returns [int x]          // return values
 : ...
 // pass 2 args, get return 

testcblock 
{int y;}  
: y=cblock[1,"John"]    ;

8)header節

一個header節包含了一些將直接被替換到輸出的語法分析器中的源碼,這些源碼將在全部的ANTLR生成的代碼以前。這個主要用在C++的輸出中,由於C++須要一些元素在引用以前必須被聲明。在Java中,這能夠用來爲最後的語法分析器指定一些包文件。一個header節看起來像下面這樣:

  header {    source code in the language generated by ANTLR;  }

 header 節是語法文件的第一個節。根據選擇的目標語言的不一樣,不一樣類型header節都是可能出現的

9)典型的詞法解析器

{optional class code preamble } 
class YourLexerClass extends Lexer; 
options 
tokens  
{ optional action for instance vars/methods }
lexer rules..

10)典型的語法分析器

{ optional class code preamble }  
class YourParserClass extends Parser; 
options 
tokens  
{ optional action for instance vars/methods }  
parser rules...

11)典型的樹分析器

{ optional class code preamble }  
class YourTreeParserClass extends TreeParser; 
options 
tokens  
{ optional action for instance vars/methods } 
tree parser rules...

12)關鍵字

Keyword  |  Description
---------+--------------------------------------------------------
scope    |  Dynamically-scoped attribute
fragment |  lexer rule is a helper rule, not real token for parser
lexer    |  grammar type
tree     |  grammar type
parser   |  grammar type
grammar  |  grammar header
returns  |  rule return value(s)
throws   |  rule throws exception(s)
catch    |  catch rule exceptions
finally  |  do this no matter what
options  |  grammar or rule options
tokens   |  can add tokens with this; usually imaginary tokens
import   |  import grammar(s)

13)fragment的字面意思,就是片斷,就是用於別的,完整的總體,所調用的;

其自己不能單獨做爲一個token(標示,只能被別人調用,使用)

其含義有點相似於:

inline函數,達到直接替換的效果

結構體或類的私有變量(不被外界所訪問,僅供本身(文件內部)所使用)

14)Options

language	The target language for code generation. Default is Java. See Code Generation Targets for list of currently supported target languages.
tokenVocab	Where ANTLR should get predefined tokens and token types. Tree grammars need it to get the token types from the parser that creates its trees. TODO: Default value? Example?
output 		The type of output the generated parser should return. Valid values are AST and template. TODO: Briefly, what are the interpretations of these values? Default value?
ASTLabelType Set the type of all tree labels and tree-valued expressions. Without this option, trees are of type Object. TODO: Cross-reference default impl (org.antlr.runtime.tree.CommonTree in Java)?
TokenLabelType	Set the type of all token-valued expressions. Without this option, tokens are of type org.antlr.runtime.Token in Java (IToken in C#).
superClass	Set the superclass of the generated recognizer. TODO: Default value (org.antlr.runtime.Parser in Java)?
filter		In the lexer, this allows you to try a list of lexer rules in order. The first one that matches, wins. This is the token that nextToken() returns. If nothing matches, the lexer consumes a single character and tries the list of rules again. See Lexical filters for more.
rewrite		Valid values are true and false. Default is false. Use this option when your translator output looks very much like the input. Your actions can modify the TokenRewriteStream to insert, delete, or replace ranges of tokens with another object. Used in conjunction with output=template, you can very easily build translators that tweak input files.
k		Limit the lookahead depth for the recognizer to at most k symbols. This prevents the decision from using acyclic LL* DFA.
backtrack	Valid values are true and false. Default is false. Taken from http://www.antlr.org:8080/pipermail/antlr-interest/2006-July/016818.html : The new feature (a big one) is the backtrack=true option for grammar, rule, and block that lets you type in any old crap and ANTLR will backtrack if it can't figure out what you meant. No errors are reported by antlr during analysis. It implicitly adds a syn pred in front of every production, using them only if static grammar LL* analysis fails. Syn pred code is not generated if the pred is not used in a decision. This is essentially a rapid prototyping mode. It is what I have used on the java.g. Oh, it doesn't memoize partial parses (i.e. rule parsing results) during backtracking automatically now. You must also say memoize=true. Can make a HUGE difference to turn on.
memoize		Valid values are true and false. When backtracking, remember whether or not rule references succeed so that the same input position cannot be parsed more than once by the same rule. This effectively guarantees linear parsing when backtracking at the cost of more memory. TODO: Default value (false)?

15)Rules

T				Token reference. An uppercase identifier; lexer grammars may use optional arguments for fragment token rules.
T<node=V> or T<V>	Token reference with the optional token option node to indicate tree construction note type; can be followed by arguments on right hand side of -> rewrite rule
T[«args»]		Lexer rule (token rule) reference. Lexer grammars may use optional arguments for fragment token rules.
r [«args»]		Rule reference. A lowercase identifier with optional arguments.
'«one-or-more-char»'	String or char literal in single quotes. In parser, a token reference; in lexer, match that string.
{«action»}		An action written in target language. Executed right after previous element and right before next element.
{«action»}?		Semantic predicate.
{«action»}?=>	Gated semantic predicate.
(«subrule»)=>	Syntactic predicate.
(«x»|«y»|«z»)	Subrule. Like a call to a rule with no name.
(«x»|«y»|«z»)?	Optional subrule.
(«x»|«y»|«z»)*	Zero-or-more subrule.
(«x»|«y»|«z»)+	One-or-more subrule.
«x»?			Optional element.
«x»*			Zero-or-more element.
«x»+			One-or-more element.

16)Other說明

1. grammar SimpleCalc;,定義語法名稱。語法文件(.g文件)名稱必須與這裏指定的名稱一致。默認狀況生成的語法分析器類名爲"語法名稱"+Parser,詞法分析器類名爲"語法名稱"+Lexer。
2. options {...},定義全局配置參數,設置ANTLR生成過程當中的一些控制選項。
3. tokens {...},定義全局的符號表。
4. @members {...},這裏面給出的代碼將放入到生成的語法分析器類中,做爲分析器類的成員屬性、方法等。
   示例中爲分析器添加了一個Main方法,免得再格外寫一個測試類。
   Main方法先從命令行讀取輸入字符,將輸入字符傳給生成的詞法分析器SimpleCalcLexer,獲得詞彙序列CommonTokenStream。接下來使用生成的語法分析器SimpleCalcParser對詞彙序列進行分析,在生成語法樹的過程當中直接由expr()方法返回計算結果。
5. 語法規則。
   把語法規則中的{...}、[...]等相關ANTLR的Action去掉就是EBNF。
   ANTLR生成的語法解析器代碼中,對應每一個語法規則都會生成一個方法,這個方法完成對應語法規則的分析邏輯。{...}、[...]等就是咱們本身的recognizer、translator須要的一些額外控制(Action),分析匹配的邏輯由ANTLR完成,咱們添加的這些控制就是對匹配到的結果怎樣進行處理,是生成一個語法樹,再由其它程序對語法樹進行翻譯,仍是直接結合ANTLR的分析過程進行翻譯轉換或計算處理等,由咱們進行控制。示例中是直接進行計算求值。
   returns [...]告訴ANTLR生成的方法須要返回什麼內容,[int value]表示返回值類型爲int,名字叫作value,就是聲明瞭一個變量,用它來返回。在方法體裏面,咱們經過{...}中的內容進行求值運算,並把結果設置給value。{...}咱們能夠看做是一個宏,或者是一個模板,在裏面咱們可使用ANTLR在代碼生成時內置的一些變量/對象,它們以$開始(象StringTemplate語法,但不須要對應的$進行閉合),這些變量在代碼生成過程當中ANTLR爲咱們設置好。其它的代碼保持不變,放入到方法的相應位置上。
6. 詞法規則。
   { $channel = HIDDEN; }。默認狀況下ANTLR在詞法分析器和語法分析器之間使用兩個通道通信,一個default和一個hidden。語法分析器監聽default通道接收詞彙序列,因此若是將某個詞彙發送到hidden通道,這個詞彙就會被語法分析器忽略掉。示例中將回車換行等空白字符都過濾掉,不進行語法分析。
   fragment: 對詞法規則有效(沒有看到對語法規則有做用),它在生成的語法樹上不會有對應的節點,便可以這樣理解,它是咱們在語法中定義的一個宏,能夠被其它語法規則調用,但它不會成爲最終語法樹上的節點。
本站公眾號
   歡迎關注本站公眾號,獲取更多信息