当变量或函数的类型由用户定义时,Java CUP(解析器)会产生移位/减少冲突
我的语法需要有用户定义的类型ID组合。以下代码的问题在于它生成以下内容: 我试图在我的语法中去掉可变decls和Stmts中的E产品,但没有成功。我还尝试用type生成ID,然后生成e产品。 包装杯。示例当变量或函数的类型由用户定义时,Java CUP(解析器)会产生移位/减少冲突,java,parsing,cup,Java,Parsing,Cup,我的语法需要有用户定义的类型ID组合。以下代码的问题在于它生成以下内容: 我试图在我的语法中去掉可变decls和Stmts中的E产品,但没有成功。我还尝试用type生成ID,然后生成e产品。 包装杯。示例 import java_cup.runtime.*; import java.io.IOException; import java.io.File; import java.io.FileInputStream; parser code {: TScanner scanner; P
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]"); :};
我认为这是流行的“无咖啡因”语言的一种变体,通常用于CS入门课程 我不太清楚为什么CUP只报告两个冲突,因为afaics在你的语法中有四个冲突。您粘贴的版本可能不是生成问题中包含的错误消息的版本 错误消息中报告的冲突是对变量声明列表和构成语句块的语句列表使用右递归的结果 传统智慧会告诉您,应该尽可能避免正确的递归,因为它使用了无限量的解析器堆栈。相比之下,左递归使用的解析器堆栈数量是恒定的。这是一个很好的经验法则,但大多数情况下,左递归和右递归之间的选择将由语法决定。例如,如果你在使用算术声明的情况下使用语法,而不使用优先声明,那么你将使用左递归算子来计算左关联算子(几乎所有的算子)和右递归算子的右递归(如C、C++和java中的赋值算子)。 项目列表通常可以以任何一种方式编写,因为它们通常会折叠成一个向量,而不是保持为二叉树,因此正常情况下将保留递归:
x_list ::= x_list x_element |
// EMPTY
;
您还将看到上述模式的许多变体。例如,如果ITME列表不能为空,则第一个产品将是x\u列表:x\u元素
。如果元素后面需要跟一个标记或用标记分隔,您还必须进行修改,因此您经常会看到这样的情况:
// 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,但接受方式不同:
ε•
因为解析器在这两种情况下都是自底向上的,所以解析器操作总是从底部开始执行。这将导致第一个(左递归)解析器按输入顺序执行操作,而第二个解析器将从右向左执行操作
无论如何,回到问题上来。实际上,您有以下语法,它将派生一个可能为空的声明序列,后跟一个可能为空的语句序列:
StatementBody ::= OBRACE VariableDeclarations Statements CBRACE
VariableDeclarations ::= VariableDeclaration VariableDelarations | // EMPTY
Statements ::= Statement Statements | // EMPTY
考虑到上面导出的解析树,语句
和声明
都需要有效地以空生产结束。换句话说,在解析器可以移动语句中的第一个标记之前,它需要减少一个空的变量声明
非终结符。这意味着它需要确切地知道哪个令牌将是语句中的第一个令牌
不幸的是,这是不可能的,因为语句
和变量声明
都可以以ID
开头。因此,如果解析器刚刚到达变量声明的末尾,并且先行标记是ID
,那么它无法判断是切换到解析语句
还是继续解析变量声明
请注意,如果我们将这两个列表都更改为左递归,情况将不会得到改善,因为在这种情况下,解析器必须在完全相同的点上减少一个空的语句
非终结符。避免让解析器猜测在何处插入空的非终结符的唯一方法是将两个空的非终结符都放在StatementBody
的末尾。换句话说,VariableDeclarations
必须是左递归的,因此空的VariableDeclarations
位于开头,而语句
必须是右递归的,因此空的语句
位于结尾:
StatementBody ::= OBRACE VariableDeclarations Statements CBRACE
VariableDeclarations ::= VariableDeclarations VariableDelaration | // EMPTY
Statements ::= Statement Statements | // EMPTY
但是,这不会很有效,因为(出于其他原因)解析器必须能够通过查看紧跟在ID
之后的标记来知道它是在解析语句还是VariableDeclaration
。在那里,它将遇到以下非决定论:
b [ ] a; // Declaration
b [ 3 ] = a; // Assignment
在看到第三个标记之前,无法区分这两种可能性。但是解析器需要提前知道一个标记,以便决定是否将b
转换为Lvalue
解决这一冲突更令人恼火。我相信通常的方法是强制词法扫描程序完成这项工作,方法是将[]
作为单个标记返回。可以肯定的是,这就解决了问题——在这个更改中,单个开括号始终表示解析器正在查看表达式,而[]
对始终表示声明。但是在扫描仪里很尴尬;特别是,扫描仪将需要能够处理以下内容
[ /* A comment */
/* Another comment */ ]
作为单个[]
令牌。(我们希望没有人会编写这样的代码,但它是合法的。)
这就引出了减少冲突的第三个转变,这是区分虚线分配和虚线调用的结果:
a . b ( 3 ) ;
a . b = 3 ;
不过,这是一个简单得多的问题,可以通过仔细查看Decaf的参考语法来解决。根据你的语法,这个电话需要修改
[ /* A comment */
/* Another comment */ ]
a . b ( 3 ) ;
a . b = 3 ;