UNPKG

jungle-organic

Version:

The organic programming framework

385 lines (303 loc) 13.3 kB
namespace Jungle { export namespace Util { enum JResultNatures { Single, Keyed, Indexed, Appended, Uninferred } const WAITING = "WAIT"; export class Junction { private resultNature:JResultNatures; awaits:any; index:number; silentAwaits:boolean[]; silentIndex:number; residue:any; leashed:any; error:{ message:string; key:string|number; } catchCallback; thenCallback; future:Junction; fried:boolean; blocked:boolean; cleared:boolean; thenkey:boolean|string|number; constructor(){ this.leashed = []; this.silentIndex = 0; this.silentAwaits = []; this.resultNature = JResultNatures.Uninferred; this.blocked = false; this.cleared = false; this.fried = false; } //phase complete; private proceedThen(){ this.cleared = true; if (this.thenCallback !== undefined){ let propagate, handle; handle = new Junction(); let future = this.future; //console.log('residue planted: ', this.residue) propagate = this.thenCallback(this.awaits, handle); //this.residue, if(handle.isClean()){ //handle unused so keep the return value; future.unleash(propagate); }else { //lose the return from the callback, could be immediate, could be later; //console.log('proceeding from a tampered handle ') // // let handleFrontier = handle.frontier() // if(handleFrontier.isTampered()){ // handle.then(function(result, residue){ // console.log('tampered handle result:', result, "residue", residue) // future.unleash(result); // }); // }else{ // handle.then(function(result, residue){ // console.log('Idle handle result:', result, "residue", residue) // future.unleash(residue); // }); // } handle.then(function(result, handle){ future.unleash(result); }); } }else{ //cascade residue this.future.unleash(this.awaits); } } private unleash(propagated){ let [release , raise ] = this._hold(this.thenkey); //this.residue = propagated; this.blocked = false; //the last act may proceed for(let i = 0; i < this.leashed.length; i++){ this.leashed[i](); } delete this.leashed; release(propagated); //proceed when //console.log('check from unleash') // if(this.isReady()){ // this.proceedThen(); // } } private proceedCatch(error){ if( this.catchCallback !== undefined){ this.catchCallback(error); }else if ( this.future !== undefined){ this.future.proceedCatch(error); }else{ throw new Error(`Error raised from hold, arriving from ${error.key} with message ${error.message}`); } } /* the first uncleared junction beyond this */ private successor():Junction{ if(this.cleared || !this.hasFuture()){ return this; }else{ return this.future.successor(); } } private frontier():Junction{ if(this.future){ return this.future.frontier(); }else{ return this } } public realize():any{ if(this.isIdle()){ return this.awaits; }else{ if(this.hasFuture()){ return this.future.realize(); }else{ return this; } } } private isClean(){ return !this.hasFuture() && !this.isTampered() && this.isPresent(); } private isIdle(){ return this.allDone() && this.isPresent() } private isReady(){ return this.allDone() && this.isPresent() && this.hasFuture() && !this.fried; } private isTampered(){ return !(this.silentAwaits.length <= 1 && this.resultNature === JResultNatures.Uninferred); } private isPresent(){ return !(this.blocked || this.cleared); } private hasFuture(){ return this.future != undefined; } private allDone(){ let awaitingAnySilent = false; this.silentAwaits.forEach((swaiting) => {awaitingAnySilent = swaiting || awaitingAnySilent}); let awaitingAny; if( this.resultNature === JResultNatures.Single ){ awaitingAny = this.awaits === WAITING; } else { awaitingAny = Util.typeCaseSplitR( function(thing, key){ return thing === WAITING })(this.awaits, false, function(a,b,k){return a||b}); } //console.log(`allDone: cleared ${this.cleared}, awaitingAny ${awaitingAny}, $awaitSilent ${awaitingAnySilent}, awaits:`, this.awaits, " silent; ", this.silentAwaits) return this.cleared || (!awaitingAny && !awaitingAnySilent); } public hold(returnkey?):((result?:any) => any)[]{ return this.frontier()._hold(returnkey); } public _hold(returnkey?):((result?:any) => any)[]{ let accessor, silent = false // sets this. if (returnkey === true){//APPENDED if(this.resultNature === JResultNatures.Uninferred){ this.resultNature = JResultNatures.Appended; this.awaits = []; this.index = 0; } if(this.resultNature !== JResultNatures.Appended){throw new Error("Cannot combine appended result with other")}; accessor = this.index; this.awaits[accessor] = WAITING; this.index++; //console.log(`index ${this.index}`) }else if(returnkey === false){ //SINGLE if(this.resultNature === JResultNatures.Uninferred){ this.resultNature = JResultNatures.Single; } if(this.awaits !== undefined){throw new Error("Single result feed from : hold(false) is unable to recieve any more results")} this.awaits = WAITING; }else if(typeof(returnkey) === 'string'){ // KEYED if(this.resultNature === JResultNatures.Uninferred){ this.resultNature = JResultNatures.Keyed; this.awaits = {}; } if(this.resultNature !== JResultNatures.Keyed){throw new Error("cannot use hold(string) when it is used for something else")} accessor = returnkey; this.awaits[accessor] = WAITING; }else if(typeof(returnkey) === 'number'){ ///INDEXED if(this.resultNature === JResultNatures.Uninferred){ this.resultNature = JResultNatures.Indexed; this.awaits = []; } if(this.resultNature !== JResultNatures.Indexed){throw new Error("cannot use hold(number) when it is used for something else")} accessor = returnkey; this.awaits[accessor] = WAITING; }else if(returnkey === undefined){ // SILENT accessor = this.silentIndex; this.silentAwaits[this.silentIndex++] = true; silent = true; }else{ throw new Error("Invalid hold argument, must be string, number, boolean or undefined") } return [ ((res)=>{ if((accessor !== undefined) && !silent){ this.awaits[accessor] = res; }else if((accessor !== undefined) && silent){ this.silentAwaits[accessor] = false; }else if(accessor === undefined){ //console.log('Set single await to ', res, ' is cleared ? ', this.cleared) this.awaits = res; } //console.log('check from merge') if(this.isReady()){ this.proceedThen(); //proceed from awaited } }), ((err)=>{ this.fried = true; this.error = { message:err, key:accessor }; //Default value, sideways reports, thrown error if (this.fried && this.hasFuture()){ this.proceedCatch(this.error); } }) ] } await(act:(done:(returned:any)=>Junction, raise:(message:string)=>void)=>any, label?):Junction { let frontier = this.frontier() let [done, raise] = frontier.hold(label); if(frontier.blocked){ frontier.leashed.push(act.bind(null, done, raise)); }else{ act(done, raise); } return frontier; } merge(upstream:any, label?){ let frontier = this.frontier(); if(upstream instanceof Junction){ return frontier.await(function(done, raise){ upstream.then(done); upstream.catch(raise); }, label); }else{ frontier.hold(label)[0](upstream); return frontier; } } then(callback:(results:any, residue:any, handle:Junction)=>void, thenkey?):Junction{ let frontier = this.frontier(); frontier.future = new Junction(); frontier.future.thenkey = thenkey; frontier.future.blocked = true; frontier.thenCallback = callback; if(frontier.isReady()){ frontier.proceedThen(); } return frontier.future; } catch(callback:Function):Junction{ let frontier = this.frontier(); frontier.future = new Junction(); frontier.future.blocked = true; frontier.catchCallback = callback; if (frontier.fried && frontier.hasFuture()){ frontier.proceedCatch(frontier.error); } return frontier.future; } } export class Gate{ private locks:boolean[]; private locki:number; private data:any[]; constructor(public callback?, public context?){ this.locks = []; this.locki = 0; this.data = []; } lock():(arg) => void { this.locks[this.locki] = true; return (function(locki, arg){ if(arg != undefined){ this.data = arg; } this.locks[locki] = false; if(this.allUnlocked()){ this.callback.call(this.context, this.data); } }).bind(this, this.locki++) } reset(){ this.locks = []; this.locki = 0; } isClean(){ return this.locki === 0; } allUnlocked():boolean{ return this.locks.filter(function(x){return x}).length === 0; } } } }