Public
Edited
Jan 14, 2020
2 forks
Importers
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
visitor = {
let _cache_id = -1;

const visitor = {
VariableDeclaration(path) {
// This is an ugly trick to get the scope data "out" easily
// If we leave the variables as let and const, then we run into non-initialized errors
// when reporting back the scope's contents.
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;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more