从上图可以看出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 '一起返回。
动作变量
以下变量可用于词法动作:
StringBuffer image (READ/WRITE)
int lengthOfMatch (READ ONLY)
int curLexState (READ ONLY)
inputStream (READ ONLY)
Token matchedToken (READ/WRITE)
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++;}
}