avocado-old/packages/behavior/context/index.js

164 lines
4.1 KiB
JavaScript
Raw Normal View History

2019-04-20 14:13:04 -05:00
import D from 'debug';
2019-04-15 22:47:25 -05:00
2019-05-02 20:55:29 -05:00
import {fastApply} from '@avocado/core';
2019-09-08 03:21:27 -05:00
import * as MathExt from '@avocado/math';
2019-05-02 20:55:29 -05:00
2019-09-08 03:44:23 -05:00
import * as Flow from './flow';
import * as Timing from './timing';
2019-04-15 22:47:25 -05:00
import {TraversalCompiler} from './traversal-compiler';
2019-09-08 03:44:23 -05:00
import * as Utility from './utility';
2019-04-15 22:47:25 -05:00
const compiled = new Map();
2019-04-20 14:13:04 -05:00
const debug = D('@avocado:behavior:context');
2019-04-15 22:47:25 -05:00
2019-09-08 04:08:29 -05:00
class Context {
constructor() {
this.map = new Map();
}
2019-03-17 23:45:48 -05:00
add(key, value) {
2019-09-08 04:08:29 -05:00
this.map.set(key, value);
2019-03-17 23:45:48 -05:00
}
clear() {
2019-09-08 04:08:29 -05:00
this.map.clear();
this.add('context', this);
2019-09-08 03:44:23 -05:00
this.add('Flow', Flow);
2019-09-08 03:21:27 -05:00
this.add('Math', MathExt);
2019-09-08 03:44:23 -05:00
this.add('Timing', Timing);
this.add('Utility', Utility);
}
2019-04-15 22:47:25 -05:00
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);
}
2019-06-05 20:12:21 -05:00
destroy() {
2019-09-08 04:08:29 -05:00
this.map.clear();
2019-06-05 20:12:21 -05:00
}
static renderSteps(steps) {
return steps.reduce((rendered, step) => {
switch (step.type) {
case 'key':
if (rendered) {
rendered += '.';
}
rendered += step.key;
break;
case 'invoke':
rendered += `(${step.args.length > 0 ? '...' : ''})`;
break;
}
return rendered;
}, '');
}
static renderStepsUntilNow(steps, step) {
2019-03-17 23:45:48 -05:00
const stepsUntilNow = steps.slice(0, steps.indexOf(step));
return this.renderSteps(stepsUntilNow);
2019-03-17 23:45:48 -05:00
}
2019-04-15 22:47:25 -05:00
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);
}
2019-03-17 23:45:48 -05:00
}
traverseAndDo(steps, fn) {
const [first, ...rest] = steps;
if ('key' !== first.type) {
throw new TypeError(`First step in a traversal must be type "key"`);
}
let previousNode = null;
return rest.reduce((node, step, index) => {
return this.takeStep(node, (node) => {
const result = fn(node, previousNode, step, index);
previousNode = node;
return result;
2019-04-15 22:47:25 -05:00
});
2019-09-08 04:08:29 -05:00
}, this.map.get(first.key));
2019-03-17 23:45:48 -05:00
}
traverseOneStep(steps, previousNode, node, step) {
2019-03-17 23:45:48 -05:00
if ('undefined' === typeof node) {
const rendered = this.constructor.renderStepsUntilNow(steps, step);
2019-04-20 14:13:04 -05:00
debug(`"${rendered}" is traversed through but undefined`);
return;
2019-03-17 23:45:48 -05:00
}
switch (step.type) {
case 'key':
return node[step.key];
case 'invoke':
2019-05-02 20:55:29 -05:00
const args = step.args.map((arg) => arg.get(this));
2019-06-06 00:09:51 -05:00
// Pass the context itself as the last arg.
args.push(this);
// Any arg promises will be resolved; the arg values will be passed
// transparently to the invocation.
let hasPromise = false;
for (let i = 0; i < args.length; i++) {
if (args[i] instanceof Promise) {
hasPromise = true;
break;
}
}
if (hasPromise) {
return Promise.all(args).then((args) => {
return fastApply(previousNode, node, args);
});
}
else {
return fastApply(previousNode, node, args);
}
2019-03-17 23:45:48 -05:00
}
}
2019-04-15 22:47:25 -05:00
traverseRaw(traversal) {
const {steps, value} = traversal;
return this.traverseAndDo(steps, (node, previousNode, step, index) => {
2019-04-15 22:47:25 -05:00
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, previousNode, node, step);
2019-04-15 22:47:25 -05:00
}
// Try to set the value.
switch (step.type) {
case 'key':
return node[step.key] = value.get(this);
case 'invoke':
const rendered = this.constructor.renderStepsUntilNow(steps, step);
2019-04-20 14:13:04 -05:00
debug(`invalid assignment to function "${rendered}"`);
return;
2019-04-15 22:47:25 -05:00
}
});
}
2019-03-17 23:45:48 -05:00
}
export function createContext() {
const context = new Context();
context.clear();
2019-03-17 23:45:48 -05:00
return context;
}