Antlr

Antlr簡介

ANTLR 語言識別的一個工具 (ANother Tool for Language Recognition ) 是一種語言工具,它提供了一個框架,可以通過包含 Java, C++, 或 C# 動作(action)的語法描述來構造語言識別器,編譯器和解釋器。 計算機語言的解析已經變成了一種非常普遍的工作。使用 Antlr 等識別工具來識別,解析,構造編譯器比手工編程更加容易,同時開發的程序也更易于維護。

在 Java 社區里,除了 Antlr 外,語言識別工具還有 JavaCC 和 SableCC 等。

和大多數語言識別工具一樣,Antlr 使用上下文無關文法描述語言。最新的 Antlr 是一個基于 LL(*) 的語言識別器。在 Antlr 中通過解析用戶自定義的上下文無關文法,自動生成詞法分析器 (Lexer)、語法分析器 (Parser) 和樹分析器 (Tree Parser)。

Antlr 能做什么

ANTLR能夠根據用戶定義的語法文件自動生成詞法分析器和語法分析器,并將輸入文本處理為語法分析樹。這一切都是自動進行的,

所需的僅僅是一份描述該語言的語法文件

ANTLR自動生成的編譯器高效、準備,能夠將開發者從繁雜的編譯理論中解放出來,集中精力處理自己的業務邏輯。ANTRL4引入的自動語法分析樹創建與遍歷機制,極大地提高了語言識別程序的開發效率。時至今日,仍然是Java世界中實現編譯器的不二之選,同時,它也對其他編程語言也提供了支持。

典型應用如下:

1、編程語言處理

識別和處理編程語言是 Antlr 的首要任務,編程語言的處理是一項繁重復雜的任務,為了簡化處理,一般的編譯技術都將語言處理工作分為前端和后端兩個部分。其中前端包括詞法分析、語法分析、語義分析、中間代碼生成等若干步驟,后端包括目標代碼生成和代碼優化等步驟。

Antlr 致力于解決編譯前端的所有工作。使用 Anltr 的語法可以定義目標語言的詞法記號和語法規則,Antlr 自動生成目標語言的詞法分析器和語法分析器;此外,如果在語法規則中指定抽象語法樹的規則,在生成語法分析器的同時,Antlr 還能夠生成抽象語法樹;最終使用樹分析器遍歷抽象語法樹,完成語義分析和中間代碼生成。整個工作在 Anltr 強大的支持下,將變得非常輕松和愉快。

2、文本處理

當需要文本處理時,首先想到的是正則表達式,使用 Anltr 的詞法分析器生成器,可以很容易的完成正則表達式能夠完成的所有工作;除此之外使用 Anltr 還可以完成一些正則表達式難以完成的工作,比如識別左括號和右括號的成對匹配等。

傳統解析器工作原理

如果一個程序能夠分析計算或者執行語句,我們就把它稱之為解釋器(interpreter)。解釋器需要識別出一門特定的語言的所有的有意義的語句,詞組和子詞組。識別一個詞組意味著我們可以將它從眾多的組成部分中辨認和區分出來。

比如我們會把sp=100;識別成賦值語句, 這意味著我們能夠辨識出sp是被賦值的目標,100則是要被賦予的值。我們也都知道我們在學習英語的時候,識別英語語句,需要辨認出一段對話的不同部分,例如主謂賓。在識別成功之后,程序還能執行適當的操作。

識別語言的程序被稱為語法分析器(parser)或者句法分析器(syntax analyzer),syntax是指約束語言中的各個組成部分之間關系的規則。grammar是一系列規則的集合,每條規則表述出一種詞匯結構。ANTLR就是能夠將其轉成如同經驗豐富的開發者手工構建的一般的語法分析器(ANTLR是一個能夠生產其他程序的程序)

1、語法解析器

語法解析器是傳統解析器重要組件,語法解析器工作流程包括詞法分析和語法分析兩個階段。

詞法分析,主要負責將符號文本分組成符號類tokens,把輸入的文本轉換成詞法符號的程序稱為詞法分析器(lexer);

語法解析,目標就是構建一個語法解析樹。語法解析的輸入是tokens,輸出就是一顆語法解析樹。


語法分析示例

語法分析樹的內部節點是?詞組名,這些名字用于識別它們的子節點,并可以將子節點歸類。根節點是比較抽象的一個名字,在這里是?stat(statement)。

2、解析方法?

根據推導策略的不同,語法解析分為LL與LR兩種:LR自低向上(bottom-up)的語法分析方法;LL是自頂向下(top-down)的語法分析方法。兩類分析器各有其優勢,適用不同的場景,很難說誰要更好一些。普遍的說法是LR可以解析的語法形式更多,LL的語法定義更簡單易懂。Antrl就是一種自頂向下的解析器;

Antrl 語法規則文件

以一個計算器的規則文件做示例:


grammar Cal;

prog: stat+;?//一個程序由至少一條語句組成

/*為了以后的運算的方便性,我們需要給每一步規則打上標簽,標簽以”#”開頭,出現在每一條規則的右邊。打上標簽后,antlr會為每一個規則都生成一個事件*/

stat: ID'='expr?';'#Assign?//變量賦值語句

|'print''('expr?')'';'#printExpr???//輸出語句

;

expr: expr op=('*'|'/') expr #MulDiv??//表達式可以是表達式之間乘除

| expr op=('+'|'-') expr #AddSub??//表達式可以是表達式之間加減

| NUM #NUM???//表達式可以是一個數字

| ID #ID?//表達式可以是一個變臉

|'('expr?')'#parens????//表達式可以被括號括起來

;

MUL:'*';

DIV:'/';

ADD:'+';

SUB:'-';

ID: [a-zA-Z][a-zA-Z0-9]*;?//變量可以是數字和字母,但必須以字母開頭

//負數必須要用"()"括起來

NUM: [0-9]+???//正整數

|'(''-'[0-9]+?')'//負整數

| [0-9]+'.'[0-9]+???//正浮點數

|'(''-'[0-9]+'.'[0-9]+?')'//負浮點數

;

WS: [ \t\r\n] -> skip;???//跳過空格、制表符、回車、換行


Antlr文法概念中的一些關鍵概念。文法由一組描述語法的規則組成。其中包括詞法與語法規則。語法規則是以小寫字母組成。如prog,stat。詞法規則由大寫字母組成。如ID:[a-z A-Z]+。通過使用 | 運算符來將不同的規則分割,還可以使用括號構成子規則。

ANTLR兩種遍歷分析樹的機制

默認情況下,ANTLR使用內建的遍歷器訪問生成的語法分析樹,并為每個遍歷時可能觸發的事件生成一個語法分析樹監聽器接口?(ANTLR generates a parse-tree listener interface) 。除了監聽器的方式,還有一種遍歷語法分析樹的方式:訪問者模式(vistor pattern);

Parse-Tree Listeners

為了將遍歷樹時觸發的事件轉化為監聽器的調用,ANTLR提供ParseTreeWalker類。我們可以自行實現ParseTreeListener的接口,在其中填充自己的邏輯。ANTLR為每個語法文件生成一個ParseTreeListener的子類,在該類中,語法的每條規則都有對應的enter方法和exit方法。

層次遍歷(先序遍歷)訪問


層次遍歷(先序遍歷)訪問

監聽器方式的優點在于,回調是自動進行的。我們不需要編寫對語法分析樹的遍歷代碼,也不需要讓我們的監聽器顯式地訪問子節點

Parse-Tree Visitors

有時候,我們希望控制遍歷語法分析樹的過程,通過顯式的方法調用來訪問子節點。語法中的每條規則對應接口中的一個visit方法。

代碼demo

ParseTree tree = ... ;// tree is result of parsingMyVisitor v =newMyVisitor();v.visit(tree);

ANTLR內部為訪問者模式提供的支持代碼會在根節點處調用visitStat方法,接下來,visitStat方法的實現將會調用visit方法,并將所用的子節點作為參數傳遞給它,從而繼續遍歷的過程;

Parse-Tree Visitor

為了更好的使用訪問者模式我們用以下針對每個規則加了標簽的語法文件來說明:


grammar LabeledExpr;//rename to distinguish from Expr.g4

prog:stat+ ;

stat????:????expr NEWLINE? ? ????????????# printExpr

? ? ? ? ? |? ? ?ID '=' expr NEWLINE ???? # assign

? ? ? ? ? |? ? ?NEWLINE????????????????????????# blank

? ? ? ? ? ;

expr????:????expr op=('*'|'/') expr? ? ????? # MulDiv

? ? ? ? ? ?|????expr op=('+'|'-') expr? ? ? ? ? # AddSub

? ? ? ? ? ?|????INT????????????????????????????????????# int

? ? ? ? ? ?|? ID? ? ? ? ? ? ? ? ? ? ? ? ????????????????? # id

? ? ? ? ? ?|'('expr')'? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?# parens

? ? ? ? ? ?;????????

MUL :'*';? ?//assigns token name to'*'used aboveingrammar

DIV :'/';

ADD :'+';

SUB :'-';

ID? :? [a-zA-Z]+ ;//match identifiers

INT :? [0-9]+ ;//match integers

NEWLINE:'\r'?'\n';//returnnewlines to parser (isend-statement? signal)

WS? :? [ \t]+ -> skip ;//toss out whitespace


為不同的備選分支添加的了標簽(#MulDiv/#AddSub),如果沒有標簽,ANTLR是為每條規則來生成方法如果希望每個備選分支都有相應的方法來訪問,就可以像我這樣在右側加上#標簽。

生成的visitor類

自定義訪問器類

自定義vistor

如果進入了?visitAssign方法說明我們進入了標簽#assign,結構很簡單,是一個復制的語句,ID符號內的文本是被賦值的變量,expr所代表的值是要賦值的數。我們對expr的分析樹進行進行分析,我們發現expr的所有的分支都相應的方法可以訪問visitInt、visitId、visitMulDiv、visitAddSub、visitParens,假如進入的分支是#int,把它獲取出來既可,因為?INT代表的就是具體的值


參考:http://www.lxweimin.com/p/dc1b68dfe2d7

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容