UNPKG

evaljs

Version:

A JavaScript interpreter written in JavaScript

920 lines (826 loc) 22.5 kB
/* jshint esversion: 6 */ /* jshint noyield: true */ "use strict"; //TODO: //- LabeledStatement -> including use in break/continue //- nicer error handling? //-> TESTS //-> BENCHMARKS var parse = require('acorn').parse; var util = require("util"); var EventEmitter = require("events").EventEmitter; function noop() {} function execute(func) { var result = func(); if ('' + result === 'null') { return result; } // FIXME: Convert to yield* if (result !== undefined) { if (result.next) { var iter = result; var res = iter.next(); while (!res.done) { res = iter.next(); } if ('' + res.value === 'null') { return res.value; } if ('' + res.value === 'undefined') { return res.value; } return res.value; } } return result; } function Arguments() { //TODO: add es3 'arguments.callee'? } Arguments.prototype.toString = function () { return '[object Arguments]'; }; function Return(val) { this.value = val; } // need something unique to compare a against. var Break = {}; var Continue = {}; function Environment(globalObjects) { EventEmitter.call(this); if (!Array.isArray(globalObjects)) { globalObjects = [globalObjects]; } var parent; globalObjects.forEach(function (vars) { parent = createVarStore(parent, vars); }); // the topmost store is our current store this._curVarStore = parent; this._curDeclarations = {}; this._globalObj = globalObjects[0]; this._curThis = this._globalObj; this._boundGen = this._gen.bind(this); this.DEBUG = false; this.DELAY = 0; this.STATE = 'running'; } util.inherits(Environment, EventEmitter); function createVarStore(parent, vars) { vars = vars || {}; return { parent: parent, vars: vars }; } Environment.prototype.gen = function (node) { var opts = { 'locations': true }; if (typeof node === 'string') { node = parse(node, opts); } var resp = this._gen(node); addDeclarationsToStore(this._curDeclarations, this._curVarStore); this._curDeclarations = {}; return resp; }; Environment.prototype._gen = function (node) { var closure = ({ BinaryExpression: this._genBinExpr, LogicalExpression: this._genBinExpr, UnaryExpression: this._genUnaryExpr, UpdateExpression: this._genUpdExpr, ObjectExpression: this._genObjExpr, ArrayExpression: this._genArrExpr, CallExpression: this._genCallExpr, NewExpression: this._genNewExpr, MemberExpression: this._genMemExpr, ThisExpression: this._genThisExpr, SequenceExpression: this._genSeqExpr, Literal: this._genLit, Identifier: this._genIdent, AssignmentExpression: this._genAssignExpr, FunctionDeclaration: this._genFuncDecl, VariableDeclaration: this._genVarDecl, BlockStatement: this._genProgram, Program: this._genProgram, ExpressionStatement: this._genExprStmt, EmptyStatement: this._genEmptyStmt, ReturnStatement: this._genRetStmt, FunctionExpression: this._genFuncExpr, IfStatement: this._genIfStmt, ConditionalExpression: this._genCondStmt, ForStatement: this._genLoopStmt, WhileStatement: this._genLoopStmt, DoWhileStatement: this._genDoWhileStmt, ForInStatement: this._genForInStmt, WithStatement: this._genWithStmt, ThrowStatement: this._genThrowStmt, TryStatement: this._genTryStmt, ContinueStatement: this._genContStmt, BreakStatement: this._genBreakStmt, SwitchStatement: this._genSwitchStmt }[node.type] || function () { console.warn("Not implemented yet: " + node.type); return noop; }).call(this, node); if (this.DEBUG) { return function () { var info = 'closure for ' + node.type + ' called'; var line = ((node.loc || {}).start || {}).line; if (line) { info += ' while processing line ' + line; } var resp = closure(); info += '. Result:'; console.log(info, resp); return resp; }; } return closure; }; Environment.prototype._genBinExpr = function (node) { var a = this._gen(node.left); var b = this._gen(node.right); function* callExpr(expr) { var result; if (expr.constructor.name == 'GeneratorFunction') { result = yield* expr(); } else { result = expr(); } return result; } var cmp = { '==': function* () { return (yield* callExpr(a)) == (yield* callExpr(b)); }, '!=': function* () { return (yield* callExpr(a)) != (yield* callExpr(b)); }, '===': function* () { return (yield* callExpr(a)) === (yield* callExpr(b)); }, '!==': function* () { return (yield* callExpr(a)) !== (yield* callExpr(b)); }, '<': function* () { return (yield* callExpr(a)) < (yield* callExpr(b)); }, '<=': function* () { return (yield* callExpr(a)) <= (yield* callExpr(b)); }, '>': function* () { return (yield* callExpr(a)) > (yield* callExpr(b)); }, '>=': function* () { return (yield* callExpr(a)) >= (yield* callExpr(b)); }, '<<': function* () { return (yield* callExpr(a)) << (yield* callExpr(b)); }, '>>': function* () { return (yield* callExpr(a)) >> (yield* callExpr(b)); }, '>>>': function* () { return (yield* callExpr(a)) >>> (yield* callExpr(b)); }, '+': function* () { return (yield* callExpr(a)) + (yield* callExpr(b)); }, '-': function* () { return (yield* callExpr(a)) - (yield* callExpr(b)); }, '*': function* () { return (yield* callExpr(a)) * (yield* callExpr(b)); }, '/': function* () { return (yield* callExpr(a)) / (yield* callExpr(b)); }, '%': function* () { return (yield* callExpr(a)) % (yield* callExpr(b)); }, '|': function* () { return (yield* callExpr(a)) | (yield* callExpr(b)); }, '^': function* () { return (yield* callExpr(a)) ^ (yield* callExpr(b)); }, '&': function* () { return (yield* callExpr(a)) & (yield* callExpr(b)); }, 'in': function* () { return (yield* callExpr(a)) in (yield* callExpr(b)); }, 'instanceof': function* () { return (yield* callExpr(a)) instanceof (yield* callExpr(b)); }, // logic expressions '||': function* () { return (yield* callExpr(a)) || (yield* callExpr(b)); }, '&&': function* () { return (yield* callExpr(a)) && (yield* callExpr(b)); } }[node.operator]; return function () { // FIXME: Convert to yield* var iter = cmp(); var res = iter.next(); while (!res.done) { res = iter.next(); } return res.value; }; }; Environment.prototype._genUnaryExpr = function (node) { if (node.operator === 'delete') { return this._genDelete(node); } var a = this._gen(node.argument); var op = { '-': function () { return -a(); }, '+': function () { return +a(); }, '!': function () { return !a(); }, '~': function () { return ~a(); }, 'typeof': function () { return typeof a(); }, 'void': function () { return void a(); } }[node.operator]; return function () { return op(); }; }; Environment.prototype._genDelete = function (node) { var obj = this._genObj(node.argument); var attr = this._genName(node.argument); return function () { return delete obj()[attr()]; }; }; Environment.prototype._genObjExpr = function (node) { //TODO property.kind: don't assume init when it can also be set/get var self = this; var items = []; node.properties.forEach(function (property) { // object expression keys are static so can be calculated // immediately var key = self._objKey(property.key)(); items.push({ key: key, getVal: self._gen(property.value) }); }); return function () { var result = {}; items.forEach(function (item) { result[item.key] = item.getVal(); }); return result; }; }; Environment.prototype._genArrExpr = function (node) { var items = node.elements.map(this._boundGen); return function () { return items.map(execute); }; }; Environment.prototype._objKey = function (node) { var key; if (node.type === 'Identifier') { key = node.name; } else { key = this._gen(node)(); } return function () { return key; }; }; Environment.prototype._genCallExpr = function (node) { var self = this; var callee; if (node.callee.type === 'MemberExpression') { var obj = self._genObj(node.callee); var name = self._genName(node.callee); callee = function () { var theObj = obj(); return theObj[name()].bind(theObj); }; } else { callee = self._gen(node.callee); } var args = node.arguments.map(self._gen.bind(self)); return function* () { self.emit('line', node.loc.start.line); var c = callee(); if (c === undefined) { return c; } var result; var res; if (c.next) { res = yield* c; result = res.apply(self._globalObj, args.map(execute)); } else { result = c.apply(self._globalObj, args.map(execute)); } if (result !== undefined) { if (result.next) { res = yield* result; return res; } } return result; }; }; Environment.prototype._genNewExpr = function (node) { var callee = this._gen(node.callee); var args = node.arguments.map(this._boundGen); var self = this; return function* () { self.emit('line', node.loc.start.line); var cl = callee(); var ar = args.map(execute); var newObject = Object.create(cl.prototype); var constructor = cl.apply(newObject, ar); yield* constructor; return newObject; }; }; Environment.prototype._genMemExpr = function (node) { var self = this; var obj = this._gen(node.object); var property = this._memExprProperty(node); return function () { self.emit('line', node.loc.start.line); return obj()[property()]; }; }; Environment.prototype._memExprProperty = function (node) { return node.computed ? this._gen(node.property) : this._objKey(node.property); }; Environment.prototype._genThisExpr = function () { var self = this; return function () { return self._curThis; }; }; Environment.prototype._genSeqExpr = function (node) { var exprs = node.expressions.map(this._boundGen); return function () { var result; exprs.forEach(function (expr) { result = expr(); }); return result; }; }; Environment.prototype._genUpdExpr = function (node) { var self = this; var update = { '--true': function (obj, name) { return --obj[name]; }, '--false': function (obj, name) { return obj[name]--; }, '++true': function (obj, name) { return ++obj[name]; }, '++false': function (obj, name) { return obj[name]++; } }[node.operator + node.prefix]; var obj = this._genObj(node.argument); var name = this._genName(node.argument); return function* () { self.emit('line', node.loc.start.line); yield; return update(obj(), name()); }; }; Environment.prototype._genObj = function (node) { if (node.type === 'Identifier') { return this._getVarStore.bind(this, node.name); } else if (node.type === 'MemberExpression') { return this._gen(node.object); } else { console.warn("Unknown _genObj() type: " + node.type); return noop; } }; Environment.prototype._genName = function (node) { if (node.type === 'Identifier') { return function () { return node.name; }; } else if (node.type === 'MemberExpression') { return this._memExprProperty(node); } else { console.warn("Unknown _genName() type: " + node.type); return noop; } }; Environment.prototype._genLit = function (node) { return function () { return node.value; }; }; Environment.prototype._genIdent = function (node) { var self = this; return function () { return self._getVarStore(node.name)[node.name]; }; }; Environment.prototype._getVarStore = function (name) { var store = this._curVarStore; do { if (store.vars.hasOwnProperty(name)) { return store.vars; } } while ((store = store.parent)); // global object as fallback return this._globalObj; }; Environment.prototype._genAssignExpr = function (node) { var self = this; var setter = { '=': function (obj, name, val) { return (obj[name] = val); }, '+=': function (obj, name, val) { return obj[name] += val; }, '-=': function (obj, name, val) { return obj[name] -= val; }, '*=': function (obj, name, val) { return obj[name] *= val; }, '/=': function (obj, name, val) { return obj[name] /= val; }, '%=': function (obj, name, val) { return obj[name] %= val; }, '<<=': function (obj, name, val) { return obj[name] <<= val; }, '>>=': function (obj, name, val) { return obj[name] >>= val; }, '>>>=': function (obj, name, val) { return obj[name] >>>= val; }, '|=': function (obj, name, val) { return obj[name] |= val; }, '^=': function (obj, name, val) { return obj[name] ^= val; }, '&=': function (obj, name, val) { return obj[name] &= val; } }[node.operator]; var obj = this._genObj(node.left); var name = this._genName(node.left); var val = this._gen(node.right); return function* () { self.emit('line', node.left.loc.start.line); var v = val(); if (v !== undefined) { if (v.next) { v = yield* v; } } return setter(obj(), name(), v); }; }; Environment.prototype._genFuncDecl = function (node) { this._curDeclarations[node.id.name] = this._genFuncExpr(node); return function* () { return noop; }; }; Environment.prototype._genVarDecl = function (node) { var assignments = []; for (var i = 0; i < node.declarations.length; i++) { var decl = node.declarations[i]; this._curDeclarations[decl.id.name] = noop; if (decl.init) { assignments.push({ type: 'AssignmentExpression', operator: '=', left: decl.id, right: decl.init }); } } return this._gen({ type: 'BlockStatement', body: assignments }); }; Environment.prototype.getState = function () { return this.STATE; }; Environment.prototype.setState = function (state) { this.STATE = state; }; Environment.prototype._genFuncExpr = function (node) { var self = this; var oldDeclarations = self._curDeclarations; self._curDeclarations = {}; var body = self._gen(node.body); var declarations = self._curDeclarations; self._curDeclarations = oldDeclarations; // reset var store return function () { var parent = self._curVarStore; return function* () { // build arguments object var args = new Arguments(); args.length = arguments.length; for (var i = 0; i < arguments.length; i++) { args[i] = arguments[i]; } // switch interpreter 'stack' var oldStore = self._curVarStore; var oldThis = self._curThis; self._curVarStore = createVarStore(parent); self._curThis = this; addDeclarationsToStore(declarations, self._curVarStore); self._curVarStore.vars.arguments = args; // add function args to var store node.params.forEach(function (param, i) { self._curVarStore.vars[param.name] = args[i]; }); // run function body var result = yield* body(); // switch 'stack' back self._curThis = oldThis; self._curVarStore = oldStore; if (result instanceof Return) { return result.value; } }; }; }; function addDeclarationsToStore(declarations, varStore) { for (var key in declarations) { if (declarations.hasOwnProperty(key) && !varStore.vars.hasOwnProperty(key)) { varStore.vars[key] = declarations[key](); } } } Environment.prototype._genProgram = function (node) { var self = this; var stmtClosures = node.body.map(function (stmt) { return self._gen(stmt); }); return function* () { var result; for (var i = 0; i < stmtClosures.length; i++) { if (stmtClosures[i].constructor.name === 'GeneratorFunction') { result = yield* stmtClosures[i](); yield; } else { result = stmtClosures[i](); yield; } if (result === Break || result === Continue || result instanceof Return) { break; } } //return last return result; }; }; Environment.prototype._genExprStmt = function (node) { return this._gen(node.expression); }; Environment.prototype._genEmptyStmt = function () { return noop; }; Environment.prototype._genRetStmt = function (node) { var self = this; var arg = node.argument ? this._gen(node.argument) : noop; return function () { self.emit('line', node.loc.start.line); return new Return(arg()); }; }; Environment.prototype._genIfStmt = function (node) { var self = this; var test = function () { self.emit('line', node.loc.start.line); return self._gen(node.test)(); }; var consequent = this._gen(node.consequent); var alternate = node.alternate ? this._gen(node.alternate) : function* () { return noop; }; return function* () { var result = test() ? yield* consequent() : yield* alternate(); return result; }; }; Environment.prototype._genCondStmt = function (node) { var self = this; var test = function () { self.emit('line', node.loc.start.line); return self._gen(node.test)(); }; var consequent = this._gen(node.consequent); var alternate = node.alternate ? this._gen(node.alternate) : noop; return function () { return test() ? consequent() : alternate(); }; }; Environment.prototype._genLoopStmt = function (node, body) { var self = this; var init = node.init ? this._gen(node.init) : function* () { return noop; }; var test = node.test ? function* () { self.emit('line', node.loc.start.line); return self._gen(node.test)(); } : function* () { return true; }; var update = node.update ? this._gen(node.update) : function* () { return noop; }; body = body || this._gen(node.body); return function* () { self.emit('line', node.loc.start.line); var resp; for (yield* init(); yield* test(); yield* update()) { var newResp = yield* body(); if (newResp === Break) { break; } if (newResp === Continue) { continue; } resp = newResp; if (newResp instanceof Return) { break; } } return resp; }; }; Environment.prototype._genDoWhileStmt = function (node) { var body = this._gen(node.body); var loop = this._genLoopStmt(node, body); return function* () { yield* body(); yield* loop(); }; }; Environment.prototype._genForInStmt = function (node) { var self = this; var right = self._gen(node.right); var body = self._gen(node.body); var left = node.left; if (left.type === 'VariableDeclaration') { self._curDeclarations[left.declarations[0].id.name] = noop; left = left.declarations[0].id; } return function* () { self.emit('line', node.loc.start.line); var resp; for (var x in right()) { self.emit('line', node.loc.start.line); yield* self._genAssignExpr({ operator: '=', left: left, right: { type: 'Literal', value: x } })(); resp = yield* body(); } return resp; }; }; Environment.prototype._genWithStmt = function (node) { var self = this; var obj = self._gen(node.object); var body = self._gen(node.body); return function* () { self._curVarStore = createVarStore(self._curVarStore, obj()); var result = yield* body(); self._curVarStore = self._curVarStore.parent; return result; }; }; Environment.prototype._genThrowStmt = function (node) { var arg = this._gen(node.argument); return function () { throw arg(); }; }; Environment.prototype._genTryStmt = function (node) { var block = this._gen(node.block); var handler = this._genCatchHandler(node.handler); var finalizer = node.finalizer ? this._gen(node.finalizer) : function (x) { return x; }; return function () { try { return finalizer(block()); } catch (err) { return finalizer(handler(err)); } }; }; Environment.prototype._genCatchHandler = function (node) { if (!node) { return noop; } var self = this; var body = self._gen(node.body); return function (err) { var old = self._curVarStore.vars[node.param.name]; self._curVarStore.vars[node.param.name] = err; var resp = body(); self._curVarStore.vars[node.param.name] = old; return resp; }; }; Environment.prototype._genContStmt = function () { return function () { return Continue; }; }; Environment.prototype._genBreakStmt = function () { return function () { return Break; }; }; Environment.prototype._genSwitchStmt = function (node) { var self = this; var discriminant = self._gen(node.discriminant); var cases = node.cases.map(function (curCase) { return { test: curCase.test ? self._gen(curCase.test) : null, code: self._genProgram({ body: curCase.consequent }) }; }); return function* () { var foundMatch = false; var discriminantVal = discriminant(); var resp, defaultCase; for (var i = 0; i < cases.length; i++) { var curCase = cases[i]; if (!foundMatch) { if (!curCase.test) { defaultCase = curCase; continue; } if (discriminantVal !== curCase.test()) { continue; } foundMatch = true; } // foundMatch is guaranteed to be true here var newResp = yield* curCase.code(); if (newResp === Break) { return resp; } resp = newResp; if (resp === Continue || resp instanceof Return) { return resp; } } if (!foundMatch && defaultCase) { return yield* defaultCase.code(); } }; }; exports.Environment = Environment; exports.evaluate = function (code) { var env = new Environment(global); var iterator = env.gen(code)(); var result = iterator.next(); while (!result.done) { result = iterator.next(); } return result.value; }; //console.log(exports.evaluate("1 + 1"));