代碼地址:github.com/pflhm2005/V…javascript
const kMaxAscii = 127;
const UnicodeToAsciiMapping = [];
for(let i = 0;i < kMaxAscii;i ++) {
UnicodeToAsciiMapping.push(String.fromCharCode(i));
}
/** * 源碼確實是一個超長的三元表達式 * Token是一個枚舉 這裏直接用字符串代替了 * 由於太多了 只保留幾個看看 */
const TokenToAsciiMapping = (c) => {
return c === '(' ? 'Token::LPAREN' :
c == ')' ? 'Token::RPAREN' :
// ...不少不少
c == '"' ? 'Token::STRING' :
c == '\'' ? 'Token::STRING' :
// ...不少不少
'Token::ILLEGAL'
};
const UnicodeToToken = UnicodeToAsciiMapping.map(v => TokenToAsciiMapping(v));複製代碼
class Stream {
constructor(source_string) {
/** * buffer_不會在構造函數中初始化 * 但爲了模擬v8這裏暫時保存源字符串 */
this.source_string = source_string;
/** * 做爲容器存儲字符 */
this.buffer_ = [];
/** * 三個指針分別表明當前解析進度 */
this.buffer_start_ = 0
this.buffer_cursor_ = 0
this.buffer_end_ = 0
}
ReadBlockChecked() {
return this.ReadBlock();
}
ReadBlock() {
this.buffer_ = this.source_string.split('').map(v => UnicodeToAsciiMapping.indexOf(v));
this.buffer_end_ = this.buffer_.length;
/** * 這裏的返回與源碼不一樣 涉及gc 不作展開 */
return this.buffer_.length;
}
/** * 返回當前字符 並前進一格 */
Advance() {
let tmp = this.peek();
this.buffer_cursor_++;
return tmp;
}
/** * 返回當前字符 * 同時會作初始化 */
peek() {
if(this.buffer_cursor_ < this.buffer_end_) {
return this.buffer_[this.buffer_cursor_];
} else if(this.ReadBlockChecked()) {
return this.buffer_[this.buffer_cursor_];
} else {
return null;
}
}
}複製代碼
class TokenDesc {
constructor() {
/** * 源碼中是一個結構體 * 除了標記起始、結束位置還有若干方法 */
this.location = {
beg_pos: 0,
end_pos: 0,
};
/** * 負責管理字符串 * 還有一個名爲raw_literal_chars的同類型屬性負責儲存源字符串 */
this.literal_chars = new LiteralBuffer();
/** * Token類型 */
this.token = null;
/** * 處理小整數 */
this.smi_value = 0;
this.after_line_terminator = false;
}
}複製代碼
class Scanner {
constructor(source_string) {
this.source_ = new stream(source_string);
/** * 當前字符的Unicode編碼 * 若是爲null表明解析完成 */
this.c0_ = null;
/** * 其實v8有三個詞法描述類 * token_storage_是一個數組 裏面裝着那個三個類 這裏就不用了 * 爲了方便就弄一個 */
this.TokenDesc = new TokenDesc();
this.token_storage_ = [];
}
/** * 源碼有current_、next_、next_next_三個標記 這裏搞一個 */
next() {
return this.TokenDesc;
}
Initialize() {
this.Init();
this.next().after_line_terminator = true;
this.Scan();
}
Init() {
this.Advance();
// 後面會有一些詞法描述類對token_storage_的映射 這裏跳過
}
Advance() {
this.c0_ = this.source_.Advance();
}
/** * 這裏有函數重載 JS就直接用默認參數模擬了 */
Scan(next = this.TokenDesc) {
next.token = this.ScanSingleToken();
next.location.end_pos = this.source_.buffer_cursor_ - 1;
}
/** * 單個詞法的解析 */
ScanSingleToken() {
let token = null;
do {
this.next().location.beg_pos = this.source_.buffer_cursor_;
if(this.c0_ < kMaxAscii) {
token = UnicodeToToken[this.c0_];
switch(token) {
case 'Token::LPAREN':
/** * 有不少其餘的case * 由於只講字符串 * 這裏就不實現這個方法了 */
return this.Select(token);
case 'Token::STRING':
return this.ScanString();
// ...
}
}
/** * 源碼中這裏處理一些特殊狀況 不展開了 */
} while(token === 'Token::WHITESPACE')
return token;
}
}複製代碼
const Latin1_kMaxChar = 255;
// constexpr int kOneByteSize = kCharSize = sizeof(char);
const kOneByteSize = 1;
class LiteralBuffer {
constructor() {
/** * 源碼中是一個Vector容器 * 有對應擴容算法 */
this.backing_store_ = [];
this.position_ = 0;
/** * 當字符串中有字符的Unicode值大於255 * 斷定爲雙字節類型 這裏先不處理這種 */
this.is_one_byte_ = null;
}
/** * 啓動這個時默認字符串爲單字節 */
start() {
this.position_ = 0;
this.is_one_byte_ = true;
}
/** * 只關心單字節字符 因此那兩個方法不給出實現了 */
AddChar(code_unit) {
if(this.is_one_byte_) {
if(code_unit <= Latin1_kMaxChar) {
return this.AddOneByteChar(code_unit);
}
this.ConvertToTwoByte();
}
this.AddTwoByteChar(code_unit);
}
AddOneByteChar(one_byte_char) {
/** * 擴容算法簡述就是以64爲基準 每次擴容*4 * 當所需容器大於(1024 * 1024) / 3時 寫死爲2 * 1024 * 1024 */
if (this.position_ >= this.backing_store_.length) this.ExpandBuffer();
this.backing_store_[this.position_] = one_byte_char;
this.position_ += kOneByteSize;
}
}複製代碼
class Scanner {
// ...
ScanString() {
// 保存當前字符串的標記符號 ' 或 "
let quote = this.c0_;
this.next().literal_chars.Start();
while(true) {
this.AdvanceUntil();
/** * 特殊符號直接前進一格 */
while(this.c0_ === '\\') {
this.Advance();
}
/** * 遇到結束的標記表明解析結束 */
if (this.c0_ === quote) {
this.Advance();
return 'Token::STRING';
}
this.AddLiteralChar(this.c0_);
}
}
AddLiteralChar(c) {
this.next().literal_chars.AddChar(c);
}
}複製代碼
class Scanner {
// ...
/** * 這裏相對源碼有改動 * 一、實際調用的是source_上的方法 並把返回值給了c0_ * 二、判斷函數在這裏寫實現 */
AdvanceUntil() {
/** * 這裏須要實現std標準庫中一個方法 * 其實是三個參數 且前兩個參數爲迭代器 爲了方便暫時就不完美實現了 */
const find_if = (arr, start, end, callback) => {
let tarArr = arr.slice(start, end);
let tarIdx = tarArr.findIndex(v => callback(v));
return tarIdx === -1 ? end : tarIdx;
}
const callback = (c0) => {
/** * 表明當前字符多是一個結束符 這裏簡化了判斷 源碼以下 * uint8_t char_flags = character_scan_flags[c0]; * if (MayTerminateString(char_flags)) return true; */
if(["\'", "\""].includes(UnicodeToAsciiMapping[c0])) return true;
this.AddLiteralChar(c0);
return false;
}
/** * 在字符串中尋找第一個字符結尾標記的位置 * 例如'、"等等 */
let next_cursor_pos = find_if(this.source_.buffer_, this.source_.buffer_cursor_, this.source_.buffer_end_, callback);
if(next_cursor_pos === this.source_.buffer_end_) {
this.source_.buffer_cursor_ = this.source_.buffer_end_;
this.c0_ = null;
} else {
this.source_.buffer_cursor_ = next_cursor_pos + 1;
this.c0_ = this.source_.buffer_[next_cursor_pos + 1];
}
}
}複製代碼
let scanner = new Scanner(source_code);
scanner.Initialize();
console.log(scanner)複製代碼