Контекстно-свободные грамматики
15.2.5. Используя сказанное, составить процедуру распознавания выражений для грамматики (пример 3, см. "пункт 15.1." ):
Решение. Эта грамматика не полностью подпадает под рассмотренные частные случаи: в правых частях есть комбинации терминалов и нетерминалов
и группы из трех символов В грамматике есть также несколько правил с одной левой частью и с правыми частями разного рода, например Эти ограничения не являются принципиальными. Так, правило типа можно было бы заменить на два правила и , терминальные символы в правой части - на нетерминалы (с единственным правилом замены на соответствующие терминалы). Несколько правил с одной левой частью и разнородными правыми также можно свести к уже разобранному случаю: например, можно заменить на правила Но мы не будем этого делать - а сразу же запишем то, что получится, если подставить описания процедур для новых терминальных символов в места их использования. Например, для правила это дает процедуру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).
Аналогичным образом правила
приводят к процедуре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).
Читая приведенную далее программу, полезно иметь в виду соответствие между русскими и английскими словами:
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 либо завершаются, либо уменьшают длину непрочитанной части входа. Поскольку любой цикл вызовов включает одну из них, то зацикливание невозможно.