Простая грамматика для свободного владения языком?

Я новичок в antlr4, и я пытаюсь создать грамматику для анализа файлов конфигурации Fluent в дереве. Можете ли вы указать мне, что я делаю неправильно здесь?

Синтаксис fluentd очень похож на синтаксис Apache (псевдо-xml, комментарии в стиле оболочки, kv-пары в теге), например:

# Receive events from 24224/tcp
<source>
  @type forward
  port 24224
</source>

# example
<match>
    # file or memory
    buffer_type    file
    <copy>
      file /path
    </copy>
</match>

Это моя грамматика до сих пор:

grammar Fluentd;

// root element
content: (entry | comment)*;

entry: '<' name tag? '>' (entry | comment | param)* '<' '/' close_ '>';

name: NAME;

close_: NAME;

tag: TAG;

comment: '#' NL;

param: name value NL;

value: ANY;



ANY: .*?;

NL: ('\r'?'\n'|'\n') -> skip;

TAG: ('a'..'z' | 'A'..'Z' | '_' | '0'..'9'| '$' |'.' | '*' | '{' | '}')+;

NAME: ('a'..'z'| 'A..Z' | '@' | '_' | '0'..'9')+;

WS: (' '|'\t') -> skip;

... И это с треском проваливается на приведенном выше вводе:

line 2:2 mismatched input 'Receive' expecting NL
line 3:1 missing NAME at 'source'
line 4:8 mismatched input 'forward' expecting ANY
line 6:2 mismatched input 'source' expecting NAME
line 8:2 mismatched input 'example' expecting NL
line 9:1 missing NAME at 'match'
line 10:6 mismatched input 'file' expecting NL
line 12:2 mismatched input 'match' expecting NAME

person julius    schedule 11.02.2018    source источник


Ответы (1)


Первое, что вы должны понять, это то, что лексер работает независимо от парсера. Лексер просто создает токены, пытаясь сопоставить как можно больше символов. Если два или более правила лексера соответствуют одинаковому количеству символов, правило, определенное первым, «выиграет».

Сказав это, вход source никогда не может быть токенизирован как NAME, поскольку правило TAG также соответствует этому и определено до NAME.

Решением этого может быть:

tag  : SIMPLE_ID | TAG;
name : SIMPLE_ID | NAME;

SIMPLE_ID : [a-zA-Z_0-9]+ ;
TAG       : [a-zA-Z_0-9$.*{}]+ ;
NAME      : [a-zA-Z_0-9@]+ ;

Таким образом, foobar станет SIMPLE_ID, foo.bar станет TAG, а @mu станет NAME.

В вашей грамматике есть и другие неправильные вещи:

  • в вашем лексере вы skipping NL токены, но вы также используете их в правилах парсера: вы не можете этого сделать (поскольку такие токены никогда не будут созданы)

  • ANY: .*?; потенциально может соответствовать пустой строке (которых существует бесконечное количество): правила лексера всегда должны соответствовать хотя бы 1 символу! Однако, если вы измените .*? на .+?, он всегда будет соответствовать только 1 символу, поскольку вы сделали его нежадным (конечный ?). И вы не можете сделать .+, потому что тогда он будет соответствовать всему вводу. Вы должны сделать что-то вроде этого:

    // Use a parser rule to "glue" all single ANY tokens to each other
    any : ANY+ ;
    
    // all other lexer rules
    
    // This must be very last rule!
    ANY : . ;
    

    Если вы не определите ANY в качестве последнего правила, входные данные типа X будут размечены не как TAG, а как ANY (помните мой первый абзац).

  • правило comment: '#' NL; не имеет смысла: комментарий не является #, за которым следует разрыв строки. Я бы ожидал правила лексера для такой вещи:

    COMMENT : '#' ~[\r\n]* -> skip;
    

    И нет необходимости включать в это правило разрыв строки: он уже обрабатывается в NL.

person Bart Kiers    schedule 11.02.2018
comment
Спасибо, Барт. Следуя вашему совету, я добился большего успеха, но, глядя на то, как Fluentd анализирует конфигурацию (с использованием регулярных выражений), я теперь думаю, что использование специального анализатора может быть излишним: github.com/fluent/fluentd/blob/master/lib/fluent/config/ - person julius; 08.03.2018