Dutow寫道:
或者,我可以每次重新分析所有行,但:
這將是緩慢的 有說明,我不想跑兩次 可以這樣用ANTLR完成,或者如果沒有,用別的東西?
是的,ANTLR可以做到這一點。也許不是開箱即用,但有了一些自定義代碼,它肯定是可能的。您也不需要爲它重新解析整個令牌流。
假設您想要逐行解析非常簡單的語言,其中每行是program
聲明或uses
聲明或statement
。
應該總是以一個program
聲明,其次是零點或多個uses
聲明後跟零個或多個statement
先從。 uses
聲明不能在statement
之後出現,並且不能有多於一個的program
聲明。
爲簡單起見,statement
只是一個簡單的賦值:a = 4
或b = a
。
的ANTLR語法這種語言看起來是這樣的:
grammar REPL;
parse
: programDeclaration EOF
| usesDeclaration EOF
| statement EOF
;
programDeclaration
: PROGRAM ID
;
usesDeclaration
: USES idList
;
statement
: ID '=' (INT | ID)
;
idList
: ID (',' ID)*
;
PROGRAM : 'program';
USES : 'uses';
ID : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
INT : '0'..'9'+;
SPACE : (' ' | '\t' | '\r' | '\n') {skip();};
但是,我們需要添加一對,當然檢查。此外,默認情況下,解析器在其構造函數中使用標記流,但由於我們計劃在解析器中逐行添加標記,因此我們需要在解析器中創建一個新的構造函數。您可以分別在@parser::members { ... }
或@lexer::members { ... }
部分中將自定義成員添加到詞法分析器或解析器類中。我們還將添加一對布爾標誌來跟蹤是否已經發生了program
聲明,並且允許uses
聲明。最後,我們將添加一個process(String source)
方法,該方法爲每個新行創建一個詞法分析器,並將其提供給解析器。
所有這一切都將是這樣的:
@parser::members {
boolean programDeclDone;
boolean usesDeclAllowed;
public REPLParser() {
super(null);
programDeclDone = false;
usesDeclAllowed = true;
}
public void process(String source) throws Exception {
ANTLRStringStream in = new ANTLRStringStream(source);
REPLLexer lexer = new REPLLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
super.setTokenStream(tokens);
this.parse(); // the entry point of our parser
}
}
現在我們的語法裏面,我們將通過幾個gated semantic predicates檢查,如果我們按照正確的順序解析聲明。在解析某個聲明或聲明之後,我們需要翻轉某些布爾標誌來允許或者不允許從此聲明。這些布爾標誌的翻轉是通過每條規則的@after { ... }
部分來完成的(並不奇怪)在之後來自該解析器規則的令牌被匹配。
您最終的語法文件現在看起來是這樣(包括一些System.out.println
的用於調試):
grammar REPL;
@parser::members {
boolean programDeclDone;
boolean usesDeclAllowed;
public REPLParser() {
super(null);
programDeclDone = false;
usesDeclAllowed = true;
}
public void process(String source) throws Exception {
ANTLRStringStream in = new ANTLRStringStream(source);
REPLLexer lexer = new REPLLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
super.setTokenStream(tokens);
this.parse();
}
}
parse
: programDeclaration EOF
| {programDeclDone}? (usesDeclaration | statement) EOF
;
programDeclaration
@after{
programDeclDone = true;
}
: {!programDeclDone}? PROGRAM ID {System.out.println("\t\t\t program <- " + $ID.text);}
;
usesDeclaration
: {usesDeclAllowed}? USES idList {System.out.println("\t\t\t uses <- " + $idList.text);}
;
statement
@after{
usesDeclAllowed = false;
}
: left=ID '=' right=(INT | ID) {System.out.println("\t\t\t " + $left.text + " <- " + $right.text);}
;
idList
: ID (',' ID)*
;
PROGRAM : 'program';
USES : 'uses';
ID : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
INT : '0'..'9'+;
SPACE : (' ' | '\t' | '\r' | '\n') {skip();};
可以測試機智以下類:
import org.antlr.runtime.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
REPLParser parser = new REPLParser();
while(true) {
System.out.print("\n> ");
String input = keyboard.nextLine();
if(input.equals("quit")) {
break;
}
parser.process(input);
}
System.out.println("\nBye!");
}
}
要運行該測試類別,請執行以下操作:
# generate a lexer and parser:
java -cp antlr-3.2.jar org.antlr.Tool REPL.g
# compile all .java source files:
javac -cp antlr-3.2.jar *.java
# run the main class on Windows:
java -cp .;antlr-3.2.jar Main
# or on Linux/Mac:
java -cp .:antlr-3.2.jar Main
正如你所看到的,你只能申報一個program
一次:
> program A
program <- A
> program B
line 1:0 rule programDeclaration failed predicate: {!programDeclDone}?
uses
不能來statement
S:
> program X
program <- X
> uses a,b,c
uses <- a,b,c
> a = 666
a <- 666
> uses d,e
line 1:0 rule usesDeclaration failed predicate: {usesDeclAllowed}?
,你必須用一個program
申報開始:
> uses foo
line 1:0 rule parse failed predicate: {programDeclDone}?
你能舉一個這樣的「看起來像X」輸入的具體例子嗎? – 2011-02-24 22:03:13
我的意思是說,pascal程序的第一行必須是「程序X」,使用聲明(「使用x,y,z」)是可選的,但是如果指定必須在程序聲明之後。因此,在類似帕斯卡的外殼中,第一個有效表達式總是「程序x」; – Dutow 2011-02-24 22:26:43