使用递归下降解析器验证“break”语句

时间:2018-05-08 07:34:26

标签: parsing syntax break formal-languages recursive-descent

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 )? ;  

现在并不是不可行的,但是一旦语言增长,处理它就会很麻烦。即使在今天写作也很无聊且容易出错!

我在CJava BNF规格中寻找灵感。显然,这些规范都没有禁止break外部循环。我猜他们的解析器有特殊的代码来防止这种情况。所以,我跟着做了added code into the parser to prevent break outside loops

TL; DR

我的问题是:

  1. 我的第二次尝试的方法是否会起作用?换句话说,递归下降解析器是否可以处理仅出现在循环内的break语句?
  2. 是否有更实用的方法在语法规范中烘焙break命令?
  3. 或者标准方法确实是在解析时更改解析器以防止外部循环中断?

2 个答案:

答案 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)

  
      
  1. 我的第二次尝试的方法是否会起作用?换句话说,递归下降解析器是否可以处理仅出现在循环内的break语句?
  2.   

不确定。但是你需要大量的重复。由于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是否在循环内。

  
      
  1. 是否有更实用的方法在语法规范中烘焙break命令?
  2.   

除非您的语法规范具有标记宏,例如用于描述ECMAScript的规范。

  
      
  1. 有不同的方法吗?
  2.   

由于这是一个自上而下(递归下降)解析器,因此在解析器的执行中处理这个条件非常简单。您只需要为每个(或许多)解析函数添加一个参数,该函数指定是否可以中断。 whileStmt调用的任何解析函数都会将该参数设置为True(或者指示可以中断的枚举),而其他语句类型只会传递参数,而顶级解析函数会将参数设置为FalsebreakStmt实现只有在使用False调用时才会返回失败。