【问题标题】:Theory: LL(k) parser vs parser for LL(k) grammars理论:LL(k) 解析器与 LL(k) 语法的解析器
【发布时间】:2021-01-03 00:30:15
【问题描述】:

我担心 therms 之间非常重要的区别:“LL(k) 解析器”和“LL(k) 语法解析器”。当LL(1) 回溯解析器有问题时,它ISLL(k) 语法的解析器,因为它可以解析它们,但它的NOT LL(k) 解析器,因为它不使用k 标记从语法中的单个位置向前看,而是通过回溯可能的情况进行探索,尽管它仍然使用k 标记进行探索。 我说的对吗?

问题可能会分解为执行前瞻的方式。如果前瞻实际上仍在处理带有回溯的语法,则不会使其成为LL(k) 解析器。要成为LL(k) 解析器,解析器不能使用具有回溯机制的语法,因为那样它将是“可以解析LL(k) 语法的具有回溯的LL(1) 解析器”。 我又是对的吗?

我认为差异与 LL(1) 解析器对每个令牌使用 constant 时间的预期有关,而 LL(k) 解析器最多使用 k * constant(与前瞻线性)每个令牌的时间,不是exponential time,因为它会在回溯解析器的情况下。

更新 1:为了简化 - 每个令牌,解析 LL(k) 是否预计会相对于 k 以指数方式运行,或者相对于 k 以线性时间运行?

更新 2:我已将其更改为 LL(k),因为该问题与 k 的范围(整数或无穷大)无关。

【问题讨论】:

  • 除非您精确定义回溯算法(即使这样也很难进行分析),否则无法回答有关“回溯 LL(1) 解析器”的问题。我提供了一个只讨论 LL(k) 语法的新答案;我希望它能回答您在“更新 1”中列出的问题。

标签: parsing grammar theory


【解决方案1】:

LL(k) 解析器需要在内循环中的每个点执行以下操作:

  • 收集下一个 k 个输入符号。由于这是在输入中的每个点完成的,因此可以通过将前瞻向量保持在循环缓冲区中来在恒定时间内完成。

  • 如果预测栈顶是终结符,则与下一个输入符号进行比较;要么两者都被丢弃,要么发出错误信号。这显然是恒定的时间。

  • 如果预测堆栈的顶部是非终结符,则查询动作表,使用非终结符、当前状态和当前前瞻向量作为键。 (并非所有 LL(k) 解析器都需要维护一个状态;这是最通用的公式。但它不会影响复杂性。)这种查找也可以在恒定时间内完成,同样利用增量前瞻向量的性质。

  • 预测动作通常是通过将所选产品的右侧推入堆栈来完成的。一个简单的实现将花费与右侧长度成正比的时间,这与前瞻k 或输入N 的长度无关,而是与语法本身的大小有关。只需将引用推送到右侧,就可以避免这项工作的可变性,它可以像符号列表一样使用(因为列表在解析期间不能更改)。

    但是,这还不是全部。执行预测操作不会消耗输入,并且有可能 - 甚至可能 - 将对单个输入符号进行多个预测。同样,最大预测数仅与语法本身有关,与kN 无关。

    更具体地说,由于不能在不违反 LL 属性的情况下在同一个地方预测相同的非终结符两次,所以预测的总数不能超过语法中非终结符的数量。因此,即使您确实将整个右侧压入堆栈,在连续移位操作之间压入的符号总数也不能超过语法的大小。 (每个右手边最多只能推一次。事实上,对于给定的非终端,只能推一个右手边,但有可能几乎每个非终端只有一个右手边,所以这不会减少渐近线。)如果只是将一个引用推入堆栈,则在连续移位动作之间推送的对象数 - 即两个连续移位动作之间的预测动作数 - 不能超过大小的非终结字母表。 (但同样,|V| 可能是 O(|G|)

我相信,LL(k) 解析的线性是在 Lewis 和 Stearns (1968) 中建立的,但我现在手头没有那篇论文,所以我将向您推荐 Sippu 和 Soisalon 中的证明-Soininen 的 Parsing Theory(1988 年),第 5 章证明了强 LL(K)(定义为 Rosenkrantz & Stearns 1970),第 8 章证明了规范 LL(K)。 p>

简而言之,预计 LL(k) 算法在移位两个连续输入符号之间花费的时间为 O(|G|),这与 kN 无关(当然,对于 a给定语法)。

这实际上与LL(*) 解析器没有任何关系,因为LL(*) 解析器不只是尝试连续的LL(k) 解析(无论如何这是不可能的)。对于 Terence Parr 提出的 LL(*) 算法(这是我所知道的唯一参考,它定义了 LL(*) 的含义),对于连续换档动作之间可以花费的时间量没有限制。解析器可能会将前瞻扩展到整个剩余的输入(因此,这会使时间复杂度取决于输入的总大小),或者它可能会故障转移到回溯算法,在这种情况下定义更复杂“处理输入符号”是什么意思。

【讨论】:

  • 这是非常好的解释,但是...如果解析器使用此堆栈模型,每个 k 标记(不是一个),在单个起始位置,它实际上将执行深度递归通过“扩展”非终结符在语法中搜索,非终结符可以与 k 成指数关系(非左递归 CFG 的非确定性下推自动机)。这就是问题所在。当没有左递归时,时间是语法的上限(但是与语法大小非线性),确实如此,但这不会使总工作量保持不变,它仍然是指数的,只是上限。
  • @verbessern:幸运的是,有一个简单的替代方法可以控制堆栈大小。您实际上不必推送 RHS 的副本;只是对 RHS 中一个点的引用。所以这是 O(1)。您仍然可以通过更新对下一个点的引用来在恒定时间内执行移位操作。有证据表明,如果语法为 LL(k),这将导致线性总时间解析。
  • 如果您愿意,我可以添加参考。证明本身对于 SO 答案来说太长了。
  • 我不这么认为。请务必添加。然而,它不是要推动这个单个项目,而是要扩展它直到可以识别令牌。这只是每个令牌完整步骤的一部分,是的,它的 O(1),但这不是每个 k 符号完成的总工作。它甚至不是每个符号完成的总工作量,它只是单个符号完成的工作量的一部分(一小部分)。
  • 您所描述的实际上是每个符号的下推自动机。我认为 LL(k) 解析器不能作为下推自动机工作,即使下推自动机可以识别 LL(k) 语法,我的问题也导致了这个问题。我认为,一个真正的 LL(k) 解析器将对每个符号按 k 线性执行,否则对同一事物有两个术语是没有意义的(下推自动机与 LL(k))。
【解决方案2】:

我建议您阅读 Aho Ullman Volume 1 的第 5.1 章。

https://dl.acm.org/doi/book/10.5555/578789

  1. LL(k) 解析器是一种 k 预测算法(k 是前瞻整数 >= 1)。
  2. LL(k) 解析器可以解析任何 LL(k) 语法。 (第 5.1.2 章)
  3. 对于所有 a, b 你有一个 LL(b) 语法也是一个 LL(a) 语法。但反之则不然。
  4. LL(k) 解析器是可预测的。所以没有回溯。
  5. 所有 LL(k) 解析器都是 O(n) n 是被解析句子的长度。
  6. 了解 LL(3) 解析器的解析速度并不比 LL(1) 快,这一点很重要。但是 LL(3) 解析器可以解析比 LL(1) 更多的语法。 (参见第 2 点和第 3 点)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多