Skip to content

Commit

Permalink
testing custom eval
Browse files Browse the repository at this point in the history
  • Loading branch information
EmileSonneveld committed Jul 27, 2022
1 parent 8fc98aa commit 9b8bfc1
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ dist/
*.log
*.tgz
*.sh
*.sublime-workspace
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@
},
"rules": {
"key-spacing": 0,
"indent": ["error", 2, { "SwitchCase": 1 }]
"indent": [
"error",
2,
{
"SwitchCase": 1
}
]
}
},
"dependencies": {},
"dependencies": {
"acorn": "^8.8.0"
},
"devDependencies": {
"@babel/cli": "^7.8.3",
"@babel/core": "^7.8.3",
Expand Down
131 changes: 131 additions & 0 deletions src/custom-eval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
let acorn = require("acorn");
const utils = require("../src/internal/utils");
const world = utils.world
world.acorn = acorn


// derived from https://blog.bitsrc.io/build-a-js-interpreter-in-javascript-using-acorn-as-a-parser-5487bb53390c
function CustomEval(str) {
const globalScope = new Map()
const whiteListFunctions = new Set([console.log, console.time, console.timeEnd])

const programNode = acorn.parse(str, {
ecmaVersion: 2020
})

function visitVariableDeclaration(node) {
const nodeKind = node.kind
return visitNodes(node.declarations)
}

function visitVariableDeclarator(node) {
console.assert(node.id.type === "Identifier")
// TODO: var, const, let
const name = node.id.name
const value = visitNode(node.init)
globalScope.set(name, value)
return value
}

function visitAssignmentExpression(node) {
console.assert(node.left.type === "Identifier")
const name = node.left.name
const value = visitNode(node.right)
if (!globalScope.has(name))
throw new Error("Varibable '" + name + "' not found to assign to.");
globalScope.set(name, value)
return value
}

function visitIdentifier(node) {
const name = node.name
if (globalScope.get(name))
return globalScope.get(name)
const descs = Object.getOwnPropertyDescriptors(world)
if (descs.get == null) // Make exception for window.window?
return world[name]
throw new ReferenceError(name + "is not defined")
}

function visitLiteral(node) {
return node.value
}

function visitBinaryExpression(node) {
const leftNode = visitNode(node.left)
const operator = node.operator
const rightNode = visitNode(node.right)
switch (operator) {
case "+":
return leftNode + rightNode
case "-":
return leftNode - rightNode
case "/":
return leftNode / rightNode
case "*":
return leftNode * rightNode
}
}

function evalArgs(nodeArgs) {
let g = []
for (const nodeArg of nodeArgs) {
g.push(visitNode(nodeArg))
}
return g
}

function visitCallExpression(node) {
const callee = visitNode(node.callee)
const _arguments = evalArgs(node.arguments)
if (whiteListFunctions.has(callee))
return callee(..._arguments)
throw Error("TODO: Allow self defined functions")
}

function visitNode(node) {
console.log(node.type)
switch (node.type) {
case 'VariableDeclaration':
return visitVariableDeclaration(node)
case 'VariableDeclarator':
return visitVariableDeclarator(node)
case 'Literal':
return visitLiteral(node)
case 'Identifier':
return visitIdentifier(node)
case 'BinaryExpression':
return visitBinaryExpression(node)
case "CallExpression":
return visitCallExpression(node)
case "ExpressionStatement":
return visitNode(node.expression)
case "MemberExpression":
const obj = visitNode(node.object)
const descs = Object.getOwnPropertyDescriptors(obj)
if (descs.get == null) //
return obj[node.property.name]
throw Error("TODO: also interpret getters")
case "AssignmentExpression":
return visitAssignmentExpression(node)
default:
throw Error("Not implemented yet: " + node.type)
}
}

function visitNodes(nodes) {
for (const node of nodes) {
visitNode(node)
}
}
let lastResult = undefined
for (const node of programNode.body) {
lastResult = visitNode(node)
}
return lastResult
}
world.CustomEval = CustomEval

module.exports = {
CustomEval,
}
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

'use strict'

const customEval = require('./custom-eval')
const utils = require('./internal/utils')
const world = utils.world
const Ref = require('./internal/reference')
Expand Down
31 changes: 31 additions & 0 deletions test/custom-eval.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint no-new-func: off */
'use strict'

// npm run test test/custom-eval.test.js

const assert = require('assert')
const src = require('../src')
const {search} = require('../src/search')
const {expect} = require("chai");
const {createReadStream} = require("fs");
const path = require("path");
const {CustomEval} = require("../src/custom-eval");
const serialize = src.serialize

describe("customEval test", () => {
it("simple math", () => {
assert.equal(CustomEval("2+3"), 5)
})
it("declare and return", () => {
assert.equal(CustomEval("var test = 4; test;"), 4)
})
it("declare and return. const", () => {
assert.equal(CustomEval("const test = 4; test;"), 4)
})
it("declare, change and return", () => {
assert.equal(CustomEval("var test = 4; test = 5; test;"), 5)
})
it("simple math", () => {
CustomEval("console.log('hello')")
})
})

0 comments on commit 9b8bfc1

Please sign in to comment.