【问题标题】:LR(1) Shift/Reduce DisambiguationLR(1) 移位/减少歧义
【发布时间】:2019-02-09 17:48:51
【问题描述】:

给定重复BLOCKs 的输入,其中每个块都有重复的BEGIN EVENTEND EVENT 条目(END EVENT 总是跟在BEGIN EVENT 后面):

[TIMESTAMP] BLOCK
[TIMESTAMP] BEGIN EVENT
[TIMESTAMP] END EVENT
[TIMESTAMP] BEGIN EVENT
[TIMESTAMP] END EVENT
...
[TIMESTAMP] BLOCK

你如何用 LR(1) 来消除这个语法的歧义?我正在使用LALRPOP,最小的例子是:

Timestamp = "[TIMESTAMP]";
BlockHeader = Timestamp "BLOCK";
Begin = Timestamp "BEGIN" "EVENT";
End = Timestamp "END" "EVENT";

Block = BlockHeader (Begin End)+;
pub Blocks = Block*

因为 LR(1) 只能向前看一个标记,所以这个语法是模棱两可的,因为 LALRPOP 会帮助你告诉你(部分错误):

Local ambiguity detected

  The problem arises after having observed the following symbols in the input:
    BlockHeader (Begin End)+
  At that point, if the next token is a `"[TIMESTAMP]"`, then the parser can proceed in two different ways.

  First, the parser could execute the production at
  /home/<snip>.lalrpop:51:9: 51:32, which would consume
  the top 2 token(s) from the stack and produce a `Block`. This might then yield a parse tree like
    BlockHeader (Begin End)+ Block
    ├─Block────────────────┤     │
    ├─Block+───────────────┘     │
    └─Block+─────────────────────┘

  Alternatively, the parser could shift the `"[TIMESTAMP]"` token and later use it to construct a
  `Timestamp`. This might then yield a parse tree like
    (Begin End)+ "[TIMESTAMP]" "BEGIN" "EVENT" End
    │            ├─Timestamp─┘               │   │
    │            └─Begin─────────────────────┘   │
    └─(Begin End)+───────────────────────────────┘

我看到它告诉我,在解析 BlockHeader、Begin 和 End 之后,它无法确定下一个标记是另一个 Begin,还是另一个 Block 的开始。我还没有找到在 LR(1) 中消除歧义的方法,但我只能假设这是我缺乏理解,而不是 LR(1) 语法的继承限制?

【问题讨论】:

  • BlockBody = (Begin End)+; Block = BlockBody BlockHeader; pub Blocks = BlockHeader Block+ BlockBody; 呢?
  • 不幸的是,它仍然模棱两可,error output。我开始认为用 LR(1) 语法一次通过是不可能的:,(

标签: rust lr1


【解决方案1】:

不幸的是,如果不完全重构语法,这种“需要更多前瞻”问题很难解决,这通常会丢失输入的理想结构,并且有时会接受原始语法会拒绝的退化输入。您通常可以拒绝这些输入并通过对解析树进行后处理来恢复该结构,但这需要更多的工作。在你的情况下,语法:

Timestamp = "[TIMESTAMP]";
BlockHeader = Timestamp "BLOCK";
Begin = Timestamp "BEGIN" "EVENT";
End = Timestamp "END" "EVENT";
Event = Begin End;
Item = BlockHeader | Event;
pub Input = Item*

应该可以解决问题,但问题是它丢失了块结构(而不​​是给你一个非结构化的块头和事件序列),并且它接受空块。您可以通过对项目列表进行后处理来轻松处理这两个问题。

当所需的前瞻很小且有界时,另一种选择是在您的分词器中处理它。我不熟悉 LALRPOP,但应该可以将 [TIMESTAMP] 标记与紧随其后的关键字标记“组合”(因此时间戳不会出现在语法中,而只是关键字的属性),在这种情况下,使用单令牌前瞻一切都可以正常工作。

【讨论】:

  • 详细周到的回答,谢谢克里斯!我也开始研究组合标记,但仍想在同一通道中解析实际时间戳 [YYYY-MM-DD HH:MM]。如果我(非常模糊但固化)对解析器的理解是正确的,那么这在 LR(1) 中根本不可能,因为根据定义,它只会向前看 1 个标记,而前面的 1 个标记总是模棱两可的。我也玩过一些 PEG 语法,我更喜欢这些语法,但它们的库不像 LALRPOP 那样流畅,后者将 AST 生成结合到解析中。
  • 如果您还需要解析时间戳,那么在词法分析器中可能需要太多的前瞻性,因此您可能最好使用上述方法,将输入解析为一个简单的列表项目,然后将其后处理成块。取决于您的解析器生成器,它可能是即时可用的(解析成一个项目队列并随时处理该队列,只需要在队列中进行一些有限的前瞻)。您甚至可以将其构建为两级解析器(一个解析器生成一系列项目,这些项目被视为第二个解析器的标记。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多