- •Глава 12 Классы кс-языков
- •Алгоритм разбора по методу рекурсивного спуска
- •Пример реализации метода рекурсивного спуска
- •Расширенное применение распознавателей на основе метода рекурсивного спуска
- •Определение ll(k)-грамматики
- •Принципы построения распознавателей для ll(к)-грамматик
- •Алгоритм разбора для ll(1)-грамматик
- •Алгоритм построения множества first(1,a)
- •Алгоритм построения множества follow(1,a)
- •Пример построения распознавателя для ll(1)-грамматики
- •Принципы построения распознавателей для lr(k)-грамматик
- •Распознаватель для lr(0)-грамматики
- •Распознаватель для lr(1)-грамматики
- •Грамматики предшествования (основные принципы)
- •Грамматики простого предшествования
- •Алгоритм «сдвиг-свертка» для грамматики простого предшествования
- •Пример распознавателя для грамматики простого предшествования
- •Грамматики операторного предшествования
- •2. Различные порождающие правила имеют разные правые части, λ-правила отсутствуют.
- •Алгоритм «сдвиг-свертка» для грамматики операторного предшествования
- •Отношения между классами кс-грамматик
- •Отношения между классами кс-языков
Распознаватель для lr(1)-грамматики
Другим употребительным классом LR(к)-грамматик являются LR(1) грамматики. В этих грамматиках основанием для принятия расширенным МП-автоматом решения о выполнении сдвига или свертки служит информация о содержимом стека автомата и текущий символ, обозреваемый считывающей головкой. Рассмотрим простую КС-грамматику G({a,b}, {S}, {S-»SaSb|X}. S). Пополненная грамматика для нее будет иметь вид G({a,b}. {S. S1}, {S'-»S, S-»SaSb|X}, S'). Эта грамматика является LR(1)-грамматикой [5, 15, 65]. Управляющая таблица для нее приведена в табл. 12.3.
Колонка «Стек», присутствующая в таблице, в принципе не нужна для распознавателя. Она введена исключительно для пояснения каждого состояния стека автомата. Пустые клетки в таблице соответствуют состоянию «ошибка». Правила в грамматике пронумерованы от 1 до 3 (при этом будем считать, что состоянию «успех» — свертке к нулевому символу — в пополненной грамматике всегда соответствует первое правило). Колонка «Действие» в таблице содержит перечень действий, соответствующих текущему входному символу, обозреваемому считывающей головкой расширенного МП-автомата.
Рассмотрим примеры распознавания цепочек этой грамматики по шагам, которые совершает распознаватель. Конфигурацию расширенного МП-автомата будем отображать в виде трех компонентов: не прочитанная еще часть входной цепочки символов, содержимое стека МП-автомата, последовательность номеров примененных правил грамматики (поскольку автомат имеет только одно состояние, его можно не учитывать). В стеке МП-автомата вместе с помещенными туда символами показаны и номера строк управляющей таблицы, соответствующие этим символам в формате {символ, номер строки}.
Разбор цепочки abababb.
Соответствующая цепочка вывода будет иметь вид (используется правосторонний вывод): S' => S => SaSb => SaSaSbb => SaSabb => SaSaSbabb => SaSababb => Saababb => aababb.
Невозможно непосредственно сравнить работу двух рассмотренных вариантов распознавателей: восходящего (LR) и нисходящего (LL). Это очевидно, поскольку приведенная в примере грамматика не является LR(1)-грамматикой. Соответственно, она не может быть разобрана и методом рекурсивного спуска (можно убедиться в этом, построив множества FIRST и FOLLOW для символов грамматики: FIRST(1,S) = {a}, FOLLOW(l.S) = {а,ЬS}; FIRST(1,S) n FOLLOW(l.S) * 0). На основании этой грамматики вообще невозможно построить нисходящий распознаватель, поскольку она явно содержит левую рекурсию. Устранив левую рекурсию (см. алгоритм в разделе «Преобразование КС-грамматик. Приведенные грамматики», глава И) и выполнив ряд несложных преобразований, можно получить эквивалентную ей грамматику G"({a,b},{S},{S->A|aSbS},S), которая будет относиться к классу LL(l)-грамматик (действительно, для нее FIRST(1,S) = {a}, FOLLOW(1,S) = = {bS};. FIRST(1,S) n FOLLOW(l.S) - 0)1. Теперь уже возможно сравнить работу двух вариантов распознавателей — нисходящего и восходящего.
1 Чтобы доказать, что две рассмотренные грамматики эквивалентны (определяют один и тот же язык: L(G) = L(G")), предлагаем читателям выполнить преобразования G в G" самостоятельно. Это несложно: первым шагом будет устранение левой рекурсии, затем необходимо несколько раз выполнить левую факторизацию (см. раздел «Нисходящие распознаватели КС-языков без возвратов» этой главы), после чего дальнейшее преобразование становится очевидным.
Несмотря на то что распознаватель на основе LL(l)-грамматики в данном случае имеет более простой алгоритм функционирования, нельзя сказать, что им легче воспользоваться, поскольку его создание требует дополнительных преобразований исходной грамматики. Для более сложных LR(l)-грамматик такие преобразования могут быть в принципе невозможны, поскольку класс языков, заданных LR(l)-граммаоикама, шире, чем класс языков, заданных LL(1)-грамматиками. Поэтому для конкретной заданной грамматики чаще бывает проще построить восходящий распознаватель, чем нисходящий.
На практике LR(k)-грамматики при k > 1 не применяются. На это имеются две причины. Во-первых, управляющая таблица для LR(k)-грамматики при k > 1 будет содержать очень большое число состояний, и распознаватель будет достаточно сложным и не столь эффективным1. Во-вторых, для любого языка, определяемого LR(k)-грамматики, существует LR(1)-грамматика, определяющая тот же язык. То есть для любой LR(k)-грамматики с k > 1 всегда существует эквивалентная ей LR(1)-грамматика. Более того, для любого детерминированного КС-языка существует LR(l)-грамматика (другое дело, что далеко не всегда такую грамматику можно легко построить)2.
