Написание правил синтаксического анализатора, чувствительных к пробелам, при пропуске WS из лексера

У меня проблемы с обработкой пробелов. В следующем отрывке грамматики я настроил лексический анализатор так, чтобы синтаксический анализатор пропускал пробелы:

ENTITY_VAR
    : 'user'
    | 'resource'
    ;

INT : DIGIT+ | '-' DIGIT+ ;
ID : LETTER (LETTER | DIGIT | SPECIAL)* ;
ENTITY_ID : '__' ENTITY_VAR ('_w_' ID)?;

NEWLINE : '\r'? '\n';

WS : [ \t\r\n]+ -> skip; // skip spaces, tabs, newlines

fragment LETTER : [a-zA-Z];
fragment DIGIT : [0-9];
fragment SPECIAL : ('_' | '#' );

Проблема в том, что я хотел бы сопоставить имена переменных в форме ENTITY_ID, чтобы в сопоставленной строке не было пробелов. Было бы достаточно написать его как правило лексера, как я сделал здесь, но дело в том, что я хотел бы сделать это с помощью правила синтаксического анализатора, потому что я хочу иметь прямой доступ к этим двум токенам ENTITY_VAR и ID индивидуально из мой код, а не сжимать их обратно в единый токен ENTITY_ID.

Есть идеи, пожалуйста? В принципе, любое решение, которое позволяло бы мне обращаться напрямую к ENTITY_VAR и ID, подойдет мне, оставив ENTITY_ID как правило лексера или переместив его в синтаксический анализатор.


person Riccardo T.    schedule 30.07.2014    source источник
comment
Возможно, вам помогут лексические режимы? Как только вы наткнетесь на '__', вы переключаете режимы, в которых не пропускаете пробелы?   -  person Bart Kiers    schedule 30.07.2014
comment
Спасибо за ваше предложение. Итак, если я правильно понимаю, я бы написал правило синтаксического анализатора entityVar таким образом, чтобы при сопоставлении с '__' оно переключалось в режим, в котором правило лексера WS отключено?   -  person Riccardo T.    schedule 30.07.2014
comment
Упс, я имел в виду entityId, а не Var   -  person Riccardo T.    schedule 30.07.2014
comment
Режимы лексера не зависят от правил парсера. Когда лексер соответствует '__', он будет переключать режимы, независимо от того, существует ли правило парсера, которое фактически использует токен '__'.   -  person Bart Kiers    schedule 30.07.2014
comment
Не могли бы вы привести пример кода ввода и что бы вы хотели получить?   -  person Onur    schedule 31.07.2014
comment
Старое название @Onur, спасибо. Я исправил это в вопросе :) Образец ввода для ENTITY_ID будет __user_w_something, в то время как я не хотел бы анализировать __user _w_something или любые другие варианты, содержащие пробелы.   -  person Riccardo T.    schedule 31.07.2014
comment
@BartKiers гудит, проблема в том, что я не знаю, где вернуться к режиму по умолчанию, поскольку нет определенного типа токена, который точно отмечает конец ENTITY_ID: это может быть ENTITY_VAR, а также ID.   -  person Riccardo T.    schedule 31.07.2014
comment
@BartKiers У меня нет доступа к опубликованной вами ссылке на лексические режимы. Было бы здорово, если бы вы могли поделиться примером, фантастическим, если бы вы использовали интервал ISO 8601 :-) Я полагаю, вы имели в виду antlr4   -  person Dinesh    schedule 22.09.2017


Ответы (3)


Я могу придумать несколько подходов (не в особом порядке):

  1. Выпустите несколько токенов из правила ENTITY_ID. См. ANTLR4: Как вводить токены для вдохновения.
  2. Разрешить пробелы в парсере и потом проверить
  3. Используйте один токен и разделите код
  4. Используйте единственный токен и измените поток токенов перед передачей его синтаксическому анализатору. Т.е. lex, измените ENTITY_ID токены и разделите их на несколько других токенов, затем передайте этот поток синтаксическому анализатору
  5. Не пропускайте пробелы и при работе с этими «лишними токенами» проверяйте, находятся ли они в ENTITY_ID части (=> это ошибка) или нет (=> игнорировать ошибку).
  6. Не пропускайте пробелы и не добавляйте «WS *» везде в вашей грамматике, где разрешены пробелы (хорошо, если грамматика не слишком велика).
  7. Вставьте предикаты в правило синтаксического анализатора, которое проверяет наличие пробелов между ними.
  8. Создайте такое правило «ловушки»:

    INVALID_ENTITY_ID : '__' WS+ ENTITY_VAR WS? ('_w_' WS? ID)?
                      | '__' WS? ENTITY_VAR WS+ ('_w_' WS? ID)?
                      | '__' WS? ENTITY_VAR WS? ('_w_' WS+ ID)
                      ;
    

    Это приведет к обнаружению недействительных ENTITY_IDs, поскольку они длиннее, чем части, которые в этом случае также будут отдельными токенами.

Я бы выбрал 2, если он не изменяет синтаксический анализ в случае «отсутствия ошибок», т.е. никакой код не интерпретируется иначе, разрешая пробелы.

person Onur    schedule 31.07.2014
comment
В конце концов, я выбрал вариант 3. Любой другой вариант предполагал добавление слишком большого количества сложных изменений в грамматику, которые в данном случае просто того не стоили. Снова проанализировав его с помощью регулярного выражения из java-кода, я смог сохранить грамматику простой и понятной. - person Riccardo T.; 01.08.2014

Насколько мне удалось понять, просмотрев документацию, это не похоже на то, что это возможно.

Кажется, что правила парсера работают только на канале по умолчанию, поэтому я не могу послать WS на channel(HIDDEN), а затем восстановить его только для одного правила парсера.

С другой стороны, автор antlr объясняет здесь что невозможно разбить токены, начиная с версии 4.

Несмотря на то, что мне это совсем не нравится, кажется, что самый быстрый способ - проанализировать его из лексера (как в коде из вопроса), только чтобы снова проанализировать его из Java всей строки.

Тем не менее, любой другой лучший вариант или исправление моих выводов приветствуется.

person Riccardo T.    schedule 30.07.2014

Подключение двух парсеров в своего рода конвейер, как предполагает ваш собственный ответ, - это разумный и простой дизайн / решение, и я почти уверен, что ANTLR способен помочь с этим.

Я не знаю, как далеко продвинулись специалисты ANTLR в своей работе по синтаксическому анализу потока / потока. Но использование двухпроходной стратегии должно быть достаточно эффективным, поскольку первый проход будет просто лексированием обычного языка, который на O(c * N) больше размера ввода с очень маленьким c.

Если вам нужен один проход, который стоит O(k * N) (с большим k), вы можете подумать о PEG, для которых существуют реализации на Java (которые я не пробовал).

person Apalala    schedule 30.07.2014
comment
Спасибо, но строки, которые я буду анализировать с помощью этого решения, будут настолько маленькими, что использование любой библиотеки определенно будет излишним. Меня не интересует, как снова разобрать ENTITY_ID, а скорее как полностью избежать этого, используя один проход с Antlr :) - person Riccardo T.; 31.07.2014