Россия, Пошатово |
Контекстно-свободные грамматики
15.2.6. Пусть в грамматике имеются два правила с нетерминалом K в левой части, имеющих вид
по которым 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 (последнее прочитанное является L -словом).
Сохранение инварианта: если осталось последнее слово, это очевидно; если осталось несколько, то за первым L -словом (из числа оставшихся) идет символ из Нач(L), и потому это слово - максимальное начало входа, являющееся L -началом.
На практике при записи грамматики используют сокращения. Если правила для какого-то нетерминала K имеют вид
(т.е. K -слова - это последовательности L -слов), то этих правил не пишут, а вместо K пишут L в фигурных скобках. Несколько правил с одной левой частью и разными правыми записывают как одно правило, разделяя альтернативные правые части вертикальной чертой.Например, рассмотренная выше грамматика для может быть записана так:
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.