![](/img/trans.png)
[英]JavaCC: How can I specify which token(s) are expected in certain context?
[英]Can JavaCC distinguish token by its context?
基本要求是使用关键字作为标识符,因此我想将令牌与它的上下文区分开。(例如, 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"
}
来源在这里 ,我可以通过curLexState
将标记与上下文区curLexState
。
这是可行的,但要做的事情繁琐,需要添加很多额外的状态并维护很多状态。是否有任何简单的方法来实现这一目标?
如果将词法分析器和解析器合并为面向字符的解析器,则区分上下文中的关键字相对容易,因为解析器都是关于保留上下文的。 您可以对字符标记操作JavaCC以实现此效果,但是由于其LL性质,由于其他原因,它可能无法编写实用的语法。
如果将词法分析器和解析器分开,这并不容易。
您要让词法分析器知道什么时候是标识符或关键字,这只能通过知道找到ID /关键字的上下文来完成。
理想情况下,词法分析器仅向语法分析器询问其状态,即可识别做出选择的上下文。 很难组织; 大多数解析器的设计目的都不是为了轻松地揭示其状态,也不是为了提取所需的上下文信号而易于解释。 JavaCC显然不是以这种方式组织的。
您另一个明显的选择是将不同的上下文建模为词法分析器中的状态,词法化状态之间的转换对应于有趣的上下文之间的转换。 根据上下文,这可能容易,也可能不容易。 如果可以做到,则必须在词法分析器中编写状态和转换的代码,并使它们保持最新。 当您可以“轻松”做到这一点时,这不是一个坏办法。 根据特定的上下文,这可能很难或不可能。
出于OP的目的(显然是汇编程序的解析器),上下文通常由源代码行中的位置确定。 通过观察空白,可以将汇编程序输入的内容定性地划分为Label,Opcode,Operand,Comment上下文:换行符将上下文设置为Label,Label模式下的空白将上下文设置为Opcode,Opcode中的空白将Operand上下文设置为操作符,而Operand上下文集则为空白。上下文。 通过这些状态转换,可以为每个上下文编写不同的子词法分析器,从而在每个子上下文中具有不同的关键字。
此技巧不适用于PL / I之类的语言,因为它们在上下文中具有大量关键字(实际上,对于PL / I,每个关键字都仅在上下文中!)。
一个非显而易见的选择是根本不尝试区分。 找到ID /关键字后,将两个令牌都馈入解析器,并让其找出导致可行解析的原因。 (注意:它可能处理多个歧义标记的叉积,因此在进行排序时可能会进行许多解析。)这需要解析器能够处理歧义,无论是在解析时还是在其接受的令牌中(或不能)同时接受ID和关键字令牌)。 当您拥有正确的解析器时,这是一个非常简单的解决方案。 JavaCC不是那种机器。
[有关GLR解析引擎的信息,请参阅我的简历,在其中可以轻松访问所有3个解决方案。 它可以轻松处理Pl / I。
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 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)。 必须在两个地方基于一个以上的未来令牌进行选择。 IfElse
一个标记为“ if”时,一种是在Assignment
和IfElse
之间进行选择。 考虑块
a := b
if := a
VS
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()
|
{}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.