動機
準備寫一個 javascript 的解析器,將 javascript 解析成爲一個抽象語法樹(AST),準備着手寫這個多半是出自興趣,要寫代碼首先就須要選擇一個語言去實現,本來也是比較理想去用 c++,可是如今時機還不算成熟,對 c++ 語言還不熟練,相比於 c++ 我更熟悉 javascript 來寫一個解析器,這裏不會依賴於其餘 javascript 的庫,基本是白手起家。javascript
即便是處於興趣也好,娛樂也好,也但願把這件事作好html
搭建項目
項目你們很簡單,只須要安裝當前穩定版本 node 建立一個文件夾後,在文件夾下運行以下命令便可java
npm init -y
複製代碼
完成後會在項目目錄下,自動建立一個package.json
文件,注意這裏進行node
{ "name": "implementation_parser_with_js", "type":"module", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } 複製代碼
先去實現 lexer
編譯的第一個步驟稱爲 Lexer, 詞法分析,其功能是將文本輸入轉爲多個 tokens,接下來主要就是去實現這部分代碼功能。咱們將把每一個識別出的 token 標記一個類型c++
const input = ""; function lexer(str){ } console.log(lexer(input)); 複製代碼
先去實現一個 lexer
一切從簡單開始,input
就是讀取 code,咱們先以簡單開始,用於一個空字符串""
做爲輸入。git
const input = ""; function lexer(str){ let c = str[0]; if(c === undefined){ return { type: 'EOF' } } } console.log(lexer(input)); 複製代碼
這裏輸入代碼爲空input=""
,lexer 函數讀取 code 而後逐一去解析,當解析到字符串爲空也就是說明已經讀取文件完畢,這裏返回的 token 的類型(type)就是 EOF
.github
解析數值型
這裏咱們仍是從簡單起見,假設輸入爲input="7"
, 而後判讀當前字符是否爲 7,若是爲數值型 7 這返回一個 token。npm
const input = "7"; function* lexer(str){ for (let cursor = 0; cursor <= str.length; cursor++) { const char = str[cursor]; if(char === undefined){ yield { type: 'EOF' } }else if(char === '7'){ yield{ type:'number', value:7 } } } } for(const token of lexer(input)){ console.log(token) } // console.log(lexer(input)); 複製代碼
輸出類型爲 number
值爲 7 的 token,表示識別出一個數值類型的 token。json
{ type: 'number', value: 7 } { type: 'EOF' } 複製代碼
對比 if else if
和 if if
對於分支語句咱們一般有幾種選擇,第一種也就是咱們在學習 javascript 第一個接觸分支語句 if ... else if ...
也能夠用於 if
羅列來進行分支判斷,固然也能夠用switch
, 這裏就不討論 switch
結構比較複雜,也不推薦,就是看一看用 if ... else if ...
仍是用 if
羅列進行分支結構。函數
function* lexer(str){ for (let cursor = 0; cursor <= str.length; cursor++) { const char = str[cursor]; if( trace("checking undefine",(char === undefined))){ yield { type: 'EOF' } } if (trace("checking 7",(char === '7'))){ yield{ type:'number', value:7 } } } } for(const token of lexer(input)){ console.log(token) } 複製代碼
簡單地去實現一個方法語言輸出程序走到某一個分支後輸出內容
function trace(name,v){ console.log(name); return v; } 複製代碼
checking undefine checking 7 { type: 'number', value: 7 } checking undefine checking 7 { type: 'number', value: 7 } checking undefine checking 7 { type: 'number', value: 7 } checking undefine checking 7 { type: 'number', value: 7 } checking undefine checking 7 { type: 'number', value: 7 } checking undefine { type: 'EOF' } checking 7 複製代碼
對於if
·和 if
這種形式,輸出每一次都會確認兩次,這是由於 yield
對每一個 if 語句都進檢查,而用 if... else if
若是將對數值型檢查放在前面以下
const input = "77777"; function trace(name,v){ console.log(name); return v; } function* lexer(str){ for (let cursor = 0; cursor <= str.length; cursor++) { const char = str[cursor]; if (trace("checking 7",(char === '7'))){ yield{ type:'number', value:7 } }else if( trace("checking undefine",(char === undefined))){ yield { type: 'EOF' } } } } for(const token of lexer(input)){ console.log(token) } 複製代碼
這樣作的好處顯而易見,這一次對於 undefine 和 7 檢查,只進行一次檢查
checking 7 { type: 'number', value: 7 } checking 7 { type: 'number', value: 7 } checking 7 { type: 'number', value: 7 } checking 7 { type: 'number', value: 7 } checking 7 { type: 'number', value: 7 } checking 7 checking undefines { type: 'EOF' } 複製代碼
異常處理
在解析過程當沒有批評的狀況這應該拋出一個異常,這裏異常類型SyntaxError
const input = "78"; function* lexer(str){ for (let cursor = 0; cursor <= str.length; cursor++) { const char = str[cursor]; if( char === '7'){ yield{ type:'number', value:7 } }else if (char === undefined){ yield { type: 'EOF' } }else{ throw new SyntaxError(`unexpected character "${char}"`) } } } for(const token of lexer(input)){ console.log(token) } 複製代碼
SyntaxError: unexpected character "8" 複製代碼
一般咱們還須要給出
const input = "777"; function trace(name,v){ console.log(name); return v; } function* lexer(str){ for (let cursor = 0; cursor <= str.length; cursor++) { let char = str[cursor]; function number(){ let value = "" for(; cursor <= str.length; cursor++){ char = str[cursor]; if(char === '7'){ //TODO value +=7 }else{ break; } } return { type:'number', value, } } if( char === '7'){ yield number() }else if (char === undefined){ yield { type: 'EOF', // begin:cursor, // end:cursor+1, } }else{ throw new SyntaxError(`unexpected character "${char}" at ${cursor+1}`) } } } for(const token of lexer(input)){ console.log(token) } 複製代碼
整理代碼
對代碼進行整理,將number
方法提取出來,隨後咱們不只限於數值型 token 還有字符串、對象等等類型,並且將數值型處理爲一個總體 777
因此在 number
函數加入一個內循環,內循環將同一類型的值記做一個 token 返回。到如今,程序變得有點複雜,
- 首先將 cursor 和 char 兩個變量移除 for 循環之外來進行初始化
- 在 number 中進行內循環,若是類型仍是 7(數值型)將其追加到 value 上,在內循環會將
cursor
增長 - 爲拋出語法的異常語句添加定位信息
const input = "777"; function trace(name,v){ console.log(name); return v; } function* lexer(str){ let cursor = 0; let char = undefined; function number(){ let value = "" for(; cursor <= str.length; cursor++){ char = str[cursor]; if(char === '7'){ //TODO value += char }else{ break; } } return { type:'number', value, } } for (;cursor < str.length; ) { const token = number() if( token ){ yield token // }else if (char === undefined){ // yield { // type: 'EOF', // } }else{ throw new SyntaxError(`unexpected character "${char}" at ${cursor+1}`) } } } for(const token of lexer(input)){ console.log(token) }
總結
創做不易,若是對你們有所幫助,但願你們點贊支持,有什麼問題也能夠在評論區裏討論😄~