feat: traversal compilation

This commit is contained in:
cha0s 2019-04-15 22:47:25 -05:00
parent c7b333aec1
commit f1db16c09a
4 changed files with 199 additions and 33 deletions

View File

@ -1,18 +1,79 @@
import {Globals} from './globals';
import {TraversalCompiler} from './traversal-compiler';
import {TypeMap} from './types';
const compiled = new Map();
class Context extends Map {
add(key, value) {
this.set(key, value);
}
compile(traversal) {
// Compile traversal.
if (!compiled.has(traversal.hash)) {
const compilation = new TraversalCompiler(traversal);
const fn = new Function('context', compilation.emit());
compiled.set(traversal.hash, fn);
}
return compiled.get(traversal.hash);
}
renderStepsUntilNow(steps, step) {
const stepsUntilNow = steps.slice(0, steps.indexOf(step));
return TypeMap.renderSteps(stepsUntilNow);
}
traverse(steps, value) {
takeStep(value, fn) {
if (value instanceof Promise) {
return value.then((value) => {
return fn(value);
});
}
else {
return fn(value);
}
}
traverse(traversal) {
// Compile?
if (traversal.hash) {
return this.compile(traversal)(this);
}
else {
return this.traverseRaw(traversal);
}
}
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) => {
return this.takeStep(walk, (walk) => {
return fn(walk, step, index);
});
}, this.get(first.key));
}
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)));
}
}
traverseRaw(traversal) {
const {steps, value} = traversal;
return this.traverseAndDo(steps, (node, step, index) => {
const isLastStep = index === steps.length - 2;
// Traverse if we're not at the final step with a value.
@ -29,36 +90,6 @@ class Context extends Map {
}
});
}
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));
}
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() {

View File

@ -0,0 +1,131 @@
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;
}
allocateLiteral(literal) {
return this.allocateSymbol(JSON.stringify(literal.value));
}
allocateItem(item) {
switch (item.constructor.type()) {
case 'traversal':
return this.allocateTraversal(item);
case 'literal':
return new Symbol(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);
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('');
}
}

View File

@ -7,6 +7,7 @@ export class Traversal {
}
constructor() {
this.hash = undefined;
this.steps = [];
this.value = undefined;
}
@ -26,6 +27,9 @@ export class Traversal {
if (json.value) {
this.value = behaviorItemFromJSON(json.value);
}
if (json.hash) {
this.hash = json.hash;
}
return this;
}
@ -35,7 +39,7 @@ export class Traversal {
traverse(context) {
if (context) {
return context.traverse(this.steps, this.value);
return context.traverse(this);
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@avocado/behavior",
"version": "1.0.6",
"version": "1.0.8",
"main": "index.js",
"author": "cha0s",
"license": "MIT",