Yacc派生未被识别

时间:2014-11-03 00:04:19

标签: parsing syntax yacc lex

这是一个各种类别的项目,我已经解决了99%的问题,但现在我被卡住了。语法适用于MiniJava

我有以下 lex 文件,该文件按预期工作:

%{
#include "y.tab.h"
%}
delim       [ \t\n]
ws          {delim}+
comment     ("/*".*"*/")|("//".*\n)
id          [a-zA-Z]([a-zA-Z0-9_])*
int_literal [0-9]*
op          ("&&"|"<"|"+"|"-"|"*")
class       "class"
public      "public"
static      "static"
void        "void"
main        "main"
string      "String"
extends     "extends"
return      "return"
boolean     "boolean"
if          "if"
new         "new"
else        "else"
while       "while"
length      "length"
int         "int"
true        "true"
false       "false"
this        "this"
println     "System.out.println"
lbrace      "{"
rbrace      "}"
lbracket    "["
rbracket    "]"
semicolon   ";"
lparen      "("
rparen      ")"
comma       ","
equals      "="
dot         "."
exclamation "!"

%%

{ws}        { /* Do nothing! */ }
{comment}   { /* Do nothing! */ }
{println}   {  return PRINTLN; } /* Before {period} to give this pre
cedence */
{op}        {  return OP;      }
{int_literal}   {  return INTEGER_LITERAL; }
{class}     {  return CLASS; }
{public}    {  return PUBLIC; }
{static}    {  return STATIC; }
{void}      {  return VOID; }
{main}      {  return MAIN; }
{string}    {  return STRING; }
{extends}   {  return EXTENDS; }
{return}    {  return RETURN; }
{boolean}   {  return BOOLEAN; }
{if}        {  return IF; }
{new}       {  return NEW; }
{else}      {  return ELSE; }
{while}     {  return WHILE; }
{length}    {  return LENGTH; }
{int}       {  return INT; }
{true}      {  return TRUE; }
{false}     {  return FALSE; }
{this}      {  return THIS; }
{lbrace}    {  return LBRACE; }
{rbrace}    {  return RBRACE; }
{lbracket}  {  return LBRACKET; }
{rbracket}  {  return RBRACKET; }
{semicolon} {  return SEMICOLON; }
{lparen}    {  return LPAREN; }
{rparen}    {  return RPAREN; }
{comma}     {  return COMMA; }
{equals}    {  return EQUALS; }
{dot}       {  return DOT; }
{exclamation}   {  return EXCLAMATION; }
{id}        {  return ID; }
%%

int main(void) {
  yyparse();
  exit(0);
}

int yywrap(void) {
  return 0;
}

int yyerror(void) {
  printf("Parse error. Sorry bro.\n");
  exit(1);
}

yacc 文件:

%token PRINTLN
%token INTEGER_LITERAL
%token OP
%token CLASS
%token PUBLIC
%token STATIC
%token VOID
%token MAIN
%token STRING
%token EXTENDS
%token RETURN
%token BOOLEAN
%token IF
%token NEW
%token ELSE
%token WHILE
%token LENGTH
%token INT
%token TRUE
%token FALSE
%token THIS
%token LBRACE
%token RBRACE
%token LBRACKET
%token RBRACKET
%token SEMICOLON
%token LPAREN
%token RPAREN
%token COMMA
%token EQUALS
%token DOT
%token EXCLAMATION
%token ID

%%

Program:    MainClass ClassDeclList
MainClass:  CLASS ID LBRACE PUBLIC STATIC VOID MAIN LPAREN STRING LB
RACKET RBRACKET ID RPAREN LBRACE Statement RBRACE RBRACE
ClassDeclList:  ClassDecl ClassDeclList
    |   
ClassDecl:  CLASS ID LBRACE VarDeclList MethodDeclList RBRACE
    |   CLASS ID EXTENDS ID LBRACE VarDeclList MethodDeclList RB
RACE
VarDeclList:    VarDecl VarDeclList
    |
VarDecl:    Type ID SEMICOLON
MethodDeclList: MethodDecl MethodDeclList
    |   
MethodDecl: PUBLIC Type ID LPAREN FormalList RPAREN LBRACE VarDeclLi
st StatementList RETURN Exp SEMICOLON RBRACE
FormalList: Type ID FormalRestList
    |
FormalRestList: FormalRest FormalRestList
    |   
FormalRest: COMMA Type ID   
Type:       INT LBRACKET RBRACKET
    |   BOOLEAN
    |   INT
    |   ID
StatementList:  Statement StatementList
    |   
Statement:  LBRACE StatementList RBRACE
    |   IF LPAREN Exp RPAREN Statement ELSE Statement
    |   WHILE LPAREN Exp RPAREN Statement
    |   PRINTLN LPAREN Exp RPAREN SEMICOLON
    |   ID EQUALS Exp SEMICOLON
    |   ID LBRACKET Exp RBRACKET EQUALS Exp SEMICOLON
Exp:        Exp OP Exp
    |   Exp LBRACKET Exp RBRACKET
    |   Exp DOT LENGTH
    |   Exp DOT ID LPAREN ExpList RPAREN
    |   INTEGER_LITERAL
    |   TRUE
    |   FALSE
    |   ID
    |   THIS
    |   NEW INT LBRACKET Exp RBRACKET
    |   NEW ID LPAREN RPAREN
    |   EXCLAMATION Exp
    |   LPAREN Exp RPAREN
ExpList:    Exp ExpRestList
    |
ExpRestList:    ExpRest ExpRestList
    |   
ExpRest:    COMMA Exp

%%

不起作用的派生是以下两个:

Statement:

 | ID EQUALS Exp SEMICOLON
 | ID LBRACKET Exp RBRACKET EQUALS Exp SEMICOLON

如果我只是lex文件并获得令牌流,则令牌完全匹配模式。这是输入和输出的示例:

num1 = id1;
num2[0] = id2;

给出:

ID
EQUALS
ID
SEMICOLON
ID
LBRACKET
INTEGER_LITERAL
RBRACKET
EQUALS
ID
SEMICOLON

我不明白这个令牌流如何与语法完全匹配,而 yyerror 正在被调用。我已经试图解决这个问题几个小时了,我终于放弃了。我很欣赏能够解决导致问题的原因。

有关完整示例,您可以通过解析器运行以下输入:

class Minimal {
    public static void main (String[] a) {
        // Infinite loop
        while (true) {
            /* Completely useless // (embedded comment) stat
ements */
            if ((!false && true)) {
                if ((new Maximal().calculateValue(id1, i
d2) * 2) < 5) {
                    System.out.println(new int[11].l
ength < 10);
                }
                else { System.out.println(0); }
            }
            else { System.out.println(false); }
        }
    }
}

class Maximal {

    public int calculateValue(int[] id1, int id2) {
        int[] num1; int num2;
        num1 = id1;
        num2[0] = id2;
        return (num1[0] * num2) - (num1[0] + num2);
    }
}

它应该正确解析,但它会在num1 = id1;num2[0] = id2;上绊倒。

PS - 我知道这是语义不正确的MiniJava,但从语法上讲,应该没问题。)

1 个答案:

答案 0 :(得分:2)

您对Statement的定义没有任何问题。他们触发错误的原因是他们以ID开头。

首先,当野牛处理您的输入时,它会报告:

minijava.y: conflicts: 8 shift/reduce

转换/减少冲突并不总是一个问题,但你不能忽略它们。您需要知道导致它们的原因以及默认行为是否正确。 (默认行为是首选转换为reduce。)

转移/减少冲突中的六个来自以下事实:

Exp: Exp OP Exp

本质上是模棱两可的。您需要通过使用实际运算符而不是OP并插入优先级规则(或特定产品)来解决这个问题。这与直接问题无关,因为它(现在)并不重要,无论第一个Exp还是第二个获得优先权,默认分辨率都可以。

其他产品来自以下产品:

VarDeclList: VarDecl VarDeclList
           | %empty

此处,VarDecl可能以ID开头(如果是用作类型的类名)。

VarDeclList来自MethodDecl

MethodDecl: ... VarDeclList StatementList ...

现在,让我们说我们正在解析输入;我们刚刚解析过:

int num2;

我们正在查看下一个令牌,即num1(来自num1 = id1)。 int num2;肯定是VarDecl,因此它会匹配

中的VarDecl
VarDeclList: VarDecl VarDeclList

在此上下文中,VarDeclList可能为空,也可能以另一个声明开头。如果它是空的,我们需要立即减少它(因为我们没有获得另一次机会:非终端需要在不迟于其右侧完成时减少)。如果它不是空的,我们可以简单地移动第一个令牌。但我们需要根据当前的前瞻标记做出决定,该标记是ID

不幸的是,这对我们没有帮助。 VarDeclListStatementList都可以以ID开头,因此减少和转移都是可行的。因此,bison会发生变化。

现在,让我们假设VarDeclList使用左递归而不是右递归。 (左递归在LR语法中几乎总是更好。):

VarDeclList: VarDeclList VarDecl
           | %empty

现在,当我们到达VarDecl的末尾时,我们只有一个选项:缩小VarDeclList。然后我们将处于以下状态:

MethodDecl: ... VarDeclList · StatementList
VarDeclList: VarDeclList · VarDecl

现在,我们看到ID标头,我们不知道它是StatementList还是VarDecl但它并不重要因为我们不需要减少其中任何一个非终端;在承诺之前,我们可以等到接下来会发生什么。

请注意,在这种情况下,左右递归之间存在小的语义差异。显然,语法树是不同的:

         VDL                          VDL
        /   \                        /   \
      VDL  Decl                    Decl  VDL
     /   \                              /   \
   VDL  Decl                          Decl  VDL
    |                                        |
    λ                                        λ

然而,在实践中,最可能的行动将是:

VarDeclList: %empty              { $$ = newVarDeclList(); }
           | VarDeclList VarDecl { $$ = $1; appendVarDecl($$, $2); }

效果很好。


顺便说一下:

1)虽然flex允许你使用定义来简化正则表达式,但它并不要求你使用它们,并且(据我所知)最好不要编写使用定义的最佳实践。我只是谨慎地使用定义,通常只有当我要用相同的组件写两个正则表达式时,或偶尔当正则表达式真的很复杂并且我想把它分解成碎片时。但是,绝对不需要使用以下内容来混淆您的flex文件:

begin           "begin"
...
%%
...
{begin}         { return BEGIN; }

而不是更简单,更易读的

"begin"         { return BEGIN; }

2)同样,bison有助于您将单字符标记编写为单引号文字:'('。这具有许多优点,首先是它提供了更可读的语法视图。此外,您不需要声明这些令牌,或者为它们想出一个好名字。此外,由于令牌的值是字符本身,因此您的flex文件也可以简化。而不是

"+"     { return PLUS; }
"-"     { return MINUS; }
"("     { return LPAREN; }
...

你可以写:

[-+*/(){}[\]!]   { return yytext[0]; }

事实上,我通常建议不要使用它;只需在最后使用catch-all flex规则:

.                { return yytext[0]; }

这将把所有其他无法比拟的角色作为单字符标记传递给野牛;如果bison不知道令牌,它将发出语法错误。因此,所有错误处理都集中在野外,而不是在两个文件之间进行分割,并且您节省了大量的输入(无论谁在阅读您的代码,都会节省大量的阅读。)

3)没有必要将"System.out.println"放在"."之前。他们永远不会混淆,因为他们不是以相同的角色开始。唯一的时间顺序是两个模式在同一点最大匹配相同的字符串(这就是为什么ID模式需要在所有单个关键字之后)。