Грамматики атрибутов хороши в таких вещах. Определите унаследованный атрибут (я назову его LC для подсчета цикла). Нетерминал 'program' передает LC = 0 своим дочерним элементам; циклы передают LC = $ LC + 1 своим детям; все другие конструкции передают LC = $ LC своим детям. Сделайте правило синтаксически допустимым для break, только если $ LC> 0.
Не существует стандартного синтаксиса для грамматик атрибутов или для использования значений атрибутов в охранниках (как я предлагаю для 'break'), но при использовании грамматической нотации с определенным предложением Prolog ваша грамматика может выглядеть примерно так: Я добавил несколько примечаний к нотации DCG, на случай, если прошло слишком много времени с тех пор, как вы их использовали.
/* nt(X) means, roughly, pass the value X as an inherited attribute.
** In a recursive-descent system, it can be passed as a parameter.
** N.B. in definite-clause grammars, semicolon separates alternatives,
** and full stop ends a rule.
*/
/* DCD doesn't have regular-right-part rules, so we have to
** handle repetition via recursion.
*/
program -->
statement(0);
statement(0), program.
statement(LC) -->
exprStmt(LC);
ifStmt(LC);
printStmt(LC);
whileStmt(LC);
block(LC);
break(LC).
block(LC) -->
"{", star-declaration(LC), "}".
/* The notation [] denotes the empty list, and matches zero
** tokens in the input.
*/
star-declaration(LC) -->
[];
declaration(LC), star-declaration(LC).
/* On the RHS of a rule, braces { ... } contain Prolog code. Here,
** the code "LC2 is LC + 1" adds 1 to LC and binds LC2 to that value.
*/
whileStmt(LC) -->
{ LC2 is LC + 1 }, "while", "(", expression(LC2), ")", statement(LC2).
ifStmt(LC) --> "if", "(", expression(LC), ")", statement(LC), opt-else(LC).
opt-else(LC) -->
"else", statement(LC);
[].
/* The definition of break checks the value of the loop count:
** "LC > 0" succeeds if LC is greater than zero, and allows the
** parse to succeed. If LC is not greater than zero, the expression
** fails. And since there is no other rule for 'break', any attempt
** to parse a 'break' rule when LC = 0 will fail.
*/
break(LC) --> { LC > 0 }, "break", ";".
Хорошие введения в атрибутивные грамматики можно найти в Grune and Jacobs, Методы синтаксического анализа и в томах Springer. . Журдан) и 545 ( Грамматики атрибутов, приложения и системы , изд. Х. Альблас и Б. Меличар.
Техника дублирования некоторых произведений для различения двух ситуаций (я в цикле? Или нет?), Как показано в ответе @rici, может рассматриваться как способ вставить логический атрибут в не Терминальные имена.