如何从纯 JavaScript 函数恢复源代码?
How can you recover the source code from a pure JavaScript function?
我所说的 Pure 是指在 λ 演算的意义上,即除了单参数函数和单参数函数调用之外,其主体上不包含任何内容的单参数函数。通过恢复源代码,我的意思是变量重命名。所以,例如,
n2 = function(v0){return function(v1){return v0(v0(v1))}}
console.log(source(n2));
console.log(source(n2(n2)));
应该打印:
function(v0){return function(v0){return v0(v0(v1))}}
function(v0){return function(v0){return v0(v0(v0(v0(v1))))}}
即第一行显示函数n2
的原始源码,第二行显示n2(n2)
求值返回的函数源码。
我已经设法实现如下:
function source(f){
var nextVarId = 0;
return (function recur(f){
if (typeof f === "function"){
if (f.isVarFunc) return f(null);
else {
var varName = "v"+(nextVarId++);
var varFunc = function rec(res){
var varFunc = function(arg){
return arg === null
? "("+res.join(")(")+")"
: rec(res.concat(recur(arg)));
};
varFunc.isVarFunc = true;
return varFunc;
};
varFunc.isVarFunc = true;
var body = f(varFunc([varName]));
body = body.isVarFunc ? body(null) : recur(body);
return "(function("+varName+"){return "+body+"})";
};
} else return f;
})(f);
};
问题是我使用了一些相当丑陋的方法来标记函数,方法是将它们的名称设置为特定值,并且它在多次应用的函数中不起作用(例如 a(b)(b)
).有没有更好的原则性方法来解决这个问题?
编辑:我设法设计了一个似乎在所有情况下都是正确的版本,但它仍然是一个丑陋的不可读的无原则的混乱。
最后,这是一个大大清理了上面的混乱的版本。
// source :: PureFunction -> String
// Evaluates a pure JavaScript function to normal form and returns the
// source code of the resulting function as a string.
function source(fn){
var nextVarId = 0;
return (function normalize(fn){
// This is responsible for collecting the argument list of a bound
// variable. For example, in `function(x){return x(a)(b)(c)}`, it
// collects `a`, `b`, `c` as the arguments of `x`. For that, it
// creates a variadic argumented function that is applied to many
// arguments, collecting them in a closure, until it is applied to
// `null`. When it is, it returns the JS source string for the
// application of the collected argument list.
function application(argList){
var app = function(arg){
return arg === null
? "("+argList.join(")(")+")"
: application(argList.concat(normalize(arg)));
};
app.isApplication = true;
return app;
};
// If we try to normalize an application, we apply
// it to `null` to stop the argument-collecting.
if (fn.isApplication)
return fn(null);
// Otherwise, it is a JavaScript function. We need to create an
// application for its variable, and call the function on it.
// We then normalize the resulting body and return the JS
// source for the function.
else {
var varName = "v"+(nextVarId++);
var body = normalize(fn(application([varName])));
return "(function("+varName+"){return "+body+"})";
};
})(fn);
};
它仍然不完美,但看起来好多了。它按预期工作:
console.log(source(function(a){return function(b){return a(b)}}))
输出:
(function(v0){return (function(v1){return (v0)((v1))})})
虽然我想知道那是多么低效。
我所说的 Pure 是指在 λ 演算的意义上,即除了单参数函数和单参数函数调用之外,其主体上不包含任何内容的单参数函数。通过恢复源代码,我的意思是变量重命名。所以,例如,
n2 = function(v0){return function(v1){return v0(v0(v1))}}
console.log(source(n2));
console.log(source(n2(n2)));
应该打印:
function(v0){return function(v0){return v0(v0(v1))}}
function(v0){return function(v0){return v0(v0(v0(v0(v1))))}}
即第一行显示函数n2
的原始源码,第二行显示n2(n2)
求值返回的函数源码。
我已经设法实现如下:
function source(f){
var nextVarId = 0;
return (function recur(f){
if (typeof f === "function"){
if (f.isVarFunc) return f(null);
else {
var varName = "v"+(nextVarId++);
var varFunc = function rec(res){
var varFunc = function(arg){
return arg === null
? "("+res.join(")(")+")"
: rec(res.concat(recur(arg)));
};
varFunc.isVarFunc = true;
return varFunc;
};
varFunc.isVarFunc = true;
var body = f(varFunc([varName]));
body = body.isVarFunc ? body(null) : recur(body);
return "(function("+varName+"){return "+body+"})";
};
} else return f;
})(f);
};
问题是我使用了一些相当丑陋的方法来标记函数,方法是将它们的名称设置为特定值,并且它在多次应用的函数中不起作用(例如 a(b)(b)
).有没有更好的原则性方法来解决这个问题?
编辑:我设法设计了一个似乎在所有情况下都是正确的版本,但它仍然是一个丑陋的不可读的无原则的混乱。
最后,这是一个大大清理了上面的混乱的版本。
// source :: PureFunction -> String
// Evaluates a pure JavaScript function to normal form and returns the
// source code of the resulting function as a string.
function source(fn){
var nextVarId = 0;
return (function normalize(fn){
// This is responsible for collecting the argument list of a bound
// variable. For example, in `function(x){return x(a)(b)(c)}`, it
// collects `a`, `b`, `c` as the arguments of `x`. For that, it
// creates a variadic argumented function that is applied to many
// arguments, collecting them in a closure, until it is applied to
// `null`. When it is, it returns the JS source string for the
// application of the collected argument list.
function application(argList){
var app = function(arg){
return arg === null
? "("+argList.join(")(")+")"
: application(argList.concat(normalize(arg)));
};
app.isApplication = true;
return app;
};
// If we try to normalize an application, we apply
// it to `null` to stop the argument-collecting.
if (fn.isApplication)
return fn(null);
// Otherwise, it is a JavaScript function. We need to create an
// application for its variable, and call the function on it.
// We then normalize the resulting body and return the JS
// source for the function.
else {
var varName = "v"+(nextVarId++);
var body = normalize(fn(application([varName])));
return "(function("+varName+"){return "+body+"})";
};
})(fn);
};
它仍然不完美,但看起来好多了。它按预期工作:
console.log(source(function(a){return function(b){return a(b)}}))
输出:
(function(v0){return (function(v1){return (v0)((v1))})})
虽然我想知道那是多么低效。