class Symbol { constructor(name, value) { this.dependencies = []; this.name = name; this.value = value; } emit(isTopLevel) { const lines = []; const befores = []; const afters = []; // Each dependency wraps its dependent. for (const dependency of this.dependencies) { const deps = dependency.emit(); const middle = deps.length / 2; befores.push(...deps.slice(0, middle)); afters.push(...deps.slice(middle)); } lines.push(...befores); // Top level returns the value. if (isTopLevel) { lines.push(`return ${this.value};`); } // Otherwise just another step. else { lines.push(`return context.takeStep(${this.value}, (${this.name}) => {`); lines.push(`});`); } lines.push(...afters); return lines; } } export class TraversalCompiler { constructor(traversal) { this.symbolCaret = 0; this.firstSymbolMap = new Map(); this.traversal = traversal; } allocateItem(item) { switch (item.constructor.type()) { case 'traversal': return this.allocateTraversal(item); case 'literal': return new Symbol(JSON.stringify(item.value)); } } allocateStep(step, currentSymbol) { switch (step.type) { case 'key': return this.allocateSymbol(`${currentSymbol.name}.${step.key}`); case 'invoke': const argSymbols = []; const dependencies = []; for (let i = 0; i < step.args.length; ++i) { const arg = step.args[i]; const argSymbol = this.allocateItem(arg); // Traversal might need dependencies. if ('traversal' === arg.constructor.type()) { dependencies.push(argSymbol); } argSymbols.push(argSymbol); } const invokeArgs = argSymbols.map((symbol) => { return symbol.name; }).join(', '); // Invoke directly (.value). const invokeSymbol = this.allocateSymbol( `${currentSymbol.value}(${invokeArgs})`, ); invokeSymbol.dependencies.push(...dependencies); return invokeSymbol; } } allocateSymbol(value) { return new Symbol( `V${this.symbolCaret++}`, value, ); } allocateTraversal(traversal) { const [first, ...rest] = traversal.steps; // TODO: Remove duplicates? const firstSymbol = this.allocateSymbol(`context.get('${first.key}')`); // Tree walker. let currentSymbol = firstSymbol; // Value to set? let valueSymbol; if (traversal.value) { valueSymbol = this.allocateItem(traversal.value); if ('traversal' === traversal.value.constructor.type()) { currentSymbol.dependencies.push(valueSymbol); } } // Allocate steps. for (const i in rest) { const step = rest[i]; const stepSymbol = this.allocateStep(step, currentSymbol); switch (step.type) { case 'key': stepSymbol.dependencies.push(currentSymbol); break; case 'invoke': stepSymbol.dependencies.push(...currentSymbol.dependencies); break; } currentSymbol = stepSymbol; } // Assign value. if (valueSymbol) { currentSymbol.value += ` = ${valueSymbol.name}`; } return currentSymbol; } emit() { const symbol = this.allocateTraversal(this.traversal); return symbol.emit(true).join(''); } }