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