基於JavaScript的簡單解釋器實現(二)——函數解析與執行

前言

昨晚奮鬥了一下,終於把這題了解了。今天完善了一下代碼,把剩下的部分放上來。目前剩下的有兩個主要模塊即函數解析與函數執行,以及兩個小模塊即運算符執行和變量解析。
題目地址:http://www.codewars.com/kata/52ffcfa4aff455b3c2000750/train/javascript
github地址:https://github.com/woodensail/SimpleInteractiveInterpreter
前文地址:http://segmentfault.com/a/1190000004044789
本文地址:http://segmentfault.com/a/1190000004047915javascript

函數解析

var index = tokens.indexOf('=>'), paramObj = {}, params = [], fnName = tokens[1];

初始化參數,paramObj用於統計函數體中用到的參數,params爲形參列表,index爲函數運算符的位置,fnName爲函數名java


if (this.vars[fnName] !== void 0) {
    throw 'name conflicting'
}

若是全局變量中存在該名稱的變量,則拋出異常git


for (var i = 2; i < index; i++) {
    if (paramObj[tokens[i]]) {
        throw 'param conflicting'
    }
    paramObj[tokens[i]] = 1;
    params.push(tokens[i]);
}

統計形參,若是同名的形參則拋出異常。github


var result = this.expressionParser(tokens.slice(index + 1));
var syntaxTree = result[0], varList = result[1];
varList.forEach(function (v) {
    if (!paramObj[v]) {
        throw 'nonexistent param'
    }
});
this.functions[fnName] = {params: params, syntaxTree: syntaxTree}

調用表達式解析器解析函數體部分。檢查函數體中用到的參數,若是存在形參列表中不存在的參數則拋出異常。
最後將該函數存入函數表。express

變量解析

Interpreter.prototype.extractValue = function (key, scope) {
    scope = scope || {};
    var value = scope[key];
    if (value === void 0) {
        value = this.vars[key];
    }
    if (value === void 0) {
        value = key;
    }
    if ('number' === typeof value) {
        return value;
    }
    throw 'nonexistent var';
};

按照就優先級分別嘗試提取做用域中的變量和全局變量以及key自身。提取完畢後若value不爲number則所請求的值不存在。segmentfault

運算符實現

Interpreter.prototype.add = function (x, y, scope) {
    return this.extractValue(x, scope) + this.extractValue(y, scope);
};
Interpreter.prototype.sub = function (x, y, scope) {
    return this.extractValue(x, scope) - this.extractValue(y, scope);
};
Interpreter.prototype.mul = function (x, y, scope) {
    return this.extractValue(x, scope) * this.extractValue(y, scope);
};
Interpreter.prototype.div = function (x, y, scope) {
    return this.extractValue(x, scope) / this.extractValue(y, scope);
};
Interpreter.prototype.mod = function (x, y, scope) {
    return this.extractValue(x, scope) % this.extractValue(y, scope);
};
Interpreter.prototype.assign = function (x, y, scope) {
    var value = this.extractValue(y, scope);
    if (scope.x !== void 0) {
        return scope[x] = value;
    } else if ('number' === typeof x) {
        throw 'assign to lValue'
    } else if (!this.functions[x]) {
        return this.vars[x] = value;
    }
    throw 'name conflicting'
};

加減乘除模沒什麼特殊的就是解析變量後運算而後返回結果便可。
賦值語句須要對被賦值變量進行判斷,若是當前函數做用域中有該變量則賦值後返回,若是被賦值對象爲數字,則拋出左值異常。若是函數表中不存在對應函數則存入全局變量,不然拋出重名異常。app

函數執行

函數執行使用後續遍歷的方式來遍歷語法樹。先依次計算每一個參數的結果後,再用得到的結果集執行根節點。函數

Interpreter.prototype.exec = function (syntaxTree, scope) {
    scope = scope || {};
    ……
};

形參爲語法樹和做用域。若未指定做用域則新建空做用域。this


for (var i = 1; i < syntaxTree.length; i++) {
    if (syntaxTree[i] instanceof Array) {
        syntaxTree[i] = this.exec(syntaxTree[i], scope);
    }
}

對於每個子節點,若其爲函數則遞歸調用執行函數。這一步執行完畢後當前參數列表中應該只存在變量或數字當即量。spa


if (this.native[name]) {
    params = syntaxTree.slice(1);
    params.push(scope);
    return this.native[name].apply(this, params);
}

若是當前方法是運算符方法,則調用該運算符的執行函數,並返回結果


else if (this.functions[name]) {
    var fun = this.functions[name];
    params = {};

    fun.params.forEach(function (key, i) {
        var k = syntaxTree[i + 1];
        params[key] = _this.extractValue(k, scope);
    });

    return this.exec(fun.syntaxTree, params);
}

若是當前方法是函數,則解析全部形參的值後生產函數做用域,並以改做用域執行當前函數。


else {
    return this.extractValue(syntaxTree, scope);
}

若是不是以上任一種,則當前執行的語句爲數據,直接提取後返回。

總結

一個基本的解釋器就算是完成了,有些沒有技術含量的銜接代碼我沒有貼上來,你們能夠去git上看。這個解釋器再加上輸入輸出部分就能夠構成一個REPL了。順便,曬個AC圖。
圖片描述

相關文章
相關標籤/搜索