安卓启动流程(三) - tokenizer分词器

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_TEXTT_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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容