在Crafting Interpreters中,我们使用递归下降解析器实现了一些编程语言。在许多其他事情中,它有这些陈述:
statement → exprStmt
| ifStmt
| printStmt
| whileStmt
| block ;
block → "{" declaration* "}" ;
whileStmt → "while" "(" expression ")" statement ;
ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
其中一项练习是add a break
statement语言。此外,将此语句置于循环之外应该是语法错误。当然,如果它们在循环中,它可以出现在其他块if
语句等内。
我的第一个方法是创建一个新规则whileBody
,以接受break
:
## FIRST TRY
statement → exprStmt
| ifStmt
| printStmt
| whileStmt
| block ;
block → "{" declaration* "}" ;
whileStmt → "while" "(" expression ")" whileBody ;
whileBody → statement
| break ;
break → "break" ";" ;
ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
但是我们必须在嵌套循环,break
条件等中接受if
。我能想象的是,我需要一个接受break
的块和条件的新规则:
## SECOND TRY
statement → exprStmt
| ifStmt
| printStmt
| whileStmt
| block ;
block → "{" declaration* "}" ;
whileStmt → "while" "(" expression ")" whileBody ;
whileBody → statement
| break
| whileBlock
| whileIfStmt
whileBlock→ "{" (declaration | break)* "}" ;
whileIfStmt → "if" "(" expression ")" whileBody ( "else" whileBody )? ;
break → "break" ";"
ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
现在并不是不可行的,但是一旦语言增长,处理它就会很麻烦。即使在今天写作也很无聊且容易出错!
我在C和Java BNF规格中寻找灵感。显然,这些规范都没有禁止break
外部循环。我猜他们的解析器有特殊的代码来防止这种情况。所以,我跟着做了added code into the parser to prevent break
outside loops。
我的问题是:
break
语句?break
命令?答案 0 :(得分:2)
属性语法很擅长这种事情。定义一个继承的属性(我将其称为LC用于循环计数)。 '程序'非终端将LC = 0传递给其子女;循环通过LC = $ LC + 1给他们的孩子;所有其他结构都通过LC = $ LC给他们的孩子。只有当$ LC>时,才能使“break”规则在语法上有效。 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和Jacobs,解析技术和Springer卷中的计算机科学讲义461(属性语法及其应用*,编辑P. Deransart和M. Jourdan)和545(属性语法,应用程序和系统,编辑H.Alblas和B. Melichar。
复制某些作品以区分两种情况的技巧(我是否处于循环中?是否?),如@rici的答案所示,可以视为将布尔属性推入非布局的一种方式 - 终端名称。
答案 1 :(得分:1)
- 我的第二次尝试的方法是否会起作用?换句话说,递归下降解析器是否可以处理仅出现在循环内的
醇>break
语句?
不确定。但是你需要大量的重复。由于while
不是唯一的循环结构,我使用了另一种描述备选方案的方法,包括将_B
添加到非终端的名称,其中可能包含break
语句
declaration → varDecl
| statement
declaration_B → varDecl
| statement_B
statement → exprStmt
| ifStmt
| printStmt
| whileStmt
| block
statement_B → exprStmt
| printStmt
| whileStmt
| breakStmt
| ifStmt_B
| block_B
breakStmt → "break" ";"
ifStmt → "if" "(" expression ")" statement ( "else" statement )?
ifStmt_B → "if" "(" expression ")" statement_B ( "else" statement_B )?
whileStmt → "while" "(" expression ")" statement_B ;
block → "{" declaration* "}"
block_B → "{" declaration_B* "}"
并非所有语句类型都需要重复。像exprStmt
这样的非复合语句不会,因为它们不可能包含break
语句(或任何其他语句类型)。而statement
这个循环语句的目标whileStmt
总是包含break
,无论while
是否在循环内。
- 是否有更实用的方法在语法规范中烘焙break命令?
醇>
除非您的语法规范具有标记宏,例如用于描述ECMAScript的规范。
- 有不同的方法吗?
醇>
由于这是一个自上而下(递归下降)解析器,因此在解析器的执行中处理这个条件非常简单。您只需要为每个(或许多)解析函数添加一个参数,该函数指定是否可以中断。 whileStmt
调用的任何解析函数都会将该参数设置为True
(或者指示可以中断的枚举),而其他语句类型只会传递参数,而顶级解析函数会将参数设置为False
。 breakStmt
实现只有在使用False
调用时才会返回失败。