当前位置: 首页 > 工具软件 > TokenManager > 使用案例 >

javacc之路1--- Token Manager

卞轶
2023-12-01

词法状态

JavaCC 词法规范被组织成一组词法状态,每个词法状态都用一个唯一的标识符命名。有一个标准的词汇状态称为 DEFAULT。生成的令牌管理器随时处于这些词法状态之一。默认情况下,初始化令牌管理器时,它将在状态中启动。在构造令牌管理器对象时,也可以将起始词法状态指定为参数。

每个词法状态都包含一个正则表达式的有序列表 - 顺序派生自输入文件中的出现顺序。有四种类型的正则表达式:

  • SKIP,
  • MORE,
  • TOKEN,
  • SPECIAL_TOKEN

在语法中作为扩展单元出现的所有正则表达式都被视为处于 DEFAULT 词法状态,其出现顺序由它们在语法文件中的位置确定。

Token匹配

当前词法状态中的所有正则表达式都被视为潜在的匹配候选项。Token 管理器使用输入流中与这些正则表达式之一匹配的最大字符数。也就是说,Token 管理器首选尽可能长的匹配。如果有多个长度相同的最长匹配项,则匹配的正则表达式是语法文件中出现顺序最早的正则表达式。

如上所述,Token 管理器在任何时候都处于一种状态。此时,Token 管理器仅考虑在此状态下定义的正则表达式以进行匹配。匹配后,可以指定要执行的操作以及要移动到的新词汇状态。如果未指定新的词法状态,则Token 管理器将保持当前状态。

TOKEN: {
      <token名1 : 正则表达式1>
    | <token名2 : 正则表达式2>
    | <token名3 : 正则表达式3>
          …
          …
    | <token名n : 正则表达式n>
}

扫描器扫描符合正则表达式模式的字符串并生成对应的token。并且TOKEN命令的块在一个文件中可以出现任意多次,因此按照逻辑上的相关性分开记载TOKEN命令比较好。

选择匹配规则

  • JavaCC会同时尝试匹配所有的正则表达式,并选择匹配字符串最长的规则
  • 多个规则的正则表达式匹配的字符串长度相同的情况下,JavaCC优先选择在文件中先定义的token规则

词汇操作

正则表达式类型指定成功匹配正则表达式时要执行的操作:

SKIP 只需丢弃匹配的字符串(在执行任何词法操作后)。
MORE 继续下一个状态,带走匹配的字符串。此字符串将是新匹配字符串的前缀。
TOKEN 使用匹配的字符串创建令牌,并将其发送到解析器(或任何调用方)。
SPECIAL_TOKEN 创建不参与分析的特殊令牌。前面已经描述过了。
注:注:访问特殊令牌的机制位于本页末尾。

每当检测到文件末尾 时,无论词法分析器的当前状态如何,都会导致创建 token。但是,如果 token在正则表达式的匹配过程中检测到 -(或在匹配 MORE 正则表达式后立即检测到 -),则会报告错误。

匹配正则表达式后,将执行词法操作。在 TOKEN_MGR_DECLS region 中声明的所有变量和方法(见下文)都可以在此处使用。此外,下面列出的变量和方法也可供使用。

在此之后,Token 管理器将状态立即更改为指定的状态(如果有)。

之后,执行由正则表达式类型指定的操作(SKIP, MORE 等)。如果类型为 TOKEN,则返回匹配的TOKEN。如果类型为 SPECIAL_TOKEN,则保存匹配的令牌,以便与匹配的下一个一起返回。


不使用TOKEN命令而使用SKIP命令的话就不会生成token,因此使用SKIP命令可以省略token名。还可以用SPECIAL_TOKEN命令(SPECIAL_TOKEN directive)来跳过token。SKIP命令和SPECIAL_TOKEN命令的区别在于是否保存跳过的token。使用SKIP命令无法访问跳过的字符串,使用SPECIAL_TOKEN命令就可以借助下面被扫描的TOKEN对象来取得跳过的字符串

SKIP: {
      <token名: 模式>
    | <token名: 模式>
          …
          …
    | <token名: 模式>
}

词法操作内的变量

下面的变量可以在词法操作中使用:

  1. StringBuffer image (READ/WRITE)
     image 变量(不同于image 字段匹配的 Token )是一个包含自上次SKIP, TOKEN, or SPECIAL_TOKEN 匹配的所有StringBuffer的字符。你可以自由地做任何改变你希望它只要你不将它分配给空自生成的token,生成的 token 管理器也使用这个变量。  
      如果你改变 image ,这变化是传递给后续匹配(如果当前匹配 MORE)。image 的内容并不会自动分配给 image 匹配字符的 image 字段。如果你希望这样的事情发生,您必须显式地指定在词法行动令牌或SPECIAL_TOKEN正则表达式。
      Example 1
<DEFAULT> MORE : { "a" : S1 }

<S1> MORE :
{
  "b"
    { int l = image.length()-1; image.setCharAt(l, image.charAt(l).toUpperCase()); }
    ^1                                                                             ^2
    : S2
}

<S2> TOKEN :
{
  "cd" { x = image; } : DEFAULT
       ^3
}

上面的例子,3处的 image 值:

At ^1: “ab”
At ^2: “aB”
At ^3: “aBcd”

  1. int lengthOfMatch (READ ONLY)
    这是当前匹配的长度(它不是 MORE的积累)。只读。
    例:

At ^1: 1 (the size of “b”)
At ^2: 1 (does not change due to lexical actions)
At ^3: 2 (the size of “cd”)

  1. int curLexState (READ ONLY)
    这是当前词法状态的索引。你不应该修改这个变量。整型常量的名字是那些词汇生成状态进入…Constant常量文件,因此您可以参考词汇而不用担心实际索引值。

  2. inputStream (READ ONLY)
    输入流是以下的一种:
    ASCII_CharStream
    ASCII_UCodeESC_CharStream
    UCode_CharStream
    UCode_UCodeESC_CharStream

  3. Token matchedToken (READ/WRITE)

  4. void SwitchTo(int)

Special Tokens

特殊token就像token,除了他们被允许出现在输入文件(任何两个token之间)。特殊token可以指定输入文件的语法使用保留字SPECIAL_TOKEN 而不是 token ,例如:

SPECIAL_TOKEN :
{
  <SINGLE_LINE_COMMENT: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")>
}

任何定义为 SPECIAL_TOKEN 的正则表达式,可能以特殊的方式从词法和语法的用户操作规范访问。这允许将这些 token 在解析时恢复,同时这些 token 不参与解析。
从输入语法文件复制到生成的文件,JavaCC一直引导使用此功能会自动复制相关comments 。  
类令牌现在有一个额外的字段:

Token specialToken;

这个字段指向 SPECIAL_TOKEN 在当前令牌之前(特殊或其他)。如果在当前 token 之前的 token 是一个常规的token (而不是一个特殊的token ),那么这个字段被设置为null。常规token的下一个字段具有相同的意义,即他们指向下一个常规token除了EOF token。EOF token next字段为空。下一个字段特殊标记的特殊令牌后立即当前令牌。如果跟随当前令牌之后是一个常规的token ,next字段被设置为null。

例:

// if there are no special tokens return control to the caller
if (t.specialToken == null) {
  return;
}

// walk back the special token chain until it reaches the first special token after the previous regular token
Token tmp_t = t.specialToken;
while (tmp_t.specialToken != null) {
  tmp_t = tmp_t.specialToken;
}

// now walk the special token chain forward and print them in the process
while (tmp_t != null) {
  System.out.println(tmp_t.image);
  tmp_t = tmp_t.next;
}
 类似资料: