Skip to content

Instantly share code, notes, and snippets.

@dhil
Created April 22, 2022 10:44
Show Gist options
  • Save dhil/8bcfeca810307e085eb1b44e30f92bca to your computer and use it in GitHub Desktop.
Save dhil/8bcfeca810307e085eb1b44e30f92bca to your computer and use it in GitHub Desktop.
/* 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