【发布时间】:2018-04-28 01:31:28
【问题描述】:
我在 PLY (Python Lexx Yacc) 中有一个大型语法,用于在解析方面有一些特殊挑战的语言。该语言允许两种调用的前导语法看起来几乎相同,直到调用非终端结束。这为减少/减少冲突提供了很多机会,因为沿途令牌的语义是不同的,但可以从相同的终端令牌构建。我提取了以下语法的简单前后版本,我会稍微解释一下。
最初,表达式是一种典型的“分层语法”,将调用和文字等进行初级,然后通过一元进行初级,然后将二进制变为一般表达式。问题是带有两个参数的Call_expr 与Iter_expr 的版本冲突,该版本以'/' 之前的两个ID 开头。冲突在调用中的第一个参数之后的逗号上,因为最初,Expr -> ... -> Primary_expr -> Name_expr -> Id 是允许的。解析器可以将Id 减少到Expr 以匹配Call_expr,或者让它匹配Iter_expr。展望逗号并没有帮助它做出决定。如果调用的第一个参数只是一个标识符(如变量),这是合法的歧义。考虑输入id > id ( id , id ...。
我的方法是制作一种不可能只是Id的表达式。我通过所有表达式添加了生产链,以赋予它们“_nn”版本--“不是名称”。然后我可以为Call_expr 定义产生式,在第一个参数中使用任何语法,使其不仅仅是一个名称(例如运算符、调用等),以将其简化为BinOp_expr_nn,也是 允许仅以Id 作为第一个参数的调用产生。这应该会说服解析器只是移动,直到它可以解析 Iter_expr 或 Call_expr(或者至少知道它在哪条路径上。)
正如您可能已经猜到的那样,这搞砸了一切 :)。修改表达式链也修改了Primary_expr,我仍然需要允许将其减少到Id。但现在,这是一个减少/减少冲突——每个Primary_expr 可以留在那里或继续Unary_expr。我可以命令他们做出选择(这可能会奏效),但我希望我最终会追逐一个又一个。
所以,我的问题是:是否有人可以阐明如何让相同的标记表示不同的语义(即 expr 与 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_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
【问题讨论】:
-
我只是将 Iter_Expr 和 Call_Expr 折叠成一个语法,然后通过遍历 AST 来梳理它是什么。如果你知道任何 Lisp,那应该是显而易见的。也就是说,在 Call_Expr 语法中允许那些 Id : Id / Expr 形式。然后遍历 Call_Expr 节点以确定它们是否实际上是有效的 Iter_exprs。这将过度生成:允许对 Id 的无效使用:Id / Expr;您可以检查并诊断。
-
@rici:很抱歉出现错误,但您的解释最正确。 '>' 应该在终端列表中;它将某些类型的呼叫与我的玩具版本中未包含的其他呼叫区分开来。所有这些调用都可以链接为“foo > bar(baz / a + baz) > qux(b, c, d, e)”,并且“>”的每个 lhs 都是一个 primary_expr。 '/' 是另一个语法分隔符,而不是运算符。很抱歉造成混淆:其中大部分来自简化,断章取意,我敢肯定这看起来很奇怪。
-
我仍然认为你想要
BinOp_expr -> Unary_expr | BinOp_expr '+' Unary_expr(这样+的关联性较低;正如所写,它是模棱两可的)和Unary_expr -> Primary_expr | '^' Unary_expr这样^ a + b意味着(^a) + b而不是@ 987654349@)。 (或者可能是Unary_expr -> Primary_expr | '^' Primary_expr,如果不允许使用^^a。) -
您可能还需要
Primary_expr -> '(' Expr ')'以允许使用括号。 -
是的,我正在考虑这些。目前,我正在使用优先级来解决所有这些问题,但会解决歧义。感谢您和@Kaz 的精彩建议。非常感谢,我现在应该上路了!