Skip to content

Instantly share code, notes, and snippets.

@hackwaly
Last active October 28, 2020 02:30
Show Gist options
  • Save hackwaly/1e5cfd0daf4c95f0014d to your computer and use it in GitHub Desktop.
Save hackwaly/1e5cfd0daf4c95f0014d to your computer and use it in GitHub Desktop.
Coroutine, go and call/cc implemented in javascript (use generator, less then 100 line).
var go;
var callcc;
(function () {
function noop() {}
var iteratorConstructor = function* () {}().constructor;
function isIterator(p) {
return p != null && p.constructor === iteratorConstructor;
}
var yieldToken = {};
var currentCoroutine = null;
function go_(f) {
schedule(new Coroutine(f));
}
function Coroutine(f) {
this._f = f;
}
Coroutine.prototype._continuedValue = undefined;
Coroutine.prototype._next = function () {
var r = (this._f)();
if (isIterator(r)) {
this._f = r;
this._next = function () {
var p = this._continuedValue;
this._continuedValue = undefined;
var r = this._f.next(p);
if (r.done) {
this._next = noop;
} else if (r.value !== yieldToken) {
this._continuedValue = r.value;
schedule(this);
}
};
this._next();
} else {
this._next = noop;
}
};
function callcc_(f) {
var coroutine = currentCoroutine;
f(function (p) {
if (coroutine !== null) {
coroutine._continuedValue = p;
schedule(coroutine);
coroutine = null;
}
});
return yieldToken;
}
var queuedCoroutines = [];
var scheduled = false;
var resolvedPromise = Promise.resolve();
function schedule(coroutine) {
queuedCoroutines.push(coroutine);
if (!scheduled) {
scheduled = true;
resolvedPromise.then(run);
}
}
function run() {
while (queuedCoroutines.length > 0) {
var coroutines = queuedCoroutines;
queuedCoroutines = [];
for (var i = 0; i < coroutines.length; i++) {
var coroutine = coroutines[i];
currentCoroutine = coroutine;
coroutine._next();
}
}
currentCoroutine = null;
scheduled = false;
}
go = go_;
callcc = callcc_;
})();
var chan;
var end;
(function () {
var end_ = {};
// Non-buffered channel
function Channel() {
this._put = null;
this._take = null;
}
Channel.prototype.take = function () {
if (this._take !== null) {
return this._take();
}
var self = this;
return callcc(function (continue_) {
self._put = function (data) {
self._put = null;
continue_(data);
return true;
};
});
};
Channel.prototype.put = function (data) {
if (this._put !== null) {
return this._put(data);
}
var self = this;
return callcc(function (continue_) {
self._take = function () {
self._take = null;
continue_();
return data;
};
})
};
Channel.prototype.close = function () {
var self = this;
function close() {
if (self._put !== null) {
self._put(end_);
}
self._put = function () {
return false;
};
self._take = function () {
return end_;
};
}
if (this._take !== null) {
var take = this._take;
this._take = function () {
var r = take();
close();
return r;
};
} else {
close();
}
};
function chan_() {
return new Channel();
}
chan = chan_;
end = end_;
})();
function* player(name, table) {
while (true) {
var ball = yield table.take();
if (ball === end) {
console.log(name + ": table's gone");
return;
}
ball.hits += 1;
console.log(name + " " + ball.hits);
yield sleep(100);
yield table.put(ball);
}
}
go(function* () {
var table = chan();
go(player.bind(null, "ping", table));
go(player.bind(null, "pong", table));
yield table.put({hits: 0});
yield sleep(1000);
table.close();
});
function sleep(time) {
return callcc(function (continue_) {
setTimeout(continue_, time);
});
}
go(function *() {
for (var i = 0; i < 10; i++) {
console.log(i);
yield sleep(1000);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment