有一种实现递归解析器的常用方法,通过预先读取下一个标记、存储它并针对它进行测试来避免对unread
or的需要:peek
- 当你读入一个令牌时,你将它存储在一个(全局)变量中。
- 然后您只需使用您正在寻找的所有令牌(例如
<li>
和</ul>
)对其进行测试
- 当您找到正确的方法时,您调用处理该问题的方法(或继续)
- (读取下一个令牌,“消耗”了匹配的令牌)
实际上,您已经窥视了前方。
Dragon 编译器书的第 1 版有一个很好的例子,在他们早期的概述章节中,用 C 语言(他们在第 2 版中使用了 Java,但没有必要夸大其词,恕我直言——C 风格在 Java 中运行良好)。
我将尝试从我自己的源代码中提取一个示例,但我的代码被分成一个库层,其中包含处理更易于使用的方法。我将尝试将它们结合起来做一个清晰的例子,但它可能不会独立运行。将其视为伪代码,以说明这个想法,您需要填补空白。
XMLStreamReader in;
int token;
String localname;
public void parse() {
next();
if (token==START_ELEMENT && localname.equals("ul")) ul();
}
void ul() {
next(); // assume we are called when a <ul> is seen, so we consume it
while (true) { // loops for list
if (token==START_ELEMENT && localname.equals("li")) li(); // ifs for choice
else if (token==START_ELEMENT && localname.equals("sometag")) sometag();
else break;
}
if (token==END_ELEMENT && localname.equals("ul")) next();
else throw new RuntimeException("expected </ul>");
// <li> or <sometag> would also be acceptable
}
void li() {
next();
...
}
void next() {
token = in.next(); // consume the token means to set up the next one
localname = in.getLocalName();
}
如果你创建一个层库来处理重复的东西,我发现它更容易使用,例如我有:
boolean startTag(String name)
如果匹配则返回 true
void requireStartTag(String name)
如果匹配则消耗,否则抛出异常
但我认为这个例子更清楚,保持字面意思。
还有其他问题,例如跳过非元素标记(如评论、PI 等);跟踪您所在的线路以获取更多有用的例外情况等。