jungle-organic
Version:
The organic programming framework
385 lines (303 loc) • 13.3 kB
text/typescript
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;
}
}
}
}