it-swarm.com.ru

Разница между LL и парсером рекурсивного спуска?

Недавно я пытался научить себя, как работают парсеры (для языков/контекстно-свободных грамматик), и большая часть этого, кажется, имеет смысл, за исключением одной вещи. Я сосредотачиваю свое внимание, в частности, на грамматиках LL (k) , для которых два основных алгоритма выглядят как LL-парсер (с использованием стека/таблицы разбора) и парсер рекурсивного спуска = (просто используя рекурсию).

Насколько я вижу, алгоритм рекурсивного спуска работает на всех грамматиках LL (k) и, возможно, даже больше, тогда как синтаксический анализатор LL работает на всех грамматиках LL (k). Парсер рекурсивного спуска явно намного проще, чем парсер LL для реализации (как и LL проще, чем LR).

Итак, мой вопрос: с какими преимуществами/проблемами можно столкнуться при использовании любого из алгоритмов? Почему можно выбрать LL вместо рекурсивного спуска, учитывая, что он работает на одном и том же наборе грамматик и сложнее в реализации?

70
Noldorin

LL обычно более эффективный метод разбора, чем рекурсивный спуск. На самом деле, наивный анализатор рекурсивного спуска на самом деле будет O (k ^ n) (где n размер ввода) в худшем случае. Некоторые методы, такие как запоминание (которое дает синтаксический анализатор Packrat ), могут улучшить это, а также расширить класс грамматик, принимаемых синтаксическим анализатором, но всегда есть компромисс. LL-парсеры - это (насколько мне известно) всегда линейное время.

С другой стороны, вы правы в своей интуиции, что парсеры рекурсивного спуска могут обрабатывать больший класс грамматик, чем LL. Рекурсивный спуск может обрабатывать любую грамматику, которая является LL (*) (то есть без ограничений lookahead), а также небольшой набор неоднозначных грамматик. Это связано с тем, что рекурсивный спуск на самом деле является реализацией PEG с прямым кодированием или грамматика (и) выражения синтаксического анализа . В частности, дизъюнктивный оператор (a | b) не является коммутативным, что означает, что a | b не равен b | a. Парсер рекурсивного спуска будет пробовать каждую альтернативу по порядку. Таким образом, если a соответствует входному значению, оно выполнится успешно, даже если b будет соответствовать входному значению. Это позволяет обрабатывать классические неоднозначности с "самым длинным соответствием", такие как висячую проблему else, просто упорядочив дизъюнкции.

Учитывая все вышесказанное, можно реализовать синтаксический анализатор LL (k) с использованием рекурсивного спуска, чтобы он работал за линейное время. Это делается путем, по существу, встраивания наборов предсказаний, так что каждая подпрограмма синтаксического анализа определяет соответствующую производительность для данного ввода в постоянное время. К сожалению, такая техника исключает обработку целого класса грамматик. Как только мы перейдем к интеллектуальному анализу, такие проблемы, как висящий else, более не будут решаться с такой легкостью.

Что касается того, почему LL будет выбран вместо рекурсивного спуска, это в основном вопрос эффективности и ремонтопригодности. Синтаксические анализаторы с рекурсивным спуском заметно проще в реализации, но их обычно сложнее поддерживать, поскольку представляемая ими грамматика не существует ни в какой декларативной форме. В большинстве нетривиальных сценариев синтаксического анализа используется генератор синтаксического анализатора, такой как ANTLR или Bison. С такими инструментами действительно не имеет значения, является ли алгоритм прямым кодированием рекурсивного спуска или LL (k), управляемым таблицей.

Интересно также, что стоит обратить внимание на recursive-ascent , который является алгоритмом синтаксического анализа, непосредственно закодированным по принципу рекурсивного спуска, но способным обрабатывать любую грамматику LALR. Я также покопался бы в комбинаторы синтаксического анализа , которые представляют собой функциональный способ составления парсеров рекурсивного спуска.

91
Daniel Spiewak