我在理解如何在 JavaCC 中优雅地(或以某种方式)处理不明确的标记时已经遇到了很多问题。让我们举个例子:
我想解析XML处理指令。
格式为:"<?" <target> <data> "?>"
:target
是 XML 名称,data
可以是除 ?>
之外的任何内容 ,因为它是结束标记。
那么,让我们在 JavaCC 中定义它:
(我使用词汇状态,在本例中为 DEFAULT
和 PROC_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!
我知道,我可以使用MORE
JavaCC 的关键字,但这会给我整个处理指令作为一个标记,所以我必须自己进一步解析/标记它。我为什么要那么做?我是否正在编写一个不解析的解析器?
问题是(我猜):因此 <PI_DATA>
认识到“一切”,我的定义是错误的。我应该告诉 JavaCC 将“除 ?>
之外的所有内容”识别为处理指令数据。
但是如何才能做到呢?
注意:我只能使用 ~["a"|"b"|"c"]
排除单个字符 ,我无法排除字符串,例如 ~["abc"]
或~["?>"]
。 JavaCC 的另一个伟大的反特性。
谢谢。
最佳答案
关于分词器的一句话
分词器 (*TokenManager) 匹配尽可能多的输入字符。 PI_DATA 是“~[]”(1 个字符),因此如果无法找到更长的匹配项,它将匹配任何单个输入字符。 PI_END 是“?>”(2 个字符),因此始终会匹配它而不是 PI_DATA。你的这部分语法是正确的。
意外的嫌疑人
问题实际上可能来自NAME。您没有写出该标记的实际定义,所以我只能对此做出假设。如果 NAME 的定义太贪婪,它将在 PROC_INST 状态下匹配太多输入字符,并且您可能永远不会遇到 PI_DATA 或 PI_END。
小心带有空格的“(...)+”,或者邪恶的“(~[])*”,它会吞噬 EOF 之前的所有内容。
其他嫌疑人
我发现的一个潜在问题是 PI_TARGET 可能会匹配多次,尽管您希望 PI_DATA 能够匹配。再说一次,我只能猜测,因为我没有 NAME 的定义。
您可能想要澄清的另一点是:您定义了 WSS token ,但不在状态 PROC_INST 中使用它。它应该是 PI_DATA 的一部分吗?如果没有,您可能想跳过它。
不要滥用分词器
如果您发现无法使分词器服从您的要求,您可能需要将棘手的部分移至解析器。就您而言,可能很难区分 PI_TARGET 和 PI_DATA(如上所述)。
解析器可以期望 PI 目标之后的 PI 数据,而分词器不能(或很难)期望从一个标记到下一个标记。
解析器的另一个优点是,您甚至可以编写 Java 代码来查看下一个标记并做出相应的 react 。这应该被视为最后的手段,但是当您必须执行诸如将多个标记连接到一个众所周知的标记之类的操作时,这会很有用。这可能就是您在这里要寻找的内容(使用 PI_END 作为终止符标记)。
最后一个技巧
这里有一个稍微简化语法的技巧:
- 跳过 PI_START,但仍将状态更改为 PROC_INST
- 在 PROC_INST 中,将 PI_DATA 定义为 MORE(并将其重命名为 PI_DATA_CHAR,或者根本不命名)
- 在 PROC_INST 中,从 token 图像中删除最后两个字符,发出 PI_DATA 并将状态更改为 DEFAULT
- 在您的解析器产品中,将处理指令简单地定义为 ,其中 PI_DATA 的 token 图像即可使用
JavaCC 的(稀疏...)文档中提供了有关在标记器操作中操作标记图像的详细信息。就像设置 StringBuffer 的长度一样简单。
关于xml - JavaCC:如何从标记中排除字符串? (又名理解 token 歧义。),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2966785/