在不使用tokenizer状态的情况下消除歧义的歧义

时间:2014-06-08 08:10:44

标签: java parsing tokenize javacc

我无法让JavaCC根据语法中的位置正确消除令牌的歧义。我有以下JJTree文件(我称之为bug.jjt):

options
{
  LOOKAHEAD = 3;
  CHOICE_AMBIGUITY_CHECK = 2;
  OTHER_AMBIGUITY_CHECK = 1;
  SANITY_CHECK = true;
  FORCE_LA_CHECK = true;
}
PARSER_BEGIN(MyParser)
import java.util.*;
public class MyParser {
    public static void main(String[] args) throws ParseException {
        MyParser parser = new MyParser(new java.io.StringReader(args[0]));
        SimpleNode root = parser.production();
        root.dump("");
    }
}
PARSER_END(MyParser)

SKIP:
{
    " "
}

TOKEN:
{
     <STATE:  ("state")>
     |<PROD_NAME: (["a"-"z"])+ >
}


SimpleNode production():
{}
{
    (
        <PROD_NAME>
        <STATE>
        <EOF>
    )
    {return jjtThis;}
}

使用以下内容生成解析器代码:

java -cp C:\path\to\javacc.jar jjtree bug.jjt
java -cp C:\path\to\javacc.jar javacc bug.jj

现在编译完成后,您可以从命令行运行MyParser,并使用字符串作为参数进行解析。如果成功则打印production,如果失败则会发出错误。

我尝试了两个简单的输入:foo statestate state。第一个解析,但第二个解析不,因为两个state字符串都被标记为<STATE>。当我将LOOKAHEAD设置为3时,我希望它使用语法并看到一个字符串state必须是<STATE>而另一个必须是<PROD_NAME。但是,没有这样的运气。我试过改变各种前瞻参数无济于事。我也无法使用标记化器状态(在不同的状态下定义不同的标记),因为这个例子是一个更复杂的系统的一部分,可能会有很多这些模糊的类型。

有人能告诉我如何让JavaCC正确消除这些令牌的歧义,而不使用令牌化状态吗?

3 个答案:

答案 0 :(得分:2)

问题4.19中的FAQ涵盖了这一点。

那里列出了三种策略

选择语法。请参阅Bart Kiers的回答。

使用语义向前看。对于这种方法,您可以摆脱定义STATE的制作并编写像这样的语法

void SimpleNode production():
{}
{
    (
        <PROD_NAME>
        ( LOOKAHEAD({getToken(1).kind == PROD_NAME && getToken(1).image.equals("state")})
         <PROD_NAME>
         ...
        |
         ...other choices...
        )
    )
    {return jjtThis;}
}

如果没有其他选择,那么

void SimpleNode production():
{}
{
    (
        <PROD_NAME>
        ( LOOKAHEAD({getToken(1).kind == PROD_NAME && getToken(1).image.equals("state")})
         <PROD_NAME>
         ...
        |
         { int[][] expTokSeqs = new int[][] { new int[] {STATE } } ;
           throw new ParseException(token, expTokSeqs, tokenImage) ; }
        )
    )
    {return jjtThis;}
}

但是,在这种情况下,您需要STATE的制作,正如expTokSeqs的初始化中所提到的那样。所以你需要一个制作。

< DUMMY > TOKEN : { < STATE : "state" > }

其中DUMMY是一个永远不会去的州。

使用词汇状态。 OP问题的标题表明他不想这样做,但不是为什么。如果状态切换可以包含在令牌管理器中,则可以执行此操作。假设一个文件是一系列作品,每个作品都是这样的。

name state : "a" | "b" name ;

这是以名称开头,然后是关键字“state”冒号,一些标记,最后是分号。 (我只是这样做,因为我不知道OP试图解析什么样的语言。)然后你可以使用三个词法状态DEFAULT,S0和S1。

  • 在DEFAULT中,任何字母序列(包括“州”)都是PROD_NAME。在DEFAULT中,识别PROD_NAME会将状态切换为S0。
  • 在S0中,除“州”之外的任何字母序列都是PROD_NAME,“州”是州。在S0中,识别STATE令牌会导致令牌化器切换到状态S1。
  • 在S1中,任何任何字母序列(包括“州”)都是PROD_NAME。在S1中,识别SEMICOLON将状态切换为DEFAULT。

所以我们的例子像这样被标记化了

name  state  : "a" | "b" name ;
|__||______||_________________||_________
DEF-   S0             S1                    DEFAULT
AULT

制作是这样写的

<*> SKIP: { " " }

<S0> TOKEN: { <STATE: "state"> : S1 }

<DEFAULT> TOKEN:{ <PROD_NAME: (["a"-"z"])+ >  : S0 }
<S0,S1> TOKEN:{ <PROD_NAME: (["a"-"z"])+ >  }

<S1> TOKEN: { <SEMICOLON : ";" > : DEFAULT
<S0, DEFAULT> TOKEN : { <SEMICOLON : ";" > }

<*> TOKEN {
     COLON : ":"
|    ...etc...
}

解析器可以将状态切换命令发送回令牌化器,但要使其正确且易碎,这很棘手。问问题的问题3.12。

答案 1 :(得分:1)

LOOKAHEAD选项仅适用于解析器(生产规则)。令牌化程序不受此影响:它将生成令牌而不必担心生产规则试图匹配的内容。输入"state"将始终标记为STATE,即使解析器尝试匹配PROD_NAME也是如此。

你可以做这样的事情(未经测试的伪文法代码!):

SimpleNode production():
{}
{
    (
        prod_name_or_state()
        <STATE>
        <EOF>
    )
    {return jjtThis;}
}

SimpleNode prod_name_or_state():
{}
{
    (   
        <PROD_NAME>
    |   <STATE>
    )
    {return jjtThis;}
}

可以匹配"foo state""state state"

或等效,但更紧凑:

SimpleNode production():
{}
{
    (
        ( <PROD_NAME> | <STATE> )
        <STATE>
        <EOF>
    )
    {return jjtThis;}
}

答案 2 :(得分:1)

Lookahead在将字符组合成令牌时与词法分析器无关。解析器在匹配从终端(令牌)组成的非终端时使用它。

如果你定义“state”来产生一个令牌STATE,那么就是这样。

我同意你的看法,令牌化器状态不是允许将关键字用作标识符的好方法。这真的有必要吗? HLL不允许这样做是有充分理由的。

OTOH,如果您只使用<PROD_NAME>重写语法,则可能会在语义分析期间推迟对关键字的识别。