当前位置: 首页 > 面试题库 >

JavaCC可以通过上下文区分令牌吗?

狄誉
2023-03-14
问题内容

基本要求是使用关键字作为标识符,因此我想从其上下文中区分标记。(例如,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

这是可行的,但要做的事情繁琐,需要添加很多额外的状态并维护很多状态。是否有任何简单的方法来实现这一目标?


问题答案:

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()
|
    {}
}


 类似资料:
  • 实际上,我需要从我的应用程序中打开默认的下载文件夹。可以吗?如果可以,请提供一些参考。 我可以在以下帮助下获取下载文件夹的路径: 任何帮助都将不胜感激。

  • 问题内容: 实际上,我需要从应用程序中打开默认的“下载”文件夹。可能吗?如果是,请提供一些参考。 我可以借助以下方法获得“下载”文件夹的路径: 任何帮助将不胜感激。 问题答案: 您可以使用以下内容显示最近的下载活动 自API 9起可用

  • 我正在编写一个应用程序,调用来自Flutter的google fit rest api。 我需要使用(https://pub.dev/packages/google_sign_in)与谷歌签名。我可以毫无问题地获得一个令牌(见是否有人设法从谷歌登录(Flutter)获得id令牌),但如何获得一个新的令牌时,它是过期的? 我不想要求用户每小时登录并获得一个新的令牌

  • 用户必须在浏览器和应用程序之间手动复制粘贴“授权码”,这在Android中不是一种实用的获取访问令牌的方法。 为了找到一种新的方法,我使用Android API中的来获取访问令牌,同时使用Java API中的和来获取文件列表,如下所示, 不幸的是,当调用中的时,应用程序总是由于NetworkOnMainThreadException而崩溃。 所以我的问题是,如何通过使用访问令牌而不是在中应用授权代

  • 我是Kafka的新手,用< code > @ KafkaListener (spring)来定义Kafka消费者。我想检查是否有可能在运行时将分区手动分配给用户。 例如,当应用程序启动时,我不想“消耗”任何数据。我正在使用当前来实现此目的。 在某个时刻,我应该会收到一个通知(来自应用程序的另一部分),其中包含一个要处理的分区ID,所以我想“跳过”到该分区的最新可用偏移量,因为我不需要使用碰巧已经存

  • 对于身份验证,目前我们使用JWT,因此一旦创建了令牌,它就会在整个生命周期内创建,如果我们设置了时间过期,令牌将过期。 有没有办法使令牌过期? 单击注销按钮时,我需要销毁令牌。 我正在使用ASP. NET Core WebAPI。