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) => { return 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))); } } } export function createContext() { const context = new Context(); context.add('global', new Globals()); return context; }