feat: traversal compilation
This commit is contained in:
parent
c7b333aec1
commit
f1db16c09a
|
@ -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() {
|
||||
|
|
131
packages/behavior/context/traversal-compiler.js
Normal file
131
packages/behavior/context/traversal-compiler.js
Normal 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('');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@avocado/behavior",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"main": "index.js",
|
||||
"author": "cha0s",
|
||||
"license": "MIT",
|
||||
|
|
Loading…
Reference in New Issue
Block a user