tokenizer分词器,是Parser解析工具的核心逻辑工具,主要工作是将rc文件的字符串分解出令牌和单词。
/system/core/init/tokenizer.h
/system/core/init/tokenizer.cpp
token 令牌
token令牌是调用tokenizer.next_token()
的返回值,表示解析到需要调用者处理的新事件。
#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2
T_EOF
解析完成
字符串已解析到末端。end-of-file,rc文件完成解析。T_TEXT
解析到新单词
tokenizer解析到一个单词。单词的地址保存在传入的parse_state结构体中。T_NEWLINE
解析完成一行
tokenizer解析到换行符。表示当前行解析结束,准备解析下一行。
parse_state 解析数据结构体
parse_state
结构体用于存放 解析过程的状态 和 产生的临时数据。
struct parse_state
{
char *ptr;
char *text;
int line;
int nexttoken;
};
char *ptr
正在解析字符的指针
tokenizer当前正在解析的字符指针,相当于解析进度。char *text
词文本指针
tokenizer检出的单词的首字符指针。int line
文本行号
tokenizer当前正在解析的数据的行号。int nexttoken
令牌缓存
优化解析速度。在检出一个单词的过程中,解析到了换行符,意味着单词结束并产生换行,需要输出T_TEXT
和T_NEWLINE
。把后者缓存到nexttoken
,在下一次next_token()
函数的早段逻辑中直接返回。
next_token() 执行解析
/system/core/init/tokenizer.cpp
next_token()
函数有两个关键的局部变量
int next_token(struct parse_state *state) {
char *x = state->ptr;
char *s;
...
}
char *x
当前正在解析的字符指针
在调用函数时,从传入结构体中获取,在产生令牌时,储存回结构体。char *s
当前检出中的单词的尾部字符指针
指向检出中单词的最后一个字符的字符指针。检出过程中,tokenizer认为*x
是当前检出单词的一部分时,通过*s++ = *x++
,把*x
覆盖到*s
,然后各自自增指向下一数据地址。
next_token()
函数逻辑分为非单词检出和单词检出两个部分,由于内容比较紧凑,逻辑解析直接写到源码注释。
非单词检出流程:
int next_token(struct parse_state *state) {
// 当前识别位置
char *x = state->ptr;
// 单词末端位置
char *s;
// 缓存令牌, 用于优化效率
if (state->nexttoken) {
int t = state->nexttoken;
state->nexttoken = 0;
return t;
}
// 非单词检出阶段
for (;;) {
switch (*x) {
// '\0', 即NULL字符
// :字符串结尾
// :当前识别位置(+1)(记录), 返回T_EOF
case 0:
state->ptr = x;
return T_EOF;
// 换行
// :行解析结束
// :当前识别位置(+1)(记录), 返回T_NEWLINE
case '\n':
x++;
state->ptr = x;
return T_NEWLINE;
// 空格, 制表符, 回车
// :无效字符
// :当前识别位置(+1), 继续循环
case ' ':
case '\t':
case '\r':
x++;
continue;
// #号
// :注释, 跳过该行所有字符直到
// - 换行 : 该行解析结束, 当前识别位置(+1)(记录), 返回T_NEWLINE
// - NULL字符 : 文件解析结束, 当前识别位置, 返回T_EOF
case '#':
while (*x && (*x != '\n')) x++;
if (*x == '\n') {
state->ptr = x+1;
return T_NEWLINE;
} else {
state->ptr = x;
return T_EOF;
}
// 其他字符
// :识别为一个词的首部
// :开始单词检出流程
default:
goto text;
}
}
...
}
单词检出流程:
int next_token(struct parse_state *state) {
...
// 检出单词,返回T_TEXT令牌
// 单词末端位置写入NULL字符, 剪裁出字符串
// 当前识别位置(记录), 返回T_TEXT
textdone:
state->ptr = x; // 当前识别位置保存到state
*s = 0; // 单词末端位置写入NULL字符
return T_TEXT;
// 单词检出流程 (初始化单词的首尾指针)
text:
state->text = s = x; // 初始化单词的首尾指针为当前识别位置
// 单词检出流程
textresume:
for (;;) {
switch (*x) {
// '\0', 即NULL字符
// :字符串结尾
// :检出单词
case 0:
goto textdone;
// 空格, 制表符, 回车
// :词结尾
// :当前识别位置(+1), 检出单词
case ' ':
case '\t':
case '\r':
x++;
goto textdone;
// 换行
// :行解析结束
// :当前识别位置(+1), 检出单词, 并缓存T_NEWLINE
case '\n':
state->nexttoken = T_NEWLINE;
x++;
goto textdone;
// 引号(左则)
// 被引号包裹的字符串视为一个整体进行处理
case '"':
// 当前识别位置(+1), 即跳过引号
x++;
for (;;) {
switch (*x) {
// :文件解析结束
// :丢弃当前解析中的单词, 返回T_EOF
case 0:
state->ptr = x;
return T_EOF;
// 引号(右则)
// :跳到单词检出流程
// :正常情况下, 会检出单词, 跳到textdone
case '"':
x++;
goto textresume;
// 其他字符
// :单词尾端写入当前字符
default:
*s++ = *x++;
}
}
break;
// 处理转义字符
case '\\':
// 当前识别位置(+1), 即跳过当前转义字符
x++;
switch (*x) {
// '\0'
// :字符串结尾
// :跳到单词检出流程
case 0:
goto textdone;
// 需要写入的转移字符
// :单词尾端写入对应字符
case 'n':
*s++ = '\n';
break;
case 'r':
*s++ = '\r';
break;
case 't':
*s++ = '\t';
break;
case '\\':
*s++ = '\\';
break;
// \\r
// :回车
// :"\\r\n"则换行, 否则跳过字符
case '\r':
if (x[1] != '\n') {
x++;
continue;
}
// '\n'
// :换行
// :行号(+1), 跳过接下来的空格或制表符
case '\n':
state->line++;
x++;
while((*x == ' ') || (*x == '\t')) x++;
continue;
// 其他转移字符
// :直接在单词尾端写入当前字符
default:
*s++ = *x++;
}
continue;
// 其他字符
// :直接在单词尾端写入当前字符
default:
*s++ = *x++;
}
}
return T_EOF;
}