Created
January 11, 2018 11:34
-
-
Save probil/624c8b3f62871c40e122ae7aa75681c6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const acorn = require("acorn") | |
const walk = require("acorn/dist/walk") | |
var getTarget = function (field, st, cb) { | |
// next lets see its an identifier, if so, its right on one of the state objects | |
var isId = field.type == 'Identifier'; | |
var name = field.name; | |
var value, obj, prop; | |
var isObj = field.hasOwnProperty('object') && field.hasOwnProperty('property'); | |
if (isId) { | |
// we keep track of how many we found to avoid duplicates in different objects | |
var found = 0; | |
var len = st.refs && st.refs.length; | |
for (var i = 0; i < len; i++) { | |
var ref = st.refs[i]; | |
if (ref && ref[name] != undefined) { | |
obj = ref; | |
prop = name; | |
value = ref[name]; | |
found++; | |
} | |
} | |
if (found > 1) throw new Error('environment has duplicate keys named ' + name); | |
return cb(value, obj, prop); | |
} | |
if (isObj) { | |
// if we didn't find it we'll try | |
obj = field.object && field.object.value; | |
if (!obj) { | |
var found = 0; | |
var len = st.refs && st.refs.length; | |
for (var i = 0; i < len; i++) { | |
var ref = st.refs[i]; | |
if (ref && ref[field.object.name] != undefined) { | |
obj = ref[field.object.name]; | |
found++; | |
} | |
} | |
if (found > 1) throw new Error('environment has duplicate keys named ' + name); | |
} | |
prop = field.property && (field.property.name || field.property.value); | |
if (obj) { | |
value = obj[prop]; | |
} else { | |
throw new Error('no such object ' + obj) | |
} | |
return cb(value, obj, prop); | |
} | |
// see if we set a value on the node from a prior operation. | |
if (field.hasOwnProperty('value')) return cb(field.value); | |
} | |
var handlers = { | |
ExpressionStatement: function (node, st) { | |
getTarget(node.expression, st, function (v) { | |
st.result = v; | |
}) | |
}, | |
ObjectExpression: function (node, st) { | |
var obj = {}; | |
if (node.properties) { | |
for (var i = 0; i < node.properties.length; i++) { | |
var val = node.properties[i].value; | |
obj[node.properties[i].key.name] = val && val.value; | |
} | |
} | |
node.value = obj; | |
}, | |
SequenceExpression: function (node, st) { | |
node.value = (node.expressions && node.expressions.length) ? node.expressions[node.expressions.length - 1].value : null; | |
}, | |
AssignmentExpression: function (node, st) { | |
// var operator = node.operator, prefix = node.prefix, obj = node.left.object && node.left.object.value, | |
// prop = node.left.property && node.left.property.name || node.left.name, val = node.right.value; | |
var operator = node.operator, obj, prop, val; | |
getTarget(node.left, st, function (v, o, p) { | |
obj = o; | |
prop = p; | |
}); | |
getTarget(node.right, st, function (v, o, p) { | |
val = v; | |
}); | |
if (!obj) | |
throw new Error('cannot operate on variables like "' + prop + '" must be an object in the environment'); | |
if (operator == '=') { | |
obj[prop] = val; | |
node.value = val; | |
} else { | |
throw new Error('operator ' + operator + ' not recognized for assignment operation') | |
} | |
}, | |
ConditionalExpression: function (node, st) { | |
node.value = (node.test.value) ? node.consequent.value : node.alternate.value; | |
}, | |
UpdateExpression: function (node, st) { | |
var obj, prop, value, operator = node.operator, prefix = node.prefix; | |
getTarget(node.argument, st, function (val, o, p) { | |
obj = o; | |
prop = p; | |
value = val; | |
}); | |
var newVal = value; | |
switch (operator) { | |
case '++': | |
newVal = ++obj[prop]; | |
break; | |
case '--': | |
newVal = --obj[prop]; | |
break; | |
default : | |
throw new Error('update using operator ' + operator + ' not supported'); | |
} | |
node.value = (prefix) ? newVal : value; | |
}, | |
UnaryExpression: function (node, st) { | |
var obj, prop, value, operator = node.operator; | |
getTarget(node.argument, st, function (val, o, p) { | |
obj = o; | |
prop = p; | |
value = val; | |
}); | |
var newVal = value; | |
switch (operator) { | |
case '!': | |
newVal = !obj[prop]; | |
break; | |
case '~': | |
newVal = ~obj[prop]; | |
break; | |
case '-': | |
newVal = -obj[prop]; | |
break; | |
case 'empty': | |
newVal = obj === null || prop === null || obj === undefined || prop === undefined || obj[prop] === null || obj[prop] === undefined || obj[prop].length === 0; | |
break; | |
default : | |
throw new Error('unary using operator ' + operator + ' not supported'); | |
} | |
node.value = newVal; | |
}, | |
MemberExpression: function (node, st) { | |
// if (!node.value){ | |
// look it up in state | |
// var obj = st[node.object.name]; | |
var obj, prop, value, operator = node.operator, prefix = node.prefix; | |
getTarget(node, st, function (val, o, p) { | |
obj = o; | |
prop = p; | |
value = val; | |
}); | |
node.object.value = obj; | |
node.property.value = prop; | |
if (obj) | |
node.value = obj[prop]; | |
// } | |
}, | |
Identifier: function (node, st) { | |
// if (!node.value){ | |
// look it up in state | |
// var obj = st[node.object.name]; | |
var obj, prop, value, operator = node.operator, prefix = node.prefix; | |
getTarget(node, st, function (val, o, p) { | |
obj = o; | |
prop = p; | |
value = val; | |
}); | |
if (!node.object) | |
node.object = {}; | |
node.object.value = obj; | |
if (!node.property) | |
node.property = {}; | |
node.property.value = prop; | |
if (obj) | |
node.value = obj[prop]; | |
// } | |
}, | |
CallExpression: function (node, st) { | |
// var func = node.callee.name; | |
// var found = 0; | |
var args = []; | |
if (node.args) { | |
for (var i = 0; i < node.args.length; i++) { | |
args.push(node.args[i].value); | |
} | |
} | |
var obj, prop; | |
getTarget(node.callee, st, function (val, o, p) { | |
obj = o; | |
prop = p; | |
}) | |
if (typeof obj[prop] == 'function') { | |
node.value = obj[prop].apply(obj, args) | |
} else { | |
throw new Error('property ' + prop + ' on ' + node.callee.name + ' is not a function'); | |
} | |
}, | |
ArrayExpression: function (node, st) { | |
var arr = []; | |
if (node.elements) { | |
for (var i = 0; i < node.elements.length; i++) { | |
arr.push(node.elements[i].value); | |
} | |
} | |
node.value = arr; | |
}, | |
LogicalExpression: function (node, st) { | |
var left, right, operator = node.operator, result = null; | |
getTarget(node.left, st, function (val) { | |
left = val; | |
}) | |
getTarget(node.right, st, function (val) { | |
right = val; | |
}); | |
switch (operator) { | |
case 'and': | |
case '&&': | |
result = left && right; | |
break; | |
case 'or': | |
case '||': | |
result = left || right; | |
break; | |
} | |
node.value = result; | |
}, | |
BinaryExpression: function (node, st) { | |
var left, right, operator = node.operator, result = null; | |
getTarget(node.left, st, function (val) { | |
left = val; | |
}) | |
getTarget(node.right, st, function (val) { | |
right = val; | |
}); | |
switch (operator) { | |
// case '=': | |
// result = left = right; | |
// break; | |
case '+': | |
result = left + right; | |
break; | |
case '-': | |
result = left - right; | |
break; | |
case '*': | |
result = left * right; | |
break; | |
case 'div': | |
case '/': | |
result = left / right; | |
break; | |
case 'mod': | |
case '%': | |
result = left % right; | |
break; | |
case 'eq': | |
case '==': | |
result = left == right; | |
break; | |
case '===': | |
result = left === right; | |
break; | |
case 'ne': | |
case '!=': | |
result = left != right; | |
break; | |
case '!==': | |
result = left !== right; | |
break; | |
case 'gt': | |
case '>': | |
result = left > right; | |
break; | |
case 'lt': | |
case '<': | |
result = left < right; | |
break; | |
case 'ge': | |
case '>=': | |
result = left >= right; | |
break; | |
case 'le': | |
case '<=': | |
result = left <= right; | |
break; | |
case '&': | |
result = left & right; | |
break; | |
case '|': | |
result = left | right; | |
break; | |
case '^': | |
result = left ^ right; | |
break; | |
case '>>': | |
result = left >> right; | |
break; | |
case '>>>': | |
result = left >>> right; | |
break; | |
case '<<': | |
result = left << right; | |
break; | |
case 'and': | |
case '&&': | |
result = left && right; | |
break; | |
case 'or': | |
case '||': | |
result = left || right; | |
break; | |
default: | |
throw new Error('operator ' + operator + ' not supported'); | |
} | |
node.value = result; | |
} | |
}; | |
const run = (p, obj1, obj2) => { | |
var args = Array.prototype.slice.call(arguments, 1); | |
var input = {refs: args}; | |
walk.simple(p, handlers, null, input); | |
return input.result; | |
} | |
const parsed = acorn.parse("1 + 1"); | |
// helper function for traversal | |
run(parsed); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment