Популярные услуги

Все письменные КМ под ключ за 3 суток! (КМ-6 + КМ-7 + КМ-8 + КМ-9 + КМ-10)
КМ-6. Динамические массивы. Семинар - выполню любой вариант!
КМ-2. Разработка простейших консольных программ с использованием ООП + КМ-4. Более сложные элементы ООП - под ключ!
Любая задача на C/C++
Одно любое задание в mYsql
Сделаю ваше задание: Лабораторная работа на Pascal / Lazarus
Любой тест по базам данных максимально быстро на хорошую оценку - или верну деньги!
Любой реферат по объектно-ориентированному программированию (ООП)
Повышение уникальности твоей работе
Оба семинара по программированию под ключ! КМ-2. Разработка циклических алгоритмов + КМ-3. Функции и многофайловые программы в Си

Синтаксический анализ

2021-03-09СтудИзба

4. Лекция: Синтаксический анализ
В данной лекции рассматривается понятие синтаксического анализа. Приводятся определения понятий упорядоченного графа, дерева вывода, автомата с магазинной памятью и его конфигурации. Приведены примеры задач, алгоритмов и доказательства теорем синтаксического анализа.

Контекстно-свободные грамматики и автоматы с магазинной памятью

Пусть G = (N, T, P, S) - КС-грамматика. Введем несколько важных понятий и определений.

Вывод, в котором в любой сентенциальной форме на каждом шаге делается подстановка самого левого нетерминала, называется левосторонним. Если S* u в процессе левостороннего вывода, то u - левая сентенциальная форма. Аналогично определим правосторонний вывод. Обозначим шаги левого (правого) вывода l (r).

Упорядоченным графом называется пара (V,E), где V есть множество вершин, а E - множество линейно упорядоченных списков дуг, каждый элемент которого имеет вид ((v, v1), (v, v2), ... , (v, vn)). Этот элемент указывает, что из вершины v выходят n дуг, причем первой из них считается дуга, входящая в вершину v1, второй - дуга, входящая в вершинуv2, и т.д.

Упорядоченным помеченным деревом называется упорядоченный граф (V,E), основой которого является дерево и для которого определена функция f : V F (функция разметки) для некоторого множества F.

Упорядоченное помеченное дерево D называется деревом вывода (или деревом разбора) цепочки w в КС-грамматике G = (N, T, P, S), если выполнены следующие условия:

(1) корень дерева D помечен S;

(2) каждый лист помечен либо a in T, либо e;

Рекомендуемые материалы

(3) каждая внутренняя вершина помечена нетерминалом A in N;

(4) если X - нетерминал, которым помечена внутренняя вершина и X1, ... , Xn - метки ее прямых потомков в указанном порядке, то X X1 ... Xk - правило из множества P;

(5) Цепочка, составленная из выписанных слева направо меток листьев, равна w.

Процесс определения принадлежности данной строки языку, порождаемому данной грамматикой, и, в случае указанной принадлежности, построение дерева разбора для этой строки, называется синтаксическим анализом. Можно говорить о восстановлении дерева вывода (в частности, правостороннего или левостороннего) для строки, принадлежащей языку. По восстановленному выводу можно строить дерево разбора.

Грамматика G называется неоднозначной, если существует цепочка w, для которой имеется два или более различных деревьев вывода в G.

Грамматика G называется леворекурсивной, если в ней имеется нетерминал A такой, что для некоторой цепочки R существует вывод A + A.

Автомат с магазинной памятью (МП-автомат) - это семерка M = (Q, T, Γ, D, q0, Z0, F), где

(1) Q - конечное множество состояний, представляющих всевозможные состояния управляющего устройства;

(2) T - конечный входной алфавит;

(3) Γ - конечный алфавит магазинных символов;

(4) D - отображение множества Q x (T {e}) x Γ в множество конечных подмножеств Q x Γ*, называемое функцией переходов;

(5) q_0 in Q- начальное состояние управляющего устройства;

(6) Z_0 in Gamma- символ, находящийся в магазине в начальный момент (начальный символ магазина);

(7) F subseteq Q- множество заключительных состояний.

Конфигурация МП-автомата - это тройка (q, w, u), где

(1) q in Q- текущее состояние управляющего устройства;

(2) w in T^*- непрочитанная часть входной цепочки; первый символ цепочки w находится под входной головкой; если w = e, то считается, что вся входная лента прочитана;

(3) u in Gamma^*- содержимое магазина; самый левый символ цепочки u считается верхним символом магазина; если u = e, то магазин считается пустым.

Такт работы МП-автомата M будем представлять в виде бинарного отношения vdash, определенного на конфигурациях.

Будем писать

(q, ; qw, ; Zu) vdash (p, ; w, ; vu)

если множество D(q, a, Z) содержит (p, v), где q, ; p in Q, ; a in T cup {e}, ; w in T^*, ; Z in Gamma^*и u, ; v in Gamma^*(верхушка магазина слева).

Начальной конфигурацией МП-автомата M называется конфигурация вида (q0, w, Z0), где win T^*, то есть управляющее устройство находится в начальном состоянии, входная лента содержит цепочку, которую нужно проанализировать, а в магазине имеется только начальный символ Z0.

>Заключительной конфигурацией называется конфигурация вида (q, e, u), где q in F, u in Gamma^*, то есть управляющее устройство находится в одном из заключительных состояний, а входная цепочка целиком прочитана.

Введем транзитивное и рефлексивно-транзитивное замыкание отношения vdash, а также его степень k > 0 (обозначаемые vdash^+, vdash^*и vdash^kсоответственно).

Говорят, что цепочка w допускается МП-автоматом M, если (q_0, w, Z_0) vdash^* (q, e, u)для некоторых q in Fи u in Gamm^*.

Множество всех цепочек, допускаемых автоматом M называется языком, допускаемым (распознаваемым, определяемым) автоматом M (обозначается L(M)).

Пример 4.1. Рассмотрим МП-автомат

M = ({q0, q1, q2}, {a, b}, {Z, a, b}, D, q0, Z, {q2}),

у которого функция переходов D содержит элементы:

D(q0, a, Z) = {(q0, aZ)},

D(q0, b, Z) = {(q0, bZ)},

D(q0, a, a) = {(q0, aa), {q1, e)},

D(q0, a, b) = {(q0, ab)},

D(q0, b, a) = {(q0, ba)},

D(q0, b, b) = {(q0, bb), (q1, e)},

D(q1, a, a) = {(q1, e)},

D(q1, b, b) = {(q1, e)},

D(q1, e, Z) = {(q2, e)}.

Нетрудно показать, что L(M) = {ww^R ! | ! w ; in ; {a, b}^+}, где wR обозначает обращение ("переворачивание") цепочки w.

Иногда допустимость определяют несколько иначе: цепочка w допускается МП-автоматом M, если (q_0, w, Z_0) vdash^* (q, e, e)для некоторого q in Q. В таком случае говорят, что автомат допускает цепочку опустошением магазина. Эти определения эквивалентны, ибо справедлива

Теорема 4.1. Язык допускается МП-автоматом тогда и только тогда, когда он допускается (некоторым другим автоматом) опустошением магазина.

Доказательство. Пусть L = L(M) для некоторого МП- автомата M = (Q, T, Γ, D, q0, Z0, F). Построим новый МП- автомат M', допускающий тот же язык опустошением магазина.

Пусть M' = (Q cup {q'_0, q_e}, T, Gamma cup {Z'_0}, D', q'_0, Z'_0 , oslash), где функция переходов D' определена следующим образом:

  1. Если (r, u) in D(q, a, Z), то (r, u) in D'(q, a, Z)для всех q in Q, a in T cup {e}и Z in Gamma(моделирование М),
  2. D'(q'_0 , e, Z'_0) = {(q_0, Z_0 Z'_0 )}(начало работы),
  3. Для всех q in Fи Z in Gamma cup {Z'_0}множество D'(q, e, Z) содержит (qe, e) (переход в состояние сокращения магазина без продвижения),
  4. D'(qe, e, Z) = {(qe, e)} для всех Z in Gamma cup {Z'_0}, (сокращение магазина).

Автомат сначала переходит в конфигурацию (q_0, w, Z_0Z'_0)соответственно определению D' в п.2, затем в (q, e, Y_1 ldots Y_k Z'_0),

q in Fсоответственно п.1, затем в (q_e, e, Y_1 ldots Y_k Z'_0), q in Fсоответственно п.3, затем в (qe, e, e) соответственно п.4. Нетрудно показать по индукции, что (q_0, w, Z_0) vdash^+ (q, e, u)(где q in F) выполняется для автомата M тогда и только тогда, когда (q'_0 , w, Z'_0 ) vdash^+ (q_e, e, e)выполняется для автомата M'. Поэтому L(M) = L', где L' - язык, допускаемый автоматом M' опустошением магазина.

Обратно, пусть M = (Q, T, Γ, D, q0, Z0, ) - МП - автомат, допускающий опустошением магазина язык L. Построим автомат M', допускающий тот же язык по заключительному состоянию.

Пусть M' = (Q cup {q'_0 , q_f} , T, Gamma cup {Z'_0 }, D', q'_0, Z'_0 , {q_f }), где D' определяется следующим образом:

  1. D'(q'_0 , e, Z'_0) = {(q_0, Z_0Z'_0 )}- переход в "режим M",
  2. Для каждого q in Q, ain T cup {e}, и Z in Gammaопределим D'(q, a, Z) = D(q, a, Z)- работа в "режиме M" ,
  3. Для всех q in Q, (q_f , e) in D'(q, e, Z'_0)- переход в заключительное состояние.

Нетрудно показать по индукции, что L = L(M'). Одним из важнейших результатов теории контекстно-свободных языков является доказательство эквивалентности МП-автоматов и КС-грамматик.

Теорема 4.2. Язык является контекстно-свободным тогда и только тогда, когда он допускается МП-авто- матом.

Доказательство. Пусть G = (N, T, P, S) - КС-граммати- ка. Построим МП-автомат, допускающий язык L(G) опустошением магазина.

Пусть M = ({q}, T, N T, D, q, S, ), где D определяется следующим образом:

  1. Если A rightarrow u in P, то (q, u) in D(q, e, A),
  2. D(q, a, a) = {(q, e)} для всех a in T.

Фактически, этот МП-автомат в точности моделирует все возможные выводы в грамматике G. Нетрудно показать по индукции, что для любой цепочки w in T^*вывод S +w в грамматике G существует тогда и только тогда, когда существует последовательность тактов (q, w, S) vdash^+ (q, e, e)автомата M.

Наоборот, пусть дан M = (Q, T, Γ , D, q0, Z0, ) - МП- автомат, допускающий опустошением магазина язык L.

Построим грамматику G, порождающую язык L.

Пусть G = ({ [qZr] mid q, r in Q, Z in Gamma } cup {S}, T, P, S), где P состоит из правил следующего вида:

  1. S rightarrow [q_0Z_0q] in Pдля всех q in Q.
  2. Если (r, e) in D(q, a, Z), ; text{то} ; [qZr] rightarrow a in P, a in T cup {e},
  3. Если (r, X_1 ldots X_k) in D(q, a, Z), k geq 1, то

begin{align*}
text{$[qZs_k] rightarrow a[rX_1s_1][s_1X_2s_2] ldots [s_{k-1}X_ks_k]$} \
text{для любого набора $s_1, s_2, ldots , s_k$ состояний из $Q$,}
end{align*}

Нетерминалы и правила вывода грамматики определены так, что работе автомата M при обработке цепочки w соответствует левосторонний вывод w в грамматике G.

Индукцией по числу шагов вывода в G или числу тактов M нетрудно показать, что (q, w, A) vdash^+ (p, e, e)тогда и только тогда, когда [qAp] + w.

Тогда, если w in L(G), то S [q0Z0q] + w для некоторого q in Q. Следовательно, (q_0, w, Z_0) vdash^+ (q, e, e)и поэтому w in L. Аналогично, если w in L, то (q_0, w, Z_0) `+ (q, e, e). Значит, S [q0Z0q] + w, и поэтому w in L(G).

МП-автомат M = (Q, T, Γ, D, q0, Z0, F) называется детерминированным (ДМП-автоматом), если выполнены два следующих условия:

(1) Множество D(q, a, Z) содержит не более одного элемента для любых q in Q, a in T cup {e}, Z in Gamma;

(2) Если D(q, e, Z) , то D(q, a, Z) = для всех a in T.

Допускаемый ДМП-автоматом язык называется детерминированным КС-языком.

Так как функция переходов ДМП-автомата содержит не более одного элемента для любой тройки аргументов, мы будем пользоваться записью D(q, a, Z) = (p, u) для обозначения D(q, a, Z) = {(p, u)}.

Пример 4.2. Рассмотрим ДМП-автомат

M = ({q0, q1, q2}, {a, b, c}, {Z, a, b}, D, q0, Z, {q2}),

функция переходов которого определяется следующим образом:

begin{align*}
& text{$D(q_0, X, Y ) = (q_0, XY ), X in {a, b}, Y in {Z, a, b},$} \
& text{$D(q_0, c, Y ) = (q_1, Y ), Y in {a, b},$} \
& text{$D(q_1, X, X) = (q_1, e), X in {a, b},$} \
& text{$D(q_1, e, Z) = (q_2, e).$}
end{align*}

Нетрудно показать, что этот детерминированный МП-автомат допускает язык L = {wcw^R ! mid ! w in {a, b}^+}.

К сожалению, ДМП-автоматы имеют меньшую распознавательную способность, чем МП-автоматы. Доказано, в частности, что существуют КС-языки, не являющиеся детерминированными КС-языками (таковым, например, является язык из примера 4.1).

Рассмотрим еще один важный вид МП-автомата.

Расширенным автоматом с магазинной памятью назовем семерку M = (Q, T, Γ , D, q0, Z0, F), где смысл всех символов тот же, что и для обычного МП-автомата, кроме D, представляющего собой отображение конечного подмножества множества Q x(T {e}) x Γ* во множество конечных подмножеств множества Q x Γ*. Все остальные определения (конфигурации, такта, допустимости) для расширенного МП-автомата остаются такими же, как для обычного.

Теорема 4.3. Пусть M = (Q, T, Γ, D, q0, Z0, F) - расширенный МП-автомат. Тогда существует МП- автомат M', такой, что L(M') = L(M).

Расширенный МП-автомат M = (Q, T, Γ, D, q0, Z0, F) называется детерминированным, если выполнены следующие условия:

(1) Множество D(q, a, u) содержит не более одного элемента для любых q in Q, a in T cup {e}, u in Gamma^*,

(2) Если D(q, a, u) , D(q, a, v) и u v, то не существует цепочки x такой, что u = vx или v = ux,

(3) Если D(q, a, u) , D(q, e, v) , то не существует цепочки x такой, что u = vx или v = ux.

Теорема 4.4. Пусть M = (Q, T, Γ, D, q0, Z0, F) - расширенный ДМП-автомат. Тогда существует ДМП- автомат M', такой, что L(M') = L(M).

ДМП-автомат и расширенный ДМП-автомат лежат в основе рассматриваемых далее в этой главе, соответственно, LL- и LR-анализаторов.

Определение. Говорят, что КС-грамматика находится в нормальной форме Хомского, если каждое правило имеет вид:

(1) либо A BC, A, B, C - нетерминалы,

(2) либо A a, a - терминал,

(3) либо S e и в этом случае S - не встречается в правых частях правил.

Утверждение. Любую КС-грамматику можно преобразовать в эквивалентную ей в нормальной форме Хомского.

Утверждение. Если КС-грамматика находится в нормальной форме Хомского, тогда для любой цепочки , если alpha in L(G)и m - высота дерева вывода с кроной , || 2m-1.

Теорема 4.5. (Лемма о разрастании для контекстно- свободных языков). Для любого КС-языка L существуют такие целые l и k, что любая цепочка R in L, mid ! R mid ! > l, представима в виде R = uvwxy, где

(1) |vwx| k

(2) vx e

(3) uv^iwx^iy in Lдля любого i 0.

Доказательство. Пусть L = L(G), где G = (N, Σ, P, S) - контекстно- свободная грамматика в нормальной форме Хомского. Обозначим через n число нетерминалов, т.е. n = |N|, и рассмотрим l = 2n и k = 2n+1.

Для доказательства того, что l и k удовлетворяют условию теоремы, рассмотрим произвольную цепочку alpha in L, для которой || > l = 2n. В силу Утверждения получаем, что высота дерева с кроной больше n + 1 и есть путь по дереву (обозначим его через P), который проходит более чем через n + 1 вершин. Отсюда по определению дерева вывода имеем, что P содержит более n вершин, помеченных нетерминалами. Таким образом, существует нетерминал, который метит не менее двух вершин пути P. Среди всех таких нетерминалов пусть A - такой, что его вхождение, ближайшее к листу, не содержит других нетерминалов, обладающих этим свойством (если бы это было не так, то выбрали бы этот другой). Пусть q - вхождение A, ближайшее к листу, p - расположенное выше. Представим крону в виде uvwxy, где w - крона поддерева D1 с корнем q и vwx - крона поддерева D2 с корнем p. Тогда высота поддерева D2 не более (n - 1) + 2 + 1 = n + 2, так что |vwz| 2n+1.

Также очевидно, что vx e, поскольку в силу определения нормальной формы Хомского p имеет двух сыновей, помеченных нетерминалами, из которых не выводится пустая цепочка.

Кроме того, S * u Ay * uvAxy * uvwxy, а также A * vAx * vwx. Отсюда получаем A * viwxi для всех i 0 и S * uviwxiy для всех i 0.

Пример. Покажем, что язык L = {anbncn|n1} не является контекстно-свободным языком.

Если бы он был КС-языком, то мы взяли бы константу k, которая определяется в лемме о разрастании. Пусть z = akbkck. Тогда z = uvwxy. Так как |vwx| k, то в цепочке vwx не могут быть вхождения каждого из символов a, b и c. Таким образом, цепочка uwy, которая по лемме о разрастании принадлежит L, содержит либо k символов a, либо k символов c. Но она не может иметь k вхождений каждого из символов a, b и c, потому, что |uwy| < 3k. Значит, вхождений какого-то из этих символов в uwy больше, чем другого и, следовательно, uwy L. Полученное противоречие позволяет заключить, что L - не КС-язык.

Преобразования КС-грамматик

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

Назовем символ X in (N cup T)недостижимым в КС- грамматике G = (N, T, P, S), если X не появляется ни в одной выводимой цепочке этой грамматики. Иными словами, символ X является недостижимым, если в G не существует вывода S * Xβ ни для каких alpha, ; beta in (N cup T)^*.

Назовем символ X in (N cup T)несводимым (бесплодным) в той же грамматике, если в ней не существует вывода вида X * xwy, где w, x, y принадлежат T*.

Очевидно, что каждый недостижимый и/или несводимый символ является бесполезным, как и все правила, его содержащие.

При внимательном изучении вышеприведенных определений становится понятным, что а) целесообразно искать не непосредственно сами недостижимые (или несводимые) символы, а последовательно определять множество достижимых (или сводимых) символов, начиная с тех, которые по определению являются достижимыми (аксиома) и сводимыми (терминалы) - все остальные символы оказываются бесполезными, б) одновременное определение достижимых и сводимых символов невозможно, так как соответствующие процессы идут в противоположных направлениях (от корня к листьям и наоборот).

Алгоритм 4.1. Устранение недостижимых символов.

Вход. КС-грамматика G = (N, T, P, S).

Выход. КС-грамматика G' = (N', T', P', S) без недостижимых символов, такая, что L(G') = L(G).

Метод. Выполнить шаги 1-4:

(1) Положить V0 = {S} и i = 1,

(2) Положить Vi = {X | в P есть A Xβ и A in V_{i-1} } cup V_{i-1},

(3) Если Vi Vi-1, положить i = i + 1 и перейти к шагу 2, в противном случае перейти к шагу 4,

(4) Положить N' = Vi N, T' = Vi T. Включить в P' все правила из P, содержащие только символы из Vi.

Алгоритм 4.2. Устранение несводимых символов.

Вход. КС-грамматика G = (N, T, P, S).

Выход. КС-грамматика G' = (N', T', P', S) без несводимых символов, такая, что L(G') = L(G).

Метод. Выполнить шаги 1-4:

(1) Положить N' = T и i = 1,

(2) Положить N_i = {A ! mid !A rightarrow alpha in P ; text{и} ; alpha in (N_{i-1})^*} cup N_{i-1},

(3) Если Ni Ni-1, положить i = i + 1 и перейти к шагу 2, в противном случае положить Ne = Ni и перейти к шагу 4,

(4) Положить G1 = ((N Ne) {S}, T, P1, S), где P1 состоит из правил множества P, содержащих только символы из Ne T,

Чтобы устранить все бесполезные символы, необходимо применить к исходной грамматике сначала Алгоритм 4.2, а затем Алгоритм 4.1.

Пример. Все символы следующей грамматики

S AS | b

A AB

B a

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

КС-грамматика без бесполезных символов называется приведенной. Легко видеть, что для любой КС-грамматики существует эквивалентная приведенная. В дальнейшем будем предполагать, что все рассматривамые грамматики - приведенные.

Алгоритм Кока-Янгера-Касами

Приведем алгоритм синтаксического анализа, применимый для любой грамматики в нормальной форме Хомского

Алгоритм Кока-Янгера-Касами

Вход. КС-грамматика G = (N, T, P, S) в нормальной форме Хомского и входная цепочка w = a_1a_2 ldots a_n in T^+.

Выход. Таблица разбора Tab для w такая, что A in t_{ij}тогда и только тогда, когда A + aiai+1 ... ai+j-1.

Метод.

(1) Положить t_{i1} = {A mid A rightarrow a_i in P}для каждого i. Так что, если A in t_{i1}, то A + ai.

(2) Пусть tij вычислено для 1in и 1 j' < j. Положим tij = {A| для некоторого 1 k < j правило A rightarrow BC in P, B in t_{ik}, C in t_{i+k,j-k} }.

Так как 1 k < j, то k < j и j - k < j. Так что tik и ti+k,j-k вычисляются раньше, чем tij . Если A in t_{ij}, то A BC + ai ai+k-1 C + aI ... ai+k-1ai+k ... ai+j-1.

(3) Повторять шаг 2 до тех пор, пока не станут известны tij для всех 1 i n и 1 j n-i+1.

Алгоритм нахождения левого разбора по таблице разбора Tab.

Вход. КС-грамматика G = (N, T, P, S) в нормальной форме Хомского с правилами, занумерованными от 1 до p, входная цепочка w = a_1a_2 ldots a_n in T^+и таблица разбора Tab.

Выход. Левый разбор цепочки w или сигнал ошибка.

Метод. Процедура gen(i, j, A):

(1) Если j = 1 и A ai = pm, выдать m.

(2) Пусть j > 1 и k - наименьшее из чисел от 1 до j-1, для которых существует B in t_{ik}, C in t_{i+k,j-k}и правило pm = A BC. Выдать m и выполнить gen(i, k, B), затем gen(i + k, j - k, C).

Выполнить gen(1, n, S), если S in t_{1,n}, иначе ошибка.

Разбор сверху-вниз (предсказывающий разбор)

Алгоритм разбора сверху-вниз

Пусть дана КС-грамматика G = (N; T; P; S). Рассмотрим разбор сверху-вниз (предсказывающий разбор) для грамматики G.

Главная задача предсказывающего разбора - определение правила вывода, которое нужно применить к нетерминалу. Процесс предсказывающего разбора с точки зрения построения дерева разбора проиллюстрирован на рис. 4.1

Фрагменты недостроенного дерева соответствуют сентенциальным формам. Вначале дерево состоит только из одной вершины, соответствующей аксиоме S. В этот момент по первому символу входной цепочки предсказывающий анализатор должен определить правило S X1X2 ... ; которое должно быть применено к S. Затем необходимо определить правило, которое должно быть применено к X1, и т.д., до тех пор, пока в процессе такого построения сентенциальной формы, соответствующей левому выводу, не будет применено правило Y a ... : Этот процесс затем применяется для следующего самого левого нетерминального символа сентенциальной формы.

На рис. 4.2 условно показана структура предсказывающего анализатора, который определяет

4_1


Рис. 4.1.

очередное правило с помощью таблицы. Такую таблицу можно построить и непосредственно по грамматике. Таблично-управляемый предсказывающий анализатор имеет входную ленту, управляющее устройство (программу), таблицу анализа, магазин (стек) и выходную ленту. Входная лента содержит анализируемую строку, заканчивающуюся символом $ - маркером конца строки. Выходная лента содержит последовательность примененных правил вывода.

4_2


Рис. 4.2.

Таблица анализа - это двумерный массив M[A; a], где A - нетерминал, и a - терминал или символ $. Значением M[A; a] может быть некоторое правило грамматики или элемент "ошибка".

Магазин может содержать последовательность символов грамматики с $ на дне. В начальный момент магазин содержит только начальный символ грамматики на верхушке и $ на дне.

Анализатор работает следующим образом. Вначале анализатор находится в конфигурации, в которой магазин содержит S$, на входной ленте w$ (w - анализируемая цепочка), выходная лента пуста. На каждом такте анализатор рассматривает X - символ на верхушке магазина и a - текущий входной символ. Эти два символа определяют действия анализатора. Имеются следующие возможности.

  1. Если X=a=$, анализатор останавливается, сообщает об успешном окончании разбора и выдает содержимое выходной ленты.
  2. Если X= a $, анализатор удаляет X из магазина и продвигает указатель входа на следующий входной символ.
  3. Если X - терминал, и X a, то анализатор останавливается и сообщает о том, что входная цепочка не принадлежит языку.
  4. Если X - нетерминал, анализатор заглядывает в таблицу M[X; a]. Возможны два случая:
    1. Значением M[X; a] является правило для X. В этом случае анализатор заменяет X на верхушке магазина на правую часть данного правила, а само правило помещает на выходную ленту. Указатель входа не передвигается.
    2. Значением M[X; a] является "ошибка". В этом случае анализатор останавливается и сообщает о том, что входная цепочка не принадлежит языку. Работа анализатора может быть задана следующей программой:

Поместить '$', затем S в магазин;

do

   {X=верхний символ магазина;

if (X - терминал)

   if (X==InSym)

      {удалить X из магазина;

      InSym=очередной символ;

      }

   else {error(); break;}

else if (X - нетерминал)

   if (M[X,InSym]=="X->Y1Y2...Yk")

    {удалить X из магазина;

     поместить Yk,Yk-1,...Y1 в магазин

      (Y1 на верхушку);

     вывести правило X->Y1Y2...Yk;

    }

   else {error(); break;} /*вход таблицы M пуст*/

 }

while (X!='$'); /*магазин пуст*/

if (InSym != '$') error(); /*Не вся строка прочитана*/

Пример 4.3. Рассмотрим грамматику арифметических выражений G=({E; E', T, T', F}, {id, +, *, (, )}, P, E) с правилами:

begin{align*}&#10;&amp;Erightarrow TE' &amp;T&amp;' rightarrow *FT' \&#10;&amp;E'rightarrow +TE' &amp;T&amp;' rightarrow e \&#10;&amp;E'rightarrow e &amp;F&amp; rightarrow (E) \&#10;&amp;Trightarrow FT' &amp;F&amp; rightarrow id. \&#10;end{align*}

В таблица 4.3 приведена предсказывающего анализатора для этой грамматики. Пустые клетки таблицы соответствуют элементу "ошибка".

Таблица 4.3.

Нетерминал

Входной символ

id

+

*

(

)

$

E

ETE'

ETE'

E'

E'+TE'

E'e

E'e

T

TFT'

TFT'

T'

T'e

T'*FT'

T'e

T'e

F

Fid

F(E)

При разборе входной цепочки id + id * id$ анализатор совершает последовательность шагов, изображенную в таблица 4.4. Заметим, что анализатор осуществляет в точности левый вывод. Если за уже просмотренными входными символами поместить символы грамматики в магазине, то можно получить в точности левые сентенциальные формы вывода. Дерево разбора для этой цепочки приведено на рис. рис. 4.3.

Таблица 4.4.

Магазин

Вход

Выход

E$

id + id * id$

TE'$

id + id * id$

E TE'

FT'E'$

id + id * id$

T FT'

id T'E'$

id + id * id$

F id

T'E'$

+id * id$

E'$

+id * id$

T' e

+TE'$

+id * id$

E' +TE

TE'$

id * id$

FT'E'$

id * id$

T FT'

id T'E'$

id * id$

F id

T'E'$

*id$

*F'T'E'$

*id$

T' *FT'

FT'E'$

id$

id T'E'$

id$

F id

T'E'$

$

E'$

$

T' e

$

$

E' e

Функции FIRST и FOLLOW

При построении таблицы предсказывающего анализатора нам потребуются две функции - FIRST и FOLLOW.

Пусть G = (N, T, P, S) - КС-грамматика. Для - произвольной цепочки, состоящей из символов грамматики, определим FIRST() как множество терминалов, с которых

4_5


Рис. 4.3.

начинаются строки, выводимые из . Если * e, то e также принадлежит FIRST().

Определим FOLLOW(A) для нетерминала A как множество терминалов a, которые могут появиться непосредственно справа от A в некоторой сентенциальной форме грамматики, то есть множество терминалов a таких, что существует вывод вида S * Aaβ для некоторых alpha, beta in (N cup T)^*. Заметим, что между A и a в процессе вывода могут находиться нетерминальные символы, из которых выводится e. Если A может быть самым правым символом некоторой сентенциальной формы, то $ также принадлежит FOLLOW(A).

Рассмотрим алгоритмы вычисления функции FIRST.

Алгоритм 4.3. Вычисление FIRST для символов КС- грамматики.

Вход. КС-грамматика G = (N, T, P, S).

Выход. Множество FIRST(X) для каждого символа X in (N cup T).

Метод. Выполнить шаги 1-3:

(1) Если X - терминал, то положить FIRST(X) = {X}; если X - нетерминал, положить FIRST(X) = .

(2) Если в P имеется правило X e, то добавить e к FIRST(X).

(3) Пока ни к какому множеству FIRST(X) нельзя уже будет добавить новые элементы, выполнять:

do { continue = false;

  Для каждого нетерминала X

   Для каждого правила X  Y1Y2...Yk

   {i=1; nonstop = true;

   while (i  k && nonstop)

    {добавить FIRST(Yi) n {e} к FIRST(X);

    if (Были добавлены новые элементы)

      continue = true;

    if (e  FIRST (Yi)) nonstop = false;

    else i+ = 1;

    }

    if (nonstop) {добавить e к FIRST(X);

      continue = true;

  } } }

while (continue);

Алгоритм 4.4. Вычисление FIRST для цепочки.

Вход. КС-грамматика G = (N, T, P, S).

Выход. Множество FIRST(X_1 X_2 ldots X_n), X_i in (N cup T).

Метод. Выполнить шаги 1-3:

(1) При помощи алгоритма 4.3. вычислить FIRST(X) для каждого X in (N cup T).

(2) Положить FIRST(X1X2 ... Xn) = .

(3)

 {i = 1; nonstop = true;

   while (i  && nonstop)

    {добавить FIRST(Xi) n {e} к FIRST(u);

      if (e  FIRST(Xi)nonstop = false;

        else i+ = 1;

    }

if (nonstop) {добавить e к FIRST(u);

    } }

Рассмотрим алгоритм вычисления функции FOLLOW.

Алгоритм 4.5. Вычисление FOLLOW для нетерминалов грамматики.

Вход. КС-грамматика G = (N, T, P, S).

Выход. Множество FOLLOW(X) для каждого символа X in N.

Метод. Выполнить шаги 1-4:

(1) Положить FOLLOW(X) = для каждого символа X in N.

(2) Добавить $ к FOLLOW(S).

(3) Если в P eсть правило вывода A Bβ, где alpha, beta in (N cup T)^*, то все элементы из FIRST(β), за исключением e, добавить к FOLLOW(B).

(4) Пока ничего нельзя будет добавить ни к какому множеству FOLLOW(X), выполнять:

если в P есть правило A B или A Bβ, alpha, beta in (N cup T)^*, где FIRST(β) содержит e (β*e), то все элементы из FOLLOW(A) добавить к FOLLOW(B).

Пример 4.4. Рассмотрим грамматику из примера 4.3. Для нее

FIRST(E) = FIRST(T) = FIRST(F) = {(, id}

FIRST(E') = {+, e}

FIRST(T') = {*, e}

FOLLOW(E) = FOLLOW(E') = { ), $}

FOLLOW(T) = FOLLOW(T') = {+, ), $}

FOLLOW(F) = {+, *, ), $}

Например, id и левая скобка добавляются к FIRST(F) на шаге 3 при i = 1, поскольку FIRST(id) = {id} и FIRST("(") = {"("} в соответствии с шагом 1. На шаге 3 при i = 1, в соответствии с правилом вывода T FT', к FIRST(T) добавляются также id и левая скобка. На шаге 2 в FIRST(E') включается e.

Также при вычислении множеств FOLLOW на шаге 2 в FOLLOW(E) включается $. На шаге 3, на основании правила F (E), к FOLLOW(E) добавляется также правая скобка. На шаге 4, примененном к правилу E TE', в FOLLOW(E') включаются $ и правая скобка. Поскольку E' Rightarrow^* e, они также попадают и во множество FOLLOW(T). В соответствии с правилом вывода E TE', на шаге 3 в FOLLOW(T) включаются и все элементы из FIRST(E'), отличные от e.

Определим теперь функцию FIRSTk(R), где k - натуральное число и alpha in (N cup Sigma)^*.

FIRST_k(alpha) = {w in Sigma^* midлибо |w| < k и alpha Rightarrow_G w, либо mid ! w ! mid = k ; text{и} ; R Rightarrow_G wxдля некоторого x in Sigma^*}.

Если alpha in Sigma^*, то FIRSTk() = {w}, где w - это первые k символов цепочки при || k и w = при || < k.

Приведем алгоритм вычисления функции FIRSTk(β), где beta= X_1X_2 ldots X_n in (N cup Sigma)^*.

Определение. Пусть Σ - некоторый алфавит. Если L1 и L2 - подмножества Σ*, то положим

begin{align*}&#10;L_1 oplus_k L_2 = {&amp;w ! mid ; text{для некоторых} ; x in L_1 ; text{и} ; y in L2 \&#10;&amp;text{либо} ; w = xy, ; text{если} mid ! xy ! mid leq k,\&#10;&amp;text{либо } w text{ состоит из первых } k text{ символов}\&#10;&amp;text{цепочки } xy}&#10;end{align*}

Лемма 4.1. Для любой КС-грамматики G = (N, Σ, P, S) и любых alpha, beta in (N cup Sigma)^*

FIRST_k(alphabeta) = FIRST_k(alpha) oplus_k FIRST_k(beta)

Доказательство оставляем читателю в качестве упражнения.

Aлгоритм 4.6. Вычисление функции FIRSTk().

Вход. КС-грамматика G = (N, Σ, P, S) и цепочка alpha =&#10;X_1X_2 ldots X_n in (N cup Sigma)^*.

Выход. FIRSTk(β).

Метод. Так как по последней лемме

begin{align*}&#10;FIRST_k(beta) = &amp;FIRST_k(X_1) oplus_k FIRST_k(X_2) oplus_k ldots \&#10;&amp;ldots oplus_k FIRST_k(X_n);&#10;end{align*}

то достаточно показать, как найти FIRSTk(X) для X in N.

Если X in Sigma cup {e}, то очевидно, что FIRSTk(X) = {X}.

Определим множества Fi(X) для каждого X in N cup Sigmaи возрастающих значений i 0:

(1) Fi(a) = {a} для всех a in Sigmaи i 0:

(2) F_0(A) = {x mid x in Sigma^{*k}и существует правило A x из P, для которого либо |x| = k, либо |x| < k и = e}.

(3) Допустим, что F0, F1..., Fi-1 уже определены для всех A in N. Тогда

Fi(A) = Fi-1(A) {x|A Y1...Yn принадлежит P и x in F_{i-1}(Y_1) oplus_k F_{i-1}(Y_2) oplus_k ldots oplus_k F_{i-1}(Y_n)}

(4) Так как F_{i-1}(A) subseteq F_i(A) subseteq Sigma^{*k}для всех A и i, то в конце концов мы дойдем до такого i, что Fi-1(A) = Fi(A) для всех A in N. Тогда положим FIRSTk(A) = Fi(A) для этого значения i.

Конструирование таблицы предсказывающего анализатора

Для конструирования таблицы предсказывающего анализатора по грамматике G может быть использован алгоритм, основанный на следующей идее. Предположим, что A - правило вывода грамматики и a in FIRST(R). Тогда анализатор делает развертку A по , если входным символом является a. Трудность возникает, когда = e или alpha Rightarrow^* e. В этом случае нужно развернуть A в , если текущий входной символ принадлежит FOLLOW(A) или если достигнут $ и $ in FOLLOW(A).

Алгоритм 4.7. Построение таблицы предсказывающего анализатора.

Вход. КС-грамматика G = (N, T, P, S).

Выход. Таблица M[A; a] предсказывающего анализатора, A in N, a in T cup { $ }.

Метод. Для каждого правила вывода A грамматики выполнить шаги 1 и 2. После этого выполнить шаг 3.

(1) Для каждого терминала a из FIRST(R) добавить AR к M[A; a].

(2) Если e in FIRST(R), добавить A R к M[A; b] для каждого терминала b из FOLLOW(A). Кроме того, если e in FIRST(R)и $ in FOLLOW(A), добавить A к M[A; $].

(3) Положить все неопределенные входы равными "ошибка".

Пример 4.5. Применим алгоритм 4.7 к грамматике из примера 4.3. Поскольку FIRST(TE') = FIRST(T) = {(, id }, в соответствии с правилом вывода E TE' входы M[E, ( ] и M[E, id ] становятся равными E TE'.

В соответствии с правилом вывода E' +TE' значение M[E', +] равно E' +TE'. В соответствии с правилом вывода E' e значения M[E', )] и M[E', $] равны E' e, поскольку FOLLOW(E') = { ), $}.

Таблица анализа, построенная по алгоритму 4.7. для этой грамматики, приведена в таблица 4.3.

LL(1)-грамматики

Алгоритм 4.7 построения таблицы предсказывающего анализатора может быть применен к любой КС-грамматике. Однако для некоторых грамматик построенная таблица может иметь неоднозначно определенные входы. Например, нетрудно доказать, что если грамматика леворекурсивна или неоднозначна, таблица будет иметь по крайней мере один неоднозначно определенный вход.

Грамматики, для которых таблица предсказывающего анализатора не имеет неоднозначно определенных входов, называются LL(1)-грамматиками. Предсказывающий анализатор, построенный для LL(1)-грамматики, называется LL(1)-анализатором. Первая буква L в названии связано с тем, что входная цепочка читается слева направо, вторая L означает, что строится левый вывод входной цепочки, 1 - что на каждом шаге для принятия решения используется один символ непрочитанной части входной цепочки.

Доказано, что алгоритм 4.7 для каждой из LL(1)-грам- матик G строит таблицу предсказывающего анализатора, распознающего все цепочки из L(G) и только эти цепочки. Нетрудно доказать также, что если G - LL(1)-грамматика, то L(G) - детерминированный КС-язык.

Справедлив также следующий критерий LL(1)-граммати- ки. Грамматика G = (N, T, P, S) является LL(1)-грамматикой тогда и только тогда, когда для каждой пары правил A , A β из P

(то есть правил с одинаковой левой частью) выполняются следующие 2 условия:

(1) FIRST(alpha) cap FIRST(beta) = oslash;

(2) Если e in FIRST(alpha), то FIRST(β) FOLLOW(A)= .

Язык, для которого существует порождающая LL(1)- грамматика, называют LL(1)-языком. Доказано, что проблема определения, порождает ли грамматика LL-язык, является алгоритмически неразрешимой.

Пример 4.6. Неоднозначная грамматика не является LL(1). Примером может служить следующая грамматика

 

G = ({S, E}, {if, then, else, a, b}, P, S) с правилами:

   S  if E then S | if E then S else S | a

   E  b

Эта грамматика является неоднозначной, что иллюстрируется на рис. 4.4.

LL(k)-грамматики

Определение. КС-грамматика G = (N, Σ, P, S) называется LL(k)-грамматикой для некоторого фиксированного k, если из

(1) S Rightarrow^*_i ; omega A alpha Rightarrow_l omega beta alpha Rightarrow^* omega x

(2) S Rightarrow^*_i ; omega A alpha Rightarrow_l omega gamma alpha Rightarrow^* omega xдля которых

и

FIRSTk(x) = FIRSTk(y), вытекает, что β = γ.

Говоря менее формально, G будет LL(k)- грамматикой, если для данной цепочки omega A alpha in (N cup Sigma)^*и первых k символов (если они есть), выводящихся из A, существует не более одного правила, которое можно применить к A, чтобы получить вывод какой-нибудь терминальной цепочки,

4_6


Рис. 4.4.

начинающейся с ω и продолжающейся упомянутыми k терминалами.

Грамматика называется LL(k)-грамматикой, если она LL(k)-грамматика для некоторого k.

Пример 4.7. Рассмотрим грамматику G = ({S, A, B}, {0, 1, a, b}, P, S), где P состоит из правил

   S  A | B,

   A  aAb | 0,

   B  aBbb | 1.

Здесь L(G) = an0bn | n 0 an1b2n | n 0. G не является LL(k)-грамматикой ни для какого k. Интуитивно, если мы начинаем с чтения достаточно длинной цепочки, состоящей из символов a, то не знаем, какое из правил S A и S B было применено первым, пока не встретим 0 или 1.

Обращаясь к точному определению LL(k)-грамматики, положим ω = = e; β = A; γ = B; x = ak0bk и y = ak1b2k. Тогда выводы

begin{align*}&#10;&amp;S Rightarrow^0_l S Rightarrow_l A Rightarrow^*_l a^k0b^k \&#10;&amp;S Rightarrow^0_l S Rightarrow_l B Rightarrow^*_l a^k1b^{2k}&#10;end{align*}

соответствуют выводам (1) и (2) определения. Первые k символов цепочек x и y совпадают. Однако заключение β = γ ложно. Так как k здесь выбрано произвольно, то G не является LL-грамматикой.

Следствия определения LL(k)- грамматики

Теорема 4.6. КС-грамматика G = (N, Σ, P, S) является LL(k)-грамматикой тогда и только тогда, когда для двух различных правил A β и A γ из Р пересечение FIRSTk) FIRSTk) пусто при всех таких ωA, что S Rightarrow^*_l omega A alpha.

Доказательство. Необходимость. Допустим, что ω, A, , β и γ удовлетворяют условиям теоремы, а FIRSTk) FIRSTk) содержит x. Тогда по определению FIRST для некоторых y и z найдутся выводы

begin{align*}&#10;&amp;S Rightarrow^*_l omega A alpha Rightarrow_l omega beta alpha Rightarrow^*_l omega x y \&#10;&amp; text{и}\&#10;&amp;S Rightarrow^*_l omega A alpha Rightarrow_l omega gamma alpha Rightarrow^*_l omega x y &#10;end{align*}

(Заметим, что здесь мы использовали тот факт, что N не содержит бесполезных нетерминалов, как это предполагается для всех рассматриваемых грамматик.) Если |x| < k; то y = z = e. Так как β γ, то G не LL(k)-грамматика.

Достаточность. Допустим, что G не LL(k)-грамматика.

Тогда найдутся такие два вывода

begin{align*}&#10;&amp;S Rightarrow^*_l omega A alpha Rightarrow_l omega beta alpha Rightarrow^*_l omega x \&#10;&amp; text{и}\&#10;&amp;S Rightarrow^*_l omega A alpha Rightarrow_l omega gamma alpha Rightarrow^*_l omega y &#10;end{align*}

что цепочки x и y совпадают в первых k позициях, но β γ. Поэтому A β и A γ - различные правила из P и каждое из множеств FIRSTk(β) и FIRSTk(γ) содержит цепочку FIRSTk(x), совпадающую с цепочкой FIRSTk(y).

Пример 4.8. Грамматика G, состоящая из двух правил S aS | a, не будет LL(1)-грамматикой, так как

FIRST1(aS) = FIRST1(a) = a.

Интуитивно это можно объяснить так: видя при разборе цепочки, начинающейся символом a, только этот первый символ, мы не знаем, какое из правил S aS или S a надо применить к S. С другой стороны, G - это LL(2)-грамматика. В самом деле, в обозначениях только что представленной теоремы, если S Rightarrow^*_l omega A alpha, то A = S и = e. Так как для S даны только два указанных правила, то β = aS и γ = a. Поскольку FIRST2(aS) = aa и FIRST2(a) = a, то по последней теореме G будет LL(2)-грамматикой.

Удаление левой рекурсии

Основная трудность при использовании предсказывающего анализа - это нахождение такой грамматики для входного языка, по которой можно построить таблицу анализа с однозначно определенными входами. Иногда с помощью некоторых простых преобразований грамматику, не являющуюся LL(1), можно привести к эквивалентной LL(1)-грамматике. Среди этих преобразований наиболее эффективными являются левая факторизация и удаление левой рекурсии. Здесь необходимо сделать два замечания. Во-первых, не всякая грамматика после этих преобразований становится LL(1), и, во-вторых, после таких преобразований получающаяся грамматика может стать менее понимаемой.

Непосредственную левую рекурсию, то есть рекурсию вида AA, можно удалить следующим способом. Сначала группируем A-правила:

A A1 |A2| ... |Am12| ... |βn;

где никакая из строк βi не начинается с A. Затем заменяем этот набор правил на

begin{align*}&#10;&amp;Arightarrowbeta_1A' mid beta_2A' mid ldots mid beta_nA'\&#10;&amp;A'rightarrowalpha_1A' mid alpha_2A' mid ldots mid alpha_nA'mid e\&#10;end{align*}

где A' - новый нетерминал. Из нетерминала A можно вывести те же цепочки, что и раньше, но теперь нет левой рекурсии. С помощью этой процедуры удаляются все непосредственные левые рекурсии, но не удаляется левая рекурсия, включающая два или более шага. Приведенный ниже алгоритм 4.8 позволяет удалить все левые рекурсии из грамматики.

Алгоритм 4.8. Удаление левой рекурсии.

Вход. КС-грамматика G без e-правил (вида A e).

Выход. КС-грамматика G' без левой рекурсии, эквивалентная G.

Метод. Выполнить шаги 1 и 2.

(1) Упорядочить нетерминалы грамматики G в произвольном порядке.

(2) Выполнить следующую процедуру:

begin{align*}&#10;textbf{fo}&amp;textbf{r} (i=1;i&lt;=n;i++){\&#10; &amp;textbf{for} (j=1;j&lt;=i-1;j++) { \&#10; &amp;quad text{пусть } A_j rightarrow beta_1mid beta_2 mid ldots mid beta_k text{ - все текущие правила }\&#10; &amp;quadtext{для } A_j;\&#10; &amp;quadtext{заменить все правила вида } A_i rightarrow A_j alpha\&#10; &amp;quad text{на правила } A_i rightarrow beta_1alpha mid beta_2 alpha mid ldots mid beta_k alpha ;\&#10; &amp;;}\&#10; &amp;; text{удалить правила вида } A_i rightarrow A_i; \&#10; &amp;; text{удалить непосредственную левую рекурсию в} \&#10; &amp;; text{правилах для } A_i;\&#10; }&#10;end{align*}

После (i-1)-й итерации внешнего цикла на шаге 2 для любого правила вида Ak As, где k < i, выполняется s > k. В результате на следующей итерации (по i) внутренний цикл (по j) последовательно увеличивает нижнюю границу по m в любом правиле Ai Am, пока не будет m i. Затем, после удаления непосредственной левой рекурсии для Ai-правил, m становится больше i.

Алгоритм 4.8 применим, если грамматика не имеет e- правил (правил вида A e). Имеющиеся в грамматике e- правила могут быть удалены предварительно. Получающаяся грамматика без левой рекурсии может иметь e-правила.

Левая факторизация

Oсновная идея левой факторизации в том, что в том случае, когда неясно, какую из двух альтернатив надо использовать для развертки нетерминала A, нужно изменить A-правила так, чтобы отложить решение до тех пор, пока не будет достаточно информации для принятия правильного решения.

Если A β1 | β2 - два A-правила и входная цепочка начинается с непустой строки, выводимой из , мы не знаем, разворачивать ли по первому правилу или по второму. Можно отложить решение, развернув A A'. Тогда после анализа того, что выводимо из , можно развернуть по A' β1 или по A' β2. Левофакторизованные правила принимают вид

AA'

A'β12

Алгоритм 4.9. Левая факторизация грамматики.

Вход. КС-грамматика G.

Выход. Левофакторизованная КС-грамматика G', эквивалентная G.

Метод. Для каждого нетерминала A найти самый длинный префикс , общий для двух или более его альтернатив. Если e, то есть существует нетривиальный общий префикс, заменить все A-правила

A β1|β2| ... |βn|z,

где z обозначает все альтернативы, не начинающиеся с , на

A A'|z

A'β12| ... |βn

где A' - новый нетерминал. Применять это преобразование, пока никакие две альтернативы не будут иметь общего префикса.

Пример 4.9. Рассмотрим вновь грамматику условных операторов из примера 4.6:

  S  if E then S | if E then S else S | a

  E  b

После левой факторизации грамматика принимает вид

  S  if E then SS' | a

  S'  else S | e

  E  b

К сожалению, грамматика остается неоднозначной, а значит, и не LL(1)-грамматикой.

Рекурсивный спуск

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

В процедуре A для случая, когда имеется правило A ui, такое, что u_i Rightarrow^* e(напомним, что не может быть больше одного правила, из которого выводится e), приведены два варианта - 1.1 и 1.2. В варианте 1.1 делается проверка, принадлежит ли следующий входной символ FOLLOW(A): Если нет - выдается ошибка. В варианте 1.2 этого не делается, так что анализ ошибки откладывается на процедуру, вызвавшую A.

begin{align*}&#10;&amp;text{$textbf{void} ; A(){ ; // ; A rightarrow u_1mid u_2 mid ldots mid u_k$}\&#10;&amp;quad ; text{$textbf{if} ; (InSym in FIRST(u_i))$ // только одному!}\&#10;&amp; quad quad ; text{$textbf{if} ; (textrm{parse}(u_i))$}\&#10;&amp; quad quad quad text{$textrm{result}(&quot;A rightarrow u_i&quot;);$}\&#10;&amp; quad quad ; text{$textbf{else} ; textrm{error}();$}\&#10;&amp;quad ; text{$textbf{else}$}\&#10;&amp;quad ; text{//Вариант 1:}\&#10;&amp;quad ; text{$textbf{if}$ ; (имеется правило ; $A rightarrow u_i$ ; такое, что ; $u_iRightarrow^*e$)}\&#10;&amp; quad quad ;text{//Вариант 1.1}\&#10;&amp; quad quad ;text{$textbf{if} ; (textit{InSym} in textit{FOLLOW}(textit{A}))$}\&#10;&amp; quad quad quad text{$textrm{result}(&quot;A rightarrow u_i&quot;);$}\&#10;&amp; quad quad ; text{$textbf{else} ; textrm{error}();$}\&#10;&amp; quad quad ;text{//Конец варианта 1.1}\&#10;&amp; quad quad ;text{//Вариант 1.2:}\&#10;&amp; quad quad ;text{$textrm{result}(&quot;A rightarrow u_i&quot;);$}\&#10;&amp; quad quad ;text{//Конец варианта 1.2}\&#10;&amp;quad ; text{//Конец варианта 1}\&#10;&amp;quad ; text{//Вариант 2:}\&#10;&amp;quad ; text{$textbf{if}$ ;(нет правила ; $A rightarrow u_i$ ; такого, что ; $u_i Rightarrow^* e)&#10;$}\&#10;&amp; quad quad ;text{error();}\&#10;&amp;quad ; text{//Конец варианта 2}\&#10;&amp;text{}}\&#10;&amp;text{textbf{boolean} parse($u$){ // из $u$ не выводится $e$}\&#10;&amp;quad ; text{$v = u;$}\&#10;&amp;quad ; text{while ($v neq e){; //; v = Xz$}\&#10;&amp; quad quad ;text{textbf{if} ($X$ - терминал $a$)}\&#10;&amp; quad quad quad text{textbf{if} ($InSym neq a$)}\&#10;&amp; quad quad quad quad text{textbf{return}(false);}\&#10;&amp; quad quad quad text{textbf{else} textit{InSym} = getInsym();}\&#10;&amp; quad quad ;text{textbf{else} // $X$ - нетерминал $B$}\&#10;&amp; quad quad quad text{$B$();}\&#10;&amp; quad quad quad text{$v=z;$}\&#10;&amp;quad ; text{}}\&#10;&amp;quad ; text{textbf{return}(true);}\&#10;&amp; text{}}&#10;end{align*}

Восстановление процесса анализа после синтаксических ошибок

В приведенных программах рекурсивного спуска была использована процедура реакции на синтаксические ошибки error(). В простейшем случае эта процедура выдает диагностику и завершает работу анализатора. Но можно попытаться некоторым разумным образом продолжить работу. Для разбора сверху вниз можно предложить следующий простой алгоритм.

Если в момент обнаружения ошибки на верхушке магазина оказался нетерминальный символ A и для него нет правила, соответствующего входному символу, то сканируем вход до тех пор, пока не встретим символ либо из FIRST(A), либо из FOLLOW(A). В первом случае разворачиваем A по соответствующему правилу, во втором - удаляем A из магазина.

Если на верхушке магазина терминальный символ, то можно удалить все терминальные символы с верхушки магазина вплоть до первого (сверху) нетерминального символа и продолжать так, как это было описано выше.

Разбор снизу-вверх типа сдвиг- свертка

Основа

В процессе разбора снизу-вверх типа сдвиг-свертка строится дерево разбора входной цепочки, начиная с листьев (снизу) к корню (вверх). Этот процесс можно рассматривать как "свертку" цепочки w к начальному символу грамматики. На каждом шаге свертки подцепочка, которую можно сопоставить правой части некоторого правила вывода, заменяется символом левой части этого правила вывода, и если на каждом шаге выбирается правильная подцепочка, то в обратном порядке прослеживается правосторонний вывод (рис. 4.5) Здесь ко входной цепочке, так же как и при анализе LL(1)-грамматик, приписан концевой маркер $.

4_7


Рис. 4.5.

Основой цепочки называется подцепочка сентенциальной формы, которая может быть сопоставлена правой части некоторого правила вывода, свертка по которому к левой части правила соответствует одному шагу в обращении правостороннего вывода. Самая левая подцепочка, которая сопоставляется правой части некоторого правила вывода A γ, не обязательно является основой, поскольку свертка по правилу A γ может дать цепочку, которая не может быть сведена к аксиоме.

Формально, основа правой сентенциальной формы z - это правило вывода A γ и позиция в z, в которой может быть найдена цепочка γ такие, что в результате замены γ на A получается предыдущая сентенциальная форма в правостороннем выводе z. Так, если S Rightarrow^*_r ; alpha A beta Rightarrow^*_r alpha gamma beta, то A γ в позиции, следующей за , это основа цепочки γβ. Подцепочка β справа от основы содержит только терминальные символы.

Вообще говоря, грамматика может быть неоднозначной, поэтому не единственным может быть правосторонний вывод γβ и не единственной может быть основа. Если грамматика однозначна, то каждая правая сентенциальная форма грамматики имеет в точности одну основу. Замена основы в сентенциальной форме на нетерминал левой части называется отсечением основы. Обращение правостороннего вывода может быть получено с помощью повторного применения отсечения основы, начиная с исходной цепочки w. Если w - слово в рассматриваемой грамматике, то w = n, где n - n-я правая сентенциальная форма еще неизвестного правого вывода S = alpha_0 Rightarrow_r alpha_1 Rightarrow_r ldots Rightarrow_r alpha_{n-1} Rightarrow_r alpha_n = w.

Чтобы восстановить этот вывод в обратном порядке, выделяем основу γn в n и заменяем γn на левую часть некоторого правила вывода An γn, получая (n - 1)-ю правую сентенциальную форму n-1. Затем повторяем этот процесс, то есть выделяем основу γn-1 в n-1 и сворачиваем эту основу, получая правую сентенциальную форму n-2. Если, повторяя этот процесс, мы получаем правую сентенциальную форму, состоящую только из начального символа S, то останавливаемся и сообщаем об успешном завершении разбора. Обращение последовательности правил, использованных в свертках, есть правый вывод входной строки.

Таким образом, главная задача анализатора типа сдвиг- свертка - это выделение и отсечение основы.

LR(1)-анализаторы

В названии LR(1) символ L указывает на то, что входная цепочка читается слева-направо, R - на то, что строится правый вывод, наконец, 1 указывает на то, что анализатор видит один символ непрочитанной части входной цепочки.

LR(1)-анализ привлекателен по нескольким причинам:

  • LR(1)-анализ - наиболее мощный метод анализа без возвратов типа сдвиг-свертка;
  • LR(1)-анализ может быть реализован довольно эффективно;
  • LR(1)-анализаторы могут быть построены для практически всех конструкций языков программирования;
  • класс грамматик, которые могут быть проанализированы LR(1)-методом, строго включает класс грамматик, которые могут быть проанализированы предсказывающими анализаторами (сверху-вниз типа LL(1)).

Схематически структура LR(1)-анализатора изображена на рис. 4.4. Анализатор состоит из входной ленты, выходной ленты, магазина, управляющей программы и таблицы анализа (LR(1)-таблицы), которая имеет две части - функцию действий (Action) и функцию переходов (Goto). Управляющая программа одна и та же для всех LR(1)-анализаторов, разные анализаторы отличаются только таблицами анализа.

Анализатор читает символы на входной ленте по одному за шаг. В процессе анализа используется магазин, в котором хранятся строки вида S0X1S1X2S2 ... XmSm (Sm - верхушка магазина). Каждый Xi - символ грамматики (терминальный или нетерминальный), а Si - символ состояния.

Заметим, что символы грамматики (либо символы состояний) не обязательно должны размещаться в магазине. Однако, их использование облегчает понимание поведения LR-анализатора.

Элемент функции действий Action[Sm; ai] для символа состояния Sm и входа a_i in T cup {$}, может иметь одно из четырех значений:

  1. shift S (сдвиг), где S - символ состояния,
  2. reduce A γ (свертка по правилу грамматики A γ),
  3. accept (допуск),
  4. error (ошибка).

Элемент функции переходов Goto[Sm; A] для символа состояния Smи входа A in N, может иметь одно из двух значений:

4_8


Рис. 4.6.

  1. S, где S - символ состояния,
  2. error (ошибка).

Конфигурацией LR(1)-анализатора называется пара, первая компонента которой - содержимое магазина, а вторая - непросмотренный вход:

(S0X1S1X2S2 ... XmSm, aiai+1 ... an$)

Эта конфигурация соответствует правой сентенциальной форме

X1X2 ... Xmaiai+1 ... an

Префиксы правых сентенциальных форм, которые могут появиться в магазине анализатора, называются активными префиксами. Основа сентенциальной формы всегда располагается на верхушке магазина. Таким образом, активный префикс - это такой префикс правой сентенциальной формы, который не переходит правую границу основы этой формы.

Когда анализатор начинает работу, в магазине находится только символ начального состояния S0, на входной ленте - анализируемая цепочка с маркером конца.

Каждый очередной шаг анализатора определяется текущим входным символом ai и символом состояния на верхушке магазина Sm следующим ниже образом.

Пусть LR(1)-анализатор находится в конфигурации

(S0X1S1X2S2 ... XmSm, aiai+1 ... an$)

Анализатор может проделать один из следующих шагов:

  1. Если Action[Sm, ai] = shift S, то анализатор выполняет сдвиг, переходя в конфигурацию

(S_0X_1S_1X_2S_2 ldots X_mS_ma_iS, a_{i+1} ldots a_n$)

То есть, в магазин помещаются входной символ ai и символ состояния S, определяемый Action[Sm, ai]. Текущим входным символом становится ai+1.

  1. Если Action[Sm, ai] = reduce A γ, то анализатор выполняет свертку, переходя в конфигурацию

(S_0X_1S_1X_2S_2 ldots X_{m-r}S_{m-r}AS, a_ia_{i+1} ldots a_n$)

где S = Goto[Sm-r; A] и r - длина γ, правой части правила вывода. Анализатор сначала удаляет из магазина 2r символов (r символов состояния и r символов грамматики), так что на верхушке оказывается состояние Sm-r. Затем анализатор помещает в магазин A - левую часть правила вывода, и S - символ состояния, определяемый Goto[Sm-r, A]. На шаге свертки текущий входной символ не меняется. Для LR(1)- анализаторов последовательность символов грамматики Xm-r+1 ... Xm, удаляемых из магазина , всегда соответствует γ - правой части правила вывода, по которому делается свертка. После осуществления шага свертки генерируется выход LR(1)-анализатора, то есть исполняются семантические действия, связанные с правилом, по которому делается свертка, например, печатаются номера правил, по которым делается свертка. Заметим, что функция Goto таблицы анализа, построенная по грамматике G, фактически представляет собой функцию переходов детерминированного конечного автомата, распознающего активные префиксы G.

  1. Если Action[Sm, ai] = accept, то разбор успешно завершен.
  2. Если Action[Sm, ai] = error, то анализатор обнаружил ошибку, и выполняются действия по диагностике и восстановлению.

Пример 4.10. Рассмотрим грамматику арифметических выражений G = ({E, T, F}, {id, +, *}, P, E) с правилами:

  1. E E + T
  2. E T
  3. T T * F
  4. T F
  5. F id

На рис. 4.7 изображены функции Action и Goto, образующие LR(1)-таблицу для этой грамматики. Элемент Si функции Action означает сдвиг и помещение в магазин состояния с номером i, Rj - свертку по правилу номер j, acc - допуск, пустая клетка - ошибку. Для функции Goto символ i означает помещение в магазин состояния с номером i, пустая клетка - ошибку.

На входе id + id * id последовательность состояний магазина и входной ленты показаны на рис. 4.8. Например, в первой строке LR-анализатор находится в нулевом состоянии и "видит" первый входной символ id. Действие S6 в нулевой строке и столбце id в поле Action (рис. 4.7) означает сдвиг и помещение символа состояния 6 на верхушку магазина. Это и изображено во второй строке: первый символ id и символ состояния 6 помещаются в магазин, а id удаляется со входной ленты.

Текущим входным символом становится +, и действием в состоянии 6 на вход + является свертка по F id. Из магазина удаляются два символа (один символ состояния и один символ грамматики). Затем анализируется нулевое состояние. Поскольку Goto в нулевом состоянии по символу F - это 3, F и 3 помещаются в магазин. Теперь имеем конфигурацию, соответствующую третьей строке. Остальные шаги определяются аналогично.

Конструирование LR(1)-таблицы

Рассмотрим алгоритм конструирования таблицы, управляющей LR(1) - анализатором.

Пусть G = (N, T, P, S) - КС-грамматика. Пополненной грамматикой для данной грамматики G называется КС- Состояния Action Goto

4_9


Рис. 4.7.

4_10


Рис. 4.8.

грамматика

G' = (N cup {S'}, T, P cup {S' rightarrow S}, S');

то есть эквивалентная грамматика, в которой введен новый начальный символ S' и новое правило вывода S' S.

Это дополнительное правило вводится для того, чтобы определить, когда анализатор должен остановить разбор и зафиксировать допуск входа. Таким образом, допуск имеет место тогда и только тогда, когда анализатор готов осуществить свертку по правилу S' S.

LR(1)-ситуацией называется пара [A .β, a], где A β - правило грамматики, a - терминал или правый концевой маркер $. Вторая компонента ситуации называется аванцепочкой.

Будем говорить, что LR(1)-ситуация [A .β, a] допустима для активного префикса +, если существует вывод S Rightarrow^*_r gamma A w Rightarrow_r gamma alpha beta w, где + = γ и либо a - первый символ w, либо w = e и a = $.

Будем говорить, что ситуация допустима, если она допустима для какого-либо активного префикса.

Пример 4.11. Дана грамматика G = ({S, B}, {a, b}, P, S) с правилами

S BB

B aB | b

Существует правосторонний вывод S Rightrarrow^*_r aaBab Rightrarrow_r aaaBab. Легко видеть, что ситуация [B a.B, a] допустима для активного префикса + = aaa, если в определении выше положить γ = aa, A = B, w = ab, = a, β = B. Существует также правосторонний вывод S Rightarrow^*_r BaB Rightarrow_r BaaB. Поэтому для активного префикса Baa допустима ситуация [B a.B, $].

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

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

Чтобы не просматривать магазин на каждом шаге анализа, на верхушке магазина всегда хранится то состояние, в котором должен оказаться этот конечный автомат после того, как он прочитал символы грамматики в магазине от дна к верхушке. Рассмотрим ситуацию вида [A .Bβ, a] из множества ситуаций, допустимых для некоторого активного префикса z. Тогда существует правосторонний вывод S Rightarrow^*_r yAax Rightarrow_r y alpha B beta ax, где z = y. Предположим, что из βax выводится терминальная строка bw. Тогда для некоторого правила вывода вида B q имеется вывод S Rightarrow^*_r zBbw Rightarrow_r zqbw. Таким образом [B .q, b] также допустима для z и ситуация [A B.β, a] допустима для активного префикса zB. Здесь либо b может быть первым терминалом, выводимым из β, либо из β выводится e в выводе beta ax Rightarrow^*_r bwи тогда b равно a. То есть b принадлежит FIRST(β ax). Построение всех таких ситуаций для данного множества ситуаций, то есть его замыкание, делает приведенная ниже функция closure.

Система множеств допустимых LR(1)-ситуаций для всевозможных активных префиксов пополненной грамматики называется канонической системой множеств допустимых LR(1)-ситуаций. Алгоритм построения канонической системы множеств приведен ниже.

Алгоритм 4.10. Конструирование канонической системы множеств допустимых LR(1)-ситуаций.

Вход. КС-грамматика G = (N, T, P, S).

Выход. Каноническая система C множеств допустимых LR(1)-ситуаций для грамматики G.

Метод. Выполнить для пополненной грамматики G' процедуру items, которая использует функции closure и goto.

function closure(I){ /* I - множество ситуаций */

 do{

  for (каждой ситуации [A  .Bβ, a] из I,

  каждого правила вывода B  γ из G',

  каждого терминала b из FIRST(βa),

  такого, что [B .γ, b] нет в I)

  добавить [B .γ, b] к I;

 }

 while (к I можно добавить новую ситуацию);

 return I;

}

function goto(I,X){ /* I - множество ситуаций;

                  X - символ грамматики */

  Пусть J = {[A  X.β; a] | [A  .Xβ, a] in I};

  return closure(J);

 }

 

procedure items(G'){ /* G' - пополненная

               грамматика */

  I' = closure({[S'  .S, $]});

  C = {I0};

  do{

    for (каждого множества ситуаций I из

     системы C, каждого символа грамматики X

      такого, что goto(I, X) не пусто

     и не принадлежит C)

     добавить goto(I, X) к системе C;

   }

   while (к C можно добавить новое множество

         ситуаций);

  

Если I - множество ситуаций, допустимых для некоторого активного префикса +, то goto(I, X) - множество ситуаций, допустимых для активного префикса +X.

Работа алгоритма построения системы C множеств допустимых LR(1)-ситуаций начинается с того, что в C помещается начальное множество ситуаций I0 = closure({[S' .S, $]}). Затем с помощью функции goto вычисляются новые множества ситуаций и включаются в C.

По-существу, goto(I, X) - переход конечного автомата из состояния I по символу X.

Рассмотрим теперь, как по системе множеств LR(1)-ситу- аций строится LR(1)-таблица, то есть функции действий и переходов LR(1)-анализатора.

Алгоритм 4.11. Построение LR(1)-таблицы.

Вход. Каноническая система C = {I0, I1, ... , In} множеств допустимых LR(1)-ситуаций для грамматики G.

Выход. Функции Action и Goto, составляющие LR(1)- таблицу для грамматики G.

Метод. Для каждого состояния i функции Action[i, a] и Goto[i, X] строятся по множеству ситуаций Ii:

  1. Значения функции действия (Action) для состояния i определяются следующим образом:
    1. если [A rightarrow alpha .a beta , b] in I_i(a - терминал) и goto(Ii, a)= Ij , то полагаем Action[i, a] = shift j;
    2. если [A rightarrow alpha; ., a] in I_i, причем A S', то полагаем Action[i, a] = reduce A ;
    3. если [S' rightarrow S., $] in I_i, то полагаем Action[i, $] = accept.
  2. Значения функции переходов для состояния i определяются следующим образом: если goto(Ii, A) = Ij , то Goto[i, A] = j (здесь A - нетерминал).
  3. Все входы в Action и Goto, не определенные шагами 2 и 3, полагаем равными error.
  4. Начальное состояние анализатора строится из множества, содержащего ситуацию [S' .S, $].

Таблица на основе функций Action и Goto, полученных в результате работы алгоритма 4.11., называется канонической LR(1)-таблицей. Работающий с ней LR(1)-анализатор, называется каноническим LR(1)-анализатором.

Пример 4.12. Рассмотрим следующую грамматику, являющуюся пополненной для грамматики из примера 4.8.:

(0) E' E

(1) E E + T

(2) E T

(3) T T * F

(4) T F

(5) F id.

Множества ситуаций и переходы по goto для этой грамматики приведены на рис. 4.9. LR(1)-таблица для этой грамматики приведена на рис. 4.7.

Проследим последовательность создания этих множеств более подробно.

  1. Вычисляем I0 = closure({[E' .E, $]}).
    1. Ситуация [E' .E, $] попадает в него по умолчанию как исходная.
    2. Если обратиться к обозначениям функции closure, то для нее

begin{align*}&#10;&amp; alpha = beta = e, quad B=E, quad a=$,\&#10;&amp; first(beta a) = first($) = { $ }.&#10;end{align*}

Значит, для терминала $ добавляем ситуации на основе правил со знаком E в левой части правила. Это правила

begin{align*}&#10;&amp; E rightarrow E + T text{ и } E rightarrow T&#10;end{align*}

и соответствующие им ситуации

begin{align*}&#10;&amp; [E rightarrow. E + T, $] text{ и } [E rightarrow . T, $]&#10;end{align*}

    1. Просматриваем получившиеся ситуации. Для ситуации [E .E + T, $] β = +, поэтому first(βa) = first(+$) = {+}. На основе этого добавляем к I0 [E E + .T, +] и [E .T, +].
    2. Для ситуации [E .T, $] β = e, first(βa) = {$}. Поэтому добавляем к I0 [T . T * F, $] и [T .F, $].
    3. Подобно этому для ситуации [E .T, +] β = e, first(βa) = {+}. Поэтому добавляем к I0 [T .T * F, +] и [T .F, +].
    4. Из ситуации [T . T * F, +] β = *, first(βa) = {*}: Поэтому добавляем к I0

4_11sm


увеличить изображение
Рис. 4.9.

[T .T * F, *] и [T .F, *]

    1. Далее из ситуации [T .F, *] получаем ситуацию [F . id, *]. из ситуации [T . F, $] - ситуацию [F . id, $], а из ситуации [T . F, +] - [F . id, *].

Таким образом, все 14 искомых ситуаций I0 получены.

Возвращаемся в головную функцию items, включаем I0 в множество C и исследуем непустые итоги применения функции goto(I0; X), где X in {E', E, T, F, +, *, $, id}.

Если посмотреть на вид правил в функции goto(I0; X), то видно, что X должен встретиться в правой части хотя бы одного правила. Для E0 таких правил у нас нет, поэтому значение функции goto(I0, E') пусто.

Возьмем goto(I0; E). E встречается после точки в правых частях двух ситуаций из I0, значит берем эти два правила и переносим в них точки на один символ вправо (пока есть куда - не уперлись в запятую), получаем:

[E' E., $]

и

[E E. + T, $|+]

Вычислим от каждой из этих ситуаций функцию closure. Но, поскольку справа от точки здесь либо пустая цепочка, либо терминал, то никаких новых ситуаций не возникает. Дальше отслеживаем, может ли куда-то сдвинуться точка дальше на право и по какому символу. Если может, строим соответствующее множество (рис. 4.9). И т.д.

LR(1)-грамматики

Если для КС-грамматики G функция Action, полученная в результате работы алгоритма 4.11., не содержит неоднозначно определенных входов, то грамматика называется LR(1)- грамматикой.

Язык L называется LR(1)-языком, если он может быть порожден некоторой LR(1)-грамматикой.

Иногда используется другое определение LR(1)-грамматики. Грамматика называется LR(1), если из условий

  1. S' Rightarrow^*_r uAw Rightarrow_r uvw
  2. S' Rightarrow^*_r zBx Rightarrow_r uvy
  3. FIRST(w)=FIRST(y)

следует, что uAy = zBx (то есть u = z, A = B и x = y).

Согласно этому определению, если uvw и uvy - правовыводимые цепочки пополненной грамматики, у которых FIRST(w) = FIRST(y) и A β - последнее правило, использованное в правом выводе цепочки uvw, то правило A β должно применяться и в правом разборе при свертке uvy к uAy. Так как A дает β независимо от w, то LR(1)-условие означает, что в FIRST(w) содержится информация, достаточная для определения того, что uv за один шаг выводится из uA. Поэтому никогда не может возникнуть сомнений относительно того, как свернуть очередную правовыводимую цепочку пополненной грамматики.

Можно доказать, что эти два определения эквивалентны. Дадим теперь определение LR(k)-грамматики.

Определение. Пусть G = (N, Σ, P, S) - КС-грамматика и G' = (N', Σ, P', S') - полученная из нее пополненная грамматика. Будем называть G LR(k)-грамматикой для k 0, если из условий

(1) S' Rightarrow^*_{G^{'r}} alpha A omega Rightarrow_{G^{'r}} alpha beta omega

(2)S' Rightarrow^*_{G^{'r}} gamma B x Rightarrow_{G^{'r}} alpha beta y

(3)FIRSTk(w) = FIRSTk(y)

следует, что Ay = γBx (т.е. = γ, A = B и x = y):

Грамматика G называется LR-грамматикой, если она LR(k)-грамматика для некоторого k 0:

Интуитивно это определение говорит о том, что если βw и βy - правовыводимые цепочки пополненной грамматики, у которых FIRSTk(w) = FIRSTk(y), и A β - последнее правило, использованное в правом выводе цепочки βw, то правило A β должно использоваться также в правом разборе при свертке βy к Ay. Так как A даeт β независимо от w, то LR(k)-условие говорит о том, что в FIRSTk(w) содержится информация, достаточная для определения того, что β за один шаг выводится из A. Поэтому никогда не может возникнуть сомнений относительно того, как свернуть очередную правовыводимую цепочку пополненной грамматики. Кроме того, работая с LR(k)-грамматикой, мы всегда знаем, допустить ли данную входную цепочку или продолжать разбор.

Пример 4.13. Рассмотрим грамматику G с правилами

S Sa|a

Согласно определению, G не LR(0)-грамматика, так как из трeх условий

(1)S' Rightarrow^0_{G^{'r}} ; S' Rightarrow_{G^{'r}} ; S;

(2)S' Rightarrow_{G^{'r}} ; S Rightarrow_{G^{'r}} ; Sa;

(3)FIRST0(e) = FIRST0(a) = e

не следует, что S'a = S: Применяя определение к этой ситуации, имеем = e; β = S; ω = e; γ = e; A = S', B = S; x = e и y = a. Проблема здесь заключается в том, что нельзя установить, является ли S основой правовыводимой цепочки Sa, не видя символа, стоящего после S (т.е. наблюдая "нулевое" количество символов). В соответствии с интуицией G не должна быть LR(0)- грамматикой и она не будет ею, если пользоваться первым определением. Это определение мы и будем использовать далее.

Пример 4.14. Пусть G - леволинейная грамматика с правилами

begin{align*}&#10;&amp; S rightarrow Ab mid Bc \&#10;&amp; A rightarrow Aa mid e \&#10;&amp; B rightarrow Ba mid e &#10;end{align*}

Заметим, что G не является LR(k)-грамматикой ни для какого k.

Допустим, что G - LR(k)-грамматика. Рассмотрим два правых вывода в пополненной грамматике G':

begin{align*}&#10;&amp; S' Rightarrow_r S Rightarrow^*_r Aa^kb Rightarrow_r a^kb\&#10;&amp; text{и} \&#10;&amp; S' Rightarrow_r S Rightarrow^*_r Ba^kc Rightarrow_r a^kc\&#10;end{align*}

Эти два вывода удовлетворяют условию из определения LR(k)- грамматики при = e, β = e, ω = akb, γ = e и y = akc: Но так как заключение неверно, т.е. A B, то G - не LR(k)-грамматика. Более того, так как LR(k)-условие нарушается для всех k, то G - не LR-грамматика.

Если грамматика не является LR(1), то анализатор типа сдвиг-свертка при анализе некоторой цепочки может достигнуть конфигурации, в которой он, зная содержимое магазина и следующий входной символ, не может решить, делать ли сдвиг или свертку (конфликт сдвиг/свертка), или не может решить, какую из нескольких сверток применить (конфликт свертка/свертка).

В частности, неоднозначная грамматика не может быть LR(1). Для доказательства рассмотрим два различных правых вывода

(1) S Rightarrow_r u_1 Rightarrow_r ldots Rightarrow_r u_n Rightarrow_r w , text{ и }

(2) S Rightarrow_r v_1 Rightarrow_r ldots Rightarrow_r v_m Rightarrow_r w ,

Нетрудно заметить, что LR(1) - условие (согласно второму определению LR(1)-грамматики) нарушается для наименьшего из чисел i, для которых un-i vm-i.

Пример 4.15. Рассмотрим ещe раз грамматику условных операторов:

begin{align*}&#10;&amp; S rightarrow if ; E ; then ; S mid if ; E ; then ; S ; else ; S mid a \&#10;&amp; E ; rightarrow ; b&#10;end{align*}

Если анализатор типа сдвиг-свертка находится в конфигурации, такой, что необработанная часть входной цепочки имеет вид else ... $, а в магазине находится ... if E then S, то нельзя определить, является ли if E then S основой, вне зависимости от того, что лежит в магазине ниже. Это конфликт сдвиг/свертка. В зависимости от того, что следует на входе за else, правильной может быть свертка по S if E then S или сдвиг else, а затем разбор другого S и завершение основы if E then S else S. Таким образом нельзя сказать, нужно ли в этом случае делать сдвиг или свертку, так что грамматика не является LR(1).

Эта грамматика может быть преобразована к LR(1)-виду следующим образом:

begin{align*}&#10;&amp; S rightarrow M mid U \&#10;&amp; text{M $ rightarrow $ if E then M else M $ mid$ a}\&#10;&amp; text{U $rightarrow$ if E then S $mid $ if E then M else U} \&#10;&amp; E rightarrow b&#10;end{align*}

Основная разница между LL(1)- и LR(1)- грамматиками заключается в следующем. Чтобы грамматика была LR(1)- грамматикой, необходимо распознавать вхождение правой части правила вывода, просмотрев все, что выведено из этой правой части и текущий символ входной цепочки. Это требование существенно менее строгое, чем требование для LL(1)-грамматики, когда необходимо определить применимое правило, видя только первый символ, выводимый из его правой части. Таким образом, класс LL(1)-грамматик есть собственный подкласс класса LR(1)-грамматик.

Справедливы также следующие утверждения [2].

Теорема 4.7. Каждый LR(1)-язык является детерминированным КС-языком.

Теорема 4.8. Если L - детерминированный КС-язык, то существует LR(1)-грамматика, порождающая L.

Теорема 4.9. Для любой LR(k)-грамматики для k > 1 существует эквивалентная ей LR(k Gamma 1)-грамматика.

Доказано, что проблема определения, порождает ли грамматика LR-язык, является алгоритмически неразрешимой.

Восстановление процесса анализа после синтаксических ошибок

Одним из простейших методов восстановления после ошибки при LR(1)-анализе является следующий. При синтаксической ошибке просматриваем магазин от верхушки, пока не найдем состояние s с переходом на выделенный нетерминал A. Затем сканируются входные символы, пока не будет найден такой, который допустим после A. В этом случае на верхушку магазина помещается состояние Goto[s, A] и разбор продолжается. Для нетерминала A может иметься несколько таких вариантов. Обычно A - это нетерминал, представляющий одну из основных конструкций языка, например оператор.

В лекции "2 - Клеточная оболочка" также много полезной информации.

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

Варианты LR-анализаторов

Часто построенные таблицы для LR(1)-анализатора оказываются довольно большими. Поэтому при практической реализации используются различные методы их сжатия. С другой стороны, часто оказывается, что при построении для языка синтаксического анализатора типа "сдвиг-свертка" достаточно более простых методов. Некоторые из этих методов базируются на основе LR(1)-анализаторов.

Одним из способов такого упрощения является LR(0)- анализ - частный случая LR-анализа, когда ни при построении таблиц, ни при анализе не учитывается аванцепочка.

Еще одним вариантом LR-анализа являются так называемые SLR(1)-анализаторы (Simple LR(1)). Они строятся следующим образом. Пусть C = {I0, I1, ... , In} - набор множеств допустимых LR(0)-ситуаций. Состояния анализатора соответствуют Ii. Функции действий и переходов анализатора определяются следующим образом.

  1. Если [A rightarrow u.av] in I_i text{ и } goto(I_i, a) = I_j, то определим Action[i, a] = shift j.
  2. Если [A rightarrow u.] in I_i, то, для всех a in FOLLOW(A), A S', определим Action[i, a] = reduce A u
  3. Если [S' rightarrow S.] in I_i, то определим Action[i, $] = accept.
  4. Если goto(Ii, A) = Ij , где A in N, то определим Goto[i, A] = j.
  5. Остальные входы для функций Action и Goto определим как error.
  6. Начальное состояние соответствует множеству ситуаций, содержащему ситуацию [S' .S]

Распространенным вариантом LR(1)-анализа является также LALR(1)-анализ. Он основан на объединении (слиянии) некоторых таблиц. Назовем ядром множества LR(1)-ситуаций множество их первых компонент (то есть во множестве ситуаций не учитываются аванцепочки). Объединим все множества ситуаций с одинаковыми ядрами, а в качестве аванцепочек возьмем объединение аванцепочек. Функции Action и Goto строятся очевидным образом. Если функция Action таким образом построенного анализатора не имеет конфликтов, то он называется LALR(1)-анализатором (LookAhead LR(1)).Если грамматика является LR(1), то в таблицах LALR(1) анализатора могут появиться конфликты типы свертка-свертка (если одно из объединяемых состояний имело ситуации [A , a] и [B β, b], а другое [A , b] и [B β a], то в LALR(1) появятся ситуации [A , {a, b}] и [B β, {b, a}]). Конфликты типа сдвиг-свертка появиться не могут, поскольку аванцепочка для сдвига во внимание не принимается.

Свежие статьи
Популярно сейчас
Зачем заказывать выполнение своего задания, если оно уже было выполнено много много раз? Его можно просто купить или даже скачать бесплатно на СтудИзбе. Найдите нужный учебный материал у нас!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5137
Авторов
на СтудИзбе
440
Средний доход
с одного платного файла
Обучение Подробнее