javacc 教程4 Token Manager

从上图可以看出javacc在解析数据流的核心是token manager,它涵盖了词法状态(Lexical States)和词法动作(Lexical Actions)。

词法状态(Lexical States)

JavaCC词法规范被组织成一组词法状态,token manager随时处于这些词法状态之一。 当token manager初始化时,默认是' DEFAULT '状态。在构造令牌管理器对象时,也可以指定初始的词法状态。

如前面的文章所述,javacc有4种不同类型的正则表达式来定义词法规范,这里我们回忆一下:

Type Action
SKIP 简单地丢弃匹配的字符串(在执行任何词法操作之后).
MORE 继续到下一个状态,带上匹配的字符串。这个字符串将是新匹配字符串的前缀。
TOKEN 使用匹配的字符串创建一个令牌,并将其发送给解析器(或任何调用者)。
SPECIAL_TOKEN 创建不参与解析的特殊令牌。

而有些时候我们是需要定义多组词法规范的,每套词法规范都包含不同的TOKEN,SIKP....,这个时候我们就需要使用词法状态来帮我们做管理。下面的例子展示了当遇到不同的字符串切换的不同词法状态:

<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
}

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

所有处于当前词法状态的正则表达式都被认为是潜在的匹配候选。token manager使用输入流中匹配这些正则表达式的最大字符数。也就是说,token manager遵循最长匹配原则。 如果有多个长度相同的最长匹配,匹配的正则表达式是语法文件中出现顺序最早的正则表达式

词法动作(Lexical Actions)

所谓的词法动作就是当一个正则表达式成功匹配时要采取的操作:匹配正则表达式后,执行词法动作。 

' TOKEN_MGR_DECLS '区域(见下文)中声明的所有变量和方法都可以在这里使用。此外,下面列出的变量和方法也可以使用。 之后,令牌管理器立即将状态更改为指定的状态(如果有的话)。
之后,执行由正则表达式类型指定的操作(' SKIP ', ' MORE '等)。如果类型是' TOKEN ',则返回匹配的TOKEN。 如果类型是' SPECIAL_TOKEN ',匹配的token会被保存并与下一个匹配的' token '一起返回。

动作变量

以下变量可用于词法动作:

  1. StringBuffer image (READ/WRITE)
  2. int lengthOfMatch (READ ONLY)
  3. int curLexState (READ ONLY)
  4. inputStream (READ ONLY)
  5. Token matchedToken (READ/WRITE)
  6. void SwitchTo(int)

我们逐一作一个了解:

image  是一个' StringBuffer ',包含自上次' SKIP ', ' TOKEN '或' SPECIAL_TOKEN'之后匹配的所有字符。 只要不将其赋值为“null”,可以自由地对其进行任何更改,因为生成的token manager也使用这个变量。

如果你修改image, 此更改将传递给后续匹配(如果当前匹配的是“MORE”)。' image '的内容不会自动分配到匹配的token的' image '字段。 也就是说即便你改变了image变量,其token的image字段也不会有任何改变,image字段只能做临时改变,并传递给后面的匹配。

在上面的例子中,' image '在' ^1 ',' ^2 '和' ^3 '标记的3个点上的值为:

At ^1: "ab"
At ^2: "aB"
At ^3: "aBcd"

lengthOfMatch 只读变量,代表当前匹配的长度,其并不会对MORE上匹配的长度进行累加!

使用与上面相同的例子, lengthOfMatch 的值是:

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

curLexState 只读变量,tokenManager在解析数据流的过程中,会在各种词法状态之间转换,这个变量可以获取tokenManager当前词法状态的索引,具体可以看javacc代码生成后的xxxConstants。

public interface SimpleConstants {

  /** End of File. */
  int EOF = 0;
  /** RegularExpression Id. */
  int LBRACE = 5;
  /** RegularExpression Id. */
  int RBRACE = 6;

  /** Lexical state. */
  int DEFAULT = 0;

  /** Literal token values. */
  String[] tokenImage = {
    "<EOF>",
    "\" \"",
    "\"\\t\"",
    "\"\\n\"",
    "\"\\r\"",
    "\"{\"",
    "\"}\"",
  };

}

inputSteam  只读变量tokenManager的输入流,取决于javacc语法文件options区域的选项' UNICODE_INPUT '和' JAVA_UNICODE_ESCAPE '的值,它是以下类型之一:

  • ASCII_CharStream
  • ASCII_UCodeESC_CharStream
  • UCode_CharStream
  • UCode_UCodeESC_CharStream

流当前位于本次匹配消耗的最后一个字符处。可以调用' inputStream '的方法。例如,可以调用' getEndLine '和' getEndColumn '来获取当前匹配的行号和列号信息。

matchToken (读/写)这个变量只能在与' TOKEN '和' SPECIAL_TOKEN '正则表达式相关的操作中使用。之前的image变量即便修改也无法改变其token的image字段。这里可以将变量' image '的值赋给'matchedToken.image'。

如果我们修改上面例子的最后一个正则表达式规范为:

<S2> TOKEN :
{
  "cd" { matchedToken.image = image.toString(); } : DEFAULT
}

然后返回给解析器的token将其' image '字段设置为' aBcd '。 如果这个赋值没有被执行,那么'。Image '字段将保持为' abcd '。
 

SwitchTo(int)  调用此方法将切换到指定的词法状态,可结合curLexState变量切换到其他的状态,在生成的tokenManager中可以看到该方法的使用,在词法动作中也可以使用,但是要谨慎,容易产生bug。如果使用':state '语法指定了一个状态变化,它会覆盖所有的' switchTo '调用,因此,当明确指定了状态变化时,在ACTION中调用“switchTo”是没有意义的。

{
  token_source.SwitchTo(name_of_state);
}

访问类级别声明

词法动作可以访问一组类级别声明。这些声明使用以下语法在JavaCC文件中引入:

TOKEN_MGR_DECLS :
{
  int stringSize;
}

MORE :
{
  "\"" {stringSize = 0;} : WithinString
}

<WithinString> TOKEN :
{
  <STRLIT: "\""> {System.out.println("Size = " + stringSize);} : DEFAULT
}

<WithinString> MORE :
{
  <~["\n","\r"]> {stringSize++;}
}


 

猜你喜欢

转载自blog.csdn.net/gambool/article/details/133269482