用熟悉 javascript 代替 C++ 來寫解析器

動機

準備寫一個 javascript 的解析器,將 javascript 解析成爲一個抽象語法樹(AST),準備着手寫這個多半是出自興趣,要寫代碼首先就須要選擇一個語言去實現,本來也是比較理想去用 c++,可是如今時機還不算成熟,對 c++ 語言還不熟練,相比於 c++ 我更熟悉 javascript 來寫一個解析器,這裏不會依賴於其餘 javascript 的庫,基本是白手起家。javascript

001.png

即便是處於興趣也好,娛樂也好,也但願把這件事作好html

搭建項目

002.png

項目你們很簡單,只須要安裝當前穩定版本 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 ifif 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)
}

總結

創做不易,若是對你們有所幫助,但願你們點贊支持,有什麼問題也能夠在評論區裏討論😄~

若是你以爲這篇文章對你有點用的話,麻煩請給咱們的開源項目點點star:   http://github.crmeb.net/u/defu       不勝感激 !
 
來自 「開源獨尊 」 ,連接:   https://ym.baisou.ltd/post/843.html
相關文章
相關標籤/搜索