https://www.cnblogs.com/tugenhua0707/p/7863616.html
Babel是如何編譯JS代碼的及理解抽象語法樹(AST)
1. Babel的作用是?
很多瀏覽器目前還不支持ES6的代碼,但是我們可以通過Babel將ES6的代碼轉譯成ES5代碼,讓所有的瀏覽器都能理解的代碼,這就是Babel的作用。
2. Babel是如何工作的?
Babel的編譯過程和大多數其他語言的編譯器大致相同,可以分爲三個階段。
1. 解析(PARSE):將代碼字符串解析成抽象語法樹。
2. 轉換(TRANSFORM):對抽象語法樹進行轉換操作。
3. 生成(GENERATE): 根據變換後的抽象語法樹再生成代碼字符串。
比如我們在 .babelrc裏配置的presets和plugins是在第二步進行的。
我們可以看一下下面的流程圖就可以很清晰了:
3. 什麼是抽象語法樹(AST)?
我們知道javascript程序一般是由一系列的字符組成的,每一個字符都有一些含義,比如我們可以使用匹配的字符([], {}, ()), 或一些其他成對的字符('', "")和代碼縮進讓程序解析更加簡單,但是對計算機並不適用,這些字符在內存中僅僅是個數值,但是計算機並不知道一個程序內部有多少個變量這些高級問題,
這個時候我們需要尋找一些能讓計算機理解的方式,這個時候,抽象語法樹誕生了。
4. 抽象語法樹是如何產生的?
我們通過上面知道,Babel的工作的第一步是 解析操作,將代碼字符串解析成抽象語法樹,那麼抽象語法樹就是在解析過程中產生的。其實解析又可以分成兩個
步驟:
4-1 分詞: 將整個代碼字符串分割成 語法單元數組。
4-2 語義分析:在分詞結果的基礎之上分析 語法單元之間的關係。
分詞:
先來理解一下什麼是語法單元? 語法單元是被解析語法當中具備實際意義的最小單元,簡單的來理解就是自然語言中的詞語。
比如我們來看下面的一句話:
2022年亞運會將在杭州舉行,下面我們可以把這句話拆分成最小單元:2022年, 亞運會, 將, 在, 杭州, 舉行。這就是我們所說的分詞。也是最小單元,
如果我們把它再拆分出去的話,那就沒有什麼實際意義了。
那麼JS代碼中有哪些語法單元呢?大致有下面這些:
1. 空白。JS中連續的空格,換行,縮進等這些如果不在字符串裏面,就沒有任何實際的意義,因此我們可以將連續的空白組合在一起作爲一個語法單元。
2. 註釋。行註釋或塊註釋,對於編寫人或維護人註釋是有意義的,但是對於計算機來說知道這是個註釋就可以了,並不關心註釋的含義,因此我們可以將
註釋理解爲一個不可拆分的語法單元。
3. 字符串。對計算機而言,字符串的內容會參與計算或顯示,因此有可以爲一個語法單元。
4. 數字。JS中有16,10,8進制以及科學表達式等語法,因此數字也可以理解一個語法單元。
5. 標識符。沒有被引號括起來的連續字符,可包含字母 _, $ 及數字,或 true, false等這些內置常量,或 if,return,function等這些關鍵字。
6. 運算符: +, -, *, /, >, < 等。
7,還有一些其他的,比如括號,中括號,大括號,分號,冒號,點等等。
下面我們來看看代碼內是如何分詞的?
比如如下代碼:
if (1 > 0) { alert("aa"); }
我們希望得到的分詞是如下:
'if' ' ' '(' '1' ' ' '>' ' ' '0' )' ' ' '{' '\n ' 'alert' '(' "aa" ')' ";" '\n' '}'
下面我們就來一個個字符進行遍歷,然後分情況判斷,如下代碼:
<!DOCTYPE html> <html> <head> <title>分詞</title> </head> <body> <script> function tokenizeCode(code) { var tokens = []; // 保存結果數組 for (var i = 0; i < code.length; i++) { // 從0開始 一個個字符讀取 var currentChar = code.charAt(i); if (currentChar === ';') { tokens.push({ type: 'sep', value: currentChar }); // 該字符已經得到解析了,直接循環下一個 continue; } if (currentChar === '(' || currentChar === ')') { tokens.push({ type: 'parens', value: currentChar }); continue; } if (currentChar === '{' || currentChar === '}') { tokens.push({ type: 'brace', value: currentChar }); continue; } if (currentChar === '>' || currentChar === '<') { tokens.push({ type: 'operator', value: currentChar }); continue; } if (currentChar === '"' || currentChar === '\'') { // 如果是單引號或雙引號,表示一個字符的開始 var token = { type: 'string', value: currentChar }; tokens.push(token); var closer = currentChar; // 表示下一個字符是不是被轉譯了 var escaped = false; // 循環遍歷 尋找字符串的末尾 for(i++; i < code.length; i++) { currentChar = code.charAt(i); // 將當前遍歷到的字符先加到字符串內容中 token.value += currentChar; if (escaped) { // 如果當前爲true的話,就變爲false,然後該字符就不做特殊的處理 escaped = false; } else if (currentChar === '\\') { // 如果當前的字符是 \, 將轉譯狀態變爲true,下一個字符不會被做處理 escaped = true; } else if (currentChar === closer) { break; } } continue; } // 數字做處理 if (/[0-9]/.test(currentChar)) { // 如果數字是以 0 到 9的字符開始的話 var token = { type: 'number', value: currentChar }; tokens.push(token); // 繼續遍歷,如果下一個字符還是數字的話,比如0到9或小數點的話 for (i++; i < code.length; i++) { currentChar = code.charAt(i); if (/[0-9\.]/.test(currentChar)) { // 先不考慮多個小數點 或 進制的情況下 token.value += currentChar; } else { // 如果下一個字符不是數字的話,需要把i值返回原來的位置上,需要減1 i--; break; } } continue; } // 標識符是以字母,$, _開始的 做判斷 if (/[a-zA-Z\$\_]/.test(currentChar)) { var token = { type: 'identifier', value: currentChar }; tokens.push(token); // 繼續遍歷下一個字符,如果下一個字符還是以字母,$,_開始的話 for (i++; i < code.length; i++) { currentChar = code.charAt(i); if (/[a-zA-Z0-9\$\_]/.test(currentChar)) { token.value += currentChar; } else { i--; break; } } continue; } // 連續的空白字符組合在一起 if (/\s/.test(currentChar)) { var token = { type: 'whitespace', value: currentChar } tokens.push(token); // 繼續遍歷下一個字符 for (i++; i < code.length; i++) { currentChar = code.charAt(i); if (/\s/.test(currentChar)) { token.value += currentChar; } else { i--; break; } } continue; } // 更多的字符判斷 ...... // 遇到無法理解的字符 直接拋出異常 throw new Error('Unexpected ' + currentChar); } return tokens; } var tokens = tokenizeCode(` if (1 > 0) { alert("aa"); } `); console.log(tokens); </script> </body> </html>
打印的結果如下:
/* [ {type: "whitespace", value: "\n"}, {type: "identifier", value: "if"}, {type: "whitespace", value: " "}, {type: "parens", value: "("}, {type: "number", value: "1"}, {type: "whitespace", value: " "}, {type: "operator", value: ">"}, {type: "whitespace", value: " "}, {type: "number", value: "0"}, {type: "parens", value: ")"}, {type: "whitespace", value: " "}, {type: "brace", value: "{"}, {type: "whitespace", value: "\n"}, {type: "identifier", value: "alert"}, {type: "parens", value: "("}, {type: "string", value: "'aa'"}, {type: "parens", value: ")"}, {type: "sep", value: ";"}, {type: "whitespace", value: "\n"}, {type: "brace", value: "}"}, {type: "whitespace", value: "\n"} ] */
語義分析:
語義分析是把詞彙進行立體的組合,確定有多重意義的詞語最終是什麼意思,多個詞語之間有什麼關係以及又如何在什麼地方斷句等等。我們對上面的輸出代碼再進行語義分析了,請看如下代碼:
<!DOCTYPE html> <html> <head> <title>分詞</title> </head> <body> <script> var parse = function(tokens) { let i = -1; // 用於標識當前遍歷位置 let curToken; // 用於記錄當前符號 // 讀取下一個語句 function nextStatement () { // 暫存當前的i,如果無法找到符合條件的情況會需要回到這裏 stash(); // 讀取下一個符號 nextToken(); if (curToken.type === 'identifier' && curToken.value === 'if') { // 解析 if 語句 const statement = { type: 'IfStatement', }; // if 後面必須緊跟着 ( nextToken(); if (curToken.type !== 'parens' || curToken.value !== '(') { throw new Error('Expected ( after if'); } // 後續的一個表達式是 if 的判斷條件 statement.test = nextExpression(); // 判斷條件之後必須是 ) nextToken(); if (curToken.type !== 'parens' || curToken.value !== ')') { throw new Error('Expected ) after if test expression'); } // 下一個語句是 if 成立時執行的語句 statement.consequent = nextStatement(); // 如果下一個符號是 else 就說明還存在 if 不成立時的邏輯 if (curToken === 'identifier' && curToken.value === 'else') { statement.alternative = nextStatement(); } else { statement.alternative = null; } commit(); return statement; } if (curToken.type === 'brace' && curToken.value === '{') { // 以 { 開頭表示是個代碼塊,我們暫不考慮JSON語法的存在 const statement = { type: 'BlockStatement', body: [], }; while (i < tokens.length) { // 檢查下一個符號是不是 } stash(); nextToken(); if (curToken.type === 'brace' && curToken.value === '}') { // } 表示代碼塊的結尾 commit(); break; } // 還原到原來的位置,並將解析的下一個語句加到body rewind(); statement.body.push(nextStatement()); } // 代碼塊語句解析完畢,返回結果 commit(); return statement; } // 沒有找到特別的語句標誌,回到語句開頭 rewind(); // 嘗試解析單表達式語句 const statement = { type: 'ExpressionStatement', expression: nextExpression(), }; if (statement.expression) { nextToken(); if (curToken.type !== 'EOF' && curToken.type !== 'sep') { throw new Error('Missing ; at end of expression'); } return statement; } } // 讀取下一個表達式 function nextExpression () { nextToken(); if (curToken.type === 'identifier') { const identifier = { type: 'Identifier', name: curToken.value, }; stash(); nextToken(); if (curToken.type === 'parens' && curToken.value === '(') { // 如果一個標識符後面緊跟着 ( ,說明是個函數調用表達式 const expr = { type: 'CallExpression', caller: identifier, arguments: [], }; stash(); nextToken(); if (curToken.type === 'parens' && curToken.value === ')') { // 如果下一個符合直接就是 ) ,說明沒有參數 commit(); } else { // 讀取函數調用參數 rewind(); while (i < tokens.length) { // 將下一個表達式加到arguments當中 expr.arguments.push(nextExpression()); nextToken(); // 遇到 ) 結束 if (curToken.type === 'parens' && curToken.value === ')') { break; } // 參數間必須以 , 相間隔 if (curToken.type !== 'comma' && curToken.value !== ',') { throw new Error('Expected , between arguments'); } } } commit(); return expr; } rewind(); return identifier; } if (curToken.type === 'number' || curToken.type === 'string') { // 數字或字符串,說明此處是個常量表達式 const literal = { type: 'Literal', value: eval(curToken.value), }; // 但如果下一個符號是運算符,那麼這就是個雙元運算表達式 stash(); nextToken(); if (curToken.type === 'operator') { commit(); return { type: 'BinaryExpression', left: literal, right: nextExpression(), }; } rewind(); return literal; } if (curToken.type !== 'EOF') { throw new Error('Unexpected token ' + curToken.value); } } // 往後移動讀取指針,自動跳過空白 function nextToken () { do { i++; curToken = tokens[i] || { type: 'EOF' }; } while (curToken.type === 'whitespace'); } // 位置暫存棧,用於支持很多時候需要返回到某個之前的位置 const stashStack = []; function stash () { // 暫存當前位置 stashStack.push(i); } function rewind () { // 解析失敗,回到上一個暫存的位置 i = stashStack.pop(); curToken = tokens[i]; } function commit () { // 解析成功,不需要再返回 stashStack.pop(); } const ast = { type: 'Program', body: [], }; // 逐條解析頂層語句 while (i < tokens.length) { const statement = nextStatement(); if (!statement) { break; } ast.body.push(statement); } return ast; }; var ast = parse([ {type: "whitespace", value: "\n"}, {type: "identifier", value: "if"}, {type: "whitespace", value: " "}, {type: "parens", value: "("}, {type: "number", value: "1"}, {type: "whitespace", value: " "}, {type: "operator", value: ">"}, {type: "whitespace", value: " "}, {type: "number", value: "0"}, {type: "parens", value: ")"}, {type: "whitespace", value: " "}, {type: "brace", value: "{"}, {type: "whitespace", value: "\n"}, {type: "identifier", value: "alert"}, {type: "parens", value: "("}, {type: "string", value: "'aa'"}, {type: "parens", value: ")"}, {type: "sep", value: ";"}, {type: "whitespace", value: "\n"}, {type: "brace", value: "}"}, {type: "whitespace", value: "\n"} ]); console.log(ast); </script> </body> </html>
最後輸出ast值爲如下:
{ "type": "Program", "body": [ { "type": "IfStatement", "test": { "type": "BinaryExpression", "left": { "type": "Literal", "value": 1 }, "right": { "type": "Literal", "value": 0 } }, "consequent": { "type": "BlockStatement", "body": [ { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "caller": { "type": "Identifier", "value": "alert" }, "arguments": [ { "type": "Literal", "value": "aa" } ] } } ] }, "alternative": null } ] }
我們現在再來分析下上面代碼的含義:分析如下:
第一步調用parse該方法,傳入參數分詞中輸出的結果,代碼如下:
var ast = parse([ {type: "whitespace", value: "\n"}, {type: "identifier", value: "if"}, {type: "whitespace", value: " "}, {type: "parens", value: "("}, {type: "number", value: "1"}, {type: "whitespace", value: " "}, {type: "operator", value: ">"}, {type: "whitespace", value: " "}, {type: "number", value: "0"}, {type: "parens", value: ")"}, {type: "whitespace", value: " "}, {type: "brace", value: "{"}, {type: "whitespace", value: "\n"}, {type: "identifier", value: "alert"}, {type: "parens", value: "("}, {type: "string", value: "'aa'"}, {type: "parens", value: ")"}, {type: "sep", value: ";"}, {type: "whitespace", value: "\n"}, {type: "brace", value: "}"}, {type: "whitespace", value: "\n"} ]);
先初始化如下參數:
let i = -1; // 用於標識當前遍歷位置
let curToken; // 用於記錄當前符號
function nextStatement() {
// ... 很多代碼
}
function nextExpression() {
// ... 很多代碼
}
function nextToken() {
// ... 很多代碼
}
// 位置暫存棧,用於支持很多時候需要返回到某個之前的位置
const stashStack = [];
function rewind () {
// ... 很多代碼
}
function commit () {
// ... 很多代碼
}
真正初始化的代碼如下:
const ast = { type: 'Program', body: [], }; // 逐條解析頂層語句 while (i < tokens.length) { const statement = nextStatement(); if (!statement) { break; } ast.body.push(statement); } return ast;
先定義ast對象,最頂層的類型爲 Program, body爲[], 然後依次循環tokens的長度,第一步調用 nextStatement()方法,在該方法內部,先是
存儲當前的i值,代碼如下:
// 暫存當前的i,如果無法找到符合條件的情況會需要回到這裏
stash();
function stash () {
// 暫存當前位置
stashStack.push(i);
}
因此 var stashStack = [-1]了;
接着 調用 nextToken();方法 讀取下一個符號,nextToken代碼如下:
// 往後移動讀取指針,自動跳過空白
function nextToken () {
do {
i++;
curToken = tokens[i] || { type: 'EOF' };
} while (curToken.type === 'whitespace');
}
上面使用到do,while語句,該代碼的含義是先執行一次,然後再判斷條件是否符合要求,因此此時 i = 0 了,因此 curToken的值變爲如下:
var curToken = {type: "whitespace", value: "\n"}; 然後while語句在判斷 curToken.type === 'whitespace' 是否相等,
很明顯是相等的,因此i++; 然後 var curToken = {type: "identifier", value: "if"}; 這個值了;然後再判斷該type是否等於?
可以看到不等於,因此curToken的值就是如上代碼的。
然後 就是if語句代碼判斷如下:
if (curToken.type === 'identifier' && curToken.value === 'if') { // 解析 if 語句 const statement = { type: 'IfStatement', }; // if 後面必須緊跟着 ( nextToken(); if (curToken.type !== 'parens' || curToken.value !== '(') { throw new Error('Expected ( after if'); } // 後續的一個表達式是 if 的判斷條件 statement.test = nextExpression(); // 判斷條件之後必須是 ) nextToken(); if (curToken.type !== 'parens' || curToken.value !== ')') { throw new Error('Expected ) after if test expression'); } // 下一個語句是 if 成立時執行的語句 statement.consequent = nextStatement(); // 如果下一個符號是 else 就說明還存在 if 不成立時的邏輯 if (curToken === 'identifier' && curToken.value === 'else') { statement.alternative = nextStatement(); } else { statement.alternative = null; } commit(); return statement; }
var curToken = {type: "identifier", value: "if"}; 因此滿足if條件判斷語句,定義 statement對象如下:
const statement = {
type: 'IfStatement'
};
調用 nextToken()方法 讀取下一個字符,因此先執行一次代碼, var curToken = {type: "whitespace", value: " "}; 然後再判斷while條件,
最後curToken的值變爲如下: var curToken = {type: "parens", value: "("}; 所以if語句後面緊跟着( 是正常的,然後就是需要判斷if語句的
表達式了;如上代碼:
// 後續的一個表達式是 if 的判斷條件
statement.test = nextExpression();
// 判斷條件之後必須是 )
nextToken();
if (curToken.type !== 'parens' || curToken.value !== ')') {
throw new Error('Expected ) after if test expression');
}
先是調用 nextExpression 方法,代碼如下:
// 讀取下一個表達式 function nextExpression () { nextToken(); if (curToken.type === 'identifier') { const identifier = { type: 'Identifier', name: curToken.value, }; stash(); nextToken(); if (curToken.type === 'parens' && curToken.value === '(') { // 如果一個標識符後面緊跟着 ( ,說明是個函數調用表達式 const expr = { type: 'CallExpression', caller: identifier, arguments: [], }; stash(); nextToken(); if (curToken.type === 'parens' && curToken.value === ')') { // 如果下一個符合直接就是 ) ,說明沒有參數 commit(); } else { // 讀取函數調用參數 rewind(); while (i < tokens.length) { // 將下一個表達式加到arguments當中 expr.arguments.push(nextExpression()); nextToken(); // 遇到 ) 結束 if (curToken.type === 'parens' && curToken.value === ')') { break; } // 參數間必須以 , 相間隔 if (curToken.type !== 'comma' && curToken.value !== ',') { throw new Error('Expected , between arguments'); } } } commit(); return expr; } rewind(); return identifier; } if (curToken.type === 'number' || curToken.type === 'string') { // 數字或字符串,說明此處是個常量表達式 const literal = { type: 'Literal', value: eval(curToken.value), }; // 但如果下一個符號是運算符,那麼這就是個雙元運算表達式 stash(); nextToken(); if (curToken.type === 'operator') { commit(); return { type: 'BinaryExpression', left: literal, right: nextExpression(), }; } rewind(); return literal; } if (curToken.type !== 'EOF') { throw new Error('Unexpected token ' + curToken.value); } }
在代碼內部調用 nextToken方法,curToken的值變爲 var curToken = {type: "number", value: "1"};
所以滿足上面的第二個if條件語句了,所以先定義 literal的值,如下:
const literal = {
type: 'Literal',
value: eval(curToken.value),
};
所以
const literal = {
type: 'Literal',
value: 1,
};
然後調用 stash()方法保存當前的的值;
const stashStack = [];
function stash () {
// 暫存當前位置
stashStack.push(i);
}
因此stashStack的值變爲 const stashStack = [-1, 4]; 接着調用 nextToken()方法,因此此時的curToken的值變爲如下:
var curToken = {type: "operator", value: ">"}; 所以它滿足 上面代碼的 if (curToken.type === 'operator') { 這個條件,
因此 會返回
return {
type: 'BinaryExpression',
left: {
type: 'Literal',
value: 1
},
right: nextExpression(),
};
right的值 使用遞歸的方式重新調用 nextExpression 函數。且在返回之前調用了 commit()函數,該函數代碼如下:
function commit () {
// 解析成功,不需要再返回
stashStack.pop();
}
如上函數使用 數組的pop方法,刪除數組的最後一個元素,因此此時的 stashStack 的值變爲 const stashStack = [-1];
如上代碼,剛剛i = 4的時候,再調用 nextToken()方法,因此此時i就等於6了,遞歸調用 nextExpression方法後,再調用nextToken();方法,
因此此時 i 的值變爲8,因此 curToken的值變爲如下;var curToken = {type: "number", value: "0"}; 和上面一樣,還是進入了第二個if
語句代碼內;此時literal的值變爲如下:
const literal = {
type: 'Literal',
value: 0
};
stash(); 調用該方法後,因此 var stashStack = [-1, 8]了,再調用 nextToken(); 方法後,此時 curToken = {type: "parens", value: ")"}; 下面的if語句不滿足,直接調用 rewind()方法; 然後返回 return literal;的值;
rewind方法如下代碼:
function rewind () {
// 解析失敗,回到上一個暫存的位置
i = stashStack.pop();
curToken = tokens[i];
};
我們之前保存的stashStack的值爲 [-1, 8]; 因此使用pop方法後,或者i的值爲8,因此curToken = {type: "number", value: "0"} 了;
最後就返回成這樣的;
return {
type: 'BinaryExpression',
left: {
type: 'Literal',
value: 1
},
right: {
type: 'Literal',
value: 0
}
};
因此 statement.test = {
type: 'BinaryExpression',
left: {
type: 'Literal',
value: 1
},
right: {
type: 'Literal',
value: 0
}
}
我們接着看 nextStatement 語句中的如下代碼;
// 下一個語句是 if 成立時執行的語句
statement.consequent = nextStatement();
又遞歸調用該方法了,因此之前( 的位置是9,因此此時再循環調用,i的值變爲11了,因此 curToken = {type: "brace", value: "{"};
所以就進入了第二個if語句的判斷條件了,如下: if (curToken.type === 'brace' && curToken.value === '{') {
先定義statement的值如下:
// 以 { 開頭表示是個代碼塊
const statement = {
type: 'BlockStatement',
body: [],
};
while (i < tokens.length) {
// 檢查下一個符號是不是 }
stash();
nextToken();
if (curToken.type === 'brace' && curToken.value === '}') {
// } 表示代碼塊的結尾
commit();
break;
}
// 還原到原來的位置,並將解析的下一個語句加到body
rewind();
statement.body.push(nextStatement());
}
// 代碼塊語句解析完畢,返回結果
commit();
return statement;
代碼如上, 此時i = 11; 進入while循環語句了,調用 stash保存當前的值 因此 var stashStack = [-1, 11]; 調用 nextToken方法後,那麼
curToken = {type: "identifier", value: "alert"}; while代碼不滿足要求,因此 調用 rewind()方法返回到 i = 11位置上了,然後繼續
調用nextStatement方法,把返回後的結果 放入 statement.body數組內,調用 nextToken(); 方法後,回到13位置上了,因此此時
var curToken = {type: "identifier", value: "alert"}; 上面的if條件語句都不滿足,所以定義如下變量了。
// 嘗試解析單表達式語句
const statement = {
type: 'ExpressionStatement',
expression: nextExpression(),
};
調用 nextExpression 該方法,該方法如下:
function nextExpression () { nextToken(); if (curToken.type === 'identifier') { const identifier = { type: 'Identifier', name: curToken.value, }; stash(); nextToken(); if (curToken.type === 'parens' && curToken.value === '(') { // 如果一個標識符後面緊跟着 ( ,說明是個函數調用表達式 const expr = { type: 'CallExpression', caller: identifier, arguments: [], }; stash(); nextToken(); if (curToken.type === 'parens' && curToken.value === ')') { // 如果下一個符合直接就是 ) ,說明沒有參數 commit(); } else { // 讀取函數調用參數 rewind(); while (i < tokens.length) { // 將下一個表達式加到arguments當中 expr.arguments.push(nextExpression()); nextToken(); // 遇到 ) 結束 if (curToken.type === 'parens' && curToken.value === ')') { break; } // 參數間必須以 , 相間隔 if (curToken.type !== 'comma' && curToken.value !== ',') { throw new Error('Expected , between arguments'); } } } commit(); return expr; } rewind(); return identifier; } if (curToken.type === 'number' || curToken.type === 'string') { // 數字或字符串,說明此處是個常量表達式 const literal = { type: 'Literal', value: eval(curToken.value), }; // 但如果下一個符號是運算符,那麼這就是個雙元運算表達式 stash(); nextToken(); if (curToken.type === 'operator') { commit(); return { type: 'BinaryExpression', left: literal, right: nextExpression(), }; } rewind(); return literal; } if (curToken.type !== 'EOF') { throw new Error('Unexpected token ' + curToken.value); } }
如上 curToken的值 curToken = {type: "identifier", value: "alert"}; 因此會進入第一個if語句內,identifier的值變爲如下:
const identifier = {
type: 'Identifier',
name: alert,
};
調用 stash()方法,此時 stashStack 的值變爲 var stashStack = [-1, 13]; 再接着調用 nextToken方法, 因此curToken的值變爲如下:
var curToken = {type: "parens", value: "("},因此會進入if條件語句了,如下:
if (curToken.type === 'parens' && curToken.value === '(') {; 的條件判斷了;接着定義expr的變量如下代碼:
// 如果一個標識符後面緊跟着 ( ,說明是個函數調用表達式
const expr = {
type: 'CallExpression',
caller: identifier,
arguments: [],
};
再調用該方法後,stash(); 此時 stashStack的值變爲 [-1, 14], 再調用 nextToken(); 方法後,此時 curToken的值變爲如下:
var curToken = {type: "string", value: "'aa'"}; 再接着執行 if (curToken.type === 'parens' && curToken.value === '(')
代碼麼有找到條件判斷,因此在調用 rewind(); 返回再返回14的位置上,此時 curToken = {type: "parens", value: "("};
因此執行後,緊着如下代碼:
// 讀取函數調用參數
rewind();
while (i < tokens.length) {
// 將下一個表達式加到arguments當中
expr.arguments.push(nextExpression());
nextToken();
// 遇到 ) 結束
if (curToken.type === 'parens' && curToken.value === ')') {
break;
}
// 參數間必須以 , 相間隔
if (curToken.type !== 'comma' && curToken.value !== ',') {
throw new Error('Expected , between arguments');
}
}
, 原理還是和上面一樣,這裏不一一解析了,太煩了;大家可以自己去理解了。
以上就是語義解析的部分主要思路。