gentyl
Version:
A Generator That You'll Love
508 lines (402 loc) • 16.9 kB
text/typescript
/// <reference path="./util.ts"/>
/// <reference path="./context.ts"/>
namespace Gentyl {
export interface IOShell {
ins:any //that is a mapping from names to infunctions
outs:any //that is a mapping from names to callback context objects
}
export class GNode {
ctx:GContext;
crown:any;
parent:GNode;
depth:number;
derefChain:(string|number)[]
isRoot:boolean;
root:GNode;
prepared:boolean;
form:GForm;
//internal io
inputNodes:any;
outputNodes:any;
ioShell:any;
outputContext:any;
outputCallback:string;
targeted:boolean;
//internal
ancestor:GNode;
isAncestor:boolean;
constructor(components:any, form:FormSpec = {}, state:any = {}){
this.depth = 0;
this.isRoot = true;
this.prepared = false;
this.targeted = false;
this.form = new GForm(form);
var context = Util.deepCopy(state);
this.ctx = new GContext(this, context, this.form.ctxmode);
var inductor = this.inductComponent.bind(this);
this.crown = Util.typeCaseSplitF(inductor, inductor, null)(components)
}
private inductComponent(component):any{
var c
if (component instanceof GNode){
c = component
//c.setParent(this);
}else if (component instanceof Object){
c = new GNode(component)
//c.setParent(this);
}else {
c = component
}
return c;
}
/**
* setup the state tree, recursively preparing the contexts
*/
public prepare(prepargs=null):GNode{
if(this.isAncestor){
throw Error("Ancestors cannot be prepared for resolution")
}
// if(this.checkComplete(false)){
// throw Error("Unable to prepare, null terminals still present");
// }
//if already prepared the ancestor is reestablished
this.ancestor = this.ancestor || this.replicate();
this.ancestor.isAncestor = true;
if(!this.prepared){
this.prepared = true;
this.ctx.prepare();
this.form.preparator.call(this.ctx, prepargs);
//create io facilities for node.
this.prepareIO();
//prepare children, object, array, primative
this.crown = Util.typeCaseSplitF(this.prepareChild.bind(this, prepargs))(this.crown);
} else {
this.ancestor = this.replicate();
this.ancestor.isAncestor = true;
}
return this;
}
private prepareChild(prepargs, child):GNode{
if(child instanceof GNode){
var replica = child.replicate();
replica.setParent(this);
replica.prepare(prepargs);
//collect nodes from children allowing parallel input not out
Util.parassoc(replica.inputNodes, this.inputNodes);
Util.assoc(replica.outputNodes, this.outputNodes);
return replica;
}else{
return child;
}
}
private prepareIO(){
this.inputNodes = {}
if (typeof(this.form.inputLabel) == 'string'){
this.inputNodes[this.form.inputLabel] = [this]
}
this.outputNodes = {}
if (typeof(this.form.outputLabel) == 'string'){
this.outputNodes[this.form.outputLabel] = this
}
}
replicate():GNode{
if(this.prepared){
//this node is prepared so we will be creating a new based off the ancestor;
return this.ancestor.replicate()
}else{
//this is a raw node, either an ancestor
var repl = new GNode(this.crown, this.form.extract(), this.ctx.extract())
//in the case of the ancestor it comes from prepared
if(this.isAncestor){
repl.ancestor = this;
}
return repl
}
}
bundle():Bundle{
function bundler(node){
if(node instanceof GNode){
var product = node.bundle()
return product
}else{
return node
}
}
var recurrentNodeBundle = Util.typeCaseSplitF(bundler, bundler, null)(this.crown)
var product = {
node:recurrentNodeBundle,
form:Gentyl.deformulate(this),
state:this.ctx.extract()
}
return product;
}
getTargets(input, root){
//create submap of the output nodes being only those activted by the current node targeting
function strtargs(targs, input, root){
var targets = {};
if(targs == undefined){
//no targets on node
}else if(targs instanceof Array){
for (let i = 0; i < targs.length; i++) {
let val = targs[i];
if(val in root.outputNodes){
targets[val] = root.outputNodes[val]
}
}
}else{
if(targs in root.outputNodes){
targets[targs] = root.outputNodes[targs]
}
}
//console.log("returned targets:", targets)
return targets
}
if(typeof(this.form.targeting) == 'function'){
return strtargs(this.form.targeting(input), input, root)
}else{
return strtargs(this.form.targeting, input, root)
}
}
shell():IOShell{
if(!this.prepared){
throw new Error("unable to shell unprepared node")
}
//implicit root labelling;
var root = this.getRoot();
//only operate on root
root.form.inputLabel = root.form.inputLabel || "_";
root.form.outputLabel = root.form.outputLabel || "_";
root.outputNodes["_"] = root
root.inputNodes["_"] = [root]
var inpnodesmap = root.inputNodes;
var outnodemap = root.outputNodes;
var shell = {
ins:{},
outs:{}
}
//create the output signals.
for(let k in outnodemap){
if(Gentyl.IO.ioShellDefault.dispatch === undefined){
//case for a contextless higher order function system
shell.outs[k] = outnodemap[k].outputCallback = Gentyl.IO.ioShellDefault.setup(k);
outnodemap[k].outputContext = undefined;
}else if(Gentyl.IO.ioShellDefault.setup === undefined){
//case fot a contextless direct function
shell.outs[k] = outnodemap[k].outputCallback = Gentyl.IO.ioShellDefault.dispatch;
outnodemap[k].outputContext = undefined;
}else{
//case for contextual callback system
var ctx = new Gentyl.IO.ioShellDefault.setup(k);
outnodemap[k].outputContext = shell.outs[k] = ctx
outnodemap[k].outputCallback = Gentyl.IO.ioShellDefault.dispatch;
}
}
//create input functions
for(let k in inpnodesmap){
var v = {inps:inpnodesmap[k], root:root}
shell.ins[k] = function(data){
//construct a map of olabel:Onode that will be activated this time
var allTargets = {};
var rootInput;
for (let i = 0; i < this.inps.length; i++){
var inode = <GNode>this.inps[i];
var iresult = inode.form.inputFunction.call(inode.ctx, data);
if(inode == this.root){rootInput = iresult}
var targets = inode.getTargets(data, this.root); //Quandry: should it be input function result
Util.assoc(targets, allTargets);
}
if(Object.keys(allTargets).length == 0){return;} //no resolution if no targets
for (let key in allTargets) {
//console.log("target %s set targets", key, allTargets[key])
allTargets[key].targeted = true; //set allTargets
}
this.root.resolve(data); //trigger root resolution. Quandry: should it be input function result
for (let key in allTargets) {
allTargets[key].targeted = false; //clear targets
}
}.bind(v)
}
root.ioShell = shell;
return shell
}
public getParent(toDepth = 1):GNode{
if (this.parent == undefined){
throw new Error("parent not set, or exceeding getParent depth")
}else if (toDepth == 1 ){
return this.parent
}else{
return this.parent.getParent(toDepth - 1)
}
}
public getRoot():GNode{
return this.isRoot ? this : this.getParent().getRoot();
}
public getNominal(label):GNode{
if(this.form.contextLabel === label){
return this;
}else{
if (this.parent == undefined){
throw new Error("Required context label is not found")
}else{
return this.parent.getNominal(label)
}
}
}
private setParent(parentNode:GNode){
this.parent = parentNode;
this.isRoot = false;
this.depth = this.parent.depth + 1;
}
private resolveArray(array:any[],resolveArgs, selection):any[]{
//TODO:selector must produce index or array thereof
if(selection instanceof Array){
var resolution = []
for (var i = 0; i < selection.length; i++){
resolution[i] = this.resolveNode(array[selection[i]], resolveArgs, true)
}
return resolution
}else {
return this.resolveNode(array[selection], resolveArgs, true)
}
}
private resolveObject(node, resolveArgs, selection):any{
if(selection instanceof Array){
var resolution = {}
for (var i = 0; i < selection.length; i++){
var k = selection[i];
resolution[k] = this.resolveNode(node[k], resolveArgs, true);
}
return resolution;
}else{
return this.resolveNode(node[selection], resolveArgs, true);
}
}
/**
* create an iterable of all the terminals in the structure
*/
terminalScan(recursive=false, collection=[], locale=null){
var locale = locale || this;
Util.typeCaseSplitF(function(thing, dereferent){
if(thing instanceof Terminal){
collection.push({node:locale, term:thing, deref:dereferent})
}else if(recursive && thing instanceof GNode){
thing.terminalScan(true, collection, locale=thing)
}//else primitives and non recursion ignored
})(this.crown);
return collection
}
/**
* check that there are no terminals in the structure
*/
checkComplete(recursive=false){
var result = true;
Util.typeCaseSplitF(function(thing){
if(thing instanceof Terminal){
result = false; //falsify result when terminal found
}else if(recursive && thing instanceof GNode){
thing.checkComplete(true)
}//else primitives and non recursion ignored
})(this.crown);
return result;
}
/**
add an extremity to the structure
*/
add(keyOrVal, val){
this.inductComponent(val)
let al = arguments.length
var ins = null
if(!(al === 1 || al === 2)){
throw Error("Requires 1 or 2 arguments")
}else if(al === 1){
if (this.crown instanceof Array){
ins = this.crown.length;
this.crown.push(val)
}else if(Util.isVanillaObject(this.crown)){
throw Error("Requires key and value to add to object crown")
}else if(this.crown instanceof Terminal){
if(this.crown.check(val)){
this.crown = val;
}
}else{
throw Error("Unable to clobber existing value")
}
}else {
if(Util.isVanillaObject(this.crown)){
ins = keyOrVal;
this.crown[keyOrVal] = val;
}else{
throw Error("Requires single arg for non object crown")
}
}
//when the structure is prepared as must be the child added.
if(this.prepared){
this.crown[ins] = this.prepareChild(null, this.crown[ins])
}
}
/**
* assure that resolultion will always return a certain type
*/
seal(typespec){
}
//main recursion
private resolveNode(node, resolveArgs, selection):any{
//log("node to resolve: ", node)
//cutting dictates that we select nothing and therefore will
var cut = false;
if(!selection){
cut = true;
}else if(selection === true && node instanceof Object){
//select all
selection = Object.keys(node);
}
//at this stage cut determines primitives are nullified and objects empty
if (node instanceof Array){
return cut ? [] : this.resolveArray(node, resolveArgs, selection)
}
else if (typeof(node) === "object"){
if(node instanceof GNode){
return cut ? null : node.resolve(resolveArgs)
}else{
return cut ? {} : this.resolveObject(node, resolveArgs, selection)
}
}
else{
//we have a primative
return cut ? null : node;
}
}
private resolveUnderscore(resolver:GNode, resolveArgs){
//now this is the parent context
var result = resolver.resolve(resolveArgs)
return result
}
resolve(resolveArgs){
if (!this.prepared){
this.prepare();
//throw Error("Node with state is not prepared, unable to resolve")
}
Object.freeze(resolveArgs)
var carried = this.form.carrier.call(this.ctx, resolveArgs)
var resolvedNode
if(this.crown != undefined){
//form the selection for this node
var selection = this.form.selector.call(this.ctx, Object.keys(this.crown), resolveArgs);
//recurse on the contained node
resolvedNode = this.resolveNode(this.crown, carried, selection)
}
//modifies the resolved context and returns the processed result
var result = this.form.resolver.call(this.ctx, resolvedNode, resolveArgs)
//dispatch if marked
//console.log(`check Output stage with olabel ${this.outputLabel} reached targeted? , `,this.targeted)
if(this.targeted){
var outresult = this.form.outputFunction.call(this.ctx, result)
console.log("Output call back called on output", this.form.outputLabel)
//when there is a context call upon it otherwise without
this.outputContext[this.outputCallback](outresult, this.form.outputLabel)
this.targeted = false;
}
return result
}
}
}