【问题标题】:Attempting to resolve shift-reduce parsing issue尝试解决 shift-reduce 解析问题
【发布时间】:2023-03-28 11:50:01
【问题描述】:

我正在尝试为 C 编写语法,但遇到了一个我不太理解的问题。语法的相关部分:

stmt :
  types decl SEMI                { marks (A.Declare ($1, $2)) (1, 2) }
 | simp SEMI                     { marks $1 (1, 1) }
 | RETURN exp SEMI               { marks (A.Return $2) (1, 2) }
 | control                       { $1 } 
 | block                         { marks $1 (1, 1) }
 ;

 control :
   if                                                    { $1 }
  | WHILE RPAREN exp LPAREN stmt                         { marks (A.While ($3, $5)) (1, 5) }
  | FOR LPAREN simpopt SEMI exp SEMI simpopt RPAREN stmt { marks (A.For ($3, $5, $7, $9)) (1, 9) }
  ;

  if :
    IF RPAREN exp LPAREN stmt                                { marks (A.If ($3, $5, None)) (1, 5) }
  | IF RPAREN exp LPAREN stmt ELSE stmt                      { marks (A.If ($3, $5, $7)) (1, 7) }
  ;

这不起作用。我运行 ocamlyacc -v 并得到以下报告:

83: shift/reduce conflict (shift 86, reduce 14) on ELSE
state 83
    if : IF RPAREN exp LPAREN stmt .  (14)
    if : IF RPAREN exp LPAREN stmt . ELSE stmt  (15)

    ELSE  shift 86
    IF  reduce 14
    WHILE  reduce 14
    FOR  reduce 14
    BOOL  reduce 14
    IDENT  reduce 14
    RETURN  reduce 14
    INT  reduce 14
    MAIN  reduce 14
    LBRACE  reduce 14
    RBRACE  reduce 14
    LPAREN  reduce 14

我已经读到移位/减少冲突是由于语法规范中的歧义造成的,但我不知道如何以不歧义的方式指定这一点?

【问题讨论】:

    标签: parsing compiler-construction yacc ocamlyacc


    【解决方案1】:

    语法肯定是模棱两可的,尽管您知道每个字符串的含义,而且尽管ocamlyacc 报告了移位/归约冲突,但它生成的语法也会为每个有效输入生成正确的解析。

    歧义来自于

    if ( exp1 ) if ( exp2) stmt1 else stmt2;
    

    显然stmt1 仅在exp1exp2 都为真时执行。但是如果exp1 为假,或者exp1 为真而exp2 为假,stmt1 是否会执行?那些代表不同的解析;第一个(无效)解析将else stmt2 附加到if (exp1),而您、我和ocamlyacc 知道正确的解析将else stmt2 附加到if (exp2)

    语法可以重写,虽然有点麻烦。基本思想是将语句分为两类:“匹配”(意味着语句中的每个else 都匹配一些if)和“不匹配”(意味着后面的else 将匹配一些@ 987654338@ 在语句中。完整的语句可能不匹配,因为else 子句是可选的,但是在ifelse 之间永远不能有不匹配的语句,因为else 必须匹配@987654343 @ 在不匹配的语句中。

    以下语法基本上是您提供的语法,但重写为使用野牛风格的单引号标记,我发现它更具可读性。我不知道 ocamlyacc 是否处理这些。 (顺便说一句,你的语法IF RPAREN exp LPAREN...,根据左括号和右括号的通用定义,意味着if ) exp (。这就是我发现单引号字符终端更具可读性的原因之一。 )

    Bison 处理这个语法没有冲突。

     /* Fake non-terminals */
    %token types decl simp exp
     /* Keywords */
    %token ELSE FOR IF RETURN WHILE
    
    %%
    stmt: matched_stmt | unmatched_stmt ;
    stmt_list: stmt | stmt_list stmt ;
    block: '{' stmt_list '}' ;
    
    matched_stmt
     : types decl ';' 
     | simp ';'
     | RETURN exp ';'
     | block
     | matched_control
     ;
    
    simpopt : simp | /* EMPTY */;
    
    matched_control
      : IF '(' exp ')' matched_stmt ELSE matched_stmt
      | WHILE '(' exp ')' matched_stmt
      | FOR '(' simpopt ';' exp ';' simpopt ')' matched_stmt
      ;
    
    unmatched_stmt
      : IF '(' exp ')' stmt
      | IF '(' exp ')' matched_stmt ELSE unmatched_stmt
      | WHILE '(' exp ')' unmatched_stmt
      | FOR '(' simpopt ';' exp ';' simpopt ')' unmatched_stmt
      ;
    

    就个人而言,我会进行一些重构。例如:

    if_prefix  : IF '(' exp ')'
               ;
    loop_prefix: WHILE '(' exp ')'
               | FOR '(' simpopt ';' exp ';' simpopt ')'
               ;
    matched_control
               : if_prefix matched_stmt ELSE matched_stmt
               | loop_prefix matched_stmt
               ;
    unmatched_stmt
               : if_prefix stmt
               | if_prefix ELSE unmatched_stmt
               | loop_prefix unmatched_stmt
               ;
    

    一个常见且更简单但不太严格的解决方案是使用bison manual 中建议的优先级声明。

    【讨论】:

    • 实施您的解决方案后,我仍然遇到相同的移位/减少冲突
    • @dalastboss:是的,你还必须更改以stmt 结尾的其他作品的定义。我会用一个有效的语法来回答我的问题,但如果你想用其他结构扩展语言,你需要真正思考它是如何工作的。
    猜你喜欢
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多