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':
|
2019-04-15 22:58:27 -05:00
|
|
|
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);
|
2019-04-30 20:45:54 -05:00
|
|
|
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('');
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|