Контекстно-свободные грамматики
15.2.6. Пусть в грамматике имеются два правила с нетерминалом K в левой части, имеющих вид
![\begin{align*}
\hbox{\texttt{K}}&\to\hbox{\texttt{L K}}\\
\hbox{\texttt{K}}&\to
\end{align*}](/sites/default/files/tex_cache/5dff3445433089ef03822bdeb2977ef7.png)
Решение. По нашим правилам следовало бы написать
procedure ReadK; begin | if (Next принадлежит Нач(L)) then begin | | ReadL; | | if b then begin ReadK; end; | end else begin | | b := true; | end; end;
завершение работы гарантируется тем, что перед рекурсивным вызовом длина непрочитанной части уменьшается.
Эта рекурсивная процедура эквивалентна нерекурсивной:
procedure ReadK; begin | b := true; | while b and (Next принадлежит Нач(L)) do begin | | ReadL; | end; end;
Формально можно проверить эту эквивалентность так. Завершаемость в обоих случаях ясна. Достаточно проверить поэтому, что тело рекурсивной процедуры эквивалентно нерекурсивной в предположении, что ее рекурсивный вызов эквивалентен вызову нерекурсивной процедуры. Подставим:
if (Next принадлежит Нач(L)) then begin | ReadL; | if b then begin | | b := true; | | while b and (Next принадлежит Нач(L)) do begin | | | ReadL; | | end; | end; end else begin | b := true; end;
Первую команду b:=true можно выкинуть (в этом месте и так b истинно). Вторую команду можно перенести в начало:
b := true; if (Next принадлежит Нач(L) then begin | ReadL; | if b then begin | | while b and (Next принадлежит Нач(L)) do begin | | | ReadL; | | end; | end; end;
Теперь внутренний if можно выкинуть (если b ложно, цикл while все равно не выполняется) и добавить в условие внешнего if условие b (которое все равно истинно).
b := true; if b and (Next принадлежит Нач(L)) then begin | ReadL; | while b and (Next принадлежит Нач(L)) do begin | | ReadL; | end; end;
что эквивалентно приведенной выше нерекурсивной процедуре (из которой вынесена первая итерация цикла).
15.2.7. Доказать корректность приведенной выше нерекурсивной программы непосредственно, без ссылок на рекурсивную.
Решение. Рассмотрим наибольшее начало входа,
являющееся K -началом. Оно представляется в виде
конкатенации (последовательного приписывания) нескольких
непустых L -слов и, возможно, одного непустого L -начала, не являющегося L -словом. Инвариант цикла:
прочитано несколько из них; b
(последнее прочитанное является L -словом).
Сохранение инварианта: если осталось последнее слово, это очевидно; если осталось несколько, то за первым L -словом (из числа оставшихся) идет символ из Нач(L), и потому это слово - максимальное начало входа, являющееся L -началом.
На практике при записи грамматики используют сокращения. Если правила для какого-то нетерминала K имеют вид
![\begin{align*}
\hbox{\texttt{K}}&\to \hbox{\texttt{L K}}\\
\hbox{\texttt{K}}&\to
\end{align*}](/sites/default/files/tex_cache/cbc0949e7aade89911ab9e321de8a7fb.png)
Например, рассмотренная выше грамматика для может быть записана так:
![\begin{align*}
\langle{выр}\rangle &\to\langle{слаг}\rangle\ \{+\langle{слаг}\rangle\}\\
\langle{слаг}\rangle &\to \langle{множ}\rangle\ \{*\langle{множ}\rangle\}\\
\langle{множ}\rangle &\to \hbox{\texttt{x}} |(\langle{выр}\rangle )\\
\end{align*}](/sites/default/files/tex_cache/1606c02c9b23e1ea45552f070d49fb07.png)
15.2.8.
Написать процедуру, корректную для , следуя этой
грамматике и используя цикл вместо рекурсии, где можно.
Решение.
procedure ReadSymb (c: Symbol); | b := (Next = c); | if b then begin Move; end; end; procedure ReadExpr; begin | ReadAdd; | while b and (Next = '+') do begin | | Move; ReadAdd; | end; end; procedure ReadAdd; begin | ReadFact; | while b and (Next = '*') do begin | | Move; ReadFact; | end; end; procedure ReadFact; begin | if Next = 'x' do begin | | Move; b := true; | end else if Next = '(' then begin | | Move; ReadExpr; | | if b then begin ReadSymb (')'); end; | end else begin | | b := false; | end; end;
15.2.9. В последней процедуре команду b:=true можно опустить. Почему?
Решение. Можно предполагать, что все процедуры вызываются при b=true.