Java CUP (Parser) создает конфликт сдвига / уменьшения, когда тип переменной или функции определяется пользователем - PullRequest
0 голосов
/ 02 декабря 2018

В моем грамматике должны быть определенные пользователем комбинации идентификаторов типов.Проблема с кодом, приведенным ниже, заключается в том, что он генерирует следующее:

 [java] Warning : *** Shift/Reduce conflict found in state #67
 [java]   between VariableDecls ::= (*) 
 [java]   and     Type ::= (*) ID 
 [java]   under symbol ID
 [java]   Resolved in favor of shifting.
 [java] Warning : *** Shift/Reduce conflict found in state #65
 [java]   between VariableDecls ::= (*) 
 [java]   and     Type ::= (*) ID 
 [java]   under symbol ID
 [java]   Resolved in favor of shifting.
 [java] Error : *** More conflicts encountered than expected -- parser generation aborted
 [java] ------- CUP v0.11b 20160615 (GIT 4ac7450) Parser Generation Summary -------
 [java]   1 error and 4 warnings
 [java]   51 terminals, 34 non-terminals, and 93 productions declared, 
 [java]   producing 190 unique parse states.
 [java]   2 terminals declared but not used.
 [java]   0 non-terminals declared but not used.
 [java]   0 productions never reduced.
 [java]   2 conflicts detected (0 expected).
 [java]   No code produced.

Я попытался избавиться от произведений E в моем грамматике как в VariableDecls, так и в Stmts, но безуспешно.Я также попытался произвести идентификацию с типом, а затем произвести электронную продукцию.пакет чашка. пример;

import java_cup.runtime.*;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;

parser code {:
  TScanner scanner;
  Parser(TScanner scanner) { this.scanner = scanner; }
    public void syntax_error(Symbol cur_token) {
        System.out.println("WE ARE HERE");
        done_parsing();
    }
    public void unrecovered_syntax_error(Symbol cur_token) {
        System.out.println(cur_token.sym);
        System.out.println("[reject]");
    }
:}


scan with {: return scanner.next_token(); :};

/* Terminals (tokens returned by the scanner). */
terminal    BOOLN, DBL, _INT, STRING, NUL;
terminal    _IF, ELS, FR, WHLE;
terminal    INTCONST, DBLCONST, STRINGCONST, BOOLCONST;
terminal    ADDOP, SUBOP, MULOP, DIV, MOD;
terminal    LFTPRN, RTPRN, LFTBRACKET, RTBRC, LFTBRACE, RTBRACE;
terminal    LESS, LESSEQ, GRT, GRTEQ, EQL, NEQ;
terminal    AND, OR, NOT;
terminal    ASSIGN, SEMICOL, COMMA, DOT;
terminal    BRK, CLS, EXTNDS, IMPL, INTRFC, NEWAR;
terminal    PRNTLN, READLN, RTRN, _VOID, NW;
terminal    ID;

/* Non terminals */
non terminal    Program, Decls, Decl;
non terminal    VariableDecl, FunctionDecl, ClassDecl, InterfaceDecl;
non terminal    Variable, Type, Formals, Variables, Extends, Implements, Implement;
non terminal    Field, Fields, Prototype, StmtBlock, VariableDecls, Stmts, Stmt;
non terminal    OptionExpr, WhileStmt, ForStmt, BreakStmt;
non terminal    ReturnStmt, PrintStmt, Expr, Exprs, Lvalue, Call, Actuals, Constant;
non terminal    IfStmt;

/* Precedences */
precedence right ASSIGN;
precedence left OR;
precedence left AND;
precedence left EQL, NEQ;
precedence left LESS, LESSEQ, GRT, GRTEQ;
precedence left ADDOP, SUBOP;
precedence left MULOP, DIV, MOD;
precedence left NOT;
precedence left LFTBRACKET, DOT;
precedence left ELS;

/* Toy grammar */

start with Program;

Program ::=     
    Decls 
        {:  System.out.print("[reduce 1]"); System.out.print("[accept]"); done_parsing(); :};

Decls ::= 
    Decl
        {: System.out.print("[reduce 2]"); :} 
    | Decl Decls
        {: System.out.print("[reduce 3]"); :} ;

Decl ::=
    VariableDecl    
        {: System.out.print("[reduce 4]"); :} 
    | FunctionDecl  
        {: System.out.print("[reduce 5]"); :} 
    | ClassDecl         
        {: System.out.print("[reduce 6]"); :} 
    | InterfaceDecl 
        {: System.out.print("[reduce 7]"); :} ;

VariableDecl ::=
    Variable SEMICOL    
        {: System.out.print("[reduce 8]"); :} ;

Variable ::=
    Type ID
        {: System.out.print("[reduce 9]"); :} ;

Type ::=
    _INT 
        {: System.out.print("[reduce 10]"); :} 
    | DBL
        {: System.out.print("[reduce 11]"); :} 
    | BOOLN
        {: System.out.print("[reduce 12]"); :} 
    | STRING
        {: System.out.print("[reduce 13]"); :} 
    | Type LFTBRACKET RTBRC     
        {: System.out.print("[reduce 14]"); :}

    |  ID {: System.out.print("[reduce 15]"); :};


FunctionDecl ::= 
    Type ID LFTPRN Formals RTPRN StmtBlock 
        {: System.out.print("[reduce 16]"); :} 
    | _VOID ID LFTPRN Formals RTPRN StmtBlock
        {: System.out.print("[reduce 17]"); :} ;

Formals ::=
    // EMPTY
        {: System.out.print("[reduce 18]"); :} 
    | Variables
        {: System.out.print("[reduce 19]"); :} ;

Variables ::= 
    Variable
        {: System.out.print("[reduce 20]"); :}  
    | Variable COMMA Variables  
        {: System.out.print("[reduce 21]"); :} ;

ClassDecl ::= 
    CLS ID Extends Implements LFTBRACE Fields RTBRACE
        {: System.out.print("[reduce 22]"); :} ;

Extends ::=
    // EMPTY
        {: System.out.print("[reduce 23]"); :}
    | EXTNDS ID
        {: System.out.print("[reduce 24]"); :};

Implements ::= 
    // EMPTY
        {: System.out.print("[reduce 25]"); :}
    | Implement
        {: System.out.print("[reduce 26]"); :};

Implement ::= 
    IMPL ID 
        {: System.out.print("[reduce 27]"); :}
    | IMPL ID COMMA Implement
        {: System.out.print("[reduce 28]"); :};

Fields ::=  
    // EMPTY
        {: System.out.print("[reduce 29]"); :}
    | Field Fields
        {: System.out.print("[reduce 30]"); :};

Field ::= 
    VariableDecl
        {: System.out.print("[reduce 31]"); :} 
    | FunctionDecl  
        {: System.out.print("[reduce 32]"); :};

InterfaceDecl ::= 
    INTRFC ID LFTBRACE Prototype RTBRACE    
        {: System.out.print("[reduce 33]"); :};

Prototype ::=
    // EMPTY
        {: System.out.print("[reduce 34]"); :}
    | Type ID LFTPRN Formals RTPRN SEMICOL Prototype 
        {: System.out.print("[reduce 35]"); :}
    | _VOID ID LFTPRN Formals RTPRN SEMICOL Prototype
        {: System.out.print("[reduce 36]"); :};

StmtBlock ::= 
    LFTBRACE VariableDecls Stmts RTBRACE    
        {: System.out.print("[reduce 37]"); :};

VariableDecls ::=
        //EMPTY
        {:System.out.print("[reduce 38]"); :}
        |
         VariableDecl VariableDecls
        {: System.out.print("[reduce 39]"); :};

Stmts ::=
    // EMPTY
        {: System.out.print("[reduce 40]"); :}
    | Stmt Stmts
        {: System.out.print("[reduce 41]"); :};

Stmt ::= 
    OptionExpr SEMICOL 
        {: System.out.print("[reduce 42]"); :}
    | IfStmt 
        {: System.out.print("[reduce 43]"); :}
    | WhileStmt 
        {: System.out.print("[reduce 44]"); :}
    | ForStmt   
        {: System.out.print("[reduce 45]"); :}
    | BreakStmt
        {: System.out.print("[reduce 46]"); :}
    | ReturnStmt 
        {: System.out.print("[reduce 47]"); :}
    | PrintStmt 
        {: System.out.print("[reduce 48]"); :}
    | StmtBlock
        {: System.out.print("[reduce 49]"); :};

IfStmt ::= 
    _IF LFTPRN Expr RTPRN Stmt  
        {: System.out.print("[reduce 50]"); :} 
    |  _IF LFTPRN Expr RTPRN Stmt ELS Stmt  
        {: System.out.print("[reduce 51]"); :}; 

WhileStmt ::=
    WHLE LFTPRN Expr RTPRN Stmt
        {: System.out.print("[reduce 52]"); :};

ForStmt ::=
    FR LFTPRN OptionExpr SEMICOL Expr SEMICOL OptionExpr RTPRN Stmt 
        {: System.out.print("[reduce 53]"); :};

BreakStmt ::= 
    BRK SEMICOL
        {: System.out.print("[reduce 54]"); :};

ReturnStmt ::= 
    RTRN OptionExpr SEMICOL     
        {: System.out.print("[reduce 55]"); :};

PrintStmt ::= 
    PRNTLN LFTPRN Exprs RTPRN SEMICOL   
        {: System.out.print("[reduce 56]"); :};

Expr ::= 
    Lvalue ASSIGN Expr 
        {: System.out.print("[reduce 57]"); :}
    | Constant 
        {: System.out.print("[reduce 58]"); :}
    | Lvalue
        {: System.out.print("[reduce 59]"); :}
    | Call 
        {: System.out.print("[reduce 60]"); :}
    | LFTPRN Expr RTPRN 
        {: System.out.print("[reduce 61]"); :}
    | Expr ADDOP Expr   
        {: System.out.print("[reduce 62]"); :}
    | Expr SUBOP Expr 
        {: System.out.print("[reduce 63]"); :}
    | Expr MULOP Expr 
        {: System.out.print("[reduce 64]"); :}
    | Expr DIV Expr 
        {: System.out.print("[reduce 65]"); :}
    | Expr MOD Expr     
        {: System.out.print("[reduce 66]"); :}
    | Expr LESS Expr    
        {: System.out.print("[reduce 68]"); :}
    | Expr LESSEQ Expr
        {: System.out.print("[reduce 69]"); :}
    | Expr GRT Expr 
        {: System.out.print("[reduce 70]"); :}
    | Expr GRTEQ Expr 
        {: System.out.print("[reduce 71]"); :}
    | Expr EQL Expr 
        {: System.out.print("[reduce 72]"); :}
    | Expr NEQ Expr 
        {: System.out.print("[reduce 73]"); :}
    | Expr AND Expr 
        {: System.out.print("[reduce 74]"); :}
    | Expr OR Expr 
        {: System.out.print("[reduce 75]"); :}
    | NOT Expr 
        {: System.out.print("[reduce 76]"); :}
    | READLN LFTPRN RTPRN 
        {: System.out.print("[reduce 77]"); :}
    | NEWAR LFTPRN INTCONST COMMA Type RTPRN
        {: System.out.print("[reduce 78]"); :};

Lvalue ::= 
    ID
        {: System.out.print("[reduce 79]"); :}
    | Lvalue LFTBRACKET Expr RTBRC 
        {: System.out.print("[reduce 80]"); :}
    | Lvalue DOT ID
        {: System.out.print("[reduce 81]"); :};

Call ::= 
    ID LFTPRN Actuals RTPRN 
        {: System.out.print("[reduce 82]"); :}
    | ID DOT ID LFTPRN Actuals RTPRN
        {: System.out.print("[reduce 83]"); :};

Actuals ::=
    // EMPTY
        {: System.out.print("[reduce 84]"); :} 
    | Exprs 
        {: System.out.print("[reduce 85]"); :};

Exprs ::= 
    Expr
        {: System.out.print("[reduce 86]"); :}
    | Expr COMMA Exprs
        {: System.out.print("[reduce 87]"); :};

Constant ::= 
    INTCONST
        {: System.out.print("[reduce 88]"); :}
    | DBLCONST
        {: System.out.print("[reduce 89]"); :}
    | STRINGCONST 
        {: System.out.print("[reduce 90]"); :}
    | BOOLCONST
        {: System.out.print("[reduce 91]"); :};

OptionExpr ::= 
    //EMPTY
        {: System.out.print("[reduce 92]"); :}
    | Expr
        {: System.out.print("[reduce 93]"); :};

1 Ответ

0 голосов
/ 02 декабря 2018

Я предполагаю, что это какой-то вариант популярного языка "без кофеина", часто используемого во вводных курсах CS.

Мне не совсем понятно, почему CUP сообщает только о двух конфликтах, так как на самом деле есть четыре конфликтав вашей грамматике.Возможно, версия, которую вы вставили, не является версией, сгенерировавшей сообщение об ошибке, которое вы включили в свой вопрос.

Конфликты, о которых сообщается в сообщении об ошибке, являются результатом использования вами правой рекурсии для обоих списков объявлений переменныхи список операторов, которые составляют блок операторов.

Обычная мудрость скажет вам, что правильной рекурсии следует избегать всякий раз, когда это возможно, поскольку она использует неограниченный объем стека синтаксического анализатора.Левая рекурсия, напротив, использует постоянный объем стека синтаксического анализатора.Это хорошее эмпирическое правило, но большую часть времени выбор между левой и правой рекурсией будет продиктован синтаксисом.Так, например, если вы пишете грамматику для арифметических выражений без использования объявлений предшествования, вы будете использовать левую рекурсию для левоассоциативных операторов (а это почти все из них) и правую рекурсию для праворекурсивных операторов (таких как операторы присваивания)в C, C ++ и Java).

Списки элементов обычно могут быть записаны в любом случае, так как они, как правило, будут свернуты в вектор, а не останутся в виде двоичного дерева, поэтому в обычном случае будет оставлена ​​рекурсия:

x_list ::= x_list x_element |
           // EMPTY
           ;

Вы также увидите несколько вариантов вышеуказанного шаблона.Например, если список элементов не может быть пустым, первая продукция будет x_list: x_element.Вам также придется вносить изменения, если элементы должны сопровождаться или отделяться токеном, поэтому вы часто будете видеть такие вещи, как:

// In the language this comes from, statements are *always* followed by
// a semicolon. Most languages don't work that way, though.
statement_list ::= statement_list statement T_SEMICOLON |
                   // EMPTY
                   ;

// Parameter lists (and argument lists) are either empty or have the items
// *separated* by commas. Because the comma is only present if there are at
// least two items, we need to special-case the empty list:

parameter_list ::= T_OPAREN T_CPAREN |
                   T_OPAREN parameters T_CPAREN
                   ;
parameters     ::= parameter |
                   parameters T_COMMA parameter
                   ;

Хотя я написал все это как левую рекурсию,Я также мог бы использовать правильную рекурсию в этих конкретных случаях.Но есть небольшая разница между леворекурсивным и праворекурсивным синтаксическим анализом, что также влияет на порядок выполнения действий синтаксического анализатора.Рассмотрим разницу между:

id_list ::= id_list ID |                id_list ::= ID id_list |
           // EMPTY                                 // EMPTY
           ;                                        ;

Оба они принимают a b c, но принимают их по-разному: ε •

           •3                               •3
          / \                              / \
         •2   c                            a  •2
        / \                                  / \
       •1   b                                b  •1
      / \                                      / \ 
     ε   a                                    c   ε

Поскольку синтаксический анализатор является восходящим в обоихВ некоторых случаях действия парсера всегда выполняются, начиная снизу.Это приведет к тому, что первый (леворекурсивный) синтаксический анализатор выполнит действия в порядке ввода, а второй выполнит действия справа налево.

Во всяком случае, возвращаясь к проблеме.По сути, у вас есть следующая грамматика, которая выведет возможно пустую последовательность объявлений, за которой следует возможно пустая последовательность операторов:

StatementBody        ::= OBRACE VariableDeclarations Statements CBRACE 
VariableDeclarations ::= VariableDeclaration VariableDelarations     | // EMPTY
Statements           ::= Statement Statements                        | // EMPTY

Принимая во внимание дерево разбора, полученное выше, оба Statements и Declarations необходимо эффективно завершить с пустым производством.Другими словами, прежде чем парсер сможет сдвинуть первый токен в Statements, он должен уменьшить пустой нетерминал VariableDeclarations.А это значит, что ему нужно точно знать, какой токен будет первым токеном в Statements.

К сожалению, это невозможно, потому что и Statement, и VariableDeclaration могут начинаться с ID.Поэтому, если синтаксический анализатор только что достиг конца VariableDeclaration и токен предпросмотра - ID, он не может определить, следует ли переключиться на синтаксический анализ Statements или продолжить анализ VariableDeclarations.

Примечание.что ситуация не улучшится, если мы изменим оба этих списка на левую рекурсию, потому что в этом случае парсер должен будет уменьшить пустой нетерминал Statements в одной и той же точке.Единственный способ избежать угадывания парсером места вставки пустого нетерминала - поместить оба пустых нетерминала на концах StatementBody.Другими словами, VariableDeclarations должен быть леворекурсивным, чтобы пустой VariableDeclarations был в начале, а Statements должен быть праворекурсивным, чтобы пустой Statements был в конце:

StatementBody        ::= OBRACE VariableDeclarations Statements CBRACE 
VariableDeclarations ::= VariableDeclarations VariableDelaration     | // EMPTY
Statements           ::= Statement Statements                        | // EMPTY

Это не совсем работает, хотя, потому что (по другим причинам) парсер должен знать, анализирует ли он Statement или VariableDeclaration, начиная с ID, посмотрев на токенсразу после ID.И там он столкнется со следующим недетерминизмом:

    b [ ] a;      // Declaration
    b [ 3 ] = a;  // Assignment

Эти две возможности невозможно различить, пока не будет замечен третий маркер.Но парсер должен знать один токен ранее, чтобы он мог решить, превратить b в Lvalue.

Разрешение этого конфликта более раздражает.Я считаю, что обычный подход заключается в том, чтобы заставить лексический сканер выполнять работу, возвращая [ ] как один токен.Конечно, это решает проблему - с этим изменением одна открытая скобка всегда означает, что анализатор смотрит на выражение, в то время как пара [ ] всегда означает объявление.Но это неудобно в сканере;в частности, сканер должен иметь возможность обрабатывать что-то вроде

[ /* A comment */
  /* Another comment */ ]

как один [ ] токен.(Мы надеемся, что никто не будет писать такой код, но он допустим.)

И это приводит нас к третьему конфликту с уменьшением сдвига, который является результатом различия между точечными присваиваниями и точечными вызовами:

a . b ( 3 ) ;
a . b = 3 ;

Это гораздо более простая проблема, и ее можно решить, внимательно посмотрев на справочную грамматику для Decaf.С вашей грамматикой, вызов должен соответствовать производственному ID DOT ID OPAREN ..., в то время как назначение будет соответствовать Lvalue DOT ID.Другими словами, когда DOT - это запрос, анализатор должен решить, следует ли уменьшить a до Lvalue.Этого можно избежать, сделав две правые стороны более похожими.

...