#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
.