上篇簡單介紹了入口方法的流程以及scanner類相關的部份內容,這一篇主要講scanner的初始化,即
scanner_.Initialize();複製代碼
注意,這不是調用靜態方法。實際上Parser實例生成的時候也把scanner屬性初始化了,因此這裏能夠直接用。
Parser::Parser(ParseInfo* info) : ParserBase<Parser>()
scanner_(info->character_stream(), info->is_module()),複製代碼
實際上,就是初始化了scanner上的source_屬性與模塊的flag,以便調用Initialize方法。
這個方法有點相似於libuv的異步操做,不過固然徹底不是一個東西,源碼以下。
void Scanner::Initialize() {
Init();
next().after_line_terminator = true;
Scan();
}複製代碼
第二步我也不曉得是幹啥的,暫時不理解那個變量的意義,因此只講第一和第三步,首先是Init。
void Init() {
Advance();
current_ = &token_storage_[0];
next_ = &token_storage_[1];
next_next_ = &token_storage_[2];
found_html_comment_ = false;
scanner_error_ = MessageTemplate::kNone;
}
void Advance() {
c0_ = source_->Advance();
}複製代碼
從scanner層級來看,其Advance方法的做用僅僅是對私有屬性c0_(當前字符的Unicode編碼)進行賦值,作實際操做是source_屬性上的Advance方法,而這個屬性類型爲前面轉換後的Stream類(全稱是xxxCharacterStream,由於太長了,後面所有簡稱Stream類),因此具體實現須要跳到那邊去,源碼以下。
inline uc32 Advance() {
uc32 result = Peek();
buffer_cursor_++;
return result;
}
inline uc32 Peek() {
if (V8_LIKELY(buffer_cursor_ < buffer_end_)) {
return static_cast<uc32>(*buffer_cursor_);
} else if (ReadBlockChecked()) {
return static_cast<uc32>(*buffer_cursor_);
} else {
return kEndOfInput;
}
}複製代碼
這裏有一些東西須要解釋,首先是關於Stream類的3個遊標屬性(這個名字是我本身取的,看AST的解析總讓我想到高中的遊標卡尺),分別是buffer_start_、buffer_cursor_、buffer_end_,分別表明字符解析中的開始、當前、結束位置,在Stream類初始化時這三個屬性沒有處理,默認置0。注意,這裏的屬性指向字符,跟詞法是不一樣的概念,在scanner層級的三個屬性是詞法。好比說if從詞法角度講是一個,可是從字符角度來講是兩個。
下面的3個判斷註釋中給出了意義,比較有意思的是V8_LIKELY宏,對於開發者來講算是一個無心義的宏,可是這個宏是給編譯器看的,代表這個分支比較有可能發生,推薦進行優化。因爲初始化只會走一遍,在解析未結束前大部分狀況都是走第一個分支直接返回當前遊標指向的值。不過目前是第一次調用這個方法,咱們走第二個分支。
bool ReadBlockChecked() {
size_t position = pos();
USE(position);
bool success = !has_parser_error() && ReadBlock();
DCHECK_EQ(pos(), position);
DCHECK_LE(buffer_cursor_, buffer_end_);
DCHECK_LE(buffer_start_, buffer_cursor_);
DCHECK_EQ(success, buffer_cursor_ < buffer_end_);
return success;
}
inline size_t pos() const {
return buffer_pos_ + (buffer_cursor_ - buffer_start_);
}
bool ReadBlock() final {
size_t position = pos();
buffer_pos_ = position;
buffer_start_ = &buffer_[0];
buffer_cursor_ = buffer_start_;
DisallowHeapAllocation no_gc;
Range<uint8_t> range = byte_stream_.GetDataAt(position, runtime_call_stats(), &no_gc);
if (range.length() == 0) {
buffer_end_ = buffer_start_;
return false;
}
size_t length = Min(kBufferSize, range.length());
i::CopyCharsUnsigned(buffer_, range.start, length);
buffer_end_ = &buffer_[length];
return true;
}複製代碼
這一塊的內容較多,實際上說多也很少。第一個方法只是純粹的檢查,保證遊標屬性的合法,pos方法則是直接經過地址計算來獲得當前解析位置,原理寫在註釋裏了。
ReadBlock方法負責對Stream屬性的初始化,這個類前面沒有給出聲明,buffer_是其一個私有屬性,長度爲512的short數組。DisallowHeapAllocation不要去管,v8裏面有不少奇奇怪怪的東西,目前理解不了,固然與AST自己也毫無關係。GetDataAt比較麻煩,不想講,從結果上來說,最後返回的是字符串每一個字符的Unicode編碼,經過CopyCharsUnsigned方法複製到了buffer_上面,並將buffer_end_指向了最後結尾的部分。
好比說待編譯字符串爲"'Hello' + ' World'",通過GetDataAt處理後,會變成39, 72, ...。
這裏給一個調試結果,buffer_初始化後,會有一堆髒數據,內容以下(長度512,只截取了前面一部分)。
初始化的buffer_
通過該方法的一系列處理,變成了
重置後的buffer_
加上空格,整個字符串共有18位,因此0-17的值所有被重置,後面仍是老的髒數據。這些數字手動轉換一下,能夠獲得
恰好是待編譯的字符串(先假設字符串長度小於512,複雜狀況後面再搞)。
至此,整個Init方法才完事,沒想到這麼長,Scan下一篇講,要幹活了。