earlgrey-runtime
Version:
Runtime for the Earl Grey programming language.
947 lines (834 loc) • 24.4 kB
JavaScript
"use strict";
var kaiser = require("kaiser/reg");
var sortBy = require("lodash/sortBy");
var groupBy = require("lodash/groupBy");
var partition = require("lodash/partition");
if (typeof(global) === "undefined")
global = window;
// SYMBOLS
Symbol.check = "::check"
Symbol.project = "::project"
Symbol.projectNoExc = ":::project"
Symbol.clone = "::clone"
Symbol.send = "::send"
Symbol.contains = "::contains"
Symbol.repr = "::repr"
Symbol.equals = "::equals"
Symbol.requals = "::requals"
Symbol.errorTags = "::tags"
// EXTENSIONS TO STANDARD OBJECTS
String["::check"] = function (value) {
return typeof(value) === "string";
};
String[":::project"] = function (value) {
return [true, String(value)]
};
Number["::check"] = function (value) {
return typeof(value) === "number";
};
Number[":::project"] = function (value) {
return [true, parseFloat(value)]
};
Boolean["::check"] = function (value) {
return typeof(value) === "boolean";
};
Boolean[":::project"] = function (value) {
return [true, Boolean(value)]
};
Array["::check"] = function (value) {
return this.isArray(value);
};
Array[":::project"] = function (value) {
if (value instanceof Array)
return [true, value]
else
return [true, [value]]
};
var _number_methods = {
"::lightweight": function() {
return true;
},
"::repr": function(_) {
return simpleENode(".num", this);
}
};
var _string_methods = {
"::contains": function(x) {
return this.includes(x);
},
"::lightweight": function() {
return true;
},
"::repr": function(_) {
return simpleENode(".str", this);
}
};
var _boolean_methods = {
"::lightweight": function() {
return true;
},
"::repr": function(_) {
return ENode([".bool", "." + String(this)], {}, [this]);
}
};
var _object_methods = {
"hasProperty": function (prop) {
return prop in this;
}
};
var _array_methods = {
"::check": function (value) {
if (value instanceof Array) {
for (var i = 0; i < this.length; i++) {
if (this[i] !== value[i])
return false;
}
return true;
}
else {
return false;
}
},
"::clone": function (value) {
return mergeInplace(this.slice(0), this);
},
":::project": function (value) {
if (value instanceof Array) {
for (var i = 0; i < this.length; i++) {
if (this[i] !== value[i])
return [true, this.concat([value])];
}
return [true, value];
}
else {
return [true, this.concat([value])];
}
},
"::serialize_ast": function () {
return ["array"].concat(this.map(_serialize_ast));
},
"::contains": function (b) {
return this.indexOf(b) !== -1;
},
"::repr": function (repr) {
return simpleENode(".array", this.map(function (x) {
return repr(x);
}));
},
"ejoin": function (sep) {
return ENode([], {}, this.map(function (x) {
return repr(x);
})).join(sep);
},
"sortBy": function (iteratees) {
return sortBy(this, iteratees);
},
"groupBy": function (iteratees) {
return groupBy(this, iteratees);
},
"partition": function (predicate) {
return partition(this, predicate);
},
"enumerate": function () {
return enumerate(this);
},
"zip": function () {
return zip.apply(this, [this].concat([].slice.call(arguments)));
},
"product": function (b) {
return product(this, b);
},
"neighbours": function (n) {
return neighbours(this, n);
}
};
var _re_methods = {
"::check": function (value) {
if (typeof(value) === "string")
return value.match(this) ? true : false;
else
return false;
},
":::project": function (value) {
if (typeof(value) === "string") {
var m = value.match(this);
if (m === null)
return [false, null];
else
return [true, m];
}
else
return [false, null];
},
"::lightweight": function() {
return true;
},
"::repr": function(repr) {
return simpleENode(".regexp", String(this).slice(1, -1));
},
"::contains": function(x) {
return typeof(x) === "string" && this.test(x);
}
};
var _function_methods = {
"::lightweight": function() {
return !this["::egclass"];
},
"::repr": function(repr) {
if (this["::egclass"]) {
return simpleENode(".class", [
simpleENode(".name", this["::name"]),
mktable(this.prototype, repr)
]);
}
else {
return simpleENode(".function", this.name || "<anonymous>");
}
},
"::send": function(args) {
if (Array.isArray(args)) {
var ins = args["::objinsert"];
if (ins !== undefined) {
var newargs = args.slice(0);
newargs.splice(ins, ins, args);
return this.apply(this, newargs);
}
else {
return this.apply(this, args);
}
}
else {
return this(args);
}
}
};
[[Number, _number_methods],
[String, _string_methods],
[Boolean, _boolean_methods],
[Array, _array_methods],
[RegExp, _re_methods],
[Function, _function_methods],
[Object, _object_methods]].map(function (nm) {
items(nm[1]).map(function (kv) {
if (!nm[0].prototype[kv[0]])
Object.defineProperty(nm[0].prototype, kv[0], {
enumerate: false,
value: kv[1],
writable: true
});
});
});
// EG GLOBAL FUNCTIONS
// INTERNAL
function ___build_array(arrays) {
return arrays.reduce(function (x, y) { return x.concat(y); }, []);
}
global["___build_array"] = ___build_array;
function ___hasprop(obj, key) {
var t = typeof(obj);
if (obj === null || obj === undefined)
return false;
else if (t === "string")
return key in String.prototype;
else if (t === "number")
return key in Number.prototype;
else if (t === "boolean")
return key in Boolean.prototype;
else if (t === "symbol")
return key in Symbol.prototype;
else if (key in obj || t === "function" && Array.isArray(key))
return true;
return false;
}
global["___hasprop"] = ___hasprop;
function ___serialize_ast(x) {
if (typeof(x) === "object" && x !== null)
return x["::serialize_ast"]();
else
return ["value", x];
}
global["___serialize_ast"] = ___serialize_ast;
function ___match_error(value, pattern_text) {
var err = ErrorFactory("match").createFrom(
___match_error,
(pattern_text ? "'" + pattern_text + "' c" : "C")
+ "ould not find a match for value '" + String(value) + "'",
{value: value});
throw err;
}
global["___match_error"] = ___match_error;
function ___extend(child, parent) {
items(parent).forEach(function (kv) {
child[kv[0]] = kv[1];
});
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
child["::super"] = parent.prototype;
return child;
}
global["___extend"] = ___extend;
// USER FUNCTIONS
function send(obj, msg, called) {
var t = typeof(msg);
var result = null;
if (t === "string" || t === "number" || t === "symbol") {
result = obj[msg];
}
else if (msg instanceof range)
result = obj.slice(msg.start, msg.end === undefined ? msg.end : msg.end + 1);
else if (t === "object" && (obj instanceof Object && obj["::send"]))
result = obj["::send"](msg);
else if (msg && Object.getPrototypeOf(msg) === Symbol.prototype)
result = obj[msg];
else
throw Error(obj + " cannot receive message '" + msg + "'");
if (called && typeof(result) == "function")
return result.bind(obj);
else
return result
}
global["send"] = send;
function getProperty(obj, prop, objname) {
if (Object.hasProperty.call(obj, prop)) {
return obj[prop];
}
else {
throw ErrorFactory("no_property").createFrom(
getProperty,
"'" + (objname || obj.toString()) + "' does not have property '" + prop + "'"
);
}
}
global["getProperty"] = getProperty;
function object(pairs) {
var rval = {};
if (pairs.length === undefined)
throw ErrorFactory("object").create(
"Argument to 'object' must be an array of pairs.");
for (var i = 0; i < pairs.length; i++) {
var p = pairs[i];
if (!p || !(p.length === 2))
throw ErrorFactory("object").create(
"Argument to 'object' must be an array of pairs.");
rval[p[0]] = p[1];
}
return rval;
}
global["object"] = object;
global["keys"] = Object.keys;
function items(obj) {
var rval = [];
for (var k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
rval.push([k, obj[k]]);
}
}
return rval;
}
global["items"] = items;
function enumerate(arr) {
var results = [];
for (var i = 0; i < arr.length; i++) {
results.push([i, arr[i]]);
}
return results;
}
global["enumerate"] = enumerate;
function zip() {
var r = [];
for (var i = 0; i < arguments[0].length; i++) {
var x = [];
for (var j = 0; j < arguments.length; j++) {
x.push(arguments[j][i]);
}
r.push(x);
}
return r;
}
global["zip"] = zip;
function product(a, b) {
var results = [];
for (var i = 0; i < a.length; i++) {
for (var j = 0; j < b.length; j++) {
results.push([a[i], b[j]]);
}
}
return results;
}
global["product"] = product;
function neighbours(arr, n) {
n = n || 2;
var r = [];
for (var i = 0; i <= arr.length - n; i++) {
r.push(arr.slice(i, i + n));
}
return r;
}
global["neighbours"] = neighbours;
function predicate(f) {
f["::check"] = f;
return f;
}
global["predicate"] = predicate;
function equal(a, b) {
if (a === b)
return true;
else if (typeof(a) === "number"
|| typeof(a) === "string"
|| typeof(a) === "boolean"
|| a === null || a === undefined)
return false;
else if (a instanceof Array) {
if (!(b instanceof Array) || a.length !== b.length)
return false;
for (var i = 0; i < a.length; i++) {
if (!equal(a[i], b[i]))
return false;
}
return true;
}
else if (Object.getPrototypeOf(a) === null
|| (Object.getPrototypeOf(a) === Object.prototype
&& b !== undefined && b !== null && typeof(b) === "object"
&& (Object.getPrototypeOf(b) === null
|| Object.getPrototypeOf(b) === Object.prototype))) {
var ka = Object.keys(a);
if (!equal(ka.sort(), Object.keys(b).sort()))
return false;
for (var key in a) {
if (!equal(a[key], b[key]))
return false;
}
return true;
}
else if (typeof(a) === "object" && a[Symbol.equals]) {
return a[Symbol.equals](b);
}
else if (b && typeof(b) === "object" && b[Symbol.requals]) {
return b[Symbol.requals](a);
}
else if (typeof(a) === "object" && a["::serialize"]) {
if (b !== undefined && typeof(b) === "object" && b["::serialize"]) {
return equal(a["::serialize"](), b["::serialize"]());
}
}
else {
return false;
}
}
global["__equal____equal__"] = equal;
global["equal"] = equal;
function nequal(a, b) {
return !equal(a, b);
}
global["__bang____equal__"] = nequal;
global["nequal"] = nequal;
function contains(a, b) {
return a["::contains"](b);
}
global["__in__"] = function(a, b) { return contains(b, a); };
global["contains"] = contains;
function mktable(obj, repr) {
var it = items(obj);
var ch = [];
for (var i = 0; i < it.length; i++) {
var curr = it[i];
ch.push(simpleENode(".assoc", [
repr(curr[0]),
repr(curr[1])
]));
}
return simpleENode(".object", ch);
}
function createRepr(state) {
if (!state)
state = {depth: 0, seen: new Map()};
function repr(x) {
function process(x) {
if (x === null || x === undefined) {
return simpleENode("." + String(x), String(x));
}
var prev = state.seen.get(x);
if (prev && !(x["::lightweight"] && x["::lightweight"]())) {
if (prev === true) {
var id = state.refid++;
state.seen.set(x, id);
return simpleENode(".reference", id);
}
else if (typeof(prev) === "number") {
return simpleENode(".reference", prev);
}
else if (prev.props._refid) {
return simpleENode(".reference", prev.props._refid);
}
else {
prev.props._refid = state.refid++;
return simpleENode(".reference", prev.props._refid);
}
}
else {
state.seen.set(x, true);
var subrepr = createRepr(merge(state, {depth: state.depth + 1}));
if (x["::repr"]) {
var rval = x["::repr"](subrepr);
}
else if (x.constructor && x.constructor["::egclass"]) {
var rval = simpleENode(".instance", [
simpleENode(".name", x.constructor["::name"]),
mktable(x, subrepr)
]);
}
else if (Object.getPrototypeOf(x) === Object.prototype) {
var rval = mktable(x, subrepr);
}
else if (Object.getPrototypeOf(x) === null) {
var rval = mktable(x, subrepr);
}
else {
var rval = simpleENode(".unknown", String(x));
}
var ref = state.seen.get(x);
if (typeof(ref) === "number")
rval.props._refid = ref;
state.seen.set(x, rval);
return rval;
}
}
mergeInplace(process, state);
process.repr = repr;
if (state.wrap) {
return state.wrap(x, process);
}
else {
return process(x)
}
}
mergeInplace(repr, state);
repr.withState = function (newstate) {
return createRepr(merge(state, newstate));
}
return repr;
}
function repr(x, wrap) {
return repr.withState({wrap: wrap})(x);
}
repr.withState = repr.create = function (state) {
state = merge({depth: 0, seen: new Map(), refid: 1}, state);
return createRepr(state);
}
global["repr"] = repr;
function mergeInplace(dest, values) {
for (var k in values) {
if (values.hasOwnProperty(k))
dest[k] = values[k];
}
return dest;
}
global["__amp____colon__"] = mergeInplace;
function merge(a, b) {
var x = clone(a);
return mergeInplace(x, b);
}
global["__amp__"] = merge;
function clone(a) {
if (a === undefined || a === null
|| typeof(a) === "number"
|| typeof(a) === "string"
|| typeof(a) === "boolean")
return a
if (typeof(a) === "object" && a["::clone"])
return a["::clone"]();
if (Object.getPrototypeOf(a) === Object.prototype) {
var dest = {};
for (var key in a) {
if (Object.prototype.hasOwnProperty.call(a, key))
dest[key] = a[key];
}
return dest;
}
throw ErrorFactory("clone").create("Object cannot be cloned");
}
global["clone"] = clone;
function range(from, to) {
if (!(this instanceof range))
return new range(from, to)
this.start = from
this.end = to
}
range["::egclass"] = true;
range["::name"] = "range";
range.prototype[Symbol.iterator] = function () {
var self = this;
var current = self.start;
return {
next: function () {
if (current >= self.end + 1)
return {value: undefined, done: true}
else {
var rval = {value: current, done: false}
current++;
return rval;
}
}
}
};
range.prototype.toArray = function () {
var res = [];
for (var i = this.start; i <= this.end; i++) {
res.push(i);
}
return res;
};
range.prototype["::check"] = function (x) {
return x >= this.start && x <= this.end;
};
range.prototype["::contains"] = function (x) {
return x >= this.start && x <= this.end;
};
global["range"] = range;
function getChecker(type) {
var f = type["::check"];
if (f === undefined) {
return function (value) {
return value instanceof type;
};
}
else {
return function (value) {
return f.call(type, value);
};
}
}
global["getChecker"] = getChecker;
function getProjector(type) {
var f = type[":::project"];
if (f === undefined) {
f = type["::project"];
if (f === undefined) {
return function(value) {
return [true, type(value)];
};
}
else {
return function(value) {
try {
return [true, f.call(type, value)];
}
catch (_) {
return [false, null];
}
};
}
}
else {
return f.bind(type);
}
}
global["getProjector"] = getProjector;
function consume(gen, n) {
if (n === null || n === undefined)
n = Infinity;
if (!gen || !gen.next || n <= 0) {
if (!Array.isArray(gen) && gen[Symbol.iterator]) {
var res = [];
var it = gen[Symbol.iterator]();
var curr = it.next();
var i = 0;
for (var i = 0; !curr.done && i < n; i++) {
res.push(curr.value);
curr = it.next();
}
return res;
}
return gen;
}
var curr = gen.next();
var results = [];
var i = 0;
while (!curr.done && i < n) {
results.push(curr.value);
curr = gen.next();
i++;
}
return results;
}
global["consume"] = consume;
global["take"] = consume;
// ASYNC TOOLS
function promisify(fn, custom) {
return function () {
var args = [].slice.call(arguments);
return new Promise(function (resolve, reject) {
function callback(err, result) {
if (custom) {
var self = {resolve: resolve, reject: reject};
return custom.apply(self, [].slice.call(arguments));
}
else {
if (err) { return reject(err); }
else { return resolve(result); }
}
}
args.push(callback);
return fn.apply(fn, args);
});
}
}
global["promisify"] = promisify;
// adapted from https://github.com/lukehoban/ecmascript-asyncawait
function spawn(genF, eager) {
var self = this;
var done = false;
var value = null;
var error = null;
var prom = new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
done = true;
reject(error = e);
return;
}
if(next.done) {
// finished with success, resolve the promise
done = true;
resolve(value = next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
if (eager && done) {
// If the promise is already resolved by the time we get here,
// we return the value directly
if (error)
throw error;
return value;
}
else {
return prom;
}
}
global["spawn"] = spawn;
// ERROR FACTORIES
function ErrorFactory(tags) {
if (!(this instanceof ErrorFactory))
return new ErrorFactory(tags);
this.tags = tags;
}
global["ErrorFactory"] = ErrorFactory;
ErrorFactory.prototype.createFrom = function(callee) {
var e = this.create.apply(this, [].slice.call(arguments, 1));
if (Error.captureStackTrace)
Error.captureStackTrace(e, callee);
return e;
}
ErrorFactory.prototype.create = function(message) {
var e = Error(message);
e["::tags"] = this.tags;
e.args = [].slice.call(arguments, 1);
enumerate(e.args).forEach(function (iv) {
e[iv[0]] = iv[1];
});
e.length = e.args.length;
e.name = ["E"].concat(this.tags).join(".");
if (Error.captureStackTrace)
Error.captureStackTrace(e, ErrorFactory.prototype.create);
return e;
}
ErrorFactory.prototype["::check"] = function(e) {
if (!e || !(e instanceof Error))
return false
var tags = e["::tags"] || [];
for (var i = 0; i < this.tags.length; i++) {
if (tags.indexOf(this.tags[i]) === -1)
return false;
}
return true;
}
// NODE OBJECTS
function simpleENode(tag, children) {
return ENode([tag], {}, children);
}
function ENode(tags, props, children) {
if (!(this instanceof ENode))
return new ENode(tags, props, children);
if (!(tags instanceof Array)) { tags = [tags]; }
if (!(children instanceof Array)) { children = [children]; }
this.tags = tags;
this.props = props;
this.children = children;
}
ENode["::egclass"] = true;
ENode["::name"] = "ENode";
global["ENode"] = ENode;
ENode.fromObject = function (x) {
if (x && typeof(x) === "object" && x.tags && x.props && x.children) {
return ENode(x.tags, x.props, ENode.fromObject(x.children));
}
else if (Array.isArray(x)) {
return x.map(ENode.fromObject);
}
else {
return x;
}
}
ENode.prototype["::check"] = function (n) {
if (!(n instanceof ENode))
return false;
for (var i = 0; i < this.tags.length; i++) {
if (n.tags.indexOf(this.tags[i]) === -1)
return false;
}
for (var i = 0; i < this.children.length; i++) {
if (nequal(n.children[i], this.children[i]))
return false;
}
for (var key in this.props) {
if (nequal(n.props[key], this.props[key]))
return false;
}
return true;
};
ENode.prototype[":::project"] = function (n) {
if (this["::check"](n))
return [true, [n.tags, n.props, n.children]]
else
return [false, null]
};
ENode.prototype["::repr"] = function (repr) {
return this;
};
ENode.prototype.hasTag = function (tag) {
return this.tags.indexOf(tag) !== -1;
};
ENode.prototype.concat = function (other) {
return ENode([], {}, [this, other]);
};
ENode.prototype.join = function (sep) {
if (sep === undefined)
return this;
var children = [];
for (var i = 0; i < this.children.length; i++) {
if (i > 0) children.push(sep);
children.push(this.children[i]);
}
return ENode(this.tags, this.props, children);
};
ENode.prototype.toString = function () {
// toString() ignores tags and props entirely
return this.children.map(function (x) { return String(x); }).join("");
};
kaiser.register(ENode.prototype, {
typeId: "earl-grey-runtime/ENode"
});