hopper
Version:
An interpreter for the Grace programming language
1,961 lines (1,578 loc) • 59.9 kB
JavaScript
// Primitive Grace definitions in JavaScript.
"use strict";
var Task, defs, rt, util;
Task = require("../task");
rt = require("../runtime");
defs = require("./definitions");
util = require("../util");
function addMethod(Constructor, name) {
Constructor.prototype[util.uglify(name)] =
rt.method.apply(rt, util.slice(arguments, 1));
}
function addConstructor(Constructor, name) {
Constructor.prototype[util.uglify(name)] =
rt.constructor.apply(rt, util.slice(arguments, 1));
}
function toNumber(raw) {
return defs.Number.cast(raw).then(function (number) {
return number.asPrimitiveNumber();
});
}
function toString(raw) {
return defs.String.cast(raw).then(function (string) {
return string.asPrimitiveString();
});
}
function GraceObject() {
return this;
}
GraceObject.isInternal = true;
addMethod(GraceObject, "==", 1, function (value) {
return defs.bool(this === value);
});
addMethod(GraceObject, "!=", 1, function (value) {
return this["=="](value).then(function (result) {
return result["prefix!"]().then(function (notted) {
return defs.Boolean.assert(notted).then(function () {
return notted;
});
});
});
});
addMethod(GraceObject, "asString", 0, function () {
return defs.string("object");
});
function asString(value) {
return rt.apply(value, "asString").then(function (string) {
return toString(string);
});
}
exports.asString = asString;
GraceObject.prototype.toString = function () {
var error, string;
string = null;
error = null;
asString(this).now(function (value) {
string = value;
}, rt.handleInternalError).then(null, function (reason) {
error = new Error("Unable to render exception message");
reason.exception().then(function (exception) {
return exception.name().then(function (name) {
return toString(name).then(function (nameString) {
error.name = nameString;
});
});
}).then(function () {
return reason.message().then(function (message) {
return toString(message).then(function (messageString) {
error.message = messageString;
});
});
}).now();
});
if (error !== null) {
throw error;
}
if (string === null || string.toString === GraceObject.prototype.toString) {
return "object";
}
return string.toString();
};
GraceObject.prototype.toString.isInternal = true;
function AbstractPattern() {
return this;
}
util.inherits(AbstractPattern, GraceObject);
function dirPattern(name, branch) {
return function (rawRhs) {
var self = this;
return defs.Pattern.cast(rawRhs).then(function (rhs) {
var pattern = new AbstractPattern();
pattern.match = rt.method("match()", 1, function (value) {
return self.match(value).then(function (rawMatch) {
return defs.Boolean.cast(rawMatch).then(function (match) {
return match[branch](defs.block(0, function () {
return rhs.match(value);
}));
});
});
});
pattern.asString = rt.method("asString", 0, function () {
return self.asString().then(function (string) {
return rt.string(name + "(")["++"](string);
}).then(function (string) {
return string["++"](rt.string(", "));
}).then(function (string) {
return rt.apply(rhs, "asString").then(function (rhsString) {
return string["++"](rhsString);
});
}).then(function (string) {
return string["++"](rt.string(")"));
});
});
return pattern;
});
};
}
addMethod(AbstractPattern, "&", 1, dirPattern("Both", "andAlso"));
addMethod(AbstractPattern, "|", 1, dirPattern("Either", "orElse"));
addMethod(AbstractPattern, "assert()", 1, function (value) {
var self = this;
return self.match(value).then(function (result) {
return result.orElse(defs.block(0, function () {
return defs.AssertionFailure
.raiseForValue_againstPattern([value], [self])
.then(null, function (error) {
return error;
});
}));
}).then(function (result) {
var trace;
if (result instanceof exports.ExceptionPacket) {
trace = result.object.stackTrace;
trace.splice(trace.length - 3, 3);
throw result;
}
return result;
});
});
addMethod(AbstractPattern, "asString", 0, function () {
return defs.string("object(pattern.abstract)");
});
function Singleton() {
AbstractPattern.call(this);
}
util.inherits(Singleton, AbstractPattern);
addMethod(Singleton, "match()", 1, function (value) {
return this === value ? defs.success(value) : defs.failure(value);
});
addMethod(Singleton, "asString", 0, function () {
return defs.string("object(pattern.singleton)");
});
function Block(parameters, apply) {
var paramCount;
AbstractPattern.call(this);
paramCount = typeof parameters === "number" ? parameters : parameters[1];
this.apply =
rt.method("apply" + (paramCount === 0 ? "" : "()"), [parameters], apply);
this.asString = rt.method("asString", 0, function () {
return defs.string("block/" + paramCount);
});
if (paramCount === 1) {
this.match = rt.method("match()", 1, function (object) {
var self = this;
return self.apply(object).then(function (result) {
return defs.success(result, self);
});
});
}
}
util.inherits(Block, AbstractPattern);
addMethod(Block, "asPrimitive", function () {
return this.apply;
});
addMethod(Block, "match()", 1, function () {
return rt.UnmatchableBlock.raiseDefault();
});
function AbstractBoolean() {
AbstractPattern.call(this);
}
util.inherits(AbstractBoolean, AbstractPattern);
addMethod(AbstractBoolean, "match()", 1, function (against) {
return defs.equalityMatch(this, against);
});
addMethod(AbstractBoolean, "ifTrue()", 1, function (action) {
var self = this;
return defs.Action.assert(action).then(function () {
return self.ifTrue_ifFalse([action], [defs.emptyBlock]);
}).then(function () {
return rt.done;
});
});
addMethod(AbstractBoolean, "ifFalse()", 1, function (action) {
var self = this;
return defs.Action.assert(action).then(function () {
return self.ifTrue_ifFalse([defs.emptyBlock], [action]);
}).then(function () {
return rt.done;
});
});
addMethod(AbstractBoolean, "andAlso() orElse()", [1, 1], function (fst, snd) {
var self = this;
fst = fst[0];
snd = snd[0];
return defs.Action.assert(fst).then(function () {
return defs.Action.assert(snd);
}).then(function () {
return self.ifTrue_ifFalse(rt.part([defs.Boolean], fst),
rt.part([defs.Boolean], snd));
});
});
addMethod(AbstractBoolean, "andAlso()", 1, function (action) {
var self = this;
return defs.Action.assert(action).then(function () {
return self.ifTrue_ifFalse(rt.part([defs.Boolean], [action]),
rt.part([defs.Boolean], [
defs.block(0, function () {
return self;
})
]));
});
});
addMethod(AbstractBoolean, "orElse()", 1, function (action) {
var self = this;
// TODO Type check parameters, pass generics.
return self.ifTrue_ifFalse([
defs.block(0, function () {
return self;
})
], [action]);
});
addMethod(AbstractBoolean, "&&", 1, function (rhs) {
var self = this;
return defs.Boolean.assert(rhs).then(function () {
return self.andAlso(defs.block(0, function () {
return rhs;
}));
});
});
addMethod(AbstractBoolean, "||", 1, function (rhs) {
var self = this;
return defs.Boolean.assert(rhs).then(function () {
return self.orElse(defs.block(0, function () {
return rhs;
}));
});
});
addMethod(AbstractBoolean, "prefix!", 0, function () {
return this.andAlso_orElse([
defs.block(0, function () {
return defs.bool(false);
})
], [
defs.block(0, function () {
return defs.bool(true);
})
]);
});
addMethod(AbstractBoolean, "asBoolean", 0, function () {
return this.andAlso_orElse([
defs.block(0, function () {
return defs.bool(true);
})
], [
defs.block(0, function () {
return defs.bool(false);
})
]);
});
addMethod(AbstractBoolean, "asPrimitive", 0, function () {
return this.asPrimitiveBoolean();
});
function addIfTrueIfFalse(Ctor, index) {
addMethod(Ctor, "ifTrue() ifFalse()", [[1, 1], [1, 1]], function () {
var action, part;
part = arguments[index];
action = part[1];
// TODO Type check arguments and result.
return action.apply();
});
}
function True() {
return this;
}
util.inherits(True, AbstractBoolean);
addIfTrueIfFalse(True, 0);
addMethod(True, "asPrimitiveBoolean", 0, function () {
return true;
});
addMethod(True, "asString", 0, function () {
return defs.string("true");
});
function False() {
return this;
}
util.inherits(False, AbstractBoolean);
addIfTrueIfFalse(False, 1);
addMethod(False, "asPrimitiveBoolean", 0, function () {
return false;
});
addMethod(False, "asString", 0, function () {
return defs.string("false");
});
function binaryOp(func, type) {
return function (rawRhs) {
var self = this;
return defs[type].cast(rawRhs).then(function (rhs) {
return self["asPrimitive" + type]().then(function (fst) {
return rhs["asPrimitive" + type]().then(function (snd) {
return func(fst, snd);
});
});
});
};
}
function Comparison() {
AbstractPattern.call(this);
}
util.inherits(Comparison, Singleton);
addMethod(Comparison, "ifLessThan()", 1, function (onLessThan) {
var self = this;
return defs.Action.assert(onLessThan).then(function () {
return self.ifLessThan_ifEqualTo_ifGreaterThan([onLessThan],
[defs.emptyBlock], [defs.emptyBlock]).then(function () {
return defs.done;
});
});
});
addMethod(Comparison, "ifEqualTo()", 1, function (onEqualTo) {
var self = this;
return defs.Action.assert(onEqualTo).then(function () {
return self.ifLessThan_ifEqualTo_ifGreaterThan([defs.emptyBlock],
[onEqualTo], [defs.emptyBlock]).then(function () {
return defs.done;
});
});
});
addMethod(Comparison, "ifGreaterThan()", 1, function (onGreaterThan) {
var self = this;
return defs.Action.assert(onGreaterThan).then(function () {
return self.ifLessThan_ifEqualTo_ifGreaterThan([defs.emptyBlock],
[defs.emptyBlock], [onGreaterThan]).then(function () {
return defs.done;
});
});
});
addMethod(Comparison, "ifLessThan() ifEqualTo()", [1, 1],
function (onLessThan, onEqualTo) {
var self = this;
return defs.Action.assert(onLessThan[0]).then(function () {
return defs.Action.assert(onEqualTo[0]);
}).then(function () {
return self.ifLessThan_ifEqualTo_ifGreaterThan(onLessThan,
onEqualTo, [defs.emptyBlock]).then(function () {
return defs.done;
});
});
});
addMethod(Comparison, "ifLessThan() ifGreaterThan()", [1, 1],
function (onLessThan, onGreaterThan) {
var self = this;
return defs.Action.assert(onLessThan[0]).then(function () {
return defs.Action.assert(onGreaterThan[0]);
}).then(function () {
return self.ifLessThan_ifEqualTo_ifGreaterThan(onLessThan,
[defs.emptyBlock], onGreaterThan).then(function () {
return defs.done;
});
});
});
addMethod(Comparison, "ifEqualTo() ifGreaterThan()", [1, 1],
function (onEqualTo, onGreaterThan) {
var self = this;
return defs.Action.assert(onEqualTo[0]).then(function () {
return defs.Action.assert(onGreaterThan[0]);
}).then(function () {
return self.ifLessThan_ifEqualTo_ifGreaterThan([defs.emptyBlock],
onEqualTo, onGreaterThan).then(function () {
return defs.done;
});
});
});
// TODO Implement arbitrary size.
function GraceNumber(value) {
AbstractPattern.call(this);
value = Number(value);
this.asPrimitiveNumber = rt.method("asPrimitiveNumber", 0, function () {
return value;
});
}
util.inherits(GraceNumber, AbstractPattern);
addMethod(GraceNumber, "asPrimitive", 0, function () {
return this.asPrimitiveNumber();
});
addMethod(GraceNumber, "==", 1, function (rhs) {
var self = this;
return defs.Number.match(rhs).then(function (isNumber) {
return isNumber.andAlso_orElse([
defs.block(0, function () {
return self.asPrimitiveNumber().then(function (primSelf) {
return rhs.asPrimitiveNumber().then(function (primRhs) {
return defs.bool(primSelf === primRhs);
});
});
})
], [
defs.block(0, function () {
return defs.bool(false);
})
]);
});
});
addMethod(GraceNumber, "match()", 1, function (against) {
return defs.equalityMatch(this, against);
});
addMethod(GraceNumber, "prefix-", 0, function () {
return this.asPrimitiveNumber().then(function (value) {
return defs.number(-value);
});
});
function binaryNum(func) {
return binaryOp(function (fst, snd) {
return new GraceNumber(func(fst, snd));
}, "Number");
}
function binaryNumCmp(func) {
return binaryOp(function (fst, snd) {
return defs.bool(func(fst, snd));
}, "Number");
}
addMethod(GraceNumber, "+", 1, binaryNum(function (fst, snd) {
return fst + snd;
}));
addMethod(GraceNumber, "-", 1, binaryNum(function (fst, snd) {
return fst - snd;
}));
addMethod(GraceNumber, "*", 1, binaryNum(function (fst, snd) {
return fst * snd;
}));
addMethod(GraceNumber, "/", 1, binaryOp(function (fst, snd) {
if (snd === 0) {
return rt.NotANumber.raiseDivideByZero().then(null, function (packet) {
packet.object.stackTrace = [];
throw packet;
});
}
return new GraceNumber(fst / snd);
}, "Number"));
addMethod(GraceNumber, "%", 1, binaryNum(function (fst, snd) {
return fst % snd;
}));
addMethod(GraceNumber, "^", 1, binaryNum(function (fst, snd) {
return Math.pow(fst, snd);
}));
addMethod(GraceNumber, "compareTo()", 1, binaryOp(function (fst, snd) {
return fst < snd ? defs.LessThan :
fst > snd ? defs.GreaterThan : defs.EqualTo;
}, "Number"));
addMethod(GraceNumber, "<", 1, binaryNumCmp(function (fst, snd) {
return fst < snd;
}));
addMethod(GraceNumber, "<=", 1, binaryNumCmp(function (fst, snd) {
return fst <= snd;
}));
addMethod(GraceNumber, ">", 1, binaryNumCmp(function (fst, snd) {
return fst > snd;
}));
addMethod(GraceNumber, ">=", 1, binaryNumCmp(function (fst, snd) {
return fst >= snd;
}));
function addMath(name, method, arg) {
method = method || name;
addMethod(GraceNumber, name, 0, function () {
return this.asPrimitiveNumber().then(function (value) {
var result = Math[method](value, arg);
if (isNaN(result)) {
return defs.NotANumber.raiseForOperation_on([method], [value]);
}
return new GraceNumber(result);
});
});
}
addMath("absolute", "abs");
addMath("round");
addMath("floor");
addMath("ceiling", "ceil");
addMath("log");
addMath("exponent", "exp");
addMath("sin");
addMath("cos");
addMath("tan");
addMath("asin");
addMath("acos");
addMath("atan");
addMath("square", "pow", 2);
addMath("cube", "pow", 3);
addMath("squareRoot", "sqrt");
addMethod(GraceNumber, "asString", 0, function () {
return this.asPrimitiveNumber().then(function (value) {
return defs.string(value.toString());
});
});
function GraceString(value) {
AbstractPattern.call(this);
value = String(value);
this.asPrimitiveString = rt.method("asPrimitiveString", function () {
return value;
});
}
util.inherits(GraceString, AbstractPattern);
addMethod(GraceString, "asPrimitive", 0, function () {
return this.asPrimitiveString();
});
addMethod(GraceString, "==", 1, function (rhs) {
var self = this;
return defs.String.match(rhs).then(function (isNumber) {
return isNumber.andAlso_orElse([
defs.block(0, function () {
return self.asPrimitiveString().then(function (primSelf) {
return rhs.asPrimitiveString().then(function (primRhs) {
return defs.bool(primSelf === primRhs);
});
});
})
], [
defs.block(0, function () {
return defs.bool(false);
})
]);
});
});
addMethod(GraceString, "match()", 1, function (against) {
return defs.equalityMatch(this, against);
});
addMethod(GraceString, "at()", 1, function (rawIndex) {
return defs.Number.cast(rawIndex).then(function (index) {
return this.asPrimitiveString().then(function (string) {
return index.asPrimitiveNumber().then(function (primIndex) {
return defs.string(string[primIndex - 1]);
});
});
});
});
addMethod(GraceString, "size", 0, function () {
return this.asPrimitiveString().then(function (string) {
return rt.number(string.length);
});
});
addMethod(GraceString, "contains()", 1, function (rawSubString) {
var self = this;
return defs.String.cast(rawSubString).then(function (subString) {
return subString.asPrimitiveString().then(function (primSubString) {
return self.asPrimitiveString().then(function (primSelf) {
return defs.bool(primSelf.substring(primSubString) >= 0);
});
});
});
});
addMethod(GraceString, "do()", 1, function (rawAction) {
var self = this;
return defs.Function.cast(rawAction).then(function (action) {
return self.asPrimitiveString().then(function (string) {
return Task.each(string, function (character) {
return action.apply(defs.string(character));
});
}).then(function () {
return defs.done;
});
});
});
function binaryStrCmp(func) {
return binaryOp(function (fst, snd) {
return defs.bool(func(fst, snd));
}, "String");
}
addMethod(GraceString, "compareTo()", 1, binaryOp(function (fst, snd) {
return fst < snd ? defs.LessThan :
fst > snd ? defs.GreaterThan : defs.EqualTo;
}, "String"));
addMethod(GraceString, "<", 1, binaryStrCmp(function (fst, snd) {
return fst < snd;
}));
addMethod(GraceString, "<=", 1, binaryStrCmp(function (fst, snd) {
return fst <= snd;
}));
addMethod(GraceString, ">", 1, binaryStrCmp(function (fst, snd) {
return fst > snd;
}));
addMethod(GraceString, ">=", 1, binaryStrCmp(function (fst, snd) {
return fst >= snd;
}));
addMethod(GraceString, "++", 1, function (rhs) {
var self = this;
return self.asPrimitiveString().then(function (primSelf) {
return defs.String.match(rhs).then(function (isString) {
return isString.andAlso_orElse([
defs.block(0, function () {
return rhs;
})
], [
defs.block(0, function () {
return rt.apply(rhs, "asString");
})
]).then(function (snd) {
return snd.asPrimitiveString().then(function (primSnd) {
return defs.string(primSelf + primSnd);
});
});
});
});
});
addMethod(GraceString, "fold() startingWith()", [[1, 1], 1],
function (part, value) {
var pattern, self;
self = this;
pattern = part[0];
value = value[0];
return defs.Function2.cast(part[1]).then(function (fold) {
return self["do"](rt.block(1, function (element) {
return fold.apply(value, element).then(function (result) {
return pattern.assert(result).then(function () {
value = result;
return rt.done;
});
});
})).then(function () {
return value;
});
});
});
addMethod(GraceString, "asNumber", 0, function () {
var self = this;
return self.asPrimitiveString().then(function (value) {
var number = Number(value);
if (isNaN(number)) {
return rt.NotANumber.raiseForParse(self).then(null, function (packet) {
packet.object.stackTrace = [];
throw packet;
});
}
return defs.number(number);
});
});
addMethod(GraceString, "substringFrom() to()", [1, 1], function (pFrom, pTo) {
var self = this;
return toNumber(pFrom[0]).then(function (from) {
return toNumber(pTo[0]).then(function (to) {
return self.asPrimitiveString().then(function (primSelf) {
if (from < 1 || from > primSelf.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(from));
}
if (to < 1 || to > primSelf.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(to));
}
return defs.string(primSelf.substring(from - 1, to));
});
});
});
});
addMethod(GraceString, "substringFrom() size()", [1, 1],
function (pFrom, pSize) {
var self = this;
return toNumber(pFrom[0]).then(function (from) {
return toNumber(pSize[0]).then(function (size) {
return self.asPrimitiveString().then(function (primSelf) {
var to = from + size;
if (from < 1 || from > primSelf.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(from));
}
if (to < 1 || to > primSelf.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(to));
}
return defs.string(primSelf.substring(from - 1, to - 1));
});
});
});
});
addMethod(GraceString, "substringFrom()", 1, function (from) {
var self = this;
return self.asPrimitiveString().then(function (string) {
return self.substringFrom_to([from], [defs.number(string.length + 1)]);
});
});
addMethod(GraceString, "substringTo()", 1, function (to) {
return this.substringFrom_to([defs.number(1)], [to]);
});
addMethod(GraceString, "replace() with()", [1, 1], function (pFrom, pTo) {
var self = this;
return toString(pFrom[0]).then(function (from) {
return toString(pTo[0]).then(function (to) {
return self.asPrimitiveString().then(function (primSelf) {
return defs.string(primSelf.replace(from, to));
});
});
});
});
addMethod(GraceString, "startsWith()", 1, function (rawPrefix) {
var self = this;
return toString(rawPrefix).then(function (prefix) {
return self.asPrimitiveString().then(function (primSelf) {
var index = prefix.length;
return defs.bool(index > primSelf.length ? false :
primSelf.lastIndexOf(prefix, index) === 0);
});
});
});
addMethod(GraceString, "endsWith()", 1, function (rawSuffix) {
var self = this;
return toString(rawSuffix).then(function (suffix) {
return self.asPrimitiveString().then(function (primSelf) {
var index = primSelf.length - suffix.length;
return defs.bool(index < 0 ? false :
primSelf.indexOf(suffix, index) === index);
});
});
});
function addIndexOfs(forwards) {
var defaultStart, method, name;
method = forwards ? "indexOf" : "lastIndexOf";
name = method + "_startingAt_ifAbsent";
defaultStart = forwards ? function () {
return Task.resolve(defs.number(1));
} : function (string) {
return string.asPrimitiveString().then(function (primString) {
return defs.number(primString.length);
});
};
addMethod(GraceString, method + "() startingAt() ifAbsent()",
[1, 1, [1, 1]], function (pSearch, pFrom, pIfAbsent) {
var self = this;
return toString(pSearch[0]).then(function (search) {
return toNumber(pFrom[0]).then(function (from) {
return defs.Action.cast(pIfAbsent[1]).then(function (absent) {
return self.asPrimitiveString().then(function (primSelf) {
var index;
if (from < 0 || from > primSelf.length ||
from === 0 && primSelf.length !== 0) {
return defs.OutOfBounds.raiseForIndex(defs.number(from));
}
index = primSelf[method](search, from - 1);
if (index < 0) {
return absent.apply().then(function (result) {
return pIfAbsent[0].assert(result).then(function () {
return result;
});
});
}
return defs.number(index + 1);
});
});
});
});
});
addMethod(GraceString, method + "()", 1, function (search) {
var self = this;
return defaultStart(self).then(function (from) {
return self[name]([search], [from], [
defs.block(0, function () {
return defs.FailedSearch.raiseForObject(search);
})
]);
});
});
addMethod(GraceString, method + "() startingAt()", [1, 1],
function (search, from) {
var self = this;
return self[name](search, from, [
defs.block(0, function () {
return defs.FailedSearch.raiseForObject(search);
})
]);
});
addMethod(GraceString, method + "() ifAbsent()", [1, [1, 1]],
function (search, absent) {
var self = this;
return defaultStart(self).then(function (from) {
return self[name](search, [from], rt.part(absent[0], absent[1]));
});
});
}
addIndexOfs(true);
addIndexOfs(false);
addMethod(GraceString, "asImmutable", 0, function () {
return this;
});
addMethod(GraceString, "asString", 0, function () {
return this.asPrimitiveString().then(function (value) {
return defs.string("\"" + util.escape(value) + "\"");
});
});
function Part(name, hasVarArg, generics, parameters) {
if (typeof hasVarArg !== "boolean") {
parameters = generics;
generics = hasVarArg;
hasVarArg = false;
}
if (generics === undefined) {
parameters = [];
generics = [];
} else if (parameters === undefined) {
parameters = generics;
generics = [];
}
this.name = name;
this.hasVarArg = hasVarArg;
this.generics = generics;
this.parameters = parameters;
}
Part.prototype.pretty = function () {
return this.name + (this.parameters.length > 0 ? "()" : "");
};
Part.prototype.toString = function () {
var generics, params;
generics = this.generics;
params = this.parameters;
return this.name +
(generics.length > 0 ? "<" + generics.join(", ") + ">" : "") +
(params.length > 0 ? "(" + params.join(", ") + ")" : "");
};
function Signature(parts, hasVarArg, generics, parameters) {
if (typeof parts === "string") {
this.parts = [new Part(parts, hasVarArg, generics, parameters)];
} else {
this.parts = util.map(parts, function (part) {
if (typeof part === "string") {
return new Part(part, false, [], []);
}
return part;
});
}
}
Signature.prototype.name = function () {
var i, l, name, parts;
parts = this.parts;
name = [];
for (i = 0, l = parts.length; i < l; i += 1) {
name.push(parts[i].pretty());
}
return name.join(" ");
};
Signature.prototype.toString = function () {
return this.parts.join(" ");
};
function hasSignatures(pattern) {
return pattern.object !== "undefined" &&
util.isArray(pattern.object.signatures);
}
// A proxy for hoisted type declarations that will be filled out with the values
// of a real type once the actual value is built. As such, the proxy can be
// combined with other patterns and be tested for equality, but it cannot be
// matched or stringified.
function TypeProxy(name) {
var self = this;
this.object = {
"dependents": [],
"become": function (pattern) {
var pname;
if (pattern instanceof TypeProxy && pattern.object.become) {
pattern.object.dependents.push(this);
return Task.resolve();
}
if (pattern.object && pattern.object.signatures) {
this.signatures = pattern.object.signatures;
}
for (pname in pattern) {
if (!self.hasOwnProperty(pname) && pattern[pname] !== self[name]) {
self[pname] = pattern[pname];
}
}
delete this.become;
return Task.each(this, this.dependents, function (dependent) {
return dependent.become(self);
}).then(function () {
delete this.dependents;
});
}
};
if (name !== null) {
this.asString = rt.method("asString", 0, function () {
return defs.string(name);
});
}
}
util.inherits(TypeProxy, AbstractPattern);
addMethod(TypeProxy, "match()", 1, function () {
return this.asString().then(function (name) {
return defs.IncompleteType.raiseForName(name);
});
});
function andWaitOn(andTask, lhs, rhs) {
return andTask.then(function (and) {
var become, hasLhs, hasRhs, proxy;
proxy = new TypeProxy(null);
proxy.asString = and.asString;
if (lhs instanceof TypeProxy && lhs.object.become) {
lhs.object.dependents.push(proxy.object);
hasLhs = false;
} else {
hasLhs = true;
}
if (rhs instanceof TypeProxy && rhs.object.become) {
rhs.object.dependents.push(proxy.object);
hasRhs = false;
} else {
hasRhs = true;
}
become = proxy.object.become;
proxy.object.become = function (becoming) {
if (becoming === lhs && !hasRhs) {
hasLhs = true;
} else if (becoming === rhs && !hasLhs) {
hasRhs = true;
} else {
return lhs["&"](rhs).then(function (joint) {
return become.call(proxy.object, joint);
});
}
return Task.resolve();
};
return proxy;
});
}
addMethod(TypeProxy, "&", 1, function (pattern) {
var and = AbstractPattern.prototype["&"].call(this, pattern);
if (!(pattern instanceof TypeProxy || hasSignatures(pattern))) {
return and;
}
return andWaitOn(and, this, pattern);
});
function Type(name, generics, extending, signatures) {
var i, l;
if (typeof name !== "string") {
signatures = extending;
extending = generics;
generics = name;
name = null;
}
if (typeof generics !== "number") {
signatures = extending;
extending = generics;
generics = 0;
}
if (signatures === undefined) {
signatures = extending;
extending = null;
} else if (util.isArray(extending)) {
for (i = 0, l = extending.length; i < l; i += 1) {
signatures = signatures.concat(extending[i].object.signatures);
}
} else {
signatures = signatures.concat(extending.object.signatures);
}
this.object = {
"generics": generics,
"signatures": signatures
};
if (name !== null) {
name = defs.string(name);
this.asString = rt.method("asString", 0, function () {
return name;
});
}
}
util.inherits(Type, AbstractPattern);
function typeMatch(type, value, onFail) {
var i, l, method, name, parts, signature, signatures;
signatures = type.object.signatures;
for (i = 0, l = signatures.length; i < l; i += 1) {
signature = signatures[i];
name = signature.name();
method = value[util.uglify(name)];
parts = signature.parts;
if (method === undefined) {
return onFail(value, type, name);
}
if (typeof method === "function" && method.parts !== undefined) {
if (!defs.isSubMethod(method.parts, parts)) {
return onFail(value, type, name);
}
}
}
return defs.success(value, type);
}
addMethod(Type, "match()", 1, function (value) {
return typeMatch(this, value, defs.failure);
});
addMethod(Type, "assert()", 1, function (value) {
return typeMatch(this, value, function (val, type, name) {
return defs.AssertionFailure.raiseForValue_againstType_missing([val],
[type], [rt.string(name)]);
});
});
addMethod(Type, "cast()", 1, function (value) {
var self = this;
return self.assert(value).then(function () {
var i, l, name, object, pretty, signatures;
if (defs.isGraceObject(value)) {
return value;
}
signatures = self.object.signatures;
object = defs.object();
function makeMethod(mname) {
return function () {
return value[mname].apply(value, arguments);
};
}
for (i = 0, l = signatures.length; i < l; i += 1) {
pretty = signatures[i].name();
name = util.uglify(pretty);
object[name] = rt.method(pretty, makeMethod(name));
}
if (typeof value.object === "object") {
object.object = value.object;
}
return object;
});
});
addMethod(Type, "&", 1, function (pattern) {
var andTask, self;
self = this;
andTask = AbstractPattern.prototype["&"].call(this, pattern);
if (pattern instanceof TypeProxy && pattern.object.become) {
return andWaitOn(andTask, this, pattern);
}
if (!hasSignatures(pattern)) {
return andTask;
}
return andTask.then(function (and) {
var type =
new Type(self.object.signatures.concat(pattern.object.signatures));
type.asString = and.asString;
return type;
});
});
addMethod(Type, "asString", 0, function () {
var sep, signatures;
signatures = this.object.signatures;
sep = signatures.length === 0 ? "" : " ";
return defs.string("type {" + sep + signatures.join("; ") + sep + "}");
});
function NamedPattern(name, pattern) {
this.name = rt.method("name", function () {
return name;
});
this.pattern = rt.method("pattern", function () {
return pattern;
});
}
util.inherits(NamedPattern, AbstractPattern);
addMethod(NamedPattern, "match()", 1, function (value) {
return this.pattern().then(function (pattern) {
return pattern.match(value);
});
});
addMethod(NamedPattern, "assert()", 1, function (value) {
return this.pattern().then(function (pattern) {
return pattern.assert(value);
});
});
addMethod(NamedPattern, "asString", 0, function () {
var self = this;
return this.name().then(function (name) {
return self.pattern().then(function (pattern) {
return defs.string(name.toString() +
(pattern === defs.Unknown ? "" : " : " + pattern));
});
});
});
function matchAsString(name) {
return function () {
return this.value().then(function (value) {
return asString(value).then(function (string) {
return defs.string(name + "(" + string + ")");
});
});
};
}
function Success(value, pattern) {
True.call(this);
this.value = rt.method("value", 0, function () {
return value;
});
this.pattern = rt.method("pattern", 0, function () {
return pattern;
});
}
util.inherits(Success, True);
addMethod(Success, "asString", 0, matchAsString("success"));
function Failure(value, pattern) {
False.call(this);
this.value = rt.method("value", 0, function () {
return value;
});
this.pattern = rt.method("pattern", 0, function () {
return pattern;
});
}
util.inherits(Failure, False);
addMethod(Failure, "asString", 0, matchAsString("failure"));
// Collects the elements of a collection using the do() method.
function getElements(value) {
var elements = [];
return value["do"](defs.block(1, function (element) {
elements.push(element);
return rt.done;
})).then(function () {
return elements;
});
}
// A private definition used for all collections which store their elements
// internally in an array.
function InternalArray(elements, open, close) {
this.object = {
"elements": elements,
"open": open,
"close": close
};
}
util.inherits(InternalArray, GraceObject);
addMethod(InternalArray, "size", 0, function () {
return defs.number(this.object.elements.length);
});
addMethod(InternalArray, "isEmpty", 0, function () {
return defs.bool(this.object.elements.length === 0);
});
addMethod(InternalArray, "do()", 1, function (action) {
var elements = this.object.elements;
return defs.Function.assert(action).then(function () {
return Task.each(elements, function (element) {
return action.apply(element);
});
}).then(function () {
return defs.done;
});
});
addMethod(InternalArray, "contains()", 1, function (value) {
return new Task(this, function (resolve, reject) {
return Task.each(this.object.elements, function (element) {
return rt.apply(element, "==", [[value]]).then(function (isEqual) {
return isEqual.andAlso(rt.block(0, function () {
resolve(isEqual);
return Task.never();
}));
});
}).then(function () {
resolve(defs.bool(false));
}, reject);
});
});
addMethod(InternalArray, "concatenate", 0, function () {
var joining = defs.string("");
return this["do"](rt.block(1, function (element) {
return joining["++"](element).then(function (joined) {
joining = joined;
return defs.done;
});
})).then(function () {
return joining;
});
});
addMethod(InternalArray, "concatenateSeparatedBy()", 1, function (sep) {
var joining, once;
joining = defs.string("");
once = false;
return this["do"](rt.block(1, function (element) {
return (once ? joining["++"](sep) : (once = true, Task.resolve(joining)))
.then(function (part) {
return part["++"](element);
}).then(function (joined) {
joining = joined;
return defs.done;
});
})).then(function () {
return joining;
});
});
addMethod(InternalArray, "fold() startingWith()", [[1, 1], 1],
function (fold, value) {
var pattern = fold[0];
fold = fold[1];
value = value[0];
return this["do"](rt.block(1, function (element) {
return fold.apply(value, element).then(function (result) {
return pattern.assert(result).then(function () {
value = result;
return rt.done;
});
});
})).then(function () {
return value;
});
});
addMethod(InternalArray, "asPrimitiveArray", 0, function () {
return this.object.elements.concat();
});
addMethod(InternalArray, "asPrimitive", 0, function () {
return Task.each(this.object.elements, function (element) {
if (typeof element.asPrimitive === "function") {
return element.asPrimitive();
}
return element;
});
});
addMethod(InternalArray, "asString", 0, function () {
var close, comma, elements, open;
elements = this.object.elements;
open = this.object.open;
close = this.object.close;
if (elements.length === 0) {
return defs.string(open + close);
}
elements = elements.concat();
comma = defs.string(", ");
return defs.string(open)["++"](elements.shift()).then(function (string) {
return Task.each(elements, function (element) {
return rt.apply(element, "asString").then(function (stringified) {
return string["++"](comma).then(function (commaed) {
return commaed["++"](stringified).then(function (replacement) {
string = replacement;
});
});
});
}).then(function () {
return string["++"](defs.string(close));
});
});
});
addMethod(InternalArray, "internalPush()", 1, function (element) {
this.object.elements.push(element);
return rt.done;
});
InternalArray.prototype.internalPush.isConfidential = true;
addMethod(InternalArray, "internalRemove()", 2, function (remove, rawAction) {
var elements = this.object.elements;
return defs.Action.cast(rawAction).then(function (action) {
return new Task(function (resolve, reject) {
return Task.each(elements, function (element, i) {
return rt.apply(element, "==", [remove]).then(function (bool) {
return bool.ifTrue(rt.block(0, function () {
elements.splice(i, 1);
resolve(defs.number(i + 1));
return Task.never();
}));
});
}).then(function () {
return action.apply().then(function (result) {
resolve(result);
});
}).then(null, reject);
});
});
});
InternalArray.prototype.internalRemove.isConfidential = true;
addMethod(InternalArray, "internalSplice()", rt.gte(2),
function (rawIndex, rawAmount) {
var additions, elements;
elements = this.object.elements;
additions = util.slice(arguments, 2);
return toNumber(rawIndex).then(function (index) {
return toNumber(rawAmount).then(function (amount) {
return elements
.splice.apply(elements, [index, amount].concat(additions))[0];
});
});
});
InternalArray.prototype.internalSplice.isConfidential = true;
addMethod(InternalArray, "asImmutable", 0, function () {
return this;
});
function List(elements) {
InternalArray.call(this, elements, "[", "]");
}
util.inherits(List, InternalArray);
addMethod(List, "at()", 1, function (num) {
var elements = this.object.elements;
return toNumber(num).then(function (index) {
if (index < 1 || index > elements.length) {
return defs.OutOfBounds.raiseForIndex(num);
}
return elements[index - 1];
});
});
addMethod(List, "first", 0, function () {
return this.at(defs.number(1));
});
addMethod(List, "last", 0, function () {
return this.at(defs.number(this.object.elements.length));
});
addMethod(List, "indices", 0, function () {
var i, indices, l;
indices = [];
for (i = 1, l = this.object.elements.length; i <= l; i += 1) {
indices.push(defs.number(i));
}
return new List(indices);
});
addMethod(List, "++", 1, function (rhs) {
var elements = this.object.elements;
return defs.Do.cast(rhs).then(function () {
return getElements(rhs).then(function (rhsElements) {
return defs.list(elements.concat(rhsElements));
});
});
});
addMethod(List, "sliceFrom() to()", [1, 1], function (rawFrom, rawTo) {
var elements = this.object.elements;
return toNumber(rawFrom).then(function (from) {
if (from < 1 || from > elements.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(from));
}
return toNumber(rawTo).then(function (to) {
if (to < 1 || to > elements.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(to));
}
return new List(elements.slice(from - 1, to - 1));
});
});
});
addMethod(List, "sliceFrom() to()", [1, 1], function (rawFrom, rawTo) {
var elements = this.object.elements;
return toNumber(rawFrom).then(function (from) {
if (from < 1 || from > elements.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(from));
}
return toNumber(rawTo).then(function (to) {
if (to < 1 || to > elements.length + 1) {
return defs.OutOfBounds.raiseForIndex(defs.number(to));
}
return new List(elements.slice(from - 1, to - 1));
});
});
});
addMethod(List, "sliceFrom()", 1, function (from) {
return this.sliceFrom_to(from, defs.number(this.object.elements.length + 1));
});
addMethod(List, "sliceTo()", 1, function (to) {
return this.sliceFrom_to(defs.number(1), to);
});
function ListPattern(pattern) {
this.pattern = rt.method("pattern", 0, function () {
return pattern;
});
}
util.inherits(ListPattern, AbstractPattern);
addMethod(ListPattern, "match()", 1, function (list) {
var self = this;
return self.pattern().then(function (pattern) {
return new Task(function (resolve, reject) {
defs.List.match(list).then(function (isList) {
return isList.ifTrue_ifFalse([
defs.block(0, function () {
return list["do"](defs.block(1, function (value) {
return new Task(function (next, rejectIter) {
pattern.match(value).then(function (matched) {
return matched.ifTrue_ifFalse([
defs.block(0, function () {
next(rt.done);
return Task.never();
})
], [
defs.block(0, function () {
resolve(defs.failure(list, self));
return Task.never();
})
]);
}).then(null, rejectIter);
});
})).then(function () {
return defs.success(list, self);
});
})
], [
defs.block(0, function () {
return defs.failure(list, self);
})
]);
}).then(resolve, reject);
});
});
});
addMethod(ListPattern, "asString", 0, function () {
return this.pattern().then(function (pattern) {
return asString(pattern).then(function (string) {
return defs.string("List<" + string + ">");
});
});
});
function Set(elements) {
InternalArray.call(this, elements, "{", "}");
}
util.inherits(Set, InternalArray);
addMethod(Set, "++", 1, function (rhs) {
var newElements, self;
self = this;
newElements = this.object.elements.concat();
return defs.Do.cast(rhs).then(function () {
return rhs["do"](rt.block(1, function (element) {
return self.contains(element).then(function (bool) {
return bool.ifFalse(rt.block(0, function () {
newElements.push(element);
return rt.done;
}));
});
}));
}).then(function () {
return defs.set(newElements);
});
});
addMethod(Set, "internalPush", 1, function (value) {
var self = this;
return this.contains(value).then(function (bool) {
return bool.ifFalse(rt.block(0, function () {
return InternalArray.prototype.internalPush.call(self, value);
}));
});
});
function Entry(key, value) {
this.object = {
"key": key,
"value": value
};
}
addMethod(Entry, "key", 0, function () {
return this.object.key;
});
addMethod(Entry, "value", 0, function () {
return this.object.value;
});
addMethod(Entry, "==", 1, function (rawRhs) {
var key, value;
key = this.object.key;
value = this.object.value;
return defs.Entry.match(rawRhs).then(function (isEntry) {
return isEntry.ifTrue_ifFalse([rt.block(0, function () {
return defs.Entry.cast(rawRhs).then(function (rhs) {
return rhs.key().then(function (rhsKey) {
return rt.apply(key, "==", [[rhsKey]]);
}).then(function (bool) {
return bool.andAlso(rt.block(0, function () {
return rhs.value().then(function (rhsValue) {
return rt.apply(value, "==", [[rhsValue]]);
});
}));
});
});
})], [rt.block(0, function () {
return defs.bool(false);
})]);
});
});
addMethod(Entry, "asString", 0, function () {
var key, value;
key = this.object.key;
value = this.object.value;
return rt.apply(key, "asString").then(function (keyString) {
return rt.apply(value, "asString").then(function (valueString) {
return keyString["++"](defs.string(" => ")).then(function (cat) {
return cat["++"](valueString);
});
});
});
});
function internalEntry(entry) {
if (entry instanceof Entry) {
return Task.resolve(entry);
}
return entry.key().then(function (key) {
return entry.value().then(function (value) {
return new Entry(key, value);
});
});
}
function Dictionary(elements) {
InternalArray.call(this, elements, "{", "}");
}
util.inherits(Dictionary, InternalArray);
addMethod(Dictionary, "keys", 0, function () {
return Task.each(this.object.elements, function (entry) {
return entry.key();
}).then(function (keys) {
return new Set(keys);
});
});
addMethod(Dictionary, "values", 0, function () {
return Task.each(this.object.elements, function (entry) {
return entry.value();
}).then(function (keys) {
return new Set(keys);
});
});
addMethod(Dictionary, "at() ifAbsent()", [1, 1],
function (key, onAbsent) {
var elements = this.object.elements;
return rt.Action.assert(onAbsent).then(function () {
return new Task(function (resolve, reject) {
return Task.each(elements, function (entry) {
return entry.key().then(function (rawKey) {
return rt.Object.cast(rawKey);
}).then(function (eKey) {
return rt.apply(eKey, "==", [key]).then(function (bool) {
return bool.ifTrue(rt.block(0, function () {
return entry.value().then(function (value) {
resolve(value);
return Task.never();
});
}));
});
});
}).then(function () {
return onAbsent[0].apply();
}).then(resolve, reject);
});
});
});
addMethod(Dictionary, "at()", 1, function (key) {
return this.at_ifAbsent([key], [rt.block(0, function () {
return defs.FailedSearch.raiseForObject(key);
})]);
});
addMethod(Dictionary, "at() do()", [1, 1], function (key, proc) {
var elements = this.object.elements;
return defs.Procedure.assert(proc[0]).then(function () {
return new Task(function (resolve, reject) {
return Task.each(elements, function (entry) {
return entry.key().then(function (eKey) {
return rt.apply(eKey, "==", [key]).then(function (bool) {
return bool.ifTrue(rt.block(0, function () {
return entry.value().then(function (value) {
return proc[0].apply(value);
}).then(function () {
resolve(rt.done);
return Task.never();
});
}));
});
});
}).then(function () {
resolve(rt.done);
}, reject);
});
});
});
addMethod(Dictionary, "containsKey()", 1, function (key) {
var elements = this.object.elements;
return new Task(function (resolve, reject) {
return Task.each(elements, function (entry) {
return entry.key().then(function (eKey) {
return rt.apply(eKey, "==", [[key]]).then(function (bool) {
return bool.ifTrue(rt.block(0, function () {
resolve(bool);
return Task.never();
}));
});
});
}).then(function () {
resolve(defs.bool(false));
}).then(null, reject);
});
});
addMethod(Dictionary, "containsValue()", 1, function (value) {
var elements = this.object.elements;
return new Task(function (resolve, reject) {
return Task.each(elements, function (entry) {
return entry.value().then(function (eValue) {
return rt.apply(eValue, "==", [[value]]).then(function (bool) {
return bool.ifTrue(rt.block(0, function () {
resolve(bool);
return Task.never();
}));
});
});
}).then(function () {
resolve(defs.bool(false));
}).then(null, reject);
});
});
addMethod(Dictionary, "++", 1, function (rhs) {
var newElements, self;
self = this;