wen*_*ner 6 java compiler-construction javacc token
基本要求是使用关键字作为标识符,所以我想区分令牌和它的上下文.(例如,class是一个关键字,但我们允许一个名为的变量class).
在Java中,这是可能的,但它是如此艰难,这里就是我如何做到这一点
TOKEN :
{
<I_CAL: "CAL"> : DO_CAL
| <I_CALL: "CALL">
| <I_CMP: "CMP">
| <I_EXIT: "EXIT">
| <I_IN: "IN">
| <I_JMP: "JMP">
| <I_JPC: "JPC"> : NEED_CMP_OP
| <I_LD: "LD"> : NEED_DATA_TYPE
| <I_NOP: "NOP">
| <I_OUT: "OUT">
| <I_POP: "POP">
| <I_PUSH: "PUSH">
| <I_RET: "RET">
| <I_DATA: "DATA"> : DO_DATA
| <I_BLOCK: ".BLOCK">
}
// T prefix for Token
TOKEN :
{
<T_REGISTER : "R0" | "R1" | "R2" | "R3" | "RP" | "RF" |"RS" | "RB">
// We need below TOKEN in special context, other wise they are just IDENTIFIER
// | <DATA_TYPE: "DWORD" | "WORD" | "BYTE" | "FLOAT" | "INT">
// | <PSEUDO_DATA_TYPE: "CHAR" >
// | <CAL_OP: "ADD" | "SUB" | "MUL" | "DIV" | "MOD">
// | <CMP_OP: "Z" | "B" | "BE" | "A" | "AE" | "NZ">
| <T_LABEL: <IDENTIFIER> ([" "])* <COLON>>
}
// Now we need a CMP OP
<NEED_CMP_OP> TOKEN:
{
<CMP_OP: "Z" | "B" | "BE" | "A" | "AE" | "NZ"> : DEFAULT
}
// Now we need a DATA TYPE
<NEED_DATA_TYPE,DO_CAL> TOKEN:
{
// EXTENSION Add char to data type
<DATA_TYPE: "DWORD" | "WORD" | "BYTE" | "FLOAT" | "INT" | "CHAR"> {
if(curLexState == DO_CAL){
SwitchTo(NEED_CAL_OP);
}else{
SwitchTo(DEFAULT);
}
}
}
// We need a CAL OP
<NEED_CAL_OP> TOKEN:
{
<CAL_OP: "ADD" | "SUB" | "MUL" | "DIV" | "MOD"> : DEFAULT
}
// Aslo need to skip the empty
<NEED_DATA_TYPE,NEED_CAL_OP,NEED_CMP_OP,DO_CAL,DO_DATA> SKIP:
{
" "
| "\t"
| "\r"
| "\f"
}
Run Code Online (Sandbox Code Playgroud)
源在这里,我可以区分令牌与上下文curLexState.
这是有效的,但是做得很繁琐,需要添加很多额外状态,并保持很多状态.有没有简单的方法来实现这一目标?
JavaCC FAQ中概述了三种方法可以实现此目的。
下面我将给出第三种方法的三个例子。
如果您只想允许将关键字class用作变量名,那么有一个非常简单的方法可以做到这一点。在词法分析器中输入通常的规则。
TOKEN: { <CLASS: "class"> }
TOKEN: { < VARNAME: ["a-"z","A"-Z"](["a-"z","A"-Z"])* > } // Or what you will
Run Code Online (Sandbox Code Playgroud)
在解析器中编写一个产生式
Token varName() { Token t ; } : {
{
(t = <CLASS> | t = <VARNAME>)
{return t ;}
}
Run Code Online (Sandbox Code Playgroud)
varName()然后在解析器的其他地方使用。
转向原始问题中的汇编程序示例,让我们以 JPC 指令为例。JPC(条件跳转)指令后面是比较运算符,例如 Z、B 等,然后是操作数,操作数可以是包括标识符在内的多种内容。例如我们可以有
JPC Z fred
Run Code Online (Sandbox Code Playgroud)
但我们也可以有一个名为 JPC 或 Z 的标识符,所以
JPC Z JPC
Run Code Online (Sandbox Code Playgroud)
和
JPC Z Z
Run Code Online (Sandbox Code Playgroud)
也是有效的 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>)* >
}
Run Code Online (Sandbox Code Playgroud)
在解析器中我们有
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
}
Run Code Online (Sandbox Code Playgroud)
我建议从可用作标识符的其他关键字列表中排除寄存器名称。
如果您确实包含<T_REGISTER>在该列表中,那么操作数将会有歧义,因为Operand看起来像这样
Operand Operand():{
Token t ; ... }
{
t = <T_REGISTER> ...
| t = Identifier() ...
...
}
Run Code Online (Sandbox Code Playgroud)
现在有一个歧义,因为
JPC Z R0
Run Code Online (Sandbox Code Playgroud)
有两个解析。在作为操作数的上下文中,我们希望像“R0”这样的标记被解析为寄存器而不是标识符。幸运的是,JavaCC 会更喜欢早期的选择,所以这正是将会发生的情况。您将收到来自 JavaCC 的警告。您可以忽略该警告。(我在源代码中添加了注释,以便其他程序员不必担心。)或者您可以使用前瞻规范来抑制警告。
Operand Operand():{
Token t ; ... }
{
LOOKAHEAD(1) t = <T_REGISTER> ...
| t = Identifier() ...
...
}
Run Code Online (Sandbox Code Playgroud)
到目前为止,所有示例都使用了左上下文。即,我们可以仅根据其左侧的标记序列来判断如何处理该标记。让我们看一下关键字的解释基于右侧标记的情况。
考虑这种简单的命令式语言,其中所有关键字都可以用作变量名。
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
Run Code Online (Sandbox Code Playgroud)
这个语法是明确的。您可以通过添加新类型的语句、表达式和左侧来使语法更加复杂;只要语法保持明确,这种复杂性可能不会对我接下来要说的内容产生太大影响。请随意尝试。
语法不是 LL(1)。有两个地方必须基于多个未来代币做出选择。一个是在下一个标记是“if”之间Assignment以及何时进行选择。IfElse考虑块
a := b
if := a
Run Code Online (Sandbox Code Playgroud)
与
a := b
if q
b := c
end
Run Code Online (Sandbox Code Playgroud)
我们可以向前寻找这样的“:=”
void S() : {} {
LOOKAHEAD( LHS() ":=" ) Assignment()
|
IfElse()
}
Run Code Online (Sandbox Code Playgroud)
我们需要向前看的另一个地方是在块的开头遇到“else”或“end”时。考虑
if x
end := y
else := z
end
Run Code Online (Sandbox Code Playgroud)
我们可以用以下方法解决这个问题
void Block() : {} {
LOOKAHEAD( LHS() ":=" | "if" ) S() Block()
|
{}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1669 次 |
| 最近记录: |