Действия Antlr4 и предикаты во фрагментах лексера

Я пытаюсь создать правила лексера для динамически определяемого разделителя пакетов в Antlr4. Это поддерживает два варианта использования:

  • различные системы баз данных определяют свои собственные разделители пакетов (например, 'go', ';' '/')

  • Я также хочу разрешить определяемые пользователем пакетные разделители, которые могут иметь длину до 2 символов и потенциально могут быть любыми, но для этого примера давайте предположим, что они являются символами ascii.

Таким образом, для целей этого примера пакетные разделители — это любая строка символов, которая находится на отдельной строке и соответствует известному в настоящее время пакетному разделителю. Есть несколько более сложных альтернатив, но я хочу сделать это простым, потому что речь идет о действиях и семантических предикатах во фрагментах лексера, а не о пакетных разделителях.

Итак, предположим, что я уже определил правило лексера под названием ALPHA, которое соответствует любому алфавиту верхнего или нижнего регистра. Кроме того, предположим, что я только пытаюсь сопоставить '\r'?'\n'<some-upto-two-char-string>'\r'?'\n', то есть пакетный разделитель в отдельной строке, и никаких других пробелов, с которыми можно бороться

Я определяю следующие правила лексера:

    BATCH_SEPARATOR:
     NEWLINE ALPHA (ALPHA)? NEWLINE
    ;
    NEWLINE: '\r'?'\n';

Это правило хорошо работает в большинстве ситуаций, за исключением того, что оно не учитывает динамическое сопоставление кандидата входного разделителя пакетов с допустимым значением разделителя пакетов. Таким образом, хотя он будет успешно лексировать 'go', ';' и т. д., он будет неправильно лексировать 'IN', 'AS' и т. д. в качестве разделителей пакетов, когда они появляются на отдельной строке как часть оператора SELECT или CREATE FUNCTION.

Итак, теперь я делаю следующий шаг проверки фактического совпадения символов и определяю метод с именем isValidBatchSeparator() в разделе @lexer::members. Этот метод по существу сравнивает известный разделитель пакетов (установленный приложением) с _input.LA(-1) и _input.LA(1), что выглядит примерно так (в псевдокоде):

private char[] _batchSeparator; // assume it is already set to some value and this array has at least size 1
public boolean isValidBatchSeparator()
{
   if batchSeparatorLength > 1
      if _batchSeparator[0] == (ignore case) _input.LA(-1) 
         && _batchSeparator[1] == (ignore case) _input.LA(1)
         return true
   else
    {
      if _batchSeparator[0] == (ignore case) _input.LA(-1)
      return true
    }
   return false

}

Итак, теперь я пишу свои правила лексера как:

BATCH_SEPARATOR:
  NEWLINE BATCH_SEP_INNER NEWLINE
;
fragment BATCH_SEP_INNER:
  ALPHA (ALPHA)? {isValidBatchSeparator()}?
;

Кажется, это правильно транспилируется.

Я могу выполнить код и убедиться, что семантический предикат действительно вводится и метод возвращает правильное значение. Однако такой ввод, как '\r\nGO\r\n', не лексируется как BATCH_SEPARATOR. Вместо этого где-то позже в коде у меня есть определение для IDENTIFIER, которое в общем определяет идентификаторы как набор символов, который как бы перехватывает эту строку, когда она не соответствует правилу BATCH_SEPARATOR. Таким образом, очевидно, что семантические предикаты, применяемые к фрагментам, отличаются от семантических предикатов, применяемых к правилам лексера, не относящимся к фрагментам.

Итак, я удаляю фрагмент из определения правила лексера и делаю BATCH_SEP_INNER гражданином первого класса, но снова мой семантический предикат лексера меня подводит, и хотя код сематического предиката, кажется, срабатывает и возвращает правильные значения, я все еще вижу '\r\nGO\r\n' в лексике как IDENTFIER ( даже не BATCH_SEP_INNER)

Я попробовал некоторые другие вещи, такие как применение семантического предиката к BATCH_SEPARATOR вместо BATCH_SEP_INNER. Проблема здесь в том, что _input.LA(-1) и _input.LA(1) теперь соответствуют '\r' и '\n', и нет простого способа добраться до ascii, который фактически представляет разделитель пакетов. Например, в более сложных ситуациях, когда также есть пробелы или есть несколько NEWLINE перед разделителем пакетов ascii.

Таким образом, применение этого семантического предиката к BATCH_SEPARATOR всегда будет не совпадать, и моя входная строка не будет корректно лексироваться.

Я также попытался разделить isValidBatchSeparator() на два, применив его как действие, которое сохраняет вывод этого метода в переменную, а затем использовал эту переменную в семантическом предикате, примененном к BATCH_SEPARATOR. Что-то вроде этого:

BATCH_SEPARATOR:
 (NEWLINE BATCH_SEP_INNER NEWLINE) {_isValidBatchSeparator}?
;

fragment BATCH_SEP_INNER:
  {_isValidBatchSeparator = isValidBatchSeparator();}
  (ALPHA (ALPHA)?)
;

Если я это сделаю, я получу предупреждение о том, что действие было определено во фрагменте и, следовательно, никогда не будет выполняться. Очевидно, что если сделать его нефрагментарным, это сломает гораздо больше вещей, чем исправит, потому что, как нефрагментарное правило, BATCH_SEP_INNER соответствует любым двум строкам char и ломает много-много вещей.

Поэтому в крайнем случае я пробую что-нибудь умное:

BATCH_SEPARATOR:
 (NEWLINE BATCH_SEP_INNER NEWLINE) {_isValidBatchSeparator}?
;

BATCH_SEP_INNER:
  {_isValidBatchSeparator = isValidBatchSeparator();}
  (ALPHA (ALPHA)?)
  {_isValidBatchSeparator}?
;

На последнем шаге я собираюсь заставить BATCH_SEP_INNER запустить действие, но после этого отключить правило лексера, если разделитель пакетов недействителен. Однако транспилированный лексер фактически пропускает все действие. Я вижу, как генерируется соответствующий код, но путь кода никогда не проходится.

Так что теперь у меня нет идей, и я ищу этот форум для помощи :)


person Cod.ie    schedule 25.10.2017    source источник
comment
Что нужно уточнить: хотя он будет успешно использовать lex 'go', ';' и т. д. -› соответствует ли ALPHA точке с запятой? это будет неправильно lex 'IN', 'AS' -› почему? Вы запускали с параметром -tokens и видели, как лексер интерпретирует ввод? \r\nGO\r\n' закодирован как IDENTIFIER -› на первый взгляд кажется, что у вас проблема с двусмысленностью лексера, см. устранить неоднозначность и также здесь.   -  person BernardK    schedule 25.10.2017
comment
... и, пожалуйста, предоставьте несколько строк ввода и то, как вы хотите, чтобы они были интерпретированы. Не проще ли было бы иметь правило парсера batch_separator : {_isValidBatchSeparator}? NL ID NL; ?   -  person BernardK    schedule 25.10.2017
comment
@BernardK, я знаю, что плохо описал свою проблему. Я унаследовал старую грамматику Antlr2, которую необходимо преобразовать в Antlr4, поэтому я предположил, что правила лексера в предыдущей версии должны сопоставляться с правилами лексера в новой грамматике. В целом это верно, но, как вы указываете, вероятно, полезно изучить это с помощью правил синтаксического анализатора. Спасибо за указатель.   -  person Cod.ie    schedule 26.10.2017