Вы можете посмотреть на 1 шаг вперед в токене / древовидной структуре древовидной грамматики, используя следующее: CommonTree ahead = (CommonTree)input.LT(1)
, которое вы можете поместить в раздел @init
.
Каждая CommonTree
(реализация по умолчанию Tree
в ANTLR) имеет метод 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
, поэтому в моем примере AST, созданный ref=action ID -> ^(ACTION[$ref.start] action ID)
, может выглядеть следующим образом:
[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']