105 lines
2.6 KiB
JavaScript
105 lines
2.6 KiB
JavaScript
import D from 'debug';
|
|
|
|
import {Globals} from './globals';
|
|
import {TraversalCompiler} from './traversal-compiler';
|
|
import {TypeMap} from './types';
|
|
|
|
const compiled = new Map();
|
|
const debug = D('@avocado:behavior:context');
|
|
|
|
class Context extends Map {
|
|
|
|
add(key, value) {
|
|
this.set(key, value);
|
|
}
|
|
|
|
compile(traversal) {
|
|
// Compile traversal.
|
|
if (!compiled.has(traversal.hash)) {
|
|
const compilation = new TraversalCompiler(traversal);
|
|
const fn = new Function('context', compilation.emit());
|
|
compiled.set(traversal.hash, fn);
|
|
}
|
|
return compiled.get(traversal.hash);
|
|
}
|
|
|
|
renderStepsUntilNow(steps, step) {
|
|
const stepsUntilNow = steps.slice(0, steps.indexOf(step));
|
|
return TypeMap.renderSteps(stepsUntilNow);
|
|
}
|
|
|
|
takeStep(value, fn) {
|
|
if (value instanceof Promise) {
|
|
return value.then((value) => {
|
|
return fn(value);
|
|
});
|
|
}
|
|
else {
|
|
return fn(value);
|
|
}
|
|
}
|
|
|
|
traverse(traversal) {
|
|
// Compile?
|
|
if (traversal.hash) {
|
|
return this.compile(traversal)(this);
|
|
}
|
|
else {
|
|
return this.traverseRaw(traversal);
|
|
}
|
|
}
|
|
|
|
traverseAndDo(steps, fn) {
|
|
const [first, ...rest] = steps;
|
|
if ('key' !== first.type) {
|
|
throw new TypeError(`First step in a traversal must be type "key"`);
|
|
}
|
|
return rest.reduce((walk, step, index) => {
|
|
return this.takeStep(walk, (walk) => {
|
|
return fn(walk, step, index);
|
|
});
|
|
}, this.get(first.key));
|
|
}
|
|
|
|
traverseOneStep(steps, node, step) {
|
|
if ('undefined' === typeof node) {
|
|
const rendered = this.renderStepsUntilNow(steps, step);
|
|
debug(`"${rendered}" is traversed through but undefined`);
|
|
return;
|
|
}
|
|
switch (step.type) {
|
|
case 'key':
|
|
return node[step.key];
|
|
case 'invoke':
|
|
return node(...step.args.map((arg) => arg.get(this)));
|
|
}
|
|
}
|
|
|
|
traverseRaw(traversal) {
|
|
const {steps, value} = traversal;
|
|
return this.traverseAndDo(steps, (node, step, index) => {
|
|
const isLastStep = index === steps.length - 2;
|
|
// Traverse if we're not at the final step with a value.
|
|
if (!isLastStep || !value) {
|
|
return this.traverseOneStep(steps, node, step);
|
|
}
|
|
// Try to set the value.
|
|
switch (step.type) {
|
|
case 'key':
|
|
return node[step.key] = value.get(this);
|
|
case 'invoke':
|
|
const rendered = this.renderStepsUntilNow(steps, step);
|
|
debug(`invalid assignment to function "${rendered}"`);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export function createContext() {
|
|
const context = new Context();
|
|
context.add('context', context);
|
|
context.add('global', new Globals());
|
|
return context;
|
|
}
|