Опубликован: 08.04.2009 | Уровень: для всех | Доступ: платный
Лекция 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 либо завершаются, либо уменьшают длину непрочитанной части входа. Поскольку любой цикл вызовов включает одну из них, то зацикливание невозможно.

Татьяна Новикова
Татьяна Новикова
Россия, Пошатово
Artem Bardakov
Artem Bardakov
Россия