antlr как определить необязательные детали в любом порядке
Предположим, нужна грамматика для разбора следующих шаблонов:
1. REPORT
2. BEGIN
3. QUERY
4. BEGIN
5. AGGREGATION: day
6. DIMENSION: department
7. END
8. END
Где строки № 5 и № 6 являются необязательными, и порядок 2 строк не имеет значения. Как я могу указать это в моем файле грамматики? Ниже мое решение (см. Строку № 12):
1. grammar PRL;
2. report
3. : REPORT
4. BEGIN
5. query
6. END
7. ;
8.
9. query
10. : QUERY
11. BEGIN
12. (aggregation_decl dimension_decl | dimension_decl aggregation_decl)?
13. END
14. ;
Так что это работает, однако выглядит уродливо, и если у меня будет более 2 частей, это станет очень быстро неуправляемым? Любой совет?
2 ответа
Что-то вроде этого? Как правило, на последующем этапе обработки вы должны принудительно установить, что только один элемент существует. В противном случае, как вы видите, грамматика становится громоздкой.
grammar PRL;
report
: REPORT
BEGIN
query
END
;
query
: QUERY
BEGIN
body_decl*
END
;
body_decl :
aggregation_decl dimension_decl
| dimension_decl aggregation_decl;
Как уже упоминалось Адамом: обычно это делается после того, как анализатор создал некое (абстрактное) дерево разбора. Вы просто собираете все типы объявлений следующим образом:
grammar PRL;
report
: REPORT BEGIN query END
;
query
: QUERY BEGIN decl* END
;
decl
: NAME ':' NAME
;
REPORT : 'REPORT';
BEGIN : 'BEGIN';
END : 'END';
QUERY : 'QUERY';
NAME : ('a'..'z' | 'A'..'Z')+;
SPACE : (' ' | '\t' | '\r' | '\n')+ {skip();};
и после этого проверьте наличие дубликатов в decl*
в вашем АСТ.
Но если вы действительно хотите сделать это во время разбора, вам нужно взять левую часть decl
и добавить их в Set
и когда вы наткнетесь на дубликат, создайте исключение предиката:
grammar PRL;
@parser::header {
import java.util.Set;
import java.util.HashSet;
}
report
: REPORT BEGIN query END
;
query
: QUERY BEGIN unique_decls END
;
unique_decls
@init{Set<String> set = new HashSet<String>();}
: (decl {set.add($decl.key)}?)*
;
decl returns[String key]
: k=NAME ':' NAME {$key = $k.text;}
;
REPORT : 'REPORT';
BEGIN : 'BEGIN';
END : 'END';
QUERY : 'QUERY';
NAME : ('a'..'z' | 'A'..'Z')+;
SPACE : (' ' | '\t' | '\r' | '\n')+ {skip();};
{set.add($decl.key)}?
, называемый проверяющими семантическими предикатами, вызовет исключение, когда код внутри него (set.add($decl.key)
) оценивает false
, В этом случае он оценивается как ложный, когда набор уже содержит определенный key
,