安卓啓動流程(三) - 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章