avocado-old/packages/behavior/context/traversal-compiler.js

130 lines
3.3 KiB
JavaScript
Raw Normal View History

2019-04-15 22:47:25 -05:00
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));
2019-04-15 22:47:25 -05:00
}
}
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);
}
2019-04-15 22:47:25 -05:00
}
// 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('');
}
}