UNPKG

jungle-organic

Version:

The organic programming framework

308 lines (235 loc) 11.4 kB
namespace Jungle { export namespace IO{ export interface Hook { host:ResolutionCell, label:string, tractor:Function, orientation:Orientation, eager:boolean, reactiveValue?:boolean } function orientationChange(child, node):Orientation{ if(child === Orientation.OUTPUT && node === Orientation.INPUT){ return Orientation.INPUT; }else if (child === Orientation.INPUT && node === Orientation.OUTPUT){ return Orientation.OUTPUT; }else if(node === Orientation.MIXED){} } function orientationConflict(child1, child2){ return (child1 === Orientation.OUTPUT && child2 === Orientation.INPUT) || (child1 === Orientation.INPUT && child2 === Orientation.OUTPUT) } export class ResolveIO extends BaseIO implements IOComponent{ //the internal collection of io ports hooks:Hook[]; orientation:Orientation; isShellBase:boolean; base:ResolveIO; specialInput:Hook; specialOutput:Hook; specialGate:boolean; // \/\/\/ Defined for shelled only below \/\/\/ //the map of callable input functions and output ports attributed to this node. inputs:any; outputs:any //the io hooks called by ports inputHooks:any; outputHooks:any; //the conformant array of ports shell:HookShell; host:ResolutionCell; constructor(host:ResolutionCell, iospec){ super(host, iospec); var {hooks, specialIn, specialOut} = iospec; this.isShellBase = false; this.specialGate = false; this.orientation = Orientation.NEUTRAL; this.inputs = {}; this.outputs = {}; this.initialiseHooks(hooks, specialIn, specialOut); } prepare(){ } //get the tractor functions back extract(){ var ext = {}; var hook:Hook for (hook of this.hooks){ var scores = hook.eager ? '__' : '_' var isinp = hook.orientation == Orientation.INPUT || hook.orientation == Orientation.MIXED; var isout = hook.orientation == Orientation.OUTPUT || hook.orientation == Orientation.MIXED; var label = (isinp?scores:'')+hook.label+(isout?scores:'') if(!hook.reactiveValue){ ext[label] = hook.tractor; } } return ext } initialiseHooks(hooks:Hook[], specialIn:Hook, specialOut:Hook){ this.hooks = []; this.specialInput = specialIn this.specialOutput = specialOut this.inputHooks = {}; this.outputHooks = {}; for (var hook of hooks){ this.addHook(hook) } } /** add a hook to this node io that will be picked up by the shelling process to form ports. at this stage we inform the inital orientation of the node based off this and other added hooks */ addHook(hook:Hook){ var {label, tractor, orientation} = hook //update form orientation. if(this.orientation == Orientation.NEUTRAL){ this.orientation = orientation }else if(orientation != this.orientation){ this.orientation = Orientation.MIXED; }//else it is not changing var label = label; //tractors accessible by label if(orientation == Orientation.INPUT){ //input this.inputHooks[label] = hook }else if(orientation == Orientation.OUTPUT){ //output this.outputHooks[label] = hook } this.hooks.push(hook) } enshell(){ if(!this.host.prepared){ throw new Error("unable to shell unprepared node") } //recursively check and correct the order of io-nodes this.reorient(); //regardless of orientation this is the designated base. this.isShellBase = true; this.collect(); return this.shell; } /** * set the isShellBase flag to true if the node is inverting * that is if any of the children are of opposing orientation or if this is mixed * if any siblings offer conflicting orientations then an error is created * allow neutral nodes to inherit the above */ reorient(){ var inverted = false; var upperOrientation //only valid for composite crown. if(!Util.isPrimative(child)){ for (var child of this.host.crown){ if (child instanceof ResolutionCell){ child = <ResolutionCell> child; child.io.reorient(); var upo = child.io.orientation; if(child.io.isShellBase && upo != Orientation.NEUTRAL){ //child has moot orientation continue; } else if(!upperOrientation || !orientationConflict(upperOrientation, upo)){ //child has similar or is first upperOrientation = upo; }else{ //child has different to the others, lateral conflict throw new Error("Cannot have siblings with dissimilar io orientation"); } } } }else{ upperOrientation = Orientation.NEUTRAL; } if(orientationConflict(upperOrientation, this.orientation)){ //vertical conflict - inversion; this.isShellBase = true; } if (this.orientation === Orientation.MIXED){ //mixed must be base this.isShellBase = true; } if(this.orientation == Orientation.NEUTRAL){ //percolate orientation toward root this.orientation = upperOrientation this.isShellBase = false; } } collect():{hooks:Hook[], shells:Shell[]} { //if child is an inversion node then shell it else provide the io map to the accumulated nodes //begin with the hooks of this node var accumulated = { hooks:[].concat(this.hooks), shells:[] }; const accumulator = function(child, k, accumulated : {hooks:Hook[], shells:Shell[]}) : {hooks:Hook[], shells:Shell[]}{ child = <ResolutionCell> child; let {hooks, shells} = child.io.collect(); return {hooks: accumulated.hooks.concat(hooks), shells: accumulated.shells.concat(shells)}; } //singular case handling if (!Util.isVanillaObject(this.host.crown) && !Util.isVanillaArray(this.host.crown)){ if(this.host.crown instanceof ResolutionCell){ accumulated = accumulator(this.host.crown, null, accumulated); } }else{ for (var k in this.host.crown){ let child = this.host.crown[k]; if(child instanceof ResolutionCell){ child = <ResolutionCell> child; accumulated = accumulator(child, k, accumulated); }else if (child instanceof BaseCell){ child = <BaseCell> child; //on other node if(child.io.shell != undefined){ accumulated.shells.push(child.io.shell); } } } } //shell creation post recurse means leaves shell first if(this.isShellBase){ //special hooks are needed at this point, by default they will trigger and pass. this.specialInput = this.specialInput || {tractor:passing, label:'$', host:this.host, orientation:Orientation.INPUT, eager:true}; this.specialOutput = this.specialOutput|| {tractor:passing, label:'$', host:this.host, orientation:Orientation.OUTPUT, eager:true}; //compile the accumulated hooks into a single shell this.shell = new HookShell(this, accumulated.hooks, accumulated.shells); //aliased input function by binding the outward facing port to the host for(let k in this.shell.sinks){ this.inputs[k] = (function(input){ this.shell.sinks[k].handle(input); }).bind(this); } //aliased the output sources for(let k in this.shell.sources){ this.outputs[k] = this.shell.sources[k]; } return {shells:[this.shell], hooks:[]} }else{ return {hooks: accumulated.hooks, shells:accumulated.shells}; } } dispatchResult(result:any){ var baseResult for (let k in this.outputHooks){ let hook = <Hook>this.outputHooks[k] let oresult = hook.tractor.call(this.host.ctx.exposed, result) if((oresult != HALT && (hook.eager || oresult != undefined))){ let port:ResolveOutputPort = this.base.shell.sources[k]; //the base has collected one for each label port.handle(oresult); } } if(this.isShellBase){ baseResult = this.specialOutput.tractor.call(this.specialOutput.host.ctx.exposed, result); if((baseResult != HALT && (this.specialOutput.eager || baseResult != undefined))){ let port:ResolveOutputPort = this.shell.sources.$; //the base has collected one for each label port.handle(baseResult); } }else{ baseResult = result; } return baseResult } } } }