Created
April 22, 2022 10:44
-
-
Save dhil/8bcfeca810307e085eb1b44e30f92bca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint globalstrict: true */ | |
/* jshint esversion:6 */ | |
'use strict'; | |
/* First-order continuation module */ | |
const _$K = (function() { | |
return Object.freeze({ | |
'kind': 'Default_Continuation', | |
'apply': function(k, arg) { | |
return k(arg); | |
}, | |
'yield': function(k, arg) { | |
return _$Proc.yield(function() { return _$K.apply(k, arg); }); | |
}, | |
'idy': function(x) { return; }, | |
'make': function(k) { return k; } | |
}); | |
})(); | |
/* SCHEDULER */ | |
function _Continuation(v) { this.v = v; } | |
// [IMPORTANT] | |
// The convention is that each setTimeout or XMLHttpRequest callback | |
// is responsible for correctly setting the _current_pid. The only other time | |
// _current_pid is modified is in _wrapEventHandlers in order to temporarily | |
// set it to _mainPid. | |
const _$Proc = (function() { | |
// Scheduler (trampoline) definition. | |
const Sched = (function () { | |
// Constants. | |
const MAIN_PID = 'MAIN'; | |
// Internal state. | |
let yieldGranularity = 60; // The amount of times a process is | |
// allowed to yield before getting | |
// preempted. | |
let yieldCount = 0; // The amount of times the current | |
// process has yielded. | |
let nextPid = 1; // The next available raw PID. | |
let currentPid = MAIN_PID; // The PID of the currently running | |
// process. | |
let handlingEvent = false; // Indicates whether not an event | |
// handler is currently running. | |
return Object.freeze({ | |
'yield': function(f) { | |
// yield: give up control for another "thread" to | |
// work. if we're running in an event handler then | |
// don't yield (but do throw away the stack | |
// periodically instead). | |
++yieldCount; | |
if (yieldCount === yieldGranularity) { | |
yieldCount = 0; | |
if (handlingEvent) throw new _Continuation(f); | |
else { | |
const activePid = Sched.getPid(); | |
return setZeroTimeout(function() { | |
Sched.setPid(activePid); | |
return f(); | |
}); | |
} | |
} else { | |
return f(); | |
} | |
}, | |
'getPid': function() { | |
return currentPid; | |
}, | |
'setPid': function(pid) { | |
currentPid = pid; | |
return; | |
}, | |
'swapPid': function(pid) { | |
const previous = Sched.getPid(); | |
Sched.setPid(pid); | |
return previous; | |
}, | |
'swapMainPid': function() { | |
return Sched.swapPid(MAIN_PID); | |
}, | |
'generatePid': function() { | |
const pid = nextPid++; | |
return "cl_" + pid; | |
}, | |
'wrapEventHandler': function (handler) { | |
// Wrap an event handler in a function that sets the | |
// main process id at the beginning of the handler and | |
// unsets it at the end. | |
return function (event) { | |
// set process id here | |
const active_pid = Sched.swapMainPid(); | |
handlingEvent = true; | |
let f = function () { | |
return handler(event, _$K.idy); | |
}; | |
// A trampoline for use while handling events. | |
// Since we don't yield to the browser event loop | |
// in event handlers, we quickly run out of | |
// stack. To avoid that we throw away the stack | |
// periodically by throwing an exception | |
// containing the current continuation, which we | |
// invoke when the exception is caught. | |
while (true) { | |
try { | |
f(); | |
// restore process id here. | |
handlingEvent = false; | |
Sched.setPid(active_pid); | |
return; | |
} catch (exn) { | |
if (exn instanceof _Continuation) { | |
f = exn.v; | |
continue; | |
} else { | |
handlingEvent = false; | |
Sched.setPid(active_pid); | |
throw exn; | |
} | |
} | |
} | |
}; | |
} | |
}); | |
})(); | |
// Process module definition. | |
// Internal state. | |
const blocked = {}; | |
return Object.freeze({ | |
'Sched': Sched, | |
'generatePid': Sched.generatePid, | |
'alloc': function() { | |
const pid = _$Proc.generatePid(); | |
return pid; | |
}, | |
'yield': Sched.yield, | |
'block': function(pid, f) { | |
blocked[pid] = f; | |
return; | |
}, | |
'wakeUp': function(pid) { | |
if (_$Proc.isBlocked(pid)) { | |
const proc = blocked[pid]; | |
blocked[pid] = null; | |
return setZeroTimeout(proc); | |
} else { | |
// already awake? | |
return; | |
} | |
}, | |
'isBlocked': function(pid) { | |
return Boolean(blocked[pid]); | |
} | |
}); | |
})(); | |
/* LIST MANIPULATING FUNCTIONS */ | |
const _$List = (function(){ | |
return Object.freeze({ | |
nil: null, | |
isNil: function(n) { return n === null; }, | |
cons: function(x,xs) { | |
return {_head: x, _tail: xs}; | |
}, | |
forEach: function(list, f) { | |
while(!_$List.isNil(list)) { | |
let cur = list._head; | |
f(cur); | |
list = list._tail; | |
} | |
}, | |
head: function(v) { | |
return _$List.isNil(v) ? _error('head') : v._head; | |
}, | |
tail: function(v) { | |
return _$List.isNil(v) ? _error('tail') : v._tail; | |
} | |
}); | |
})(); | |
/* Core functions */ | |
// Set up optimised setZeroTimeout | |
const setZeroTimeout = (function() { | |
return function(f) { return setTimeout(f, 0); }; | |
})(); | |
/** | |
* Provides a number of type-checking functions | |
*/ | |
const _$Types = Object.freeze({ | |
isUnit: function (val) { return (_$Types.isObject(val) && val.constructor === Object && Object.keys(val).length === 0); }, | |
isList: _$List.isList, | |
}); | |
const _$Constants = Object.freeze({ | |
AJAX: Object.freeze({ | |
XHR_STATUS_IS_LOADED: 2, | |
XHR_STATUS_IS_COMPLETE: 4, | |
HTTP_STATUS_OK: 200, | |
}), | |
UNIT: Object.freeze({}), | |
NO_PROCESS: -99 | |
}); | |
/** A package of functions used internally, not callable from Links code. */ | |
const _$Links = (function() { | |
return Object.freeze({ | |
/** | |
* Turns a direct-style js function into a continuationized one under the | |
* Links calling conventions. "trivial" means it cannot call back to a | |
* Links function, and that the scheduler can safely be suspended | |
* while it runs. | |
*/ | |
kify: function (f) { | |
return function () { | |
const kappa = arguments[arguments.length - 1]; | |
const args = Array.prototype.slice.call(arguments, 0, arguments.length - 1); | |
return _$K.apply(kappa, f.apply(f, args)); | |
}; | |
} | |
}); | |
})(); | |
function _explode (s) { | |
let cs = _$List.nil; | |
for (var i = s.length - 1; i >= 0; --i) { | |
cs = _$List.cons({ _c: s.charAt(i) }, cs); | |
} | |
return cs; | |
} | |
function _implode(cs) { | |
let s = ""; | |
_$List.forEach(cs,function(e) { | |
s += e._c; | |
}); | |
return s; | |
} | |
const explode = _$Links.kify(_explode); | |
const implode = _$Links.kify(_implode); | |
function _error(msg) { | |
throw new Error(msg); | |
} | |
function _print(str) { | |
console.info(str); | |
return _$Constants.UNIT; | |
} | |
const print = _$Links.kify(_print); | |
(function() { | |
function println_255(_254, __kappa) { let msg_253 = _254; return _$K.apply(__kappa, _print(msg_253 + '\n')); } | |
return _$K.apply(function(_2396) { | |
return _$K.apply(function(_2397) { return _$Proc.yield(function() { return println_255(_2397, function(x) { }); }); }, _implode(_2396)); | |
}, _explode('Hello World!')); | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment