Parsing 在LALR(1)解析器(PLY)中可以解析这种看似模糊的内容吗?

Parsing 在LALR(1)解析器(PLY)中可以解析这种看似模糊的内容吗?,parsing,yacc,ply,lalr,Parsing,Yacc,Ply,Lalr,我在PLY(pythonlexyacc)中有一个很大的ish语法,用于一种在解析方面有一些特殊挑战的语言。该语言允许两种调用的前导语法看起来几乎相同,直到调用结束。这为减少/减少冲突提供了很多机会,因为沿途令牌的语义不同,但可以使用相同的终端令牌构建。我提取了下面语法的简单前/后版本,我将对此进行一些解释 最初,表达式是一种典型的“分层语法”,将调用和文本等转换为主要表达式,然后从主要表达式转换为一元表达式,再从二进制转换为一般表达式。问题是带有两个参数的Call_expr与以“/”之前的两个I

我在PLY(pythonlexyacc)中有一个很大的ish语法,用于一种在解析方面有一些特殊挑战的语言。该语言允许两种调用的前导语法看起来几乎相同,直到调用结束。这为减少/减少冲突提供了很多机会,因为沿途令牌的语义不同,但可以使用相同的终端令牌构建。我提取了下面语法的简单前/后版本,我将对此进行一些解释

最初,表达式是一种典型的“分层语法”,将调用和文本等转换为主要表达式,然后从主要表达式转换为一元表达式,再从二进制转换为一般表达式。问题是带有两个参数的
Call_expr
与以“/”之前的两个Id开头的
Iter_expr
版本冲突。冲突发生在调用中第一个参数后的逗号上,因为最初,
Expr->…->允许使用主\u expr->Name\u expr->Id
。解析器可以将
Id
减少到
Expr
以匹配
调用\u Expr
,或者将其保留为匹配
Iter\u Expr
。展望逗号并不能帮助它做出决定。如果调用的第一个参数只是一个标识符(比如一个变量),这就是合法的歧义。考虑输入<代码> ID> ID(ID、ID…<代码> > < /P> 我的方法是创建一种表达式,它可以而不是仅仅是一个
Id
。我通过所有表达式添加了生产链,给它们提供了“
\u nn
”版本--“不是名称”。然后我可以为
Call\u expr
定义生产,在第一个参数中使用任何语法,使其超过just一个名称(例如运算符、调用等),将其简化为
BinOp\u expr\u nn
,并允许只使用
Id
作为第一个参数的调用生成。这应该说服解析器移动,直到它可以解析
Iter\u expr
调用expr
(或者至少知道它在哪个路径上)

你可能已经猜到了,这把一切都搞砸了:)。修补表达式链也修补了
Primary_expr
,我仍然需要允许将其减少到
Id
。但是现在,这是一个reduce/reduce冲突——每个
主表达式都可以留在那里,或者继续到
一元表达式。我可以命令他们做出选择(这可能会奏效),但我希望我最终会追逐一个又一个

所以,我的问题是:有没有一种技术可以让人们清楚地说明如何允许相同的标记表示不同的语义(即expr vs id),而这些语义仍然可以像PLY一样用LALR(1)进行解析?除此之外,还有什么有用的黑客可以帮助解决这个问题吗?这能消除歧义吗

terminals:  '+' '^' ',' '>' '(' ')' '/' ':' 'id' 'literal' 
   (i.e. punctuation (besides '->' and '|', initial-lower-case words)
non-terminals:  initial-Upper-case words
原始语法:

S'-> S
S -> Call_expr
   | Iter_expr
Expr -> BinOp_expr
BinOp_expr -> Unary_expr
BinOp_expr -> BinOp_expr '+' BinOp_expr
Unary_expr -> Primary_expr
   | '^' BinOp_expr
Primary_expr -> Name_expr
   | Call_expr
   | Iter_expr
   | Literal_expr
Name_expr -> Id
Args -> Expr
   | Args ',' Expr
Call_expr -> Primary_expr '>' Id '(' ')'
   | Primary_expr '>' Id '(' Args ')'
Iter_expr -> Primary_expr '>' Id '(' Id '/' Expr ')'
   | Primary_expr '>' Id '(' Id ':' Id '/' Expr ')'
   | Primary_expr '>' Id '(' Id ',' Id ':' Id '/' Expr ')'
Literal_expr -> literal
Id -> id
我试图消除
Call\u expr
中第一个参数的歧义:

S'-> S
S -> Call_expr
   | Iter_expr
Expr -> BinOp_expr_nn
   | BinOp_expr
BinOp_expr -> BinOp_expr_nn
   | Unary_expr
BinOp_expr_nn -> Unary_expr_nn
   | BinOp_expr '+' BinOp_expr
Unary_expr -> Primary_expr
   | Unary_expr_nn
Unary_expr_nn -> Primary_expr_nn
   | '^' BinOp_expr
Primary_expr -> Primary_expr_nn
   | Name_expr
Primary_expr_nn -> Call_expr
   | Iter_expr
   | Literal_expr
Name_expr -> Id
Args -> Expr
   | Args ',' Expr
Call_expr -> Primary_expr '>' Id '(' ')'
   | Primary_expr '>' Id '(' Expr ')'
   | Primary_expr '>' Id '(' Id , Args ')'
   | Primary_expr '>' Id '(' BinOp_expr_nn , Args ')'
Iter_expr -> Primary_expr '>' Id '(' Id '/' Expr ')'
   | Primary_expr '>' Id '(' Id ':' Id '/' Expr ')'
   | Primary_expr '>' Id '(' Id ',' Id ':' Id '/' Expr ')'
Literal_expr -> literal
Id -> id

尽管你的文章标题不同,但你的语法并不含糊。它根本不是LR(1),因为您提到的原因:输入

A ( B , 
可以是
调用\u expr
Iter\u expr
的开始。在第一种情况下,
B
必须减少为
Expr
,然后减少为
Args
;在第二种情况下,它不能减少,因为当右侧
id'('id','id':'id'/'Expr')减少时,它仍然需要是
id
。不能仅仅通过查看单个前瞻标记(The,)来做出决策,因此存在移位-减少冲突

此冲突最多可以使用两个额外的前瞻标记来解决,因为只有在后面紧跟着一个
id
和一个:。所以语法是LALR(3)。不幸的是,Ply不生成LALR(3)解析器(yacc/bison也不生成),但有一些替代方案

1.使用不同的解析算法 由于语法是明确的,因此可以使用GLR解析器对其进行解析而不会出现问题(也不会进行任何修改)。Ply也不产生GLR解析器,但Bison可以。这对您可能没有多大用处,但我想我应该提到它,以防您没有被锁定在Python的使用中

2.使用允许一些无效输入的语法,并通过语义分析丢弃它们 这几乎肯定是最简单的解决方案,这也是我通常会推荐的。如果将
Iter\u expr
的定义更改为:

Iter_expr : id '(' id '/' Expr ')'
          | id '(' Args ':' id '/' Expr ')'
然后它仍然会识别每个有效输入(因为
id
id,id
都是
Args
的有效实例)。这消除了转移,减少了冲突;实际上,它让解析器在遇到a(表示
调用expr
)或a:(表示
Iter\u expr
)之前不必做出决定。(对于
Iter_expr
的第一个备选方案没有问题,因为改变/而不是减少
id
的决定不需要额外的前瞻性。)

当然,
Iter_expr
的第二个产品将识别出许多无效的迭代表达式:包含两个以上项的列表,以及包含比单个
id
更复杂的表达式的列表。但是,这些输入根本不是有效的程序,因此可以在
Iter\u expr
的操作中简单地拒绝它们。识别有效迭代的精确代码将取决于您如何表示AST,但并不复杂:只需检查以确保
Args
的长度是一个或两个,并且列表中的每个项目都只是一个
id

3.使用词汇黑客 弥补前向信息不足的一种方法是,通过将必要的前向信息收集到缓冲区中,并仅在词素的语法类别已知时输出词素,从而在词素分析器中收集前向信息。在这种情况下
Iter_expr : id '(' id '/' Expr ')'
          | id '(' id ':' id '/' Expr ')'
          | id '(' iter_id ',' id ':' id '/' Expr ')'
Start   : Call
        | Iter
Expr    : ID
        | ExprN
ExprN   : UnaryN
        | Expr '+' Unary
Unary   : ID
        | UnaryN
UnaryN  : ChainN
        | '^' Chain
Chain   : ID
        | ChainN
ChainN  : PrimaryN
        | Chain '>' CallIter
PrimaryN: LITERAL
        | Call
        | Iter
        | '(' Expr ')'
Call    : ID '(' ')'
        | ID '(' ID ')'
        | ID '(' ID ',' ID ')'
        | ID '(' Args ')'
Iter    : ID '(' ID '/' Expr ')'
        | ID '(' ID ':' ID '/' Expr ')'
        | ID '(' ID ',' ID ':' ID '/' Expr ')'
Args    : ExprN ExprList
        | ID ',' ExprN ExprList
        | ID ',' ID ',' Expr ExprList
ExprList:
        | ExprList ',' Expr