cor-lang
Version:
The Language of the Web
604 lines (491 loc) • 14 kB
JavaScript
(function(){
/*
CRL (Cor Runtime Library)
*/
CRL = (typeof CRL === 'undefined' ? {} : CRL);
var
hasProp = Object.prototype.hasOwnProperty,
toString = Object.prototype.toString,
nativeTypes = {
'String' : String,
'Number' : Number,
'Boolean' : Boolean,
'RegExp' : RegExp,
'Array' : Array,
'Object' : Object,
'Function' : Function
};
// copy object own properties from an source `src` to a destiny `dst`
// returns the destiny object
CRL.copyObj = Object.assign ? Object.assign : function copyObj(dest, src) {
var name;
for (name in src) {
if (hasProp.call(src, name)) {
dest[name] = src[name];
}
}
return dest;
};
// convert a class in a subclass of other class
// CRL.subclass(Subclass, Superclass)
CRL.subclass = function subclass(subClass, superClass) {
CRL.copyObj(subClass, superClass);
function Proto() {
this.constructor = subClass;
}
Proto.prototype = superClass.prototype;
subClass.prototype = new Proto();
}
// extract keys from an object or array
// CRL.keys([5, 7, 3]) -> [0, 1, 2]
// CRL.keys({x: 2, y: 4}) -> ['x', 'y']
CRL.keys = function keys(obj) {
var keys, i, len;
// is array
if (obj instanceof Array) {
i = -1;
len = obj.length;
keys = [];
while (++i < len) {
keys.push(i);
}
return keys;
}
// if has key function
if (typeof Object.keys === 'function') {
return Object.keys(obj);
}
// otherwise polyfill it
for (i in obj) {
if (hasProp.call(obj, i)) {
keys.push(i);
}
}
return keys;
};
// whether a object is instance of a class or not
// CRL.assertType({}, Object)
// CRL.assertType(person, Person)
CRL.assertType = function assertType(obj, Class) {
var type;
if (Class === void 0) {
return obj === void 0;
}
if (typeof Class !== 'function') {
throw 'Trying to assert invalid class';
}
if (typeof obj === 'undefined') {
throw 'Trying to assert undefined object';
}
// try with instanceof
if (obj instanceof Class) {
return true;
}
// try with finding the native type according to "Object.prototype.toString"
type = toString.call(obj);
type = type.substring(8, type.length - 1);
if(hasProp.call(nativeTypes, type) && nativeTypes[type] === Class) {
return true;
}
return false;
};
CRL.regex = function regex(pattern, flags) {
return new RegExp(pattern, flags);
}
})();
(function(global) {
// Lightweight non standard compliant Promise
function Promise(resolverFn) {
if (typeof resolverFn !== 'function') {
throw 'provided resolver must be a function';
}
var p = this;
// this.value;
// this.reason;
this.completed = false;
this.fail = false;
this.success = false;
this.thenListeners = [];
this.catchListeners = [];
resolverFn(
function resolve(value){
Promise.doResolve(p, value);
},
function reject(reason) {
Promise.doReject(p, reason);
}
);
}
Promise.prototype = {
then: function(onSuccess, onFail) {
if (isFunction(onSuccess)) {
// proactive add
this.thenListeners.push(onSuccess);
if (this.success) {
Promise.doResolve(this, this.value);
}
}
if (isFunction(onFail)) {
// proactive add
this.catchListeners.push(onFail);
if (this.fail) {
Promise.doReject(this, this.reason);
}
}
},
catch: function(onFail) {
this.then(null, onFail);
}
};
Promise.doResolve = function doResolve(p, value) {
p.thenListeners.forEach(function(listener) {
listener(value);
})
p.success = true;
p.value = value;
p.completed = true;
};
Promise.doReject = function doReject(p, reason) {
if (p.catchListeners.length === 0) {
console.log('Uncaught (in promise): ' + reason);
}
p.catchListeners.forEach(function(listener) {
listener(reason);
})
p.fail = true;
p.reason = reason;
p.completed = true;
};
Promise.all = function all(array) {
var promise,
i = -1,
numPending = 0,
result = [],
len = array.length;
return new Promise(function(resolve) {
while (++i < len) {
promise = array[i];
if (isPromise(promise)) {
setupThen(promise, i);
}
else {
result[i] = array[i];
tryToResolve();
}
}
function setupThen(promise, i) {
numPending++;
// default value
result[i] = void 0;
promise.then(function(value) {
result[i] = value;
numPending--;
tryToResolve();
})
}
function tryToResolve() {
if (numPending === 0) {
resolve(result)
}
}
})
}
Promise.defer = function defer() {
var deferred = {};
// use CRL.Promise
deferred.promise = new CRL.Promise(function(resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
})
return deferred;
}
CRL.Promise = Promise;
// polyfill Promise
if (typeof global.Promise !== 'function') {
global.Promise = CRL.Promise;
}
// Coroutines
// Schedule
function schedule(fn, time) {
if (time === void 0 && typeof global.setImmediate !== 'undefined') {
setImmediate(fn);
} else {
setTimeout(fn, +time);
}
}
function isPromise(p) {
return p && typeof p.then === 'function';
}
function isFunction(f) {
return typeof f === 'function';
}
function isObject(obj) {
return obj && Object == obj.constructor;
}
function isArray(arr) {
return Array.isArray(arr);
}
// Generator Runner
CRL.go = function go(genf, ctx) {
var state, gen = genf.apply(ctx || {});
return new CRL.Promise(function(resolve, reject) {
// ensure it runs asynchronously
schedule(next);
function next(value) {
if (state && state.done) {
resolve(value);
return;
}
try {
state = gen.next(value);
value = state.value;
} catch (e) {
console.error(e.stack ? '\n' + e.stack : e);
return;
}
if (isPromise(value)) {
value.then(
function onFulfilled(value) {
next(value)
},
function onRejected(reason) {
gen.throw(reason)
}
)
return;
}
next(value);
}
})
}
// convert to promise as much possible
function toPromise(obj) {
if (isPromise(obj)) {
return obj;
}
if (isArray(obj)) {
return arrayToPromise(obj);
}
if (isObject(obj)) {
return objectToPromise(obj);
}
}
// convert array to promise
function arrayToPromise(array) {
var promise;
return CRL.Promise.all(array.map(function(value) {
promise = toPromise(value);
if (isPromise(promise)) {
return promise;
}
return value;
}));
}
// convert object to promise
function objectToPromise(obj) {
var key, promise, ret,
promises = [],
result = {},
i = -1,
keys = Object.keys(obj),
len = keys.length;
ret = new CRL.Promise(function(resolve) {
while (++i < len) {
key = keys[i];
promise = toPromise(obj[key]);
if (isPromise(promise)) {
setupThen(promise, key);
}
else {
result[key] = obj[key];
}
}
CRL.Promise.all(promises).then(function() {
resolve(result);
})
function setupThen(promise, key) {
// default value
result[key] = void 0;
promise.then(function(value) {
result[key] = value;
})
promises.push(promise);
}
})
return ret;
}
// receiver
CRL.receive = function receive(obj) {
var prom;
if (obj && isFunction(obj.receive)) {
return obj.receive();
}
prom = toPromise(obj);
if (isPromise(prom)) {
return prom;
}
return obj;
}
// sender
CRL.send = function send(obj, value) {
if (obj && isFunction(obj.send)) {
return obj.send(value);
}
throw 'unable to receive values';
}
function timeout(time) {
if (!isNaN(time) && time !== null) {
return new CRL.Promise(function(resolve) {
schedule(resolve, time)
})
}
throw 'Invalid time';
}
CRL.timeout = timeout;
// Buffer: simple array based buffer to use with channels
function Buffer(size) {
this.size = isNaN(size) ? 1 : size;
this.array = [];
}
Buffer.prototype = {
read: function() {
return this.array.shift();
},
write: function(value) {
if (this.isFull()) { return false }
this.array.push(value);
return true;
},
isFull: function() {
return !(this.array.length < this.size);
},
isEmpty: function() {
return this.array.length === 0;
}
}
function isBuffer(b) {
return (b
&& isFunction(b.read)
&& isFunction(b.write)
&& isFunction(b.isFull)
&& isFunction(b.isEmpty));
}
// Channel: a structure to transport messages
function indentityFn(x) {return x}
function scheduledResolve(deferred, value) {
schedule(function() { deferred.resolve(value) })
}
function Channel(buffer, transform) {
this.buffer = buffer;
this.closed = false;
this.data = void 0;
this.senderPromises = [];
this.receiverPromises = [];
this.transform = transform || indentityFn;
}
Channel.prototype = {
receive: function() {
var data, deferred;
// is unbuffered
if (! this.buffer) {
// there is data?
if (this.data !== void 0) {
// resume the first sender coroutine
if (this.senderPromises[0]) {
scheduledResolve(this.senderPromises.shift());
}
// clean and return
data = this.data;
this.data = void 0;
return data;
// if no data
} else {
// suspend the coroutine wanting to receive
deferred = Promise.defer();
this.receiverPromises.push(deferred);
return deferred.promise;
}
}
// if buffered
// empty buffer?
if (this.buffer.isEmpty()) {
// suspend the coroutine wanting to receive
deferred = Promise.defer();
this.receiverPromises.push(deferred);
return deferred.promise;
// some value in the buffer?
} else {
// resume the first sender coroutine
if (this.senderPromises[0]) {
scheduledResolve(this.senderPromises.shift());
}
// clean and return
return this.buffer.read();
}
},
send: function(data) {
if (this.closed) { throw 'closed channel' }
var deferred;
// is unbuffered
if (! this.buffer) {
// some stored data?
if (this.data !== void 0) {
// deliver data to the first waiting coroutine
if (this.receiverPromises[0]) {
scheduledResolve(this.receiverPromises.shift(), this.data);
}
// no stored data?
} else {
// pass sent data directly to the first waiting for it
if (this.receiverPromises[0]) {
this.data = void 0;
scheduledResolve(this.receiverPromises.shift(), this.transform(data));
// schedule the the sender coroutine
return timeout(0);
}
}
// else, store the transformed data
this.data = this.transform(data);
deferred = Promise.defer();
this.senderPromises.push(deferred);
return deferred.promise;
}
// if buffered
// emty buffer?
if (! this.buffer.isFull()) {
// TODO: optimize below code
// store sent value in the buffer
this.buffer.write(this.transform(data));
// if any waiting for the data, give it
if (this.receiverPromises[0]) {
scheduledResolve(this.receiverPromises.shift(), this.buffer.read());
}
}
// full buffer?
if (this.buffer.isFull()) {
// stop until the buffer start to be drained
deferred = Promise.defer();
this.senderPromises.push(deferred);
return deferred.promise;
}
},
close: function() {
this.closed = true;
this.senderPromises = [];
while (this.receiverPromises.length) {
scheduledResolve(this.receiverPromises.shift());
}
}
}
CRL.Channel = Channel;
CRL.chan = function chan(size, transform) {
if (isBuffer(size)) {
return new Channel(size, transform);
}
// isNaN(null) == false :O
if (isNaN(size) || size === null) {
return new Channel(null, transform);
}
return new Channel(new Buffer(size), transform);
}
})(this);