深入V8引擎-AST(2)

書生小龍發表於2019-07-02

  先宣告一下,這種長系列的大塊頭部落格只能保證儘可能的深入到每一行原始碼,有些程式碼我不樂意深究就寫個註釋說明一下作用。另外,由於本地整理的比較好,部落格就隨心寫了。

  整個Compile過程目前只看到asmjs之前,簡單的過了幾遍,大部分方法沒有點進去看,實在是太複雜了。上一篇的結尾指出了AST的入口,也就是名稱空間parsing的一個公共方法,如下。

bool ParseProgram(ParseInfo* info, Isolate* isolate) {
  // ...
  /**
   * 生成一個Parser例項
   * 呼叫內部方法啟動轉換
   */
  Parser parser(info);
  FunctionLiteral* result = nullptr;
  /**
   * 轉換AST後將結果賦值給ParseInfo的literal_
   */
  result = parser.ParseProgram(isolate, info);
  info->set_literal(result);
  // ...
  return (result != nullptr);
}

  所需要關心的核心程式碼就是這些,非常簡單,Parser物件的初始化屬性非常多,這裡就不列出來了。

  接下來進入第二個核心方法,即ParseProgram。

FunctionLiteral* Parser::ParseProgram(Isolate* isolate, ParseInfo* info) {
  // ...
  /**
   * scanner_為Parser類的一個私有屬性
   * 這裡僅僅進行初始化
   */
  scanner_.Initialize();
  FunctionLiteral* result = DoParseProgram(isolate, info);

  // ...
  return result;
}

  同樣,所需要關心程式碼只有兩行,其中第一步則是啟動了scanner的初始化,第二步則是開始全面解析。

  Scanner包含scanner、scanner-character-strams兩個部分,其中stream則是經過初步處理的源String,必須轉換後才能進行解析。處理的過程在之前省略的程式碼中,這裡稍微給出大概的轉換流程。

bool ParseProgram(ParseInfo* info, Isolate* isolate) {
  // ...
  /**
   * 1、info->script()返回的是字串的描述資訊 source是Local<String>型別的源字串
   * 2、ScannerStream是scanner-character-streams標頭檔案的類 內部方法均為靜態型別 可以直接呼叫
   * 3、返回的具體型別根據String型別不同而不同 但是由於均繼承於Utf16CharacterStream 所以直接用父類接
   */
  Handle<String> source(String::cast(info->script()->source()), isolate);
  std::unique_ptr<Utf16CharacterStream> stream(ScannerStream::For(isolate, source));
  info->set_character_stream(std::move(stream));
  // ...
}

/**
 * 有四種特殊的String型別 分別new不同的子類
 * ScannerStream::For(isolate, data, 0, data->length());
 */
Utf16CharacterStream* ScannerStream::For(Isolate* isolate, Handle<String> data, int start_pos, int end_pos) {
  size_t start_offset = 0;
  // ...
  if (data->IsSeqOneByteString()) {
    return new BufferedCharacterStream<OnHeapStream>(
        static_cast<size_t>(start_pos), Handle<SeqOneByteString>::cast(data),
        start_offset, static_cast<size_t>(end_pos));
  }
}

  常規的字串一般都是OneByteString,這裡就不細講了。最後返回一個特殊Stream類,其屬性記錄字串的長度、當前的解析進度、解析的開始與結束標記等等。

  將字串轉換後,就可以利用Scanner來進行逐步解析,在此之前,需要對Scanner類有一個簡單的瞭解,如下。

/**
 * Scanner類
 * 跟Utf16CharacterStream一個檔案
 */
class V8_EXPORT_PRIVATE Scanner {
  public:
    // 返回next_的token型別
    Token::Value peek() const { return next().token; }
    // 返回current_的位置資訊
    const Location& location() const { return current().location; }
  private:
    // 當前字元的Unicode編碼 -1表示結尾(typedef int32_t uc32)
    uc32 c0_;
    TokenDesc* current_;    // desc for current token (as returned by Next())
    TokenDesc* next_;       // desc for next token (one token look-ahead)
    TokenDesc* next_next_;  // desc for the token after next (after PeakAhead())
    // 從Handle<String>轉換後的型別 負責執行解析的實際類
    Utf16CharacterStream* const source_;
}

  選取了一些比較簡單的屬性和方法,Scanner內部有三個遊標屬性負責遍歷字串,分別是current_、next_、next_next_,字面意思理解就行了。source_則是之前說的轉換Stream類,所有的解析實際上都是呼叫這個屬性的方法。而兩個結構體TokenDesc、Location也非常重要,一個負責詞法描述,一個負責記錄詞法位置資訊,如下。

/**
 * 詞法結構體
 * 每一個TokenDesc代表單獨一段詞法
 */
struct TokenDesc {
  /**
   * 詞法所在位置
   * 該結構體比較簡單 就不展開了 兩個值代表起始、結束位置
   * 例如sample中 "'Hello' + ' World'" 'Hello'會被解析為TOKEN::STRING location為{0, 7}
   */
  Location location = {0, 0};
  /**
   * 字串詞法相關
   */
  LiteralBuffer literal_chars;
  LiteralBuffer raw_literal_chars;
  /**
   * 詞法的列舉型別
   * 例如 '(' 是 TOKEN::LPAREN '===' 是 TOKEN::EQ_STRICT
   * 所有型別可見token.h
   */
  Token::Value token = Token::UNINITIALIZED;
  MessageTemplate invalid_template_escape_message = MessageTemplate::kNone;
  Location invalid_template_escape_location;
  // 小整數
  uint32_t smi_value_ = 0;
  bool after_line_terminator = false;
}

  通過這個結構體和一些方法,就能完整的將源字串逐步轉換為抽象語法樹。但是實際轉換過程非常複雜,分支極多,後面再繼續探究。

相關文章