你可以使用以下代码在树语法的令牌/树流中向前查看 1 步:
CommonTree ahead = (CommonTree)input.LT(1)
,你可以将其放置在
@init
部分。每个
CommonTree
(ANTLR 中默认的
Tree
实现)都具有一个
getToken()
方法,该方法返回与此树关联的
Token
。每个
Token
都有一个
getLine()
方法,该方法返回此标记的行号。因此,如果执行以下操作:
sentence
@init {
CommonTree ahead = (CommonTree)input.LT(1);
int line = ahead.getToken().getLine();
System.out.println("line=" + line);
}
: assignCommand
| actionCommand
;
你应该能够看到一些正确的行号被打印出来。我说“一些”,因为这不会在所有情况下都按计划进行。让我用一个简单的示例语法来演示:
grammar ASTDemo;
options {
output=AST;
}
tokens {
ROOT;
ACTION;
}
parse
: sentence+ EOF -> ^(ROOT sentence+)
;
sentence
: assignCommand
| actionCommand
;
assignCommand
: ID ASSIGN NUMBER -> ^(ASSIGN ID NUMBER)
;
actionCommand
: action ID -> ^(ACTION action ID)
;
action
: START
| STOP
;
ASSIGN : '=';
START : 'start';
STOP : 'stop';
ID : ('a'..'z' | 'A'..'Z')+;
NUMBER : '0'..'9'+;
SPACE : (' ' | '\t' | '\r' | '\n')+ {skip();};
其树形语法如下:
tree grammar ASTDemoWalker;
options {
output=AST;
tokenVocab=ASTDemo;
ASTLabelType=CommonTree;
}
walk
: ^(ROOT sentence+)
;
sentence
@init {
CommonTree ahead = (CommonTree)input.LT(1);
int line = ahead.getToken().getLine();
System.out.println("line=" + line);
}
: assignCommand
| actionCommand
;
assignCommand
: ^(ASSIGN ID NUMBER)
;
actionCommand
: ^(ACTION action ID)
;
action
: START
| STOP
;
如果您运行以下测试类:
import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
public class Main {
public static void main(String[] args) throws Exception {
String src = "\n\n\nABC = 123\n\nstart ABC";
ASTDemoLexer lexer = new ASTDemoLexer(new ANTLRStringStream(src));
ASTDemoParser parser = new ASTDemoParser(new CommonTokenStream(lexer));
CommonTree root = (CommonTree)parser.parse().getTree();
ASTDemoWalker walker = new ASTDemoWalker(new CommonTreeNodeStream(root));
walker.walk();
}
}
您将看到打印以下内容:
line=4
line=0
你可以看到,
"ABC = 123"
产生了预期的输出(第4行),但
"start ABC"
没有(第0行)。这是因为
action
规则的根是一个
ACTION
标记,而此标记在词法分析器中从未定义,只在
tokens{...}
块中定义。并且由于它在输入中实际上不存在,所以默认情况下将其附加到第0行。如果你想改变行号,你需要向这个所谓的
虚拟ACTION
标记提供一个“参考”标记作为参数,它会使用该标记将属性复制到自身。
因此,如果你将合并语法中的
actionCommand
规则更改为:
actionCommand
: ref=action ID -> ^(ACTION[$ref.start] action ID)
;
行号应该是预期的(第6行)。
请注意,每个解析规则都有一个start
和end
属性,分别是第一个和最后一个标记的引用。如果action
是词法分析规则(比如FOO
),则可以省略其中的.start
:
actionCommand
: ref=FOO ID -> ^(ACTION[$ref] action ID)
;
现在,
ACTION
令牌已经从其指向的任何
$ref
中复制了所有属性,除了令牌类型,当然是
int ACTION
。但这也意味着它复制了
text
属性,因此在我的示例中,通过
ref=action ID -> ^(ACTION[$ref.start] action ID)
创建的AST可能如下所示:
[text=START,type=ACTION]
/ \
/ \
/ \
[text=START,type=START] [text=ABC,type=ID]
当然,这是一个适当的AST,因为节点类型是唯一的,但它使得调试混乱,因为ACTION和START共享相同的.text属性。您可以将所有属性复制到一个“虚拟”的标记中,除了.text和.type,方法是提供第二个字符串参数,例如:
actionCommand
: ref=action ID -> ^(ACTION[$ref.start, "Action"] action ID)
;
如果您现在再次运行相同的测试类,将会看到以下内容:
line=4
line=6
如果您检查生成的树,它将是这样的:
[type=ROOT, text='ROOT']
[type=ASSIGN, text='=']
[type=ID, text='ABC']
[type=NUMBER, text='123']
[type=ACTION, text='Action']
[type=START, text='start']
[type=ID, text='ABC']
getTreeNodeStream().getTreeAdaptor().getToken( $sentence.start ).getLine()
,你做的与我差不多! :) - Bart Kiers