我有一个特殊的结构,我在调试时遇到了问题。 我希望这种积极的简化能够恰当地证明这个问题:
%token VAR IDENTIFIER NUMBER INT
%%
root:
code_statements
;
code_statements:
code_statement
| code_statements code_statement
;
code_statement:
var_statement
| expression_statement
;
var_decl:
IDENTIFIER ':' type_expression
;
var_statement:
VAR var_decl ';'
;
expression_statement:
value_expression ';'
;
function_call:
'(' ')'
| '(' value_expression_list ')'
;
value_expression_list:
value_expression
| value_expression_list ',' value_expression
;
/**** UNKNOWN EXPRESSIONS (VALUE/TYPE?) ****/
unknown_expression:
IDENTIFIER
| unknown_expression '.' IDENTIFIER
| postfix_known_value_expression '.' IDENTIFIER
| postfix_known_type_expression '.' IDENTIFIER
;
/**** VALUE EXPRESSION ****/
primary_value_expression:
NUMBER
| '(' value_expression ')'
;
postfix_known_value_expression:
primary_value_expression
| postfix_value_expression function_call
;
postfix_value_expression:
postfix_known_value_expression
| unknown_expression
;
value_expression:
postfix_value_expression
;
/**** TYPE EXPRESSION ****/
primary_type_expression:
INT
| '(' type_expression ')'
;
postfix_known_type_expression:
primary_type_expression
| postfix_type_expression '*'
;
postfix_type_expression:
postfix_known_type_expression
| unknown_expression
;
type_expression:
postfix_type_expression
;
%%
错误输出:
State 11
14 unknown_expression: unknown_expression . '.' IDENTIFIER
22 postfix_value_expression: unknown_expression .
29 postfix_type_expression: unknown_expression .
'.' shift, and go to state 26
';' reduce using rule 22 (postfix_value_expression)
';' [reduce using rule 29 (postfix_type_expression)]
')' reduce using rule 22 (postfix_value_expression)
')' [reduce using rule 29 (postfix_type_expression)]
'*' reduce using rule 29 (postfix_type_expression)
$default reduce using rule 22 (postfix_value_expression)
这里有两个问题,一个与)
相关的问题很容易理解,但我很难想出一个优雅的解决方法。关于;
的问题,我无法看到它是如何发生的(虽然我确实知道如何影响它),而且我不确定如何从错误中取出相关信息到调试实际问题。
在我的实际语法中,还有一些,但我认为这些模式与这两种模式基本相同。
答案 0 :(得分:2)
这里的基本问题是状态11中显示的一对产品:(注意:我使用命令行选项--report=all
而不是-v
来查看先行计算< / em>的。)
22 postfix_value_expression: unknown_expression . [';', '(', ')', ',']
29 postfix_type_expression: unknown_expression . [';', ')', '*']
在这里,您可以看到postfix_value_expression
或postfix_type_expression
的计算前瞻包括; 和)。
其中第一个是(有些)虚假的;这是LALR算法混淆实际上具有不同前瞻的两个状态的罕见情况之一。 (这个问题在bison manual中进行了简要讨论,在大多数标准解析教科书中都有更长的篇幅。)如果我们要求bison使用Canonical LR算法(%define lr.type canonical-lr
),这种冲突会消失,但第二个遗体(在州52):
22 postfix_value_expression: unknown_expression . ['(', ')']
29 postfix_type_expression: unknown_expression . [')', '*']
我们这里的语法是一种真正含糊不清的模糊性。 (我假设&#34;类型&#34;可以有&#34;值&#34;成员(反之亦然),正如语法似乎表明的那样。)
假设输入包括
x.member
解析器无需知道x
是类型还是值。 x
将简单地解析为unknown_expression
(生产13),索引选择也将是unknown_expression
(生产14)。 [注意:我在本回答结尾处复制了输出文件中的号码制作规则]。
但由于类型可以用括号括起来(制作25),所以可以合理地写出上述内容:
(x).member
现在解析器需要应用生产15或16才能派生unknown_expression
,这意味着它需要使用生产22(继续生产15)或生产29来减少x
(用于生产16)。但是,它不知道x
是一个类型还是一个值,并且它无法知道(至少在没有查询符号表的情况下)。虽然两种可能的减少(可能)是等价的,但它们是不同的产品,因此语法是模糊的。因此减少/减少冲突。
看着这种方式,冲突是我们可能称之为过早分类的结果。由于语法实际上并不关心索引运算符。的左侧操作数是类型还是值,因此此时不需要解析左侧操作数。这将通过后续步骤中的语义分析来解决。在这种情况下,最简单的解决方案是不要费心区分语法中的类型和值,这对于这个简单的语言子集来说会很好。一个非常简单的语法就足够了:
expression : postfix_expression
postfix_expression: term
| postfix_expression '.' IDENTIFIER
| postfix_expression '(' expression_list ')'
| postfix_expression '*'
term : IDENTIFIER
| '(' expression ')'
一旦所有标识符都被分类,任何必要的语义检查(以确保表达式是正确的)都可以在后树行走中完成。
询问是否有必要在语法上解析表达式的类别是很有用的。实际上是否需要消除解析的歧义?
在C(和朋友)中,除非对标识符进行分类,否则语言确实不明确。例如,(x)*y
是:
y
或x
和y
如果不知道x
的类别,则无法生成正确的解析树。 (为了使这一点更加清晰,请考虑z/(x)*y
,其中解析树的形状在两个备选方案之间根本不同。还有许多其他示例。)
为C生成精确解析树的常用解决方案是求助于词汇反馈,这反过来要求在使用前声明,至少在分类的范围内。 (即使C ++坚持在使用前声明类型别名,即使它允许在声明之前使用类成员。)
一个合理的替代方法是使用类似GLR解析器的东西,它产生两个(或所有)解析树。 (这通常称为解析林。)一旦标识符被分类,后续的语义分析过程可以修剪解析林,尽管仍然需要对森林中的所有解析树进行明确的分类。 [注1]
避免上述含糊不清主要是语言设计的问题; C casts的语法使用方括号而不是括号,例如,问题可能已被消除。 (当然,这会耗尽C ++用于lambdas的语法空间。)尽管如此,很容易想象一种语法,即使不知道每个标识符的类别,其语法也是明确的,但是但是,由于句法分歧,需要考虑表达域。您的语言似乎可能是这种形式。 (在某种意义上,这是自上而下的分类而不是自下而上的分类:声明和表达式语句具有不同的解析上下文。)
例如,很容易想象您预期以与C系列类似的方式使用 * ,作为前缀解除引用运算符和中缀乘法运算符。结合上面显示的后缀类型构造运算符,最终会有一个运算符用于前缀,后缀和中缀。如果将所有用途无差别地放入同一个包中,则必然是不明确的:单个令牌最多可以表示操作数,中缀运算符,前缀运算符和后缀运算符中的两个。如果 * 可以是三种运算符类型中的任何一种,那么a * * b
可能意味着(a*) * b
或a * (*b)
。
但即使不知道标识符的类别,该表达式仍然可以明确地解析,因为我们知道前缀和中缀 * 运算符都不能应用于后缀 * 类型构造函数。因此,(a*) * b
是不可能的,唯一有效的解析是a * (*b)
。
不幸的是,由于LR(1)解析器需要根据单个先行令牌决定是否减少,因此决定a*
是否应减少为type
或遇到第二个*
时不需要这样做,这是不可能的,因为(a * *)
是一个有效的类型表达式。更糟糕的是,表达式中的星号数量没有限制,实际上可能是a*…*[b]
。我们可以根据最后一个星号后面的令牌消除歧义,但这需要任意前瞻。
鉴于上述情况,最佳解决方案可能是GLR解析器,它没有先行限制,并结合足够的语法来至少划分可以使用*
等运算符的上下文。
我们不能在OP中提出的语法中抛出一个GLR解析器,因为 - 如上所述 - 即使模糊性不重要,它仍然是模棱两可的。幸运的是,我们可以通过避免过早分类来创建一个明确的语法。
如上所示,带括号的表达式的问题在于语法要求它们是类型或值表达式,即使在它不关心哪些情况下也是如此。为了避免这种情况,我们需要允许延迟决定的可能性,直到有必要通过创建第三种类型的括号表达式。这导致了以下语法,实际上是LALR(1)。
我将表达式分离出来,因为否则样板是压倒性的。每个产品都有效地指示了参数的预期类别,运算符的语法优先级以及结果的类别(或未知)。每个产品的左侧是known_category_precedence
,断言语法需要该类别,或unknown_precedence
,表明语法本身不能预测类别。 (例如,&#39;(expr).b&#39;可能是类型或值,因此select运算符的结果为unknown_postfix
。)
在左侧,您将看到category_precedence
形式的非终端,它断言语义分析必须导致具有该类别的对象。这些非终端真的只是为了方便;每一个都被简单地定义为
category_precedence: known_category_precedence | unknown_precedence
其中第二个替代表示需要进行语义检查。如果在解析期间可以实现,则将其插入到第二个备选方案的操作中。或者,可以将语义检查节点插入AST中。
我在锅炉盘中加入了这些便利产品,但大多数都被注释掉了,因为当语法中没有使用非终端时,野牛会抱怨。
%token VAR "var"
%token IDENTIFIER TYPE NUMBER
%%
program : %empty
| program statement
statement : var_declaration
| expr_statement
var_declaration : "var" IDENTIFIER ':' type ';'
expr_statement : value ';'
/* Productions for expression syntaxes */
parameters : '(' ')'
| '(' value_list ')'
value_list : value
| value_list ',' value
known_value_postfix: value_postfix parameters { /* function call */ }
unknown_postfix : any_postfix '.' IDENTIFIER { /* select */ }
known_type_postfix : type_postfix '*' { /* pointer type constructor */ }
/* Primary terms */
unknown_primary : IDENTIFIER
| '(' unknown ')'
known_value_primary: NUMBER
| '(' known_value ')'
known_type_primary : TYPE
| '(' known_type ')'
/* Boilerplate precedence grammar with two infix precedence levels */
unknown_postfix : unknown_primary
known_value_postfix: known_value_primary
known_type_postfix : known_type_primary
value_postfix : known_value_postfix | unknown_postfix
type_postfix : known_type_postfix | unknown_postfix
any_postfix : known_value_postfix | known_type_postfix | unknown_postfix
unknown_prefix : unknown_postfix
known_value_prefix : known_value_postfix
known_type_prefix : known_type_postfix
/* value_prefix : known_value_prefix | unknown_prefix */
/* type_postfix : known_type_prefix | unknown_prefix */
unknown_infix9 : unknown_prefix
known_value_infix9 : known_value_prefix
known_type_infix9 : known_type_prefix
/* value_infix9 : known_value_infix9 | unknown_infix9 */
/* type_infix9 : known_type_infix9 | unknown_infix9 */
unknown_infix8 : unknown_infix9
known_value_infix8 : known_value_infix9
known_type_infix8 : known_type_infix9
/* value_infix9 : known_value_infix8 | unknown_infix8 */
/* type_infix9 : known_type_infix8 | unknown_infix8 */
/* The last stanza is mostly for convenience but it also serves
* to avoid the spurious reduce/reduce conflict on ';'.
*/
unknown : unknown_infix8
known_value : known_value_infix8
known_type : known_type_infix8
value : known_value | unknown
type : known_type | unknown
有了这个框架,我们就可以开始添加其他类型的表达式了。
首先,让我们面对 * 上的三方冲突。基于上述模式,基本表达式制作非常简单:
known_value_prefix : '*' value_prefix { /* dereference */ }
known_value_infix9 : value_infix9 '*' value_prefix { /* produce */ }
(我们还需要取消注释value_prefix
和value_infix9
作品。)
尽管语言仍然是明确的,但它不再是LALR(1)(或者甚至是任何k的LR(k)),如上面在该语法的讨论中所指出的。因此,野牛会抱怨减少冲突。我们无法修复该投诉,但我们可以轻松制作出有效的解析器;我们需要做的就是插入一个bison生成GLR解析器的请求:
%glr-parser
这并没有抑制冲突警告[注2],但它确实产生了一个有效的解析器。 (野牛无法验证语法是否明确,因为没有精确的算法。)由于我们无法证明语法是明确的,我们需要进行广泛的测试。如果GLR解析器遇到歧义,它将产生错误消息,因此当我们没有看到任何错误时,我们可以放心:
$ ./typevar3
(*a).b;
[EXP [ASVALUE [SELECT [DEREF [ASVALUE a]] b]]]
a*b;
[EXP [PROD [ASVALUE a] [ASVALUE b]]]
a**b;
[EXP [PROD [ASVALUE a] [DEREF [ASVALUE b]]]]
(a***b).c;
[EXP [ASVALUE [SELECT [PROD [ASVALUE a] [DEREF [DEREF [ASVALUE b]]]] c]]]
(a***).c;
[EXP [ASVALUE [SELECT [POINTER [POINTER [POINTER [ASTYPE a]]]] c]]]
现在,让我们假设我们想要添加一个数组类型构造函数,有点类似于C.我们将允许以下形式:
type[count] // eg. int[3]
请注意,表单在语法上类似于数组索引操作(a [2]),我们也会将其添加到语言中。
这与select运算符略有不同。在select运算符的情况下,语法允许从中选择值或类型,并且不能预测结果。在数组构造/索引的情况下,结果的类别恰好是第一个参数的类别。
因为我们需要通过&#34;第一个参数的类别,我们需要三个产品,而不是括号产品:
known_value_postfix: known_value_postfix '[' value ']'
known_type_postfix : known_type_postfix '[' value ']'
unknown_postfix : unknown_postfix '[' value ']'
在第三种情况下,我们需要插入&#34; construct_or_index&#34;节点进入AST。这可以通过稍后的单元生产解析为构造节点或索引节点,该单元生产将未知类别转换为某个特定类别,或者可以留给语义分析阶段。
添加这三个产品不会产生任何问题。但是,现在我们想添加一个构造可变大小数组的语法,我们将选择语法int[*]
,生成另一个不兼容的 * lexeme用法。该语法具有明确的结果类别,因为它不是有效的索引表达式,因此我们可以编写生成:
known_type_postfix : type_postfix '[' '*' ']'
这一次,我们选择在右侧使用便利的非终端。当然,这产生了大量新的语法冲突,包括shift-reduce和reduce-reduce,但语法仍然按照预期的方式工作:
var a: (int*)[*];
[DECL a [ARRAY [POINTER [TYPE int]]]]
var a: (int*)[2*2];
[DECL a [ARRAY [POINTER [TYPE int]] [PROD [VALUE 2] [VALUE 2]]]]
a[2];
[EXP [ASVALUE [INDEX_OR_ARRAY a [VALUE 2]]]]
(*a)[2];
[EXP [INDEX [DEREF [ASVALUE a]] [VALUE 2]]]
作为练习留下:使用中缀 + 运算符来表示两个值的标量添加,以及两种类型的并集。请注意,您必须处理两个操作数中只有一个具有已知类别(因此必须将另一个操作数强制转换为同一类别)的情况,以及两个操作数都没有已知类别的情况。 / p>
bison实现的GLR解析器并不适用于此目的,因为它希望生成单个解析树。通过使用%merge
声明(如手册中所述),可以使用bison生成的解析器手动创建解析林,但这不会实现其他GLR解析器可以生成的节省空间的图形表示。
野牛手册建议使用%expect-rr
指令来做到这一点,但恕我直言,只有在语法准备就绪后才能完成。不幸的是,你只能压制已知的冲突数量,而不是压制特定的预期冲突;我想这是因为很难准确地描述单个预期的冲突,但最终结果是抑制冲突使得在开发语法时很容易遗漏问题。验证冲突是预期的冲突是令人讨厌的,但是比错过语法错误更不烦人。
包含编号作品的原始语法:
1 root: code_statements
2 code_statements: code_statement
3 | code_statements code_statement
4 code_statement: var_statement
5 | expression_statement
6 var_decl: IDENTIFIER ':' type_expression
7 var_statement: VAR var_decl ';'
8 expression_statement: value_expression ';'
9 function_call: '(' ')'
10 | '(' value_expression_list ')'
11 value_expression_list: value_expression
12 | value_expression_list ',' value_expression
13 unknown_expression: IDENTIFIER
14 | unknown_expression '.' IDENTIFIER
15 | postfix_known_value_expression '.' IDENTIFIER
16 | postfix_known_type_expression '.' IDENTIFIER
17 primary_value_expression: NUMBER
18 | '(' value_expression ')'
19 postfix_known_value_expression: primary_value_expression
20 | postfix_value_expression function_call
21 postfix_value_expression: postfix_known_value_expression
22 | unknown_expression
23 value_expression: postfix_value_expression
24 primary_type_expression: INT
25 | '(' type_expression ')'
26 postfix_known_type_expression: primary_type_expression
27 | postfix_type_expression '*'
28 postfix_type_expression: postfix_known_type_expression
29 | unknown_expression
30 type_expression: postfix_type_expression