100 lines
2.3 KiB
JavaScript
100 lines
2.3 KiB
JavaScript
|
import {Globals} from './globals';
|
||
|
import {TypeMap} from './types';
|
||
|
|
||
|
class Context extends Map {
|
||
|
|
||
|
add(key, value) {
|
||
|
this.set(key, value);
|
||
|
}
|
||
|
|
||
|
renderStepsUntilNow(steps, step) {
|
||
|
const stepsUntilNow = steps.slice(0, steps.indexOf(step));
|
||
|
return TypeMap.renderSteps(stepsUntilNow);
|
||
|
}
|
||
|
|
||
|
traverse(steps) {
|
||
|
return this.traverseAndDo(steps, (node, step) => {
|
||
|
return this.traverseOneStep(steps, node, step);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
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) => {
|
||
|
if (walk instanceof Promise) {
|
||
|
return walk.then((walk) => {
|
||
|
fn(walk, step, index);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
return fn(walk, step, index);
|
||
|
}
|
||
|
}, this.get(first.key));
|
||
|
}
|
||
|
|
||
|
traverseAndSet(steps, value) {
|
||
|
return this.traverseAndDo(steps, (node, step, index) => {
|
||
|
const isLastStep = index === steps.length - 2;
|
||
|
if (!isLastStep) {
|
||
|
return this.traverseOneStep(steps, node, step);
|
||
|
}
|
||
|
switch (step.type) {
|
||
|
case 'key':
|
||
|
return node[step.key] = value.get(this);
|
||
|
case 'invoke':
|
||
|
const rendered = this.renderStepsUntilNow(steps, step);
|
||
|
throw new ReferenceError(`invalid assignment to function "${rendered}"`);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
traverseOneStep(steps, node, step) {
|
||
|
if ('undefined' === typeof node) {
|
||
|
const rendered = this.renderStepsUntilNow(steps, step);
|
||
|
throw TypeError(`"${rendered}" is traversed through but undefined`);
|
||
|
}
|
||
|
switch (step.type) {
|
||
|
case 'key':
|
||
|
return node[step.key];
|
||
|
case 'invoke':
|
||
|
return node(...step.args.map((arg) => arg.get(this)));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class TypedContext extends Context {
|
||
|
|
||
|
constructor(iterator) {
|
||
|
super(iterator);
|
||
|
|
||
|
this.types = {};
|
||
|
}
|
||
|
|
||
|
add(key, value, type) {
|
||
|
super.add(key, value);
|
||
|
if (!type) {
|
||
|
return;
|
||
|
}
|
||
|
this.types = {
|
||
|
...this.types,
|
||
|
[key]: type,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
export function createContext() {
|
||
|
const context = new Context();
|
||
|
context.add('global', new Globals());
|
||
|
return context;
|
||
|
}
|
||
|
|
||
|
export function createTypedContext() {
|
||
|
const context = new TypedContext();
|
||
|
context.add('global', new Globals(), 'globals');
|
||
|
return context;
|
||
|
}
|