2

我已经在理解方面遇到了很多问题,即如何在 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>
) {}
}

让我们测试一下<?mytarget here-goes-some-data?>

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

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

什么都没遇到?什么都没期待吗?或者是什么?谢谢你,JavaCC!

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

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

但是怎么做呢?

注意:我只能使用排除单个字符~["a"|"b"|"c"]不能排除诸如or之类的字符串。JavaCC 的另一个很棒的反特性。~["abc"]~["?>"]

谢谢你。

4

2 回答 2

4

关于分词器的一句话

标记器 (*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 的长度一样简单。

于 2011-02-08T15:34:36.770 回答
0

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

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

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

至于排除整个字符串,FAQ 中有几种方法可以做到这一点。

于 2013-05-04T14:58:27.880 回答