假设您有以下功能
var action = (function () {
var a = 42;
var b = 2;
function action(c) {
return a + 4 * b + c;
}
return action;
}());
// how would you parse action into it's serialized LISP / AST format?
var parsed = parse(action);
是否可以使用一个引用函数action
的函数,并输出LISP格式(lambda (c) (plus (plus 42 (multiply 4 2)) c))
我们可以对action
可能的内容加以限制。
主要问题是给出一个可以使用一系列输入调用的函数,它的源代码是否可以找到用自由变量代替的正确值?
对于上面的例子,你知道a和b是常数,你可以智能地绘制输出几个值并查看模式然后只知道常量是什么。
如何编写一个函数,该函数接受函数引用及其源代码,并为函数生成某种形式的AST,并用任何自由变量替换它们的运行时值。
AST格式的一个例子是代码的LISP等价物。
我基本上想要对函数进行序列化和反序列化,并使其行为相同
应该注意的是,如果将{ a: a, b: b }
传递给分析函数,问题就变得微不足道了。那将是作弊。
我想生成一个纯JavaScript函数的语言无关形式,所以我可以有效地将它传递给C ++,而不需要我的库的用户使用DSL来创建这个函数
让我们假设你有一个数据库驱动程序
var cursor = db.table("my-table").map(function (row) {
return ["foo", row.foo]
})
您希望在运行时确定函数是什么,并将其转换为AST格式,以便您可以使用高效的查询构建器将其转换为SQL或数据库具有的任何查询引擎。
这意味着你不必写:
var cursor = db.table("my-table").map(function (rowQueryObject) {
return db.createArray(db.StringConstant("foo"), rowQueryObject.getProperty("foo"))
})
DB库可以使用查询对象执行哪个函数,并且可以构建不带详细方法的查询对象转换。
答案 0 :(得分:1)
这是一个完整的解决方案(使用可由解析函数访问的变量目录):
var CONSTANTS = {
a: 42,
b: 2,
c: 4
};
function test() {
return a + 4 * b + c;
}
function getReturnStatement(func) {
var funcStr = func.toString();
return (/return\s+(.*?);/g).exec(funcStr)[1];
}
function replaceVariables(expr) {
var current = '';
for (var i = 0; i < expr.length; i += 1) {
while (/[a-zA-Z_$]/.test(expr[i]) && i < expr.length) {
current += expr[i];
i += 1;
}
if (isNumber(CONSTANTS[current])) {
expr = expr.replace(current, CONSTANTS[current]);
}
current = '';
}
return expr;
}
function isNumber(arg) {
return !isNaN(parseInt(arg, 10));
}
function tokenize(expr) {
var tokens = [];
for (var i = 0; i < expr.length; i += 1) {
if (isWhitespace(expr[i])) {
continue;
} else if (isOperator(expr[i])) {
tokens.push({
type: 'operator',
value: expr[i]
});
} else if (isParentheses(expr[i])) {
tokens.push({
type: 'parant',
value: expr[i]
});
} else {
var num = '';
while (isNumber(expr[i]) && i < expr.length) {
num += expr[i];
i += 1;
}
i -= 1;
tokens.push({
type: 'number',
value: parseInt(num, 10)
});
}
}
return tokens;
}
function toPrefix(tokens) {
var operandStack = [],
operatorStack = [],
current,
top = function (stack) {
if (stack) {
return stack[stack.length - 1];
}
return undefined;
};
while (tokens.length) {
current = tokens.pop();
if (current.type === 'number') {
operandStack.push(current);
} else if (current.value === '(' ||
!operatorStack.length ||
(getPrecendence(current.value) >
getPrecendence(top(operatorStack).value))) {
operatorStack.push(current);
} else if (current.value === ')') {
while (top(operatorStack).value !== '(') {
var tempOperator = operatorStack.pop(),
right = operandStack.pop(),
left = operandStack.pop();
operandStack.push(tempOperator, left, right);
}
operatorStack.pop();
} else if (getPrecendence(current.value) <=
getPrecendence(top(operatorStack).value)) {
while (operatorStack.length &&
getPrecendence(current.value) <=
getPrecendence(top(operatorStack).value)) {
tempOperator = operatorStack.pop();
right = operandStack.pop();
left = operandStack.pop();
operandStack.push(tempOperator, left, right);
}
}
}
while (operatorStack.length) {
tempOperator = operatorStack.pop();
right = operandStack.pop();
left = operandStack.pop();
operandStack.push(tempOperator, left, right);
}
return operandStack;
}
function isWhitespace(arg) {
return (/^\s$/).test(arg);
}
function isOperator(arg) {
return (/^[*+\/-]$/).test(arg);
}
function isParentheses(arg) {
return (/^[)(]$/).test(arg);
}
function getPrecendence(operator) {
console.log(operator);
switch (operator) {
case '*':
return 4;
case '/':
return 4;
case '+':
return 2;
case '-':
return 2;
default:
return undefined;
}
}
function getLispString(tokens) {
var result = '';
tokens.forEach(function (e) {
if (e)
switch (e.type) {
case 'number':
result += e.value;
break;
case 'parant':
result += e.value;
break;
case 'operator':
result += getOperator(e.value);
break;
default:
break;
}
result += ' ';
});
return result;
}
function getOperator(operator) {
switch (operator) {
case '+':
return 'plus';
case '*':
return 'multiplicate';
case '-':
return 'minus';
case '\\':
return 'divide';
default:
return undefined;
}
}
var res = getReturnStatement(test);
console.log(res);
res = replaceVariables(res);
console.log(res);
var tokens = tokenize(res);
console.log(tokens);
var prefix = toPrefix(tokens);
console.log(prefix);
console.log(getLispString(prefix));
我刚写了这样的风格可能会有一些问题,但我认为这个想法很清楚。
您可以使用.toString
方法获取函数体。之后,您可以使用正则表达式来匹配return语句
(/return\s+(.*?);/g).exec(funcStr)[1];
请注意,您必须使用分号才能成功匹配!在下一步中,使用CONSTANTS
对象将所有变量转换为数值(我看到您有一些参数,因此您可能需要进行少量修改)。之后,字符串被标记化,以便于解析。在下一步中,中缀表达式将转换为前缀1。在最后一步,我构建了一个字符串,使输出看起来像你需要的那样(+
- plus
,-
- minus
等等。
答案 1 :(得分:0)
由于我不确定在调用方法之后你能够获得该方法的主体,所以这是另一种解决方案:
var a = 42;
var b = 2;
function action(c) {
return a + 4 * b + c;
}
/**
* get the given func body
* after having replaced any available var from the given scope
* by its *real* value
*/
function getFunctionBody(func, scope) {
// get the method body
var body = func.toString().replace(/^.*?{\s*((.|[\r\n])*?)\s*}.*?$/igm, "$1");
var matches = body.match(/[a-z][a-z0-9]*/igm);
// for each potential var
for(var i=0; i<matches.length; i++) {
var potentialVar = matches[i];
var scopedValue = scope[potentialVar];
// if the given scope has the var defined
if(typeof scopedValue !== "undefined") {
// add "..." for strings
if(typeof scopedValue === "string") {
scopedValue = '"' + scopedValue + '"';
}
// replace the var by its scoped value
var regex = new RegExp("([^a-z0-9]+|^)" + potentialVar + "([^a-z0-9]+|$)", "igm");
var replacement = "$1" + scopedValue + "$2";
body = body.replace(regex, replacement);
}
}
return body;
}
// calling
var actionBody = getFunctionBody(action, this);
// log
alert(actionBody);
打印:
return 42 + 4 * 2 + c;
<强> DEMO 强>
然后,您必须实施自己的function toLISP(body)
或您可能需要的任何其他功能。
请注意,它不适用于复杂的范围变量,例如var a = {foo: "bar"}
。