Лекция 15:

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

15.2.6. Пусть в грамматике имеются два правила с нетерминалом K в левой части, имеющих вид

\begin{align*}
 \hbox{\texttt{K}}&\to\hbox{\texttt{L K}}\\
 \hbox{\texttt{K}}&\to
\end{align*}
по которым K -слово представляет собой конечную последовательность L -слов, причем множества Посл(L) и Нач(K) (в данном случае равное Нач(L) ) не пересекаются. Используя корректную для L процедуру ReadL, написать корректную для K процедуру ReadK, не используя рекурсии.

Решение. По нашим правилам следовало бы написать

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 \Leftrightarrow (последнее прочитанное является L -словом).

Сохранение инварианта: если осталось последнее слово, это очевидно; если осталось несколько, то за первым L -словом (из числа оставшихся) идет символ из Нач(L), и потому это слово - максимальное начало входа, являющееся L -началом.

На практике при записи грамматики используют сокращения. Если правила для какого-то нетерминала K имеют вид

\begin{align*}
 \hbox{\texttt{K}}&\to \hbox{\texttt{L K}}\\
 \hbox{\texttt{K}}&\to
\end{align*}
(т.е. K -слова - это последовательности L -слов), то этих правил не пишут, а вместо K пишут L в фигурных скобках. Несколько правил с одной левой частью и разными правыми записывают как одно правило, разделяя альтернативные правые части вертикальной чертой.

Например, рассмотренная выше грамматика для \langle{выр}\rangle может быть записана так:

\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*}

15.2.8. Написать процедуру, корректную для \langle{выр}\rangle, следуя этой грамматике и используя цикл вместо рекурсии, где можно.

Решение.

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.

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