visitor = {
let _cache_id = -1;
const visitor = {
VariableDeclaration(path) {
path.node.kind = "var";
},
Statement(path) {
if (t.isBlockStatement(path.node)) return;
path.insertBefore(
t.expressionStatement(
t.yieldExpression(
t.callExpression(t.identifier("_app._stm"), [
meta(path.node, path.scope, "before")
])
)
)
);
const report_before = path.getSibling(path.key - 1);
report_before.skip();
path.insertAfter(
t.expressionStatement(
t.yieldExpression(
t.callExpression(t.identifier("_app._stm"), [
meta(path.node, path.scope, "after")
])
)
)
);
const report_after = path.getSibling(path.key + 1);
report_after.skip();
if (t.isReturnStatement(path)) {
path.scope._hasReturn = true;
const return_value = t.identifier("_return");
path.replaceWith(
t.variableDeclaration("var", [
t.variableDeclarator(return_value, u(path.node.argument))
])
);
path.skip();
path
.get("declarations")[0]
.get("init")
.traverse(visitor); // recurse
report_after.insertAfter(t.returnStatement(return_value));
report_after.getSibling(report_after.key + 1).skip();
}
},
Function(path) {
if (path.node.generator) {
throw new Error("Generator functions are not supported yet");
}
if (t.isArrowFunctionExpression(path)) {
const block = t.isBlockStatement(path.node.body);
path.replaceWith(
t.yieldExpression(
t.callExpression(t.identifier("_app._expr"), [
meta(
path.node,
// No idea why, but for some
// reason the scope of an ArrowFunctionExpression node
// is not the surrounding scope of it as an expression,
// but rather it's own scope. Not what we're looking for!
path.parentPath.scope,
"after"
),
t.callExpression(
t.memberExpression(
t.functionExpression(
null,
path.node.params,
block
? path.node.body
: t.blockStatement([
t.returnStatement(u(path.node.body))
]),
true
),
t.identifier("bind")
),
[t.thisExpression()]
)
])
)
);
path.skip();
const body = path
.get("argument")
.get("arguments")[1]
.get("callee")
.get("object")
.get("body");
if (block) {
body.traverse(visitor); // recurse
} else {
body
.get("body")[0]
.get("argument")
.traverse(visitor); // recurse
}
} else {
path.node.generator = true;
}
},
Expression(path) {
// console.log("E", path.node.type, "@", path.node.loc.start);
// preventloop();
if (t.isCallExpression(path)) {
const contextual = t.isMemberExpression(path.get("callee"));
// Automatically works even if non-contextual,
// because then the absence of the assignment,
// the cached item will undefined
const cached_context = contextual
? t.memberExpression(
t.identifier("_cache"),
t.numericLiteral(++_cache_id),
true
)
: t.identifier("undefined");
const computed = contextual ? path.node.callee.computed : "irrelevant";
const report_before_call = t.yieldExpression(
t.callExpression(t.identifier("_app._expr"), [
meta(path.node, path.scope, "before")
])
);
path.replaceWith(
t.yieldExpression(
t.callExpression(t.identifier("_app._expr"), [
meta(path.node, path.scope, "after"),
t.yieldExpression(
t.callExpression(t.identifier("_app._lift"), [
t.callExpression(
t.memberExpression(
contextual
? t.yieldExpression(
t.callExpression(t.identifier("_app._expr"), [
meta(path.node.callee, path.scope, "after"),
t.memberExpression(
t.assignmentExpression(
"=",
cached_context,
u(path.node.callee.object)
),
computed
? u(path.node.callee.property)
: path.node.callee.property,
computed
)
])
)
: u(path.node.callee),
t.identifier("call")
),
[cached_context, ...path.node.arguments.map(u)]
)
]),
true
)
])
)
);
path.skip();
const expr = path
.get("argument") // yielded value
.get("arguments")[1];
const call = expr.get("argument").get("arguments")[0];
let caller = call.get("callee").get("object");
if (contextual) {
caller = caller.get("argument").get("arguments")[1];
caller
.get("object")
.get("right")
.traverse(visitor); // recurse
if (computed) {
caller.get("property").traverse(visitor); // recurse
}
} else {
caller.traverse(visitor); // recurse
}
call
.get("arguments")
.slice(1)
.forEach(argPath => {
argPath.traverse(visitor); // recurse
});
} else {
path.replaceWith(
t.yieldExpression(
t.callExpression(t.identifier("_app._expr"), [
meta(path.node, path.scope, "after"),
path.node
])
)
);
path.skip();
path
.get("argument")
.get("arguments")[1]
.traverse(visitor); // recurse
}
}
};
return visitor;
}