SE03-Value Stack

在任何特定的解析项目中,解析器动作都希望能够以某种方式来创建对应输入文本结构的自定义对象。Parboiled Java 提供了两种工具来在解析器规则中管理创建的自定义对象:

  • 值栈
  • 动作变量

值栈是一个简单的栈结构,作为一个临时存储为你的自定义对象提供服务。你的解析器动作可以将对象推到栈上、推出栈、推出再推入栈交换对象,等等。值栈的实现隐藏在 ValueStack 接口下面,其定义了操作值栈的一系列方法。

所有的解析器动作可以通过当前 context 的 getValueStack 来获得当前值栈。为了简化值栈操作的冗余,BaseActions 类(BaseParser)的父类提供了一些值栈操作的快捷方法,可以直接在解析器动作表达式中内联使用。

在解析器规则中使用值栈的方式通常有以下几种:

  • 匹配分隔符、空格或其他辅助结构的规则通常不会影响值栈。
  • 较底层的规则会从匹配到的输入中创建基本对象并推到栈上。
  • 调用一个或多个底层规则的高级别规则,会从栈上推出值对象,然后创建高级别的对象并重新推到栈上。
  • 根规则作为最高级别的规则会创建自定义结构的根对象。

大多时候,但一个规则被完整处理过后,最多会推一个对象到栈上(尽管在处理过程中会推多个对象到栈上)。那么你可以认为:如果规则匹配,一个规则会在栈上创建一个特定类型的对象,否则则不会影响栈。

规则定义须知

一条重要的原则是一个规则总是应该确保其对值栈的操作是“稳定的行为”,而无论输入是什么。因此,如果一个规则将一个特定类型的值对象推到栈上,则其应该为所有可能的输入都推一个值到栈上。如果不然,那么引用该规则之外的规则时将无法在规则匹配之后会值栈的状态进行假设,这将使动作设计复杂化。以下讨论着眼于各种 PEG 原语以及在使用影响值栈的解析器操作时需要注意的事项。

Sequence 规则

由于它们不提供任何可选组件,因此关于值栈操作,序列规则相当直接。它们的最终结果本质上是稳定的,仅包括所有子操作的串联。

FirstOf 规则

FirstOf 规则提供了几种替代子规则匹配。为了向外部提供稳定的“输出”,重要的是所有替代方案都表现出兼容的值堆栈行为。考虑以下例子:

1
2
3
Rule R() {
return FirstOf(A(), B(), C());
}

如果子规则 A 将推一个对象到栈,则 B 和 C 也需要这样做。

Optional 规则

Optional 规则的子规则通常不应该在值栈中添加或删除对象。由于 Optional 规则始终会匹配成功,即使其子规则不匹配,对值栈上的对象数量的任何影响都将违反“稳定行为”的原则。但是,Optional 规则可以很好的转换值栈上的内容,而避免不稳定的行为。

1
2
3
4
5
6
7
8
9
10
Rule R() {
return Sequence(
Number(), // number adds an Integer object to the stack
Optional(
'+',
Number(), // another Integer object on the stack
push(pop() + pop()) // pop two and repush one Integer
)
);
}

该规则的行为始终是稳定的,因为它总是会将一个值推送到栈上。

ZeroOrMore/OneOrMore 规则

与 Optional 规则类似,不能添加或删除值栈的元素,而可以修改值栈的元素内容。