【问题标题】:JavaCC: How can one exclude a string from a token? (A.k.a. understanding token ambiguity.)JavaCC:如何从令牌中排除字符串? (A.k.a. 理解令牌歧义。)
【发布时间】:2010-06-03 14:19:15
【问题描述】:

我已经在理解方面遇到了很多问题,即如何在 JavaCC 中优雅地(或以某种方式)处理模棱两可的标记。让我们举个例子:

我要解析 XML 处理指令。

格式为:"<?" <target> <data> "?>"target 是一个 XML 名称,data 可以是任何东西,除了?>,因为它是结束标记。

所以,让我们在 JavaCC 中定义它:
(我使用词法状态,在本例中为 DEFAULTPROC_INST

TOKEN : <#NAME : (very-long-definition-from-xml-1.1-goes-here) >
TOKEN : <WSS : (" " | "\t")+ >   // WSS = whitespaces
<DEFAULT> TOKEN : {<PI_START : "<?" > : PROC_INST}
<PROC_INST> TOKEN : {<PI_TARGET : <NAME> >}
<PROC_INST> TOKEN : {<PI_DATA : ~[] >}   // accept everything
<PROC_INST> TOKEN : {<PI_END : "?>" > : DEFAULT}

现在是识别处理指令的部分:

void PROC_INSTR() : {} {
(
    <PI_START>
    (t=<PI_TARGET>){System.out.println("target: " + t.image);}
    <WSS>
    (t=<PI_DATA>){System.out.println("data: " + t.image);}
    <PI_END>
) {}
}

让我们用&lt;?mytarget here-goes-some-data?&gt;来测试它:

目标被识别:"target: mytarget"。 但现在我得到了我的 favorite JavaCC 解析错误:

!!  procinstparser.ParseException: Encountered "" at line 1, column 15.
!!  Was expecting one of:
!!      

什么都没遇到?什么都没期待吗?要不然是啥?谢谢你,JavaCC!

我知道,我可以使用 JavaCC 的 MORE 关键字,但这会给我整个处理指令作为 one 标记,所以我不得不通过进一步解析/标记它我。我为什么要那么做?我是在写一个不解析的解析器吗?

问题是(我猜):因此&lt;PI_DATA&gt; 承认“一切”,我的定义是错误的。我应该告诉 JavaCC 将“除?&gt; 之外的所有内容”识别为处理指令数据。

但是怎么做呢?

注意:我只能使用 ~["a"|"b"|"c"] 排除 单个字符,我不能排除 字符串,例如 @987654336 @ 或 ~["?&gt;"]。 JavaCC 的另一个很棒的反特性。

谢谢。

【问题讨论】:

  • 您找到解决此问题的方法了吗?我今天刚刚提出了一个类似的问题,希望对您的体验感兴趣。
  • 我的解决方案是:“该死的 JavaCC”;) 我一次又一次地遇到这个“解析器”的问题和头痛。我迟早要编写自己的非确定性解析器。它会更慢,因为它不会生成代码,而是在运行时解释规则,但无论如何它都是值得的。

标签: xml parsing tokenize ambiguity javacc


【解决方案1】:

关于分词器的一句话

标记器 (*TokenManager) 匹配尽可能多的输入字符。 PI_DATA 是“~[]”(1 个字符),因此它将匹配任何单个输入字符 如果 它找不到更长的匹配。 PI_END 是“?>”(2 个字符),因此将始终匹配它而不是 PI_DATA。你这部分语法是正确的。

一个意想不到的嫌疑人

问题实际上可能来自NAME。您没有编写该令牌的实际定义,因此我只能对其进行假设。如果 NAME 的定义太贪心,它会在 PROC_INST 状态下匹配太多的输入字符,你可能永远不会遇到 PI_DATA 或 PI_END。

注意带有空格的“(...)+”,或者吃掉所有内容直到 EOF 的邪恶“(~[])*”。

其他嫌疑人

我看到的一个潜在问题是 PI_TARGET 可能会匹配多次,尽管您希望 PI_DATA 匹配。再说一次,我只能猜测,因为我没有 NAME 的定义。

您可能想要澄清的另一点是:您定义了 WSS 令牌,但您没有在 PROC_INST 状态下使用它。它应该是 PI_DATA 的一部分吗?如果没有,您可能想跳过它。

不要滥用分词器

如果您发现无法让分词器服从您,您可能希望将棘手的部分移至解析器。在您的情况下,可能很难区分 PI_TARGET 和 PI_DATA(如上所述)。

解析器可以期望一个 PI 目标之后的 PI 数据,而分词器不能(或几乎没有)期望从一个令牌到下一个。

解析器的另一个优点是,您甚至可以编写查看下一个标记并做出相应反应的 Java 代码。这应该被认为是最后的手段,但是当您必须执行诸如将多个令牌连接到一个众所周知的令牌等事情时可能很有用。这可能就是您在此处寻找的内容(使用 PI_END 作为终止符标记)。

最后,一个技巧

这里有一个技巧可以稍微简化你的语法:

  1. 跳过 PI_START,但仍将状态更改为 PROC_INST
  2. 在 PROC_INST 中,将 PI_DATA 定义为 MORE(并将其重命名为 PI_DATA_CHAR,或者根本不命名)
  3. 在 PROC_INST 中,从令牌图像中删除最后两个字符,发出 PI_DATA 并将状态更改为 DEFAULT
  4. 在您的解析器产品中,将处理指令简单定义为 ,其中 PI_DATA 的标记图像已准备好使用

JavaCC 的(稀疏...)文档中提供了有关在标记器操作中操作标记图像的详细信息。就像设置 StringBuffer 的长度一样简单。

【讨论】:

  • 谢谢你这么详细的解释!可悲的是,我将无法投入大量时间来尝试这个。正如其他评论中提到的,我正在尝试使用自己的解析器,它将为我完成所有非确定性的思考。
【解决方案2】:

语法的一个问题是 WSS 仅适用于默认状态。改写为

<DEFAULT, PROC_INST> TOKEN : {< WSS: (" " | "\t")+ > \}

错误消息是它期待一个 WSS,但发现一个“”。

关于排除整个字符串,常见问题解答中列出了几种方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-21
    • 1970-01-01
    • 2010-10-08
    • 1970-01-01
    相关资源
    最近更新 更多