Контекстно-свободные грамматики
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*}](/sites/default/files/tex_cache/4164e9da329d70ccd111923dc09e1738.png)
Решение. Эта грамматика не полностью подпадает под рассмотренные частные случаи: в правых частях есть комбинации терминалов и нетерминалов
![\begin{center}\ttfamily
+ \langle{выр}\rangle
\end{center}](/sites/default/files/tex_cache/777865f2054bd8a24e323528963ab623.png)
![\begin{center}\ttfamily
( \langle{выр}\rangle\ )
\end{center}](/sites/default/files/tex_cache/627d927a648c0812e6c85daf2955bfcc.png)
![\begin{align*}
\langle{оствыр}\rangle &\to \hbox{\texttt{+}}\ \langle{выр}\rangle\\
\langle{оствыр}\rangle &\to
\end{align*}](/sites/default/files/tex_cache/9d61f0e11e1fa41821dd9dfc217bfe19.png)
![{\texttt{K}}\to {\texttt{L M N}}](/sites/default/files/tex_cache/73a9c9c49805804e3804284f6dca4a13.png)
![{\texttt{K}} \to {\texttt{L Q}}](/sites/default/files/tex_cache/6dec36cb1bf2e6f8de747d478c9a6480.png)
![{\texttt{Q}} \to {\texttt{M N}}](/sites/default/files/tex_cache/be40d780a143fcf3b172836ff23dccfb.png)
![\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*}](/sites/default/files/tex_cache/bdcd79d8cdaea021fd504d6ba3e12929.png)
![\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*}](/sites/default/files/tex_cache/6111e4a0183f6d9a4bb7eef393d532fd.png)
![{\texttt{K}} \to {\texttt{L M N}}](/sites/default/files/tex_cache/c7b5b11be327f7642d2cb36e6b29c213.png)
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*}](/sites/default/files/tex_cache/bdcd79d8cdaea021fd504d6ba3e12929.png)
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}](/sites/default/files/tex_cache/a31a2e3b6e1cbaae3f4733d1f46e3276.png)
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"). Как всегда для рекурсивных процедур, помимо доказательства того, что каждая процедура работает
правильно в предположении, что используемые в ней вызовы
процедур работают правильно, надо доказать отдельно, что работа
завершается. (Это не очевидно: если в грамматике есть правило , то из K ничего не
выводится, Посл(K) и Нач(K) пусты, но написанная по нашим канонам процедура
procedure ReadK; begin | ReadK; | if b then begin | | ReadK; | end; end;
не заканчивает работы.)
В данном случае процедуры ReadRestExpr, ReadRestAdd, ReadFact либо завершаются, либо уменьшают длину непрочитанной части входа. Поскольку любой цикл вызовов включает одну из них, то зацикливание невозможно.