想象一个简单的语法:
(a|ab)c
其中读取(a或ab)后跟c。解析树看起来像这样:
and
/ \
or c
/ \
a ab
现在给它输入:
abc
我们首先遍历树的左侧,然后匹配" a",然后返回一个级别。因为" a"匹配,"或"也是如此,所以转到" c"。 " C"不匹配,我们走到了尽头。
但它可以采取另一种方式;如果我们走到" ab",我们就会找到一场比赛。
所以我想要做什么"或者#34;节点基本上是这样的:
然后,只要解析器遇到死胡同,我就想从堆栈中弹出一个项目,然后再从那里继续。
这是我无法弄清楚的部分......我如何基本上保存当前的调用堆栈?我可以保存" ab"堆栈中的节点,以便我知道我必须接下来执行该节点,但是它仍然需要知道它需要回退到"或者#34;之后。
我认为Chris正在寻找某种东西。我们必须找到一种翻译树的方法,这样就不必像这样跳过树枝。例如,这个等效的解析树没有这个问题:
or
/ \
and and
/ \ / \
a c ab c
这次我们解析左边,点击" a",它通过,所以我们尝试" c"旁边的节点,失败,"和"失败,"或"必须尝试正确的分支,..." ab"通过,另一个" c"传递,然后整个表达传递。
答案 0 :(得分:2)
您可以按照提出的方式回答问题。
您需要保存状态。棘手的部分是确定国家。保存它很容易。
你的问题是解析器在开始解析某些语法规则时“有状态”。 (如果使用LALR解析器,将许多规则的解析合并为单个状态,则会变得更加混乱)。该州包括:
当你解析并面对你所描述的选择替代时,你需要“保存状态”,在第一个术语上运行试验解析。如果成功,您可以丢弃已保存的状态并继续。如果失败,恢复状态,并尝试第二(和第n个替代)。 (无论你是否面对替代方案,它都更容易变得无脑,只有拯救状态,但这取决于你)。
你怎么能拯救国家?将它推入堆栈。 (你通常有一个解析堆栈,这是一个非常方便的地方!如果你不喜欢它,添加另一个堆栈但你会发现它并且解析堆栈通常同步移动;我只是让解析堆栈包含一个记录我需要的所有东西,包括输入空间。你会发现“调用堆栈”方便部分状态;见下文)。
首先要保存输入位置;这可能是文件源位置,并且出于优化原因可能是缓冲区索引。这只是一个标量,因此很容易保存。 恢复输入流可能更难;没有任何关于解析器输入扫描器在任何地方附近的保证。因此,您需要重新定位文件,重新读取任何缓冲区,并重新定位任何输入缓冲区指针。一些简单的检查可以使这在统计上更便宜:存储任何缓冲区的第一个字符的文件位置;然后判断是否必须重新读取缓冲区是将保存的文件位置与缓冲区起始文件位置进行比较的问题。其余的应该是显而易见的。
如果重新安排语法以最小化缓冲区,那么您将通过更少的缓冲区(例如,您的解析器运行得更快)回溯。在你的特定语法中,你有“(a | ab)c”,可以用手重写“a b?c”。后者至少不会回溯任何代表。
奇怪的部分是保存解析堆栈。好吧,您不必这样做,因为您的试用解析只会扩展您拥有的解析堆栈,并将其恢复到您的子解析成功或失败的解析状态。
“解析器继续失败的地方”和“成功的地方”只是另外两个标量。您可以将它们表示为解析代码块的索引,程序计数器(例如,continuation)或作为调用堆栈上的返回地址(参见?另一个并行堆栈!),然后是成功/失败的条件测试。
如果您想了解后者的详细信息,请查看我的SO answer on hand-coded recursive descent parsers.
如果你开始构建树,或者做其他事情作为解析的副作用,你将不得不想象如何捕获/保存副作用实体的状态,并恢复它。但不管它是什么,你最终会把它推到堆叠上。
答案 1 :(得分:1)
你应该做的是为每种可能性调用一种方法。如果你走到了死胡同,你可以回来,你将会回到原来的位置,并准备好尝试下一个选项。
您可以通过从解析方法返回值来指示您是否成功解析了分支。例如,您可以返回true表示成功,返回false表示失败。在这种情况下,如果解析返回false,则尝试下一个选项。
答案 2 :(得分:1)
除了结果之外,只需返回您的州。让我们举一个简单的例子,你可以为每个元素都有一个索引:
Grammer: (a|ab)c
Translated: AND(OR(a,ab),c)
Input: abc
Call AND
Call OR
a matches, return true,1
c does not match, start over
Call OR giving 1
ab matches, return true,2
c matches, return true
您需要一个更复杂的结构来处理更难的案例(无论是队列还是堆栈都取决于您在重建状态时如何构建和破坏)
答案 3 :(得分:0)
你需要使用递归。
类似的东西:
in或statement 对于每个声明 bool ret = eval(声明) if(ret) bool recVal =调用递归 if(recVal) 比你找到一个路径停止递归。 其他 我们继续在另一个循环中尝试下一个语句。