安卓啟動(dòng)流程(二) - Parser解析器

Parser是rc文件解析成執(zhí)行邏輯的核心工具。
內(nèi)部通過(guò)tokenizer分詞器對(duì)rc文件的字符流進(jìn)行解析,轉(zhuǎn)換成單詞(參數(shù))和對(duì)應(yīng)的token令牌。根據(jù)token令牌,派分到不同的解析器實(shí)現(xiàn)進(jìn)行的處理。
通過(guò)模板模式,Parser將實(shí)際的解析邏輯解耦到解析器模板的實(shí)現(xiàn)類中進(jìn)行。


Parser定義

/system/core/init/parser.h

class Parser {
    public:
        using LineCallback = std::function<Result<Success>(std::vector<std::string>&&)>;
        Parser();
        bool ParseConfig(const std::string& path);
        void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
        void AddSingleLineParser(const std::string& prefix, LineCallback callback);

    private:
        std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
        std::vector<std::pair<std::string, LineCallback>> line_callbacks_;
};
  • bool ParseConfig(const std::string& path);
    解析指定的rc文件
    path可以是rc文件路徑,也可以是一個(gè)目錄路徑。

  • void AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser);
    添加 SectionParser 解析器
    添加段落解析器,name為一行命令中,首個(gè)參數(shù)的完全匹配值。

  • void AddSingleLineParser(const std::string& prefix, LineCallback callback);
    添加 LineCallback 解析器
    添加單行命令解析器。prefix為一行命令中,首個(gè)參數(shù)的前序匹配值。


單行命令解析器模板:LineCallback
class Parser {
    public:
        using LineCallback = std::function<Result<Success>(std::vector<std::string>&&)>;
}

本質(zhì)是一個(gè)函數(shù)。
解析過(guò)程中,對(duì)一行命令的首個(gè)參數(shù)進(jìn)行匹配,當(dāng)匹配成功時(shí),馬上調(diào)用對(duì)應(yīng)的單行命令解析器(即回調(diào)函數(shù))。
從源碼上看,只有在ueventd進(jìn)程解析ueventd.rc時(shí),才有該類型解析器的應(yīng)用。

段落命令解析器模板:SectionParser
class SectionParser {
    public:
        virtual ~SectionParser() {}
        virtual Result<Success> ParseSection(std::vector<std::string>&& args,
                                             const std::string& filename, int line) = 0;
        virtual Result<Success> ParseLineSection(std::vector<std::string>&&, int) { return Success(); };
        virtual Result<Success> EndSection() { return Success(); };
        virtual void EndFile(){};
};

解析過(guò)程中,對(duì)一行命令的首個(gè)參數(shù)進(jìn)行匹配,當(dāng)匹配成功時(shí),即認(rèn)為該行時(shí)一個(gè)命令段落的開(kāi)始,并使用匹配到的解析器解析段落中的命令內(nèi)容。

主要定義了幾個(gè)函數(shù):

  • ParseSection()
    處理段落首行命令
    行命令處理時(shí),匹配到段落解析器的關(guān)鍵字,則視為一個(gè)段落的首行,段落的后續(xù)命令交由該段落解析器處理,并調(diào)用該解析器的ParseSection()函數(shù)。
  • ParseLineSection()
    處理段落內(nèi)容命令
    對(duì)匹配不到關(guān)鍵字的行命令,視為段落的內(nèi)容,假如存在解析中的段落解析器,則調(diào)用該解析器的ParseLineSection()函數(shù)。

  • EndSection()
    處理段落結(jié)束
    段落結(jié)束時(shí),假如存在解析中的段落解析器,則調(diào)用該解析器的EndSection()函數(shù)。

  • EndFile()
    處理文件結(jié)束
    文件解析結(jié)束時(shí),調(diào)用所有已添加解析器中的EndFile()函數(shù)。


Parser實(shí)現(xiàn)

  • AddSectionParser / AddSingleLineParser

    void Parser::AddSectionParser(const std::string& name,   std::unique_ptr<SectionParser> parser) {
        section_parsers_[name] = std::move(parser);
    }
    
    void Parser::AddSingleLineParser(const std::string& prefix, LineCallback callback) {
        line_callbacks_.emplace_back(prefix, callback);
    }
    

    把關(guān)鍵字和解析器添加到對(duì)應(yīng)的容器中。

  • ParseConfig

    Parser::ParseConfig(const std::string& path) {
        size_t parse_errors;
        return ParseConfig(path, &parse_errors);
    }
    
    bool Parser::ParseConfig(const std::string& path, size_t* parse_errors) {
        *parse_errors = 0;
        if (is_dir(path.c_str())) {
            return ParseConfigDir(path, parse_errors);
        }
        return ParseConfigFile(path, parse_errors);
    }
    

    判斷path是否文件夾,選擇直接解析文件,還是解析目錄下的所有rc文件。
    ParseConfigDir()的內(nèi)部遍歷出的文件,最終同樣調(diào)用ParseConfigFile()進(jìn)行解析。

  • ParseConfigFile

      bool Parser::ParseConfigFile(const std::string& path, size_t* parse_errors) {
        auto config_contents = ReadFile(path);
        if (!config_contents) {
            return false;
        }
        config_contents->push_back('\n'); 
        ParseData(path, *config_contents, parse_errors);
        for (const auto& [section_name, section_parser] : section_parsers_) {
            section_parser->EndFile();
        }
        return true;
    }
    

    通過(guò)ReadFile()函數(shù),將文件數(shù)據(jù)讀取為字符串?dāng)?shù)據(jù)。然后通過(guò)ParseData()函數(shù)執(zhí)行解析和處理。
    文件解析完成時(shí),調(diào)用所有添加的解析器的EndFile()函數(shù)。


ParseData() 核心解析函數(shù)

Parse解析器的核心解析函數(shù)。
通過(guò)tokenizer分詞器解析字符串獲取token令牌,根據(jù)token令牌的類型,執(zhí)行處理邏輯,或派分事件到具體解析器實(shí)現(xiàn)進(jìn)行處理。

解析過(guò)程相關(guān)變量:
void Parser::ParseData(const std::string& filename, const std::string& data, size_t* parse_errors) {
    std::vector<char> data_copy(data.begin(), data.end());
    data_copy.push_back('\0');
    ...
}
  • data_copy
    存放rc文件字符流的字符容器
    把傳入數(shù)據(jù)復(fù)制到名為data_copy的vector<char>容器,并在末尾補(bǔ)充結(jié)束字符'\0'(NULL字符)。
struct parse_state
{
    char *ptr;        // 當(dāng)前解析中字符的指針,即解析進(jìn)度
    char *text;       // 當(dāng)前檢出的單詞,即參數(shù)
    int line;         // 當(dāng)前解析中的行號(hào)
    int nexttoken;    // 令牌緩存,用于性能優(yōu)化
};

ParseData() {
    ...
    parse_state state;
    state.line = 0;
    state.ptr = &data_copy[0];
    state.nexttoken = 0;
    ...
}
  • state
    解析數(shù)據(jù)結(jié)構(gòu)體
    創(chuàng)建parse_state結(jié)構(gòu)體state并初始化。用于存放 解析過(guò)程的狀態(tài) 和 產(chǎn)生的臨時(shí)數(shù)據(jù)。
ParseData() {
    ...
    SectionParser* section_parser = nullptr;
    int section_start_line = -1;
    std::vector<std::string> args;
    ...
}
  • section_parser
    段落目標(biāo)解析器
    當(dāng)解析到新行時(shí),假如新行首個(gè)參數(shù)命中解析器關(guān)鍵字 (即段落的首行),則把命中的解析器設(shè)置為段落目標(biāo)解析器 (即當(dāng)前段落的解析器)。
    沒(méi)有命中解析器關(guān)鍵字時(shí),則嘗試使用段落目標(biāo)解析器對(duì)行進(jìn)行解析(即解析段落非首行數(shù)據(jù),即命令數(shù)據(jù))

  • section_start_line
    當(dāng)前段落開(kāi)始行號(hào)
    如果當(dāng)前解析的行,是一個(gè)段落的首行,則記錄該行行號(hào)。

  • args
    當(dāng)前行解析出的參數(shù)鏈表
    參數(shù)鏈表,存放一行命令解析出的所有參數(shù)。

ParseData() {
    ...
    auto end_section = [&] {
        if (section_parser == nullptr) return;
            if (auto result = section_parser->EndSection(); !result) {
                (*parse_errors)++;
            }
        section_parser = nullptr;
        section_start_line = -1;
    };
    ...
}
  • end_section
    Lambda表達(dá)式,用于結(jié)束段落解析和重置解析相關(guān)數(shù)據(jù)
    假如當(dāng)前段落目標(biāo)解析器不為空,則調(diào)用其EndSection()函數(shù),通知解析器該段落解析結(jié)束,并重置段落目標(biāo)解析器的指針section_parser和當(dāng)前段落開(kāi)始行號(hào)section_start_line。
解析過(guò)程邏輯階段:

tokenizer分詞器的實(shí)現(xiàn)分析:安卓啟動(dòng)流程(三) - tokenizer分詞器

ParseData() {
    ...
    for (;;) {
        switch (next_token(&state)) {
            case T_EOF: ...
            case T_NEWLINE: ...
            case T_TEXT: ...
        }
    }
    ...
}

死循環(huán),通過(guò)next_token(&state)函數(shù)獲取token令牌和檢出單詞參數(shù)。

  • case T_TEXT

    case T_TEXT:
        args.emplace_back(state.text);
        break;
    

    檢出了一個(gè)單詞
    args插入單詞,state.text指針指向單詞的首字符。

  • case T_EOF

    case T_EOF:
        end_section();
        return;
    

    表示rc文本解析結(jié)束
    調(diào)用end_section表達(dá)式,通知當(dāng)前段落目標(biāo)解析器該段落解析結(jié)束,并退出ParseData函數(shù)。

  • case T_NEWLINE

    case T_NEWLINE:
        // 當(dāng)前解析的行號(hào) +1
        state.line++;
    
        // 檢出的參數(shù)為空,不執(zhí)行處理
        if (args.empty()) break;
    
        // 該行是否匹配到單行命令解析器
        // 假如匹配到單行命令解析器,嘗試通過(guò)end_section函數(shù)結(jié)束當(dāng)前段落解析, 
        // 然后調(diào)用對(duì)應(yīng)的單行命令解析器
        for (const auto& [prefix, callback] : line_callbacks_) {
            if (android::base::StartsWith(args[0], prefix)) {
                // 嘗試通過(guò)end_section函數(shù)結(jié)束當(dāng)前段落解析
                end_section();
                // 調(diào)用對(duì)應(yīng)的單行命令解析器
                if (auto result = callback(std::move(args)); !result) {
                    (*parse_errors)++;
                }
                break;
            }
        }
        
        // 該行是否匹配到段落解析器
        if (section_parsers_.count(args[0])) {
            // 當(dāng)匹配到解析器時(shí), 嘗試通過(guò)end_section函數(shù)結(jié)束上一個(gè)段落解析
            end_section();
            // 記錄當(dāng)前解析器
            section_parser = section_parsers_[args[0]].get();
            // 記錄段落行號(hào)
            section_start_line = state.line;
            // 調(diào)用解析器的ParseSection函數(shù)
            if (auto result = section_parser->ParseSection(std::move(args), filename, state.line); !result) {
                (*parse_errors)++;
                section_parser = nullptr;
            }
        } else if (section_parser) {
            // 假如存在段落目標(biāo)解析器,則調(diào)用解析器的ParseLineSection函數(shù)
            if (auto result = section_parser->ParseLineSection(std::move(args), state.line); !result) {
                (*parse_errors)++;
            }
        }
    
        // 清空參數(shù)鏈表
        args.clear();
        break;
    }
    

    檢測(cè)到換行,該行結(jié)束,處理行命令
    該行參數(shù)已解析完成,在解析下一行前,處理該行的命令。


大致流程總結(jié)

  1. 使用ParseData()對(duì)路徑參數(shù)path下的所有rc文件,進(jìn)行解析。
  2. ParseData()中命令的單位為每一行的文本。一行命令中檢出的所有參數(shù)都會(huì)放入args鏈表,在下一行解析開(kāi)始前,處理命令,并在命令處理完成時(shí)清空args鏈表。
  3. 命令處理開(kāi)始時(shí),先進(jìn)行解析器匹配。當(dāng)args鏈表數(shù)量大于0時(shí),進(jìn)入匹配階段,首先匹配單行命令解析器,然后匹配段落命令解析器。
  4. 匹配階段無(wú)法匹配到解析器時(shí),則該行命令視為段落命令,進(jìn)入段落命令處理階段,嘗試使用已設(shè)置為處理中的段落解析器進(jìn)行處理,無(wú)法找到解析器,則跳過(guò)該行命令的處理。

下一篇:安卓啟動(dòng)流程(三) - tokenizer分詞器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容