我在野牛中构建语法,并且我将上一次减少/减少错误缩小到以下测试用例:
%{
#include <stdio.h>
#include <string.h>
extern yydebug;
void yyerror(const char *str)
{
fprintf(stderr, "Error: %s\n", str);
}
main()
{
yydebug = 1;
yyparse();
}
%}
%right '='
%precedence CAST
%left '('
%token AUTO BOOL BYTE DOUBLE FLOAT INT LONG SHORT SIGNED STRING UNSIGNED VOID
%token IDENTIFIER
%start file
%debug
%%
file
: %empty
| statement file
;
statement
: expression ';'
;
expression
: expression '=' expression
| '(' type ')' expression %prec CAST
| '(' expression ')'
| IDENTIFIER
;
type
: VOID
| AUTO
| BOOL
| BYTE
| SHORT
| INT
| LONG
| FLOAT
| DOUBLE
| SIGNED
| UNSIGNED
| STRING
| IDENTIFIER
;
据推测,问题在于它在表达式中看到(IDENTIFIER)
时无法区分类型和表达式。
输出:
fail.y: warning: 1 reduce/reduce conflict [-Wconflicts-rr]
fail.y:64.5-14: warning: rule useless in parser due to conflicts [-Wother]
| IDENTIFIER
^^^^^^^^^^
我该怎么做才能解决这个冲突?
答案 0 :(得分:4)
如果语法仅限于OP中显示的作品,则删除冲突相对容易,因为语法是明确的。唯一的问题是它是LR(2)而不是LR(1)。
OP中的分析完全正确。解析器看到时,例如:
( identifier1 · )
(其中·
标记当前点,因此前瞻标记是)),无法知道这是否是
( identifier1 · ) ;
( identifier1 · ) = ...
( identifier1 · ) identifier2
( identifier1 · ) ( ...
在前两种情况下,identifier1
必须缩减为expression
,以便( expression )
随后可以缩减为expression
,而在最后两种情况下,
identifier1
必须缩减为type
,以便( type ) expression
随后可以缩减为expression
。如果解析器可以在将来进一步看到一个令牌,则可以做出决定。
因为对于任何LR(k)语法,存在识别相同语言的LR(1)语法,显然存在解决方案;一般的方法是推迟减少,直到单一令牌前瞻足以区分。一种方法是:
cast_or_expr : '(' IDENTIFIER ')'
;
cast : cast_or_expr
| '(' type ')'
;
expr_except_id : cast_or_expr
| cast expression %prec CAST
| '(' expr_except_id ')'
| expression '=' expression
;
expression : IDENTIFIER
| expr_except_id
;
(除了从IDENTIFIER
的作品中删除type
之外,其他语法都是相同的。)
对于没有符号既可以是前缀也可以是中缀运算符(如-
)并且没有操作符可以被省略的语法(对于函数调用中有效),它可以正常工作。特别是,它不适用于C,因为它会留下含糊之处:
( t ) * a // product or cast?
( t ) ( 3 ) // function-call or cast?
这些是语法中的真正含糊不清,只能通过知道t
是类型名称还是变量/函数名称来解决。
C解析器的“常用”解决方案是通过在扫描器和解析器之间共享符号表来解决歧义;由于typedef
类型别名声明必须在第一次使用符号作为适用范围内的类型名称之前出现,因此可以在扫描令牌之前知道令牌是否已使用{{ 1}}。更准确地说,如果没有看到typedef,可以假设该符号不是类型,尽管它可能是完全未声明的。
通过使用GLR语法和语义谓词,可以将逻辑限制在解析器中。有些人发现更优雅。