这里的基本问题是您的语法需要两个前瞻标记,这使其超出了 LALR(1) 解析器的范围。 (“1”表示前瞻仅限于一个标记。)
这并不构成语言 LALR(2)。没有 LALR(2) 语言这样的东西,因为每种具有 LALR(2) 语法的语言也有 LALR(1) 语法。此外,还有一种算法可以将 LALR(2) 语法转换为 LALR(1) 语法,甚至可以恢复由 LALR(2) 语法生成的解析树,如果你有实现的话.
不幸的是,这种转换生成的语法非常庞大且难以阅读,并且有很多具有相同语义动作的产生式。此外,转换的实现很少,因为更有效的策略是使用更通用的解析算法。可以要求 Bison 本身生成 GLR 解析器,在 Perl 中有 Jeffrey Kegler's Marpa parser 等。但我会假设您不想更改解析器生成器。
LALR(2)⇒LALR(1) 转换的基本性质是将第一个前瞻标记存储为语义值的一部分。这有效地将解析器一个令牌转移到未来。如果您手动执行此操作,则仅需要对实际上需要两个前瞻标记的非终端进行转换,在您的情况下,这基本上是表示命令列表的顶级非终端。
现在,我也不打算把它写出来。相反,我将提供两个更简单且可能更易于维护的 hack。
在词法分析器中消除 ip 的歧义
这是大多数类似 yacc 的处理器处理语法描述中可选的 ; 的方式。 (我不知道分号在 yapp 中是否是可选的,但它肯定在 yacc 和 bison 中。)在 BNF 语法中,如果标识符标记后跟 :,则它无疑是左侧,但是一个天真的实现是 LR(2),就像你的语法一样;在看到 : 之前,无法关闭之前的定义。为了解决这个问题,词法分析器将后跟 : 的标识符识别为不同的令牌类型。同样,您的词法分析器可以将两个标记序列ip default-gateway 识别为单个标记。这假设语法中没有其他地方可以在ip 后面紧跟default-gateway,或者如果是,则可以将其解析为一个单一的世界。
这会使词法分析器有点复杂(特别是如果您允许在两个关键字之间使用 cmets),但如果您只有几个必须以这种方式识别的短语,那可能还不错。
即时构建接口命令
这里,顶层非终结符分为两种可能:
-
关闭,表示最后一条命令不能扩展,
-
open,表示最后一个命令是接口命令,并应附加以下
if_attr 子句。
open 非终端的语义值包括可能不完整的接口命令作为其语义值的一部分。
这也是基于解析 BNF 的策略,其中单个标识符仅保留在产生式列表的语义值中,直到明确是否将其添加到最后一个的右侧生产或它是新生产的左侧。
我已经很多年没有用 Perl 编程了,所以我把语义动作写成了伪代码。对不起。
# A list of commands
closed_program:
%empty
| closed_program unambiguous_command {
push($_[1], $_[2]); # Add the command to the end of the list
}
| open_program unambigous_command {
push($_[1]{"list"}, $_[1]{"saved"});
# The saved command is now complete
push($_[1]{"list"}, $_[2]);
# Also add the new complete command
}
# Semantic value has two attributes:
# * "list" is a list of commands
# * "saved" is an interface command
open_program:
closed_program interface_identifier {
# return {list => $_[1], saved => $_[2]}
}
| open_program if_attr {
# add $_[2] to the "saved" entry of $_[1]
}
unambiguous_command:
default_gateway_command
| ... # other commands which cannot be clauses
;
default_gateway_command:
IP DEFAULT_GATEWAY IP_ADDRESS
;
# Action needs to create a new interface command
# with an empty list of attributes
interface_identifier:
INTERFACE IDENTIFIER
;
# Action creates an attribute (however that is represented)
# which will be added to the attribute list
if_attr:
DESCRIPTION STRING
| IP ADDRESS IP_ADDRESS IP_ADDRESS
;