Java 交互式Antlr
我正在尝试使用antlr编写一个简单的交互式(使用System.in作为源代码)语言,但我对它有一些问题。我在web上找到的示例都使用每行循环,例如:Java 交互式Antlr,java,parsing,antlr,Java,Parsing,Antlr,我正在尝试使用antlr编写一个简单的交互式(使用System.in作为源代码)语言,但我对它有一些问题。我在web上找到的示例都使用每行循环,例如: while(readline) result = parse(line) doStuff(result) 但是如果我写的东西像pascal/smtp/etc,第一行看起来像X requirement呢?我知道它可以在doStuff中检查,但我认为逻辑上它是语法的一部分 或者,如果一个命令被拆分为多行怎么办?我可以试试 while(rea
while(readline)
result = parse(line)
doStuff(result)
但是如果我写的东西像pascal/smtp/etc,第一行看起来像X requirement呢?我知道它可以在doStuff中检查,但我认为逻辑上它是语法的一部分
或者,如果一个命令被拆分为多行怎么办?我可以试试
while(readline)
lines.add(line)
try
result = parse(lines)
lines = []
doStuff(result)
catch
nop
但有了这个,我也隐藏了真正的错误
或者我每次都可以重新分析所有行,但是:
这可以用ANTLR来完成吗?如果不是,可以用其他方法来完成吗?如果您使用System.in作为源,这是一个输入流,为什么不让ANTLR在读取时标记输入流,然后解析标记呢?您必须将它放在doStuff中 例如,如果您正在声明一个函数,解析将返回一个函数,对吗?没有身体,那很好,因为身体会晚一点来。你会做大多数人都会做的事 杜托写道: 或者我每次都可以重新分析所有行,但是: 会很慢的 有些说明我不想运行两次 这可以用ANTLR来完成吗?如果不能,可以用其他方法来完成吗 是的,ANTLR可以做到这一点。也许不是开箱即用的,但是有一点自定义代码,这当然是可能的。您也不需要为它重新解析整个令牌流 假设您想逐行解析一种非常简单的语言,其中每一行要么是
程序
声明,要么是使用
声明,要么是语句
它应该总是以程序
声明开始,然后是零个或多个使用
声明,然后是零个或多个语句
s<代码>使用声明不能在语句
s之后,并且不能有多个程序
声明
为简单起见,语句
只是一个简单的赋值: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{…}
节中,可以在lexer或parser类中添加自定义成员。我们还将添加两个布尔标志,以跟踪程序
声明是否已经发生,以及是否允许使用
声明。最后,我们将添加一个process(stringsource)
方法,该方法为每一行创建一个lexer,该lexer将被提供给解析器
所有这些看起来都像:
@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
}
}
现在在语法中,我们将检查几个是否以正确的顺序解析声明。在解析某个声明或语句之后,我们希望从那时起翻转某些布尔标志以允许或不允许声明。这些布尔标志的翻转是通过每个规则的@after{…}
部分完成的,该部分在匹配来自该解析器规则的标记后执行(毫不奇怪)
现在,您的最终语法文件如下所示(包括一些用于调试的System.out.println
):
要运行此测试类,请执行以下操作:
# 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 A
program <- A
> program B
line 1:0 rule programDeclaration failed predicate: {!programDeclDone}?
您必须从
程序
声明开始:
> uses foo
line 1:0 rule parse failed predicate: {programDeclDone}?
下面是一个如何解析来自
系统的输入的示例。在
中,无需首先一次手动解析一行,也无需在语法上做出重大妥协。我使用的是ANTLR 3.4。ANTLR 4可能已经解决了这个问题。不过,我仍然在使用Antlr3,也许其他有这个问题的人也在使用
在进入解决方案之前,我遇到了一些障碍,这些障碍使这个看似微不足道的问题难以解决:
- 从
派生的内置ANTLR类预先消耗整个数据流。显然,交互模式(或任何其他不确定长度的流源)不能提供所有数据CharStream
- 内置的
和派生类不会以跳过的或非通道令牌结束。在交互式设置中,这意味着当前语句不能结束(因此不能执行),直到在使用其中一个类时使用下一个语句或BufferedTokenStream
的第一个标记李>EOF
- 在下一个语句开始之前,语句本身的结尾可能是不确定的李>
statement: 'verb' 'noun' ('and' 'noun')*
;
WS: //etc...
无法以交互方式解析单个语句
(并且只能解析单个语句
)。必须启动下一个语句
(即在输入中点击“动词”),或者必须修改语法以标记语句的结尾,例如使用;”代码>
- 我还没有找到用我的解决方案管理多通道lexer的方法。它不会伤害我,因为我可以用
skip()
替换我的$channel=HIDDEN
,但它仍然是一个值得一提的限制
- 语法可能需要一个新规则来简化交互式解析李>
例如,我的语法的正常切入点是以下规则:
script
: statement* EOF -> ^(STMTS statement*)
;
我的交互式会话无法在脚本
规则下启动,因为它在EOF
之前不会结束。但是它也不能从语句
开始,因为我的树解析器可能会使用STMTS
因此,我专门为您介绍了以下规则
> uses foo
line 1:0 rule parse failed predicate: {programDeclDone}?
statement: 'verb' 'noun' ('and' 'noun')*
;
WS: //etc...
script
: statement* EOF -> ^(STMTS statement*)
;
interactive
: statement -> ^(STMTS statement)
;
interactive_start
: first_line
;
public class MyInputStream extends ANTLRStringStream {
private InputStream in;
public MyInputStream(InputStream in) {
super(new char[0], 0);
this.in = in;
}
@Override
// copied almost verbatim from ANTLRStringStream
public void consume() {
if (p < n) {
charPositionInLine++;
if (dataAt(p) == '\n') {
line++;
charPositionInLine = 0;
}
p++;
}
}
@Override
// copied almost verbatim from ANTLRStringStream
public int LA(int i) {
if (i == 0) {
return 0; // undefined
}
if (i < 0) {
i++; // e.g., translate LA(-1) to use offset i=0; then data[p+0-1]
if ((p + i - 1) < 0) {
return CharStream.EOF; // invalid; no char before first char
}
}
// Read ahead
return dataAt(p + i - 1);
}
@Override
public String substring(int start, int stop) {
if (stop >= n) {
//Read ahead.
dataAt(stop);
}
return new String(data, start, stop - start + 1);
}
private int dataAt(int i) {
ensureRead(i);
if (i < n) {
return data[i];
} else {
// Nothing to read at that point.
return CharStream.EOF;
}
}
private void ensureRead(int i) {
if (i < n) {
// The data has been read.
return;
}
int distance = i - n + 1;
ensureCapacity(n + distance);
// Crude way to copy from the byte stream into the char array.
for (int pos = 0; pos < distance; ++pos) {
int read;
try {
read = in.read();
} catch (IOException e) {
// TODO handle this better.
throw new RuntimeException(e);
}
if (read < 0) {
break;
} else {
data[n++] = (char) read;
}
}
}
private void ensureCapacity(int capacity) {
if (capacity > n) {
char[] newData = new char[capacity];
System.arraycopy(data, 0, newData, 0, n);
data = newData;
}
}
}
MyLexer lex = new MyLexer(new MyInputStream(System.in));
TokenStream tokens = new UnbufferedTokenStream(lex);
//Handle "first line" parser rule(s) here.
while (true) {
MyParser parser = new MyParser(tokens);
//Set up the parser here.
MyParser.interactive_return r = parser.interactive();
//Do something with the return value.
//Break on some meaningful condition.
}