Skip to content

Instantly share code, notes, and snippets.

@probil
Created January 11, 2018 11:34
Show Gist options
  • Save probil/624c8b3f62871c40e122ae7aa75681c6 to your computer and use it in GitHub Desktop.
Save probil/624c8b3f62871c40e122ae7aa75681c6 to your computer and use it in GitHub Desktop.
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