我无法让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 state
和state state
。第一个解析,但第二个解析不,因为两个state
字符串都被标记为<STATE>
。当我将LOOKAHEAD
设置为3时,我希望它使用语法并看到一个字符串state
必须是<STATE>
而另一个必须是<PROD_NAME
。但是,没有这样的运气。我试过改变各种前瞻参数无济于事。我也无法使用标记化器状态(在不同的状态下定义不同的标记),因为这个例子是一个更复杂的系统的一部分,可能会有很多这些模糊的类型。
有人能告诉我如何让JavaCC正确消除这些令牌的歧义,而不使用令牌化状态吗?
答案 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。
所以我们的例子像这样被标记化了
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>
重写语法,则可能会在语义分析期间推迟对关键字的识别。