Комбинаторы синтаксического анализатора: обработка пробелов в синтаксическом анализаторе без чрезмерного возврата

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

Проблема

Возьмем язык, состоящий из последовательности символов «а» и «б». Во входных данных разрешены пробелы, но они не влияют на смысл программы.

Мой текущий подход к разбору такого языка:

var token = function(p) {
    return attempt(next(
         parse.many(whitespace),
         p));
};

var a = token(char('a'));
var b = token(char('b'));

var prog = many(either(a, b));

Это работает, но требует ненужного возврата. Для такой программы, как '___ba_alb' (пробелы удалялись в сообщении, поэтому пусть _ будет пробелом), при сопоставлении 'b' пробел анализируется дважды, сначала для a, а когда a терпит неудачу, снова для b. Простое удаление attempt не работает, так как either никогда не достигнет b, если будут использованы какие-либо пробелы.

Попытки

Первым делом я переместил вызов token за пределы either:

var a = char('a');
var b = char('b');

var prog = many(token(either(a, b)));

Это работает, но теперь prog нельзя легко использовать повторно. При создании библиотеки синтаксических анализаторов это, по-видимому, требует определения синтаксических анализаторов дважды: одна версия, которая фактически использует «a» или «b» и может использоваться в других синтаксических анализаторах, и одна версия, которая правильно обрабатывает пробелы. Это также загромождает определения парсеров, требуя от них явных знаний о том, как работает каждый парсер и как он обрабатывает пробелы.

Вопрос

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

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


person Matt Bierner    schedule 13.09.2013    source источник
comment
Так почему же вы отказываетесь от отдельного лексера?   -  person 500 - Internal Server Error    schedule 14.09.2013
comment
Я работаю с ECMAScript. При лексировании символ '/' неоднозначен, он может быть символом деления или частью литерала регулярного выражения. Единственный способ определить, что означает «/», — это контекст символа во время синтаксического анализа. Это требует некоторых очень уродливых хаков с отдельными лексером и парсером. Ответ Леви на этот вопрос заставил меня поверить, что объединение лексер и парсер могут быть хорошим подходом. Также приветствуются любые альтернативные конструкции.   -  person Matt Bierner    schedule 14.09.2013
comment
Чем это отличается от, например, < является либо (частью) оператора отношения, либо открытием шаблонного выражения в языках c-стиля? Обычно вы должны иметь возможность отложить любые решения о том, что представляет собой символ, которые могут внести неоднозначность, от этапа лексера до этапа синтаксического анализа.   -  person 500 - Internal Server Error    schedule 15.09.2013
comment
Это пример использования одного и того же токена в разных продуктах, хотя я не уверен, как С++ 11 обрабатывает такие выражения, как vector<vector<int>>, где появляется >>. И реляционные выражения, и шаблонные выражения работают с < токенами пунктуатора. В Javascript '/' может быть связан либо с токеном знака пунктуации div, либо, с некоторыми другими символами, с токеном регулярного выражения. Поскольку такие символы, как ", могут появляться внутри регулярных выражений, я не думаю, что сработает лексирование для знаков препинания div и введение создания регулярных выражений с использованием знаков препинания div.   -  person Matt Bierner    schedule 16.09.2013
comment
Я собирался предложить функции высшего порядка для синтаксического анализа, но вы были там. Поскольку вы не указываете грамматику, действительно ли это две грамматики, одна из которых является островной грамматикой? См.: stackoverflow.com/questions/2561249/island-grammar-antlr3   -  person Guy Coder    schedule 01.12.2013
comment
@MattBierner, в JS еще хуже ... / может быть делением, началом или концом регулярного выражения или обозначаться как // строковым комментарием. по этой причине пустое регулярное выражение не может быть записано как //, а литералы регулярного выражения не могут начинаться с неэкранированного пробела...   -  person flow    schedule 17.06.2014


Ответы (1)


Я решил эту проблему в парсере JSON, который я создал. Вот ключевые части моего решения, в котором я несколько раз следовал подходу «написать синтаксические анализаторы дважды»:

  1. определить основные парсеры для каждого токена — число, строка и т. д.

  2. определите комбинатор token, который принимает базовый анализатор токенов и выводит анализатор, поглощающий пробелы. Жевание должно происходить после, чтобы пробел анализировался только один раз, как вы заметили:

    function token(parser) {
        // run the parser, then munch whitespace
    }
    
  3. используйте комбинатор token для создания парсеров жевательных токенов

  4. использовать парсеры из 3. для создания парсеров для остальной части языка

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

person Matt Fenwick    schedule 22.01.2014