Public
Edited
Jul 20, 2019
2 forks
8 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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function drawContext(context) {
const { scopes, objects, currentScope } = context;
function drawValue(value) {
if (typeof value === "string") return `"${value}"`;
if (typeof value === "object" && "object_ref" in value) return `object #${value.object_ref}`;
return value;
}
function drawVar({ name, kind, value }) {
return `<div class="var">
<span class="kind">${kind}</span>
${name || ""}
${value !== undefined ? `${kind === "return" ? `` : `= `}${drawValue(value)}` : ``}
</div>`;
}
function drawScope({ parent, children, variables }, ref) {
const current = ref === currentScope;
const vars = Object.values(variables);
return `<div><div class="scope ${current ? `current` : ``}">
<div class="vars">
${vars.map(drawVar).join("")}
${variables[Return.symbol] ? drawVar(variables[Return.symbol]) : ``}
</div>
${children.length > 0 ? `<div class="scopes">${children.map(ref => drawScope(scopes[ref], ref)).join("")}</div>` : ``}
</div></div>`;
}
return drawScope(scopes[0], 0);
}
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
execute = {
const executors = {};

function makeSummaryReporter () {
let i = 0;
return function (context, info) {
return `[${i++}] ${info.pre ? "pre-" : ""} ${info.summary}`;
};
}

function* exec(node, context = makeInitialContext(), options = { report: makeSummaryReporter() }) {
const node_executor = executors[node.type];
if (!node_executor) {
console.log(node);
throw new NotImplemented(`No executor for AST nodes of type: ${node.type}`);
}
return yield* node_executor(node, context, options);
}

function applyOperator(op, a, b) {
if (op === "+") return a + b;
if (op === "-") return a - b;
if (op === "*") return a * b;
if (op === "&&") return a && b;
if (op === "||") return a || b;
throw new NotImplemented(`operator not implemented: ${op}`);
}

executors.File = function* (node, context, { report }) {
yield* exec(node.program, context, { report });
};

executors.Program = function* (node, context, { report }) {
// TODO actually extract function definitions etc.
for (const statement of node.body) {
yield* exec(statement, context, { report });
}
};

executors.BlockStatement = function* (node, context, { report, skipScope }) {
// TODO make new scope instead if `skip_scope` set
for (const statement of node.body) {
yield* exec(statement, context, { report });
}
};

executors.ReturnStatement = function* (node, context, { report }) {
yield report(context, { node, pre: true, summary: `return`, detail: 1 });
const ret = new Return(yield* exec(node.argument, context, { report }));
yield report(context, { node, summary: `return`, detail: 1 });
throw ret;
};

function* invokeFunction ({ definingScope, params, body }, args, context, { report }) {
// Create an execution scope
const executionScope = {
parent: definingScope,
children: [],
variables: {}
};
const scope_ref = context.scopes.push(executionScope) - 1;
context.scopes[definingScope].children.push(scope_ref);

// Populate with given arguments
for (const [i, param] of params.entries()) {
if (param.type !== "Identifier")
throw new NotImplemented(`Sorry, can't deal with ${param.type} params yet :(`);

executionScope.variables[param.name] = {
name: param.name,
kind: "param",
value: args[i]
};
}

let result;
if (body.type === "BlockStatement") {
result = yield* Return.from(function* () {
return yield* exec(body, { ...context, currentScope: scope_ref }, { report, skipScope: true });
});
} else {
result = yield* exec(body, { ...context, currentScope: scope_ref }, { report });
}
executionScope.variables[Return.symbol] = { kind: "return", value: result };
return result;
};

executors.CallExpression = function* (node, context, { report }) {
const { callee, arguments: argnodes } = node;
yield report(context, { node, pre: true, summary: `evaluate call`, detail: 2 });

// Second, lookup the function
const fnValue = yield* exec(callee, context, { report });
if (!("object_ref" in fnValue))
throw new RuntimeError(`Cannot call non-function #1`);
const fn = context.objects[fnValue.object_ref];
if (!fn || fn.type !== "function")
throw new RuntimeError(`Cannot call non-function #2`);

// First, evaluate arguments
const args = [];
for (const argnode of argnodes) {
args.push(yield* exec(argnode, context, { report }));
}

// Finally, invoke the function
yield report(context, { node, pre: true, summary: `invoke function`, detail: 2 });
const ret = yield* invokeFunction(fn, args, context, { report });
yield report(context, { node, summary: `evaluated call`, detail: 2 });
return ret;
};

executors.FunctionDeclaration = function* (node, context, { report }) {
if (node.async || node.generator)
throw new NotSupported(`Async and generator functions are not supported`);
if (!node.id || node.id.type !== "Identifier")
throw new RuntimeError(`should not happen`);

yield report(context, { node, pre: true, summary: `declare function ${node.id.name}`, detail: 1 });
const scope = context.scopes[context.currentScope];
const fn = {
type: "function",
definingScope: context.currentScope,
params: node.params,
body: node.body
};
const object_ref = context.objects.push(fn) - 1;
scope.variables[node.id.name] = { name: node.id.name, kind: "function", value: { object_ref } };
yield report(context, { node, summary: `declared function ${node.id.name}`, detail: 1 });
};

executors.ObjectProperty = function* (node, context, { report, object_ref }) {
const { key } = node;
if (key.type !== "Identifier")
throw new NotImplemented(`Computed or otherwise property keys are sadly not implemented yet :(`);

const obj = context.objects[object_ref];
const value = yield* exec(node.value, context, { report });
obj.properties[key.name] = { name: key.name, kind: "property", value };
};

executors.ObjectExpression = function* (node, context, { report }) {
yield report(context, { node, pre: true, summary: `evaluate obj expression`, detail: 2 });
const obj = {
type: "object",
properties: {}
};
const object_ref = context.objects.push(obj) - 1;
for (const property of node.properties) {
yield* exec(property, context, { report, object_ref });
}
yield report(context, { node, summary: `evaluated obj expression`, detail: 2 });
return { object_ref };
};

executors.ArrowFunctionExpression = function* (node, context, { report }) {
if (node.async || node.generator)
throw new NotSupported(`Async and generator functions are not supported`);

const scope = context.scopes[context.currentScope];
const fn = {
type: "function",
definingScope: context.currentScope,
params: node.params,
body: node.body
};
const object_ref = context.objects.push(fn) - 1;
yield report(context, { node, summary: `evaluated arrow function`, detail: 2 });
return { object_ref };
};

executors.BinaryExpression = function* (node, context, { report }) {
const { left, right } = node;
yield report(context, { node, pre: true, summary: `evaluate binary expression`, detail: 2 });
const leftValue = yield* exec(left, context, { report });
if (node.operator === "&&" && !leftValue) return leftValue;
if (node.operator === "||" && leftValue) return leftValue;
const rightValue = yield* exec(right, context, { report });
yield report(context, { node, summary: `evaluated binary expression`, detail: 2 });
return applyOperator(node.operator, leftValue, rightValue);
};

// (a) lookup identifier
function* _LookupIdentifier (node, context, { report }) {
const defining_scope_ref = lookup(context, node.name, context.currentScope);
if (defining_scope_ref === null)
throw new RuntimeError(`variable ${node.name} is not defined [lookup identifier]`);

yield report(context, { node, summary: `looked up ${node.name}`, detail: 2 });
return {
scope_ref: defining_scope_ref,
name: node.name,
site: context.scopes[defining_scope_ref].variables[node.name]
};
};

// (b) evaluate identifier as value (more common)
executors.Identifier = function* (node, context, { report }) {
if (node.name === "undefined") {
yield report(context, { node, summary: `evaluated undefined`, detail: 2 });
return undefined;
} else {
const defining_scope_ref = lookup(context, node.name, context.currentScope);
if (defining_scope_ref === null) {
throw new RuntimeError(`variable ${node.name} is not defined [eval identifier]`);
}

yield report(context, { node, summary: `looked up ${node.name}`, detail: 2 });
return context.scopes[defining_scope_ref].variables[node.name].value;
}
};

executors.MemberExpression = function* (node, context, { report }) {
const { computed, object, property } = node;
const obj = yield* exec(object, context, { report });

let propertyName;
if (computed) {
propertyName = yield* exec();
} else {
if (property.type !== "Identifier") throw "should not happen";
propertyName = property.name;
}
const defining_object_ref = lookupProperty(context, propertyName);
if (defining_object_ref === null)
throw new RuntimeError(`property ${node.name} not found`);

yield report(context, { node, summary: `looked up property ${propertyName}`, detail: 2 });
return {
object_ref: defining_object_ref,
name: propertyName,
site: context.objects[defining_object_ref].properties[propertyName]
};
};

executors.AssignmentExpression = function* (node, context, { report }) {
const { left, right } = node;

// yield report(context, { node, pre: true, summary: `assign ${left.name}`, detail: 2 });
const { site } = (yield* _LookupIdentifier(left, context, { report }));
site.value = yield* exec(right, context, { report });
// yield report(context, { node, summary: `assigned ${left.name}`, detail: 2 });
};

executors.ExpressionStatement = function* (node, context, { report }) {
yield report(context, { node, pre: true, summary: `compute expression statement`, detail: 1 });
yield* exec(node.expression, context, { report });
yield report(context, { node, summary: `computed expression statement`, detail: 1 });
};

executors.VariableDeclaration = function* (node, context, { report }) {
const kind = node.kind;
yield report(context, { node, pre: true, summary: `${kind} variable declaration`, detail: 1 });
for (const declaration of node.declarations) {
yield* exec(declaration, context, { report, kind });
}
yield report(context, { node, summary: `completed ${kind} variable declaration`, detail: 1 });
};

executors.VariableDeclarator = function* (node, context, { report, kind }) {
if (kind !== "let" && kind !== "const")
throw new NotSupported(`only let/const supported, not ${kind}`);
if (node.id.type !== "Identifier")
throw new NotImplemented(`Not yet able to declare non-identifiers :(`);

const scope = context.scopes[context.currentScope];
const name = node.id.name;
scope.variables[name] = { name, kind };
yield report(context, { node: node.id, summary: `declared ${name}`, detail: 2 });
if (node.init) {
const value = yield* exec(node.init, context, { report });
scope.variables[name].value = value;
yield report(context, { node, summary: `initialized ${name}`, detail: 2 });
}
};

executors.NullLiteral = function* (node, context, { report }) {
yield report(context, { node, summary: `evaluated null`, detail: 2 });
return null;
};

executors.StringLiteral = function* (node, context, { report }) {
yield report(context, { node, summary: `evaluated "${node.value}"`, detail: 2});
return node.value;
};

executors.NumericLiteral = function* (node, context, { report }) {
yield report(context, { node, summary: `evaluated ${node.value}`, detail: 2 });
return node.value;
};

executors.BooleanLiteral = function* (node, context, { report }) {
yield report(context, { node, summary: `evaluated ${node.value}`, detail: 2 });
return node.value;
};

exec._executors = executors;

return exec;
}
Insert cell
Insert cell
recordHistory(babel.parse(`let hi;`))
Insert cell
function recordHistory(node) {
const executionIterator = execute(node, makeInitialContext(), { report: (context, info) => ({ context, info }) });
const history = [];

try {
for (const data of executionIterator) {
history.push(deepcopy(data));
if (history.length > 100) {
throw "too many steps!";
}
}
} catch (e) {
if (e instanceof RuntimeError) {
// noop
console.log("caught runtime error:", e);
} else throw e;
}

return history;
}
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