JavaCC:如何从令牌中排除字符串? (A.k.a.理解令牌歧义。)

时间:2010-06-03 14:19:16

标签: xml parsing tokenize ambiguity javacc

我在理解方面已经遇到了很多问题,在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!

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

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

但怎么办呢?

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

谢谢。

2 个答案:

答案 0 :(得分:4)

关于标记生成器的一个词

tokenizer(* TokenManager)匹配尽可能多的输入字符。 PI_DATA是“〜[]”(1个字符),因此它将匹配任何单个输入字符如果它找不到更长的匹配。 PI_END是“?&gt;” (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定义为更多(并将其重命名为PI_DATA_CHAR,或者根本不将其命名)
  3. 在PROC_INST中,从令牌图像中删除最后两个字符,发出PI_DATA并将状态更改为DEFAULT
  4. 在您的解析器制作中,简单地定义处理指令,其中PI_DATA的令牌图像是可以使用的
  5. JavaCC(稀疏...)文档中提供了有关在tokenizer操作中操作令牌图像的详细信息。它就像设置StringBuffer的长度一样简单。

答案 1 :(得分:0)

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

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

错误消息是它期待WSS但发现了一个&#34; &#34;

关于排除整个字符串,有几种方法可以在常见问题解答中概述。