Как вернуть несколько токенов с помощью Jison lexer

#javascript #jison

#javascript #jison

Вопрос:

Я новичок в лексикографии и синтаксическом анализе, поэтому извините, если заголовок недостаточно понятен.

По сути, я использую Jison для синтаксического анализа некоторого текста и пытаюсь заставить лексер понимать отступы. Вот о чем идет речь:

 (rn|r|n) s*      %{
                        parser.indentCount = parser.indentCount || [0];

                        var indentation = yytext.replace(/^(rn|r|n) /, '').length;

                        if (indentation > parser.indentCount[0]) {
                           parser.indentCount.unshift(indentation);
                           return 'INDENT';
                        }

                        var tokens = [];

                        while (indentation < parser.indentCount[0]) {
                          tokens.push('DEDENT');
                          parser.indentCount.shift();
                        }

                        if (tokens.length) {
                           return tokens;
                        }

                        if (!indentation.length) {
                          return 'NEWLINE';
                        }
                      %}
  

Пока что почти все это работает так, как ожидалось. Единственная проблема заключается в строке, в которой я пытаюсь вернуть массив DEDENT токенов. Похоже, что Jison просто преобразует этот массив в строку, из-за чего я получаю ошибку синтаксического анализа типа Expecting ........, got DEDENT,DEDENT .

Что я надеюсь, что смогу сделать, чтобы обойти это, так это вручную поместить некоторые DEDENT токены в стек. Возможно, с помощью функции типа this.pushToken('DEDENT') или чего-то в этом роде. Но документация Jison не так хороша, и мне могла бы понадобиться некоторая помощь.

Есть мысли?

Редактировать:

Кажется, я смог обойти это после просмотра сгенерированного кода синтаксического анализатора. Вот что, кажется, работает…

 if (tokens.length) {
  var args = arguments;

  tokens.slice(1).forEach(function () {
    lexer.performAction.apply(this, args);
  }.bind(this));

  return 'DEDENT';
}
  

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

Я все равно был бы рад, если бы у кого-нибудь были идеи о лучшем способе сделать это.

Ответ №1:

Через пару дней я в конечном итоге нашел лучший ответ. Вот как это выглядит:

 (rn|r|n) [ t]*   %{
                        parser.indentCount = parser.indentCount || [0];
                        parser.forceDedent = parser.forceDedent || 0;

                        if (parser.forceDedent) {
                          parser.forceDedent -= 1;
                          this.unput(yytext);
                          return 'DEDENT';
                        }

                        var indentation = yytext.replace(/^(rn|r|n) /, '').length;

                        if (indentation > parser.indentCount[0]) {
                           parser.indentCount.unshift(indentation);
                           return 'INDENT';
                        }

                        var dedents = [];

                        while (indentation < parser.indentCount[0]) {
                          dedents.push('DEDENT');
                          parser.indentCount.shift();
                        }

                        if (dedents.length) {
                           parser.forceDedent = dedents.length - 1;
                           this.unput(yytext);
                           return 'DEDENT';
                        }

                        return `NEWLINE`;
                      %}
  

Во-первых, я изменил свое регулярное выражение capture, чтобы убедиться, что я случайно не захватывал дополнительные новые строки после серии пробелов, отличных от перевода строки.

Далее мы удостоверяемся, что есть 2 «глобальные» переменные. indentCount будет отслеживать нашу текущую длину отступа. forceDedent заставит нас вернуть a DEDENT , если оно имеет значение выше 0.

Далее у нас есть условие для проверки истинности значения forceDedent . Если у нас есть один, мы уменьшим его на 1 и используем unput функцию, чтобы убедиться, что мы повторяем этот же шаблон по крайней мере еще один раз, но для этой итерации мы вернем DEDENT .

Если мы не вернули, мы получаем длину нашего текущего отступа.

Если текущий отступ больше, чем наш самый последний отступ, мы отследим это в нашей indentCount переменной и вернем INDENT .

Если мы не вернули, пришло время подготовиться к возможным выделениям. Мы создадим массив для их отслеживания.

Когда мы обнаруживаем удаление, пользователь может пытаться закрыть 1 или более блоков одновременно. Итак, нам нужно включить DEDENT столько блоков, сколько закрывает пользователь. Мы настраиваем цикл и говорим, что до тех пор, пока текущий отступ меньше нашего самого последнего отступа, мы будем добавлять DEDENT в наш список и удалять элемент из нашего indentCount .

Если мы отследили какие-либо деденты, нам нужно убедиться, что все они будут возвращены лексером. Поскольку lexer может возвращать только 1 токен за раз, мы вернем здесь 1, но мы также установим нашу forceDedent переменную, чтобы убедиться, что мы возвращаем и остальные из них. Чтобы убедиться, что мы повторяем этот шаблон снова и эти выделенные элементы могут быть вставлены, мы будем использовать unput функцию.

В любом другом случае мы просто вернем NEWLINE .