Опубликован: 08.04.2009 | Доступ: свободный | Студентов: 485 / 0 | Длительность: 17:26:00
Специальности: Программист
Лекция 15:

Контекстно-свободные грамматики

15.2.5. Используя сказанное, составить процедуру распознавания выражений для грамматики (пример 3, см. "пункт 15.1." ):

\begin{align*}
    \langle{выр}\rangle    &\to\langle{слаг}\rangle\ \langle{оствыр}\rangle\\
    \langle{оствыр}\rangle &\to \hbox{\texttt{+}}\ \langle{выр}\rangle\\
    \langle{оствыр}\rangle &\to \\
    \langle{слаг}\rangle   &\to \langle{множ}\rangle\ \langle{остслаг}\rangle\\
    \langle{остслаг}\rangle &\to \hbox{\texttt{*}}\ \langle{слаг}\rangle\\
    \langle{остслаг}\rangle&\to \\
    \langle{множ}\rangle   &\to \hbox{\texttt{x}}\\
    \langle{множ}\rangle   &\to \hbox{\texttt{(}}\ \langle{выр}\rangle\ \hbox{\texttt{)}}
\end{align*}

Решение. Эта грамматика не полностью подпадает под рассмотренные частные случаи: в правых частях есть комбинации терминалов и нетерминалов

\begin{center}\ttfamily
+ \langle{выр}\rangle
\end{center}
и группы из трех символов
\begin{center}\ttfamily
( \langle{выр}\rangle\ )
\end{center}
В грамматике есть также несколько правил с одной левой частью и с правыми частями разного рода, например
\begin{align*}
    \langle{оствыр}\rangle &\to \hbox{\texttt{+}}\ \langle{выр}\rangle\\
    \langle{оствыр}\rangle &\to
\end{align*}
Эти ограничения не являются принципиальными. Так, правило типа {\texttt{K}}\to {\texttt{L M N}} можно было бы заменить на два правила {\texttt{K}} \to {\texttt{L Q}} и {\texttt{Q}} \to {\texttt{M N}}, терминальные символы в правой части - на нетерминалы (с единственным правилом замены на соответствующие терминалы). Несколько правил с одной левой частью и разнородными правыми также можно свести к уже разобранному случаю: например,
\begin{align*}
\hbox{\texttt{K}}&\to\hbox{\texttt{L M N}}\\
\hbox{\texttt{K}}&\to\hbox{\texttt{P Q}}\\
\hbox{\texttt{K}}&\to
\end{align*}
можно заменить на правила
\begin{align*}
\hbox{\texttt{K}}&\to\hbox{\texttt{K}}_1\\
\hbox{\texttt{K}}&\to\hbox{\texttt{K}}_2\\
\hbox{\texttt{K}}&\to\hbox{\texttt{K}}_3\\
\hbox{\texttt{K}}_1 &\to\hbox{\texttt{L M N}}\\
\hbox{\texttt{K}}_2 &\to\hbox{\texttt{P Q}}\\
\hbox{\texttt{K}}_3 &\to
\end{align*}
Но мы не будем этого делать - а сразу же запишем то, что получится, если подставить описания процедур для новых терминальных символов в места их использования. Например, для правила
{\texttt{K}} \to {\texttt{L M N}}
это дает процедуру

procedure ReadK;
begin
| ReadL;
| if b then begin
| | ReadM;
| end;
| if b then begin
| | ReadN;
| end;
end;

Для ее корректности надо, чтобы Посл(L) не пересекалось с Нач(MN) (которое равно Нач(M), если из M не выводится пустое слово, и равно объединению Нач(M) и Нач(N), если выводится), а также чтобы Посл(M) не пересекалось с Нач(N).

Аналогичным образом правила

\begin{align*}
\hbox{\texttt{K}}&\to\hbox{\texttt{L M N}}\\
\hbox{\texttt{K}}&\to\hbox{\texttt{P Q}}\\
\hbox{\texttt{K}}&\to
\end{align*}
приводят к процедуре

procedure ReadK;
begin
| if (Next принадлежит Нач(LMN)) then begin
| | ReadL;
| | if b then begin ReadM; end;
| | if b then begin ReadN; end;
| end else if (Next принадлежит Нач(PQ)) then begin
| | ReadP;
| | if b then begin ReadQ; end;
| end else begin
| | b := true;
| end;
end;

корректность которой требует, чтобы Нач(LMN) не пересекалось с Нач(PQ).

Читая приведенную далее программу, полезно иметь в виду соответствие между русскими и английскими словами:

\begin{tabular}{ll}
 ВЫРажение   &  EXPRession \\
 ОСТаток ВЫРажения & REST of EXPRession\\
 СЛАГаемое  & ADDitive term\\
 ОСТаток СЛАГаемого  & REST of ADDitive term\\
 МНОЖитель   & FACTor
\end{tabular}

procedure ReadSymb (c: Symbol);
| b := (Next = c);
| if b then begin
| | Move;
| end;
end;

procedure ReadExpr;
| ReadAdd;
| if b then begin ReadRestExpr; end;
end;

procedure ReadRestExpr;
| if Next = '+' then begin
| | ReadSymb ('+');
| | if b then begin ReadExpr; end;
| end else begin
| | b := true;
| end;
end;

procedure ReadAdd;
| ReadFact;
| if b then begin ReadRestAdd; end;
end;

procedure ReadRestAdd;
| if Next = '*' then begin
| | ReadSymb ('*');
| | if b then begin ReadAdd; end;
| end else begin
| | b := true;
| end;
end;

procedure ReadFact;
| if Next = 'x' then begin
| | ReadSymb ('x');
| end else if Next = '(' then begin
| | ReadSymb ('(');
| | if b then begin ReadExpr; end;
| | if b then begin ReadSymb (')'); end;
| end else begin
| | b := false;
| end;
end;

Осталось обсудить проблемы, связанные с взаимной рекурсивностью этих процедур (одна использует другую и наоборот). В паскале это допускается, только требуется дать предварительное описание процедур ("forward"). Как всегда для рекурсивных процедур, помимо доказательства того, что каждая процедура работает правильно в предположении, что используемые в ней вызовы процедур работают правильно, надо доказать отдельно, что работа завершается. (Это не очевидно: если в грамматике есть правило {\texttt{K}}\to {\texttt{KK}}, то из K ничего не выводится, Посл(K) и Нач(K) пусты, но написанная по нашим канонам процедура

procedure ReadK;
begin
| ReadK;
| if b then begin
| | ReadK;
| end;
end;

не заканчивает работы.)

В данном случае процедуры ReadRestExpr, ReadRestAdd, ReadFact либо завершаются, либо уменьшают длину непрочитанной части входа. Поскольку любой цикл вызовов включает одну из них, то зацикливание невозможно.