在野牛生成的解析器中解决这个问题并非不可能,但这并不容易,而且所需的黑客量可能会降低语法的可读性和可验证性。
需要明确的是,GLR 解析器不是回退解析器。GLR 算法并行探索所有可能的解析,并拒绝无效的解析。(bison 实现要求解析收敛到一个可能的解析;原始 GLR 算法产生解析树的森林。)此外,GLR 算法不考虑多个词法分析。
如果你想在解析器的上下文中解决这个问题,你可能需要引入对空白的特殊处理,或者至少对于-没有被空白包围的空白。否则,您将无法区分a - b
(可能总是减法)和a-b
(如果定义了该变量,则可能是该变量a-b
)。撇开这个问题不谈,你会寻找这样的东西(但这不起作用,如下所述):
expr : term
| expr '-' term
term : factor
| term '*' factor
factor: var
| '(' expr ')'
var : ident { if (!isVariable($1)) { /* reject this production */ } }
ident : WORD
| ident '-' WORD { $$ = concatenate($1, "-", $3); }
这是行不通的,因为与关联的操作var : ident
在解析消除歧义之后才会执行。因此,如果产生式被拒绝,则解析失败,因为解析器已经确定产生式是必要的。(在解析器做出决定之前,操作被推迟。)
Bison 允许 GLR 语法使用语义谓词,这些谓词立即执行而不是延迟执行。但这无济于事,因为语义谓词不能使用计算的语义值(因为在评估语义谓词时,语义值的计算仍然被推迟)。您可能认为可以通过将连接标识符的计算(在第二个ident
产生式中)作为语义谓词来解决此问题,但随后会遇到另一个限制:语义谓词本身没有语义值。
可能有一个 hack 可以解决这个问题,但这可能会给你带来不同的问题。假设a
、c
和是已定义的变量a-b
。b-c
那么,到底是什么意思a-b-c
呢?是错误(a-b) - c
还是a - (b-c)
错误?
如果您认为它是一个错误,那么没有问题,因为 GLR 解析器会发现可能的解析,如果解析不明确,则野牛生成的 GLR 解析器会发出语法错误信号。但是问题就变成了:a-b-c
只有模棱两可的错误才算错误?或者它是一个错误,因为如果它的参数是连字符变量,你不能使用没有环绕空格的减法运算符?(因此,无论是否存在,a-b-c
都只能解决为(a - b) - c
或?)要强制执行后一个要求,您将需要更多的复杂性。(a-b-c)
a-b
b-c
另一方面,如果您的语言期望为“后备”方法建模,那么结果应该是(a-b) - c
. 但是进行选择并不是两个expr
归约之间的简单合并过程,因为可能存在更高优先级的*
运算符:d * a-b-c
要么解析为(d * a-b) - c
要么(d * a) - b-c
;在这两种情况下,解析树完全不同。
另一种解决方案是将连字符变量的消歧放入扫描器中,而不是解析器中。这导致了一个更简单和更清晰的定义,但它导致了一个不同的问题:当你不希望语义消歧发生时,你如何告诉扫描仪?例如,当变量名出现在声明中时,您不希望扫描程序坚持将变量名分成段。
尽管与扫描仪的语义关联有点难看,但在这种情况下我会采用这种方法。解决方案的大致轮廓如下:
首先,语法。在这里,我添加了一个简单的声明语法,它可能与您的语法有任何相似之处,也可能不相似。请参阅下面的注释。
expr : term
| expr '-' term
term : factor
| term '*' factor
factor: VARIABLE
| '(' expr ')'
decl : { splitVariables(false); } "set" VARIABLE
{ splitVariables(true); } '=' expr ';'
{ addVariable($2); /* ... */ }
(有关 的语义,请参见下文splitVariables
。)
现在,词法分析器。同样,重要的是要知道预期的结果a-b-c
是什么;我将概述两种可能的策略。一是fallback策略,可以在flex中实现:
int candidate_len = 0;
[[:alpha:]][[:alnum:]]*/"-"[[:alpha:]] { yymore();
candidate_len = yyleng;
BEGIN(HYPHENATED);
}
[[:alpha:]][[:alnum:]]* { yylval.id = strdup(yytext);
return WORD;
}
<HYPHENATED>"-"[[:alpha:]][[:alnum:]]*/"-"[[:alpha:]] {
yymore();
if (isVariable(yytext))
candidate_len = yyleng;
}
<HYPHENATED>"-"[[:alpha:]][[:alnum:]]* { if (!isVariable(yytext))
yyless(candidate_len);
yylval.id = strdup(yytext);
BEGIN(INITIAL);
return WORD;
}
它使用yymore
andyyless
来查找最长的连字符前缀序列,这是一个有效的变量。(如果没有这样的前缀,它会选择第一个单词。如果没有这样的前缀,另一种方法是选择整个序列。)
一个类似的替代方案,它只允许完整的连字符序列(在这是一个有效变量的情况下)或单个单词。同样,我们使用 yyless 和 yymore,但这次我们不费心检查中间前缀,我们使用第二个开始条件来处理我们知道我们不会组合单词的情况:
int candidate_len = 0;
[[:alpha:]][[:alnum:]]*/"-"[[:alpha:]] { yymore();
candidate_len = yyleng;
BEGIN(HYPHENATED);
}
[[:alpha:]][[:alnum:]]* { yylval.id = strdup(yytext);
return WORD;
}
<HYPHENATED>("-"[[:alpha:]][[:alnum:]]*)*[[:alpha:]][[:alnum:]]* {
if (isVariable(yytext)) {
yylval.id = strdup(yytext);
BEGIN(INITIAL);
return WORD;
} else {
yyless(candidate_len);
yylval.id = strdup(yytext);
BEGIN(NO_COMBINE);
return WORD;
}
}
<NO_COMBINE>[[:alpha:]][[:alnum:]]* { yylval.id = strdup(yytext);
return WORD;
}
<NO_COMBINE>"-" { return '-'; }
<NO_COMBINE>.|\n { yyless(0); /* rescan */
BEGIN(INITIAL);
}
上述两种解决方案都isVariable
用于确定连字符序列是否为有效变量。如前所述,必须有一种方法可以关闭检查,例如在声明的情况下。为此,我们需要实现splitVariables(bool)
. 实现很简单;它只需要设置一个可见的标志isVariable
。如果该标志设置为 true,则isVariable
始终返回 true,而不实际检查符号表中是否存在变量。
所有这些都假设符号表和 splitVariables 标志在解析器和扫描器之间共享。一个天真的解决方案会使这两个变量都成为全局变量;更简洁的解决方案是使用纯解析器和词法分析器,并将符号表结构(包括标志)从主程序传递到解析器,然后从那里(使用%lex-param
)传递到词法分析器。