按照你提出问题的方式,你已经得到了答案。
您需要保存state。棘手的部分是识别状态。保存它很容易。
您的问题是解析器在开始解析某些语法规则时“有一个状态”。 (如果您使用 LALR 解析器,这会变得更加混乱,它将许多规则的解析合并到一个状态中)。该状态包括:
- 的状态input(例如,输入流在哪里?)。
- 解析堆栈的状态(到目前为止看到的左侧上下文是什么?)
- 解析器应该在哪里继续成功,在哪里继续失败
当您正在解析并面临您所描述的选择时,您需要“保存状态”,对第一项进行试验解析。如果成功,您可以丢弃保存的状态并继续。如果失败,恢复状态,并尝试第二个(和第n个替代方案)。 (无脑更容易,无论你是否面临替代方案,只是保存状态,但这取决于你)。
怎样才能拯救国家呢?将其推入堆栈。 (您通常有一个解析堆栈,这是一个非常方便的地方!如果您不喜欢这样,请添加另一个堆栈,但您会发现它和解析堆栈通常同步移动;我只是让解析堆栈包含一条记录我需要的所有东西,包括输入空间。您会发现“调用堆栈”对于状态的某些部分很方便;见下文)。
首先要做的就是保存input地点;这可能是文件源位置,并且出于优化原因可能是缓冲区索引。这只是一个标量,因此很容易保存。正在恢复输入流可能更难;无法保证解析器输入扫描器位于其所在位置附近。因此,您需要重新定位文件,重新读取任何缓冲区,并重新定位任何输入缓冲区指针。一些简单的检查可以使这在统计上变得便宜:存储任何缓冲区的第一个字符的文件位置;然后确定是否必须重新读取缓冲区只需将保存的文件位置与缓冲区起始文件位置进行比较即可。其余的应该是显而易见的。
如果您重新排列语法以最大限度地减少缓冲区,您将回溯更少的缓冲区(例如,您的解析器运行得更快)。在您的特定语法中,您有“(a | ab ) c”,可以手动将其重写为“a b?c”。后者至少不会回溯 a 代表的任何内容。
奇怪的部分是保存解析堆栈。好吧,你不必这样做,因为你的试验解析只会扩展你拥有的解析堆栈,并将其恢复到你拥有的解析状态,无论你的子解析成功还是失败。
“解析器在哪里失败”和“在哪里成功”只是另外两个标量。您可以将它们表示为解析代码块的索引和程序计数器(例如,延续)或调用堆栈上的返回地址(参见?另一个并行堆栈!),然后进行成功/失败的条件测试。
如果您想了解后者的详细信息,请查看我的所以回答手工编码的递归下降解析器。 https://stackoverflow.com/a/2336769/120163
如果您开始构建树,或者做其他事情作为解析的副作用,您将必须弄清楚如何捕获/保存副作用实体的状态,并恢复它。但无论它是什么,你最终都会将它压入堆栈。