代码之家  ›  专栏  ›  技术社区  ›  java.is.for.desktop

JavaCC:如何从令牌中排除字符串?(又称理解标记歧义。)

  •  2
  • java.is.for.desktop  · 技术社区  · 16 年前

    我在理解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,但这会给我整个处理指令 token,所以我不得不自己进一步解析/标记它。我为什么要这么做?我在写一个不解析的解析器吗?

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

    但如何做到这一点呢?

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

    非常感谢。

    2 回复  |  直到 16 年前
        1
  •  4
  •   Laurent Pireyn    15 年前

    关于标记器的一句话

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

    意外的嫌疑犯

    问题其实可能来自名字。你没有写下这个代币的实际定义,所以我只能假设一下。如果名称的定义是 太贪婪了 ,它将匹配状态PROC_INST中的太多输入字符,并且您可能永远不会遇到PI_数据或PI_END。

    小心一个“(…)+”用空格,或者邪恶的“(~[])*”吃掉所有东西直到EOF。

    其他嫌疑人

    我看到的一个潜在问题是,PI_目标可能会被匹配好几次,尽管您希望PI_数据会被匹配。再一次,我只能猜测,因为我没有名字的定义。

    您可能想澄清的另一点是:您定义了WSS令牌,但没有在state PROC_INST中使用它。它应该是PI_数据的一部分吗?如果没有,你可能想跳过它。

    不要滥用标记器

    如果你发现你不能让标记器服从你,你可能想把棘手的部分移到解析器那里。在您的情况下,可能很难区分PI_目标和PI_数据(如上所述)。

    解析器可以 预料 PI目标之后是PI数据,而标记化器不能(或几乎不能)对从一个标记到下一个标记有期望。

    解析器的另一个优点是,您甚至可以编写Java代码来窥视下一个标记并做出相应的反应。这应该被视为最后的手段,但在您必须将多个令牌连接到一个已知的令牌时,这可能很有用。这可能就是你在这里寻找的(以PI_END为主题) 终结者令牌 ).

    最后,一个技巧

    下面是一个简化语法的技巧:

    1. 跳过PI_START,但将状态更改为PROC_INST
    2. 在PROC_INST中,将PI_数据定义为MORE(并将其重命名为PI_DATA_CHAR,或者干脆不命名)
    3. 在PROC_INST中,从令牌图像中删除最后两个字符,发出PI_数据,并将状态更改为默认值
    4. 在解析器产品中,简单地将处理指令定义为,其中PI_数据的标记图像可以使用

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

        2
  •  0
  •   Theodore Norvell    13 年前

    语法的一个问题是WSS只在默认状态下应用。重写为

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

    错误消息是,它本应为WSS,但发现了“”。

    至于排除整个字符串,常见问题解答中概述了几种方法。