JavaCC FAQ中概述了执行此操作的三种方法。
下面,我将给出第三种方法的三个示例。
如果您要做的就是允许将关键字 用作变量名,则有一种非常简单的方法。在词法分析器中放入通常的规则。
TOKEN: { <CLASS: "class"> }
TOKEN: { < VARNAME: ["a-"z","A"-Z"](["a-"z","A"-Z"])* > } // Or what you will
在解析器中写一个生产
Token varName() { Token t ; } : {
{
(t = <CLASS> | t = <VARNAME>)
{return t ;}
}
然后varName()
在解析器中的其他地方使用。
转向原始问题中的汇编器示例,让我们以JPC指令为例。JPC(跳转条件)指令后跟比较运算符(例如Z,B等),然后是一个可以是包括标识符在内的许多事物的操作数。例如我们可以
JPC Z fred
但是我们也可以有一个名为JPC或Z的标识符,因此
JPC Z JPC
和
JPC Z Z
也是有效的JPC指令。
在词汇部分,我们有
TOKEN : // Opcodes
{
<I_CAL: "CAL">
| <I_JPC: "JPC">
| ... // other op codes
<CMP_OP: "Z" | "B" | "BE" | "A" | "AE" | "NZ">
| <T_REGISTER : "R0" | "R1" | "R2" | "R3" | "RP" | "RF" |"RS" | "RB">
}
... // Other lexical rules.
TOKEN : // Be sure this rule comes after all keywords.
{
< IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>)* >
}
在解析器中,我们有
Instruction Instruction():{
Instruction inst = new Instruction();
Token o = null,dataType = null,calType = null,cmpType = null;
Operand a = null,b = null; }
{
...
o = <I_JPC> cmpType = <CMP_OP> a = Operand()
...
}
Operand Operand():{
Token t ; ... }
{
t = <T_REGISTER> ...
| t = Identifier() ...
...
}
Token Identifier : {
Token t ; }
{
t = <IDENTIFIER> {return t ;}
| t = <I_CAL> {return t ;}
| t = <I_JPC> {return t ;}
| t = <CMP_OP> {return t ;}
| ... // All other keywords
}
我建议从可以用作标识符的其他关键字列表中排除寄存器名称。
如果您确实将其包括<T_REGISTER>
在该列表中,则操作数中将存在歧义,因为Operand
看起来像这样
Operand Operand():{
Token t ; ... }
{
t = <T_REGISTER> ...
| t = Identifier() ...
...
}
现在有一个歧义,因为
JPC Z R0
有两个解析。在作为操作数的上下文中,我们希望像“ R0”这样的标记被解析为寄存器而不是标识符。幸运的是,JavaCC会更喜欢较早的选择,因此这将发生。您将收到JavaCC的警告。您可以忽略该警告。(我在源代码中添加了注释,以便其他程序员不必担心。)或者您可以通过先行规范抑制警告。
Operand Operand():{
Token t ; ... }
{
LOOKAHEAD(1) t = <T_REGISTER> ...
| t = Identifier() ...
...
}
到目前为止,所有示例都使用左上下文。即,我们可以说出如何仅基于令牌左侧的令牌序列来对待令牌。让我们看一个关键字的解释是基于右边的标记的情况。
考虑这种简单的命令式语言,其中所有关键字都可以用作变量名。
P -> Block <EOF>
Block -> [S Block]
S -> Assignment | IfElse
Assignment -> LHS ":=" Exp
LHS -> VarName
IfElse -> "if" Exp Block ["else" Block] "end"
Exp -> VarName
VarName -> <ID> | if | else | end
这种语法是明确的。您可以通过添加新的语句,表达式和左手边来使语法更加复杂。只要语法保持明确,这种复杂性可能与我接下来要说的没有太大区别。随时尝试。
语法不是LL(1)。必须在两个地方基于多个将来的令牌进行选择。一个是在下一个标记为“ if” 之间Assignment
以及IfElse
何时选择下一个标记。考虑块
a := b
if := a
与
a := b
if q
b := c
end
我们可以期待像这样的“:=”
void S() : {} {
LOOKAHEAD( LHS() ":=" ) Assignment()
|
IfElse()
}
我们需要向前看的另一个地方是在块的开头遇到“ else”或“ end”的情况。考虑
if x
end := y
else := z
end
我们可以解决这个问题
void Block() : {} {
LOOKAHEAD( LHS() ":=" | "if" ) S() Block()
|
{}
}