typify
Version:
Runtime type-checking for JavaScript.
1,688 lines (1,445 loc) • 67.9 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jsc = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
var utils = require("./utils.js");
// typify: instance Thunk
// Thunk, for the lazy-evaluation and recursive parser.
// :: fn -> undefined
function Thunk(f) {
this.thunk = f;
this.forced = false;
this.value = undefined;
}
// :: any -> Thunk
function delay(f) {
return new Thunk(f);
}
// :: any -> *
function force(thunk) {
if (thunk instanceof Thunk) {
if (!thunk.forced) {
thunk.value = thunk.thunk();
thunk.forced = true;
}
return thunk.value;
} else {
return thunk;
}
}
// :: fn -> array string -> *
function parse(p, tokens) {
var res = p(tokens, 0);
// console.log("parse", res, tokens, tokens.length);
if (res !== undefined && res[1] >= tokens.length) {
return res[0];
} else {
return undefined;
}
}
// :: array string -> nat -> (tuple undefined idx)?
function eof(tokens, idx) {
// console.log("eof", tokens, idx);
if (idx < tokens.length) {
return undefined;
} else {
return [undefined, idx];
}
}
// :: string -> fn
function token(tok) {
// :: array string -> nat -> (tuple string nat)?
return function (tokens, idx) {
// console.log("token", tokens, idx, tok);
if (idx >= tokens.length) { return undefined; }
if (tokens[idx] === tok) {
return [tok, idx+1];
} else {
return undefined;
}
};
}
// :: fn -> fn
function satisfying(predicate) {
// :: array string -> nat -> (tuple string nat)?
return function (tokens, idx) {
// console.log("satisfying", predicate.name || predicate, tokens, idx);
if (idx >= tokens.length) { return undefined; }
if (predicate(tokens[idx])) {
return [tokens[idx], idx+1];
} else {
return undefined;
}
};
}
// :: -> fn
function any() {
// :: array string -> nat -> (tuple string nat)?
return function (tokens, idx) {
if (idx < tokens.length) {
return [tokens[idx], idx+1];
} else {
return undefined;
}
};
}
// :: * -> fn
function pure(x) {
// :: array string -> nat -> tuple * nat
return function (tokens, idx) {
return [x, idx];
};
}
// :: fn... -> fn
function or() {
var args = utils.slice(arguments);
var len = args.length;
// :: array string -> nat -> (tuple * nat)?
return function (tokens, idx) {
for (var i = 0; i < len; i++) {
var res = force(args[i])(tokens, idx);
if (res !== undefined) { return res; }
}
return undefined;
};
}
// :: fn | Thunk ... -> fn
function lift() {
var len = arguments.length - 1;
var f = arguments[len];
var args = utils.slice(arguments, 0, -1);
// :: array string -> nat -> (tuple * nat)?
return function(tokens, idx) {
var resargs = new Array(len);
for (var i = 0; i < len; i++) {
var res = force(args[i])(tokens, idx);
// console.log("lift argument:", res, force(args[i]));
if (res === undefined) { return undefined; }
resargs[i] = res[0];
idx = res[1];
}
// console.log("lift value", f.apply(undefined, resargs), idx);
return [f.apply(undefined, resargs), idx];
};
}
// :: array -> * -> array string -> nat -> tuple array nat
function manyLoop(res, a, tokens, idx) {
while (true) {
var aq = a(tokens, idx);
if (aq === undefined) { return [res, idx]; }
res.push(aq[0]);
idx = aq[1];
}
}
// :: fn -> fn
function some(a) {
// :: array string -> nat -> (tuple array nat)?
return function (tokens, idx) {
a = force(a);
var res = [];
var ap = a(tokens, idx);
if (ap === undefined) { return undefined; }
res.push(ap[0]);
idx = ap[1];
return manyLoop(res, a, tokens, idx);
};
}
// :: fn -> fn
function many(a) {
// :: array string -> nat -> tuple array nat
return function (tokens, idx) {
a = force(a);
var res = [];
return manyLoop(res, a, tokens, idx);
};
}
// :: fn -> string -> fn
function sepBy(a, sep) {
// :: array string -> nat -> (tuple array nat)?
return function (tokens, idx) {
a = force(a);
var res = [];
var ap = a(tokens, idx);
if (ap === undefined) { return undefined; }
res.push(ap[0]);
idx = ap[1];
while (true) {
if (tokens[idx] !== sep) { return [res, idx]; }
idx += 1;
var aq = a(tokens, idx);
if (aq === undefined) { return [res, idx]; }
res.push(aq[0]);
idx = aq[1];
}
};
}
// :: fn -> * -> fn
function optional(p, def) {
// :: array string -> nat -> (tuple * nat)?
return function (tokens, idx) {
var res = force(p)(tokens, idx);
if (res === undefined) {
return [def, idx];
} else {
return res;
}
};
}
module.exports = {
parse: parse,
pure: pure,
or: or,
lift: lift,
many: many,
some: some,
sepBy: sepBy,
eof: eof,
token: token,
any: any,
satisfying: satisfying,
optional: optional,
delay: delay,
};
},{"./utils.js":10}],2:[function(require,module,exports){
"use strict";
var p = require("./predicates.js");
var utils = require("./utils.js");
module.exports = {
"number": p.isNumber,
"integer": p.isInteger,
"nat": function (val) {
return p.isInteger(val) && p.isNonNegative(val);
},
"positive": function (val, valueCheck) {
return p.isPositive(val) && (!valueCheck || valueCheck(val));
},
"nonnegative": function (val, valueCheck) {
return p.isNonNegative(val) && (!valueCheck || valueCheck(val));
},
"finite": function (val, valueCheck) {
return p.isFinite(val) && (!valueCheck || valueCheck(val));
},
"boolean": p.isBoolean,
"string": p.isString,
"date": p.isDate,
"regexp": p.isRegExp,
"function": p.isFunction,
"fn": p.isFunction,
"arguments": p.isArguments,
"any": p.constTrue,
"array": function (arr, valueCheck) {
return p.isArray(arr) && (!valueCheck || utils.every(arr, valueCheck));
},
"map": function (map, valueCheck) {
return (p.isObject(map) && !p.isArray(map)) && (!valueCheck || utils.every(utils.values(map), valueCheck));
},
"tuple": function (v) {
if (!Array.isArray(v)) { return false; }
var args = utils.slice(arguments, 1);
if (args.length !== v.length) { return false; }
for (var i = 0; i < args.length; i++) {
if (!args[i](v[i])) {
return false;
}
}
return true;
},
};
},{"./predicates.js":7,"./utils.js":10}],3:[function(require,module,exports){
"use strict";
var utils = require("./utils.js");
var p = require("./predicates.js");
// Forward declaration
var compileCheckableTypeRecursive;
// :: Environment -> map (array checkable) -> array string -> checkableAlt | checkableAnd -> fn
function compileAndAlt(environment, context, recNames, parsed, operator) {
var cs = utils.map(parsed.options, compileCheckableTypeRecursive.bind(undefined, environment, context, recNames));
// compiledOptAlt :: map fn -> fn -> any -> fn... -> boolean
operator = parsed.type === "and" ? "every" : "some";
return function (recChecks, varCheck, arg) {
return cs[operator](function (c) {
return c(recChecks, varCheck, arg);
});
};
}
// :: Environment -> map (array checkable) -> array string -> checkableVar -> fn
function compileVar(environment, context, recNames, parsed) {
if (utils.has(context, parsed.name)) {
// compiledContext :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
// console.log("varcheck", varCheck, arg);
return varCheck(parsed.name, arg);
};
} else if (environment.has(parsed.name)) {
var check = environment.get(parsed.name);
// compiledEnv :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck) {
var args = utils.slice(arguments, 2);
return check.apply(undefined, args);
};
} else if (recNames && utils.contains(recNames, parsed.name)) {
// compiledRec :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
return recChecks[parsed.name](recChecks, varCheck, arg);
};
} else {
throw new Error("unknown type: " + parsed.name);
}
}
// :: Environment -> map (array checkable) -> array string -> checkablePoly -> fn
function compilePoly(environment, context, recNames, parsed) {
var args = utils.map(parsed.args, compileCheckableTypeRecursive.bind(undefined, environment, context, recNames));
if (utils.has(context, parsed.name)) {
// compiledPoly :: map fn -> fn -> any -> fn... -> boolean
return function compiledPolyEnv(recChecks, varCheck, arg) {
var argsChecks = args.map(function (argCheck) {
return argCheck.bind(undefined, recChecks, varCheck);
});
return varCheck.apply(undefined, [parsed.name, arg].concat(argsChecks));
};
} else if (environment.has(parsed.name)) {
var polyCheck = environment.get(parsed.name);
// compiledPoly :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
var argsChecks = args.map(function (argCheck) {
return argCheck.bind(undefined, recChecks, varCheck);
});
return polyCheck.apply(undefined, [arg].concat(argsChecks));
};
} else {
throw new Error("unknown type: " + parsed.name);
}
}
// :: Environment -> map (array checkable) -> array string -> checkableOpt -> fn
function compileOpt(environment, context, recNames, parsed) {
var c = compileCheckableTypeRecursive(environment, context, recNames, parsed.term);
// compiledOpt :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
return arg === undefined || c(recChecks, varCheck, arg);
};
}
// :: Environment -> map (array checkable) -> array string -> checkableLiteral -> fn
function compileLiteral(environment, context, recNames, parsed) {
if (parsed.value !== parsed.value) {
// NaN
// compiledNaN :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
return arg !== arg;
};
} else {
// compiledLiteral :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
return arg === parsed.value;
};
}
}
// :: Environment -> map (array checkable) -> array string -> checkableRecord -> fn
function compileRecord(environment, context, recNames, parsed) {
var fields = {};
for (var name in parsed.fields) {
fields[name] = compileCheckableTypeRecursive(environment, context, recNames, parsed.fields[name]);
}
var closed = parsed.closed;
// compiledRecord : map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
if (!p.isObject(arg)) {
return false;
}
for (var fieldName in fields) {
if (!fields[fieldName](recChecks, varCheck, arg[fieldName])) {
return false;
}
}
if (closed) {
for (var key in arg) {
if (!fields[key]) {
return false;
}
}
}
return true;
};
}
// :: Environment -> map (array checkable) -> array string -> checkableUser -> fn
function compileUser(environment, context, recNames, parsed) {
// compiledUser :: map fn -> fn -> any -> fn... -> boolean
return function (recChecks, varCheck, arg) {
return parsed.predicate(arg);
};
}
// :: Environment -> map (array checkable) -> array string -> checkable -> fn
function compileCheckableTypeRecursive(environment, context, recNames, parsed) {
switch (parsed.type) {
case "var": return compileVar(environment, context, recNames, parsed);
case "literal": return compileLiteral(environment, context, recNames, parsed);
case "poly": return compilePoly(environment, context, recNames, parsed);
case "any": return p.constTrue;
case "opt": return compileOpt(environment, context, recNames, parsed);
case "alt": return compileAndAlt(environment, context, recNames, parsed);
case "and": return compileAndAlt(environment, context, recNames, parsed);
case "record": return compileRecord(environment, context, recNames, parsed);
case "user": return compileUser(environment, context, recNames, parsed);
}
}
// :: Environment -> map (array checkable) -> checkable -> fn
function compileCheckableType(environment, context, parsed) {
return compileCheckableTypeRecursive(environment, context, [], parsed).bind(undefined, {});
}
module.exports = {
compile: compileCheckableType,
compileRecursive: compileCheckableTypeRecursive,
compileRecord: compileRecord,
};
},{"./predicates.js":7,"./utils.js":10}],4:[function(require,module,exports){
"use strict";
var assert = require("assert");
var any = { type: "any" };
/*
typify: adt checkable
checkableAny: { type: 'any' }
checkableLiteral: { type: 'literal', value: string|number|boolean|null|undefined|nan }
checkableVar: { type: 'var', name: string }
checkableRecord: { type: 'record', fields: map checkable, closed: boolean }
checkablePoly: { type: 'poly', name: string, args: array checkable }
checkableAlt: { type: 'alt', options: array checkable }
checkableAnd: { type: 'and', options: array checkable }
checkableOpt: { type: 'opt', term: checkable }
checkableUser: { type: 'user', predicate: fn }
*/
// typify: type contextDef = { name: string, typeset: array checkable }
// typify: type context = map (array checkable)
// typify: type functionType = { name: string, context: context, params: array checkable, rest: checkable?, result: checkable }
// :: string -> { type: 'literal', value: null|boolean|infinity|ninfinity|undefined|nan } | checkableVar
function variable(name) {
switch (name) {
case "true": return { type: "literal", value: true };
case "false": return { type: "literal", value: false };
case "null": return { type: "literal", value: null };
case "infinity": return { type: "literal", value: Infinity };
case "ninfinity": return { type: "literal", value: -Infinity };
case "undefined": return { type: "literal", value: undefined };
case "nan": return { type: "literal", value: NaN };
}
return { type: "var", name: name };
}
// :: number -> { type: 'literal', value: number }
function number(value) {
assert(typeof value === "number");
return { type: "literal", value: value };
}
// :: string -> { type: 'literal', value: string }
function string(value) {
assert(typeof value === "string");
return { type: "literal", value: value };
}
// :: checkable -> checkableOpt | checkableAny
function opt(t) {
if (t.type === "any") {
return t;
} else if (t.type === "opt") {
return t;
} else {
return { type: "opt", term: t };
}
}
// :: string -> array checkable -> checkablePoly
function poly(name, args) {
return { type: "poly", name: name, args: args };
}
// :: map checkable -> boolean -> checkableRecord
function record(fields, closed) {
return { type: "record", fields: fields, closed: !!closed };
}
// :: 'and'|'alt' -> checkable -> checkable -> checkable | array checkable
function mergeOptions(type, a, b) {
if (a.type === type) {
if (b.type === type) {
return a.options.concat(b.options);
} else {
return a.options.concat([b]);
}
} else {
if (b.type === type) {
return [a].concat(b.options);
} else {
return [a, b];
}
}
}
// :: 'and'|'alt' -> array checkable -> checkable
function andOr(type, options) {
assert(options.length > 0);
if (options.length === 1) {
return options[0];
}
return options.reduce(function (a, b) {
return {
type: type,
options: mergeOptions(type, a, b),
};
});
}
// :: fn -> checkableUser
function user(predicate) {
return { type: "user", predicate: predicate };
}
module.exports = {
any: any,
variable: variable,
number: number,
string: string,
opt: opt,
poly: poly,
record: record,
and: andOr.bind(undefined, "and"),
alt: andOr.bind(undefined, "alt"),
user: user,
};
},{"assert":11}],5:[function(require,module,exports){
"use strict";
var A = require("./aparser.js");
var cons = require("./checkableConstructors.js");
var identifierRe = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
var numberRe = /^[0-9]+$/;
var stringRe = /^('[^']*'|"[^"]*")$/;
// :: string -> boolean
function isIdentifier(token) {
return identifierRe.test(token);
}
// :: string -> boolean
function isNumber(token) {
return numberRe.test(token);
}
// :: string -> boolean
function isString(token) {
return stringRe.test(token);
}
var altP;
// :: fn|Thunk -> fn
function parensP(p) {
return A.lift(A.token("("), p, A.token(")"), function(a, b, c) {
return b;
});
}
var identifierP = A.satisfying(isIdentifier);
var numberP = A.lift(A.satisfying(isNumber), function (x) {
return cons.number(parseFloat(x));
});
var stringP = A.lift(A.satisfying(isString), function (x) {
x = x.substr(1, x.length - 2);
return cons.string(x);
});
var literalP = A.or(numberP, stringP);
var anyP = A.lift(A.token("*"), function () {
return cons.any;
});
var varP = A.lift(identifierP, cons.variable);
var emptyRecordP = A.lift(A.token("{"), A.token("}"), function () {
return cons.record({});
});
var pairP = A.lift(identifierP, A.token(":"), A.delay(function () { return altP; }), function (k, c, v) {
return {
ident: k,
value: v,
};
});
var nonEmptyRecordP = A.lift(A.token("{"), A.sepBy(pairP, ","), A.token("}"), function (o, ps, c) {
var obj = {};
ps.forEach(function (p) {
obj[p.ident] = p.value;
});
return cons.record(obj);
});
var recordP = A.or(emptyRecordP, nonEmptyRecordP);
var termP = A.or(anyP, literalP, varP, recordP, parensP(A.delay(function () { return altP; })));
var optP = A.lift(termP, A.optional(A.token("?")), function (term, opt) {
if (opt === "?" && term.type !== "opt" && term.type !== "any") {
return cons.opt(term);
} else {
return term;
}
});
var polyP1 = A.lift(identifierP, A.some(optP), cons.poly);
var polyP = A.or(polyP1, optP);
var andP = A.lift(A.sepBy(polyP, "&"), cons.and);
altP = A.lift(A.sepBy(andP, "|"), cons.alt);
var checkableP = altP;
var checkableTypeCheckRe = /^([a-zA-Z_][a-zA-Z0-9_]*|"[^"]*"|'[^']*'|[0-9]+|:|,|\{|\}|\*|\?|\||&|\(|\)|\s+)*$/;
var checkableTypeTokenRe = /([a-zA-Z_][a-zA-Z0-9_]*|"[^"]*"|'[^']*'|[0-9]+|:|,|\{|\}|\*|\?|\||&|\(|\))/g;
// :: string -> checkable
function parseCheckableType(type) {
if (!checkableTypeCheckRe.test(type)) { throw new TypeError("invalid checkable type: " + type); }
var tokens = type.match(checkableTypeTokenRe);
var parsed = A.parse(checkableP, tokens);
if (parsed === undefined) { throw new TypeError("invalid checkable type: " + type); }
return parsed;
}
module.exports = {
identifierP: identifierP,
checkableP: checkableP,
polyP: polyP,
parse: parseCheckableType,
};
},{"./aparser.js":1,"./checkableConstructors.js":4}],6:[function(require,module,exports){
"use strict";
var A = require("./aparser.js");
var identifierP = require("./checkableParser").identifierP;
var polyP = require("./checkableParser").polyP;
var checkableP = require("./checkableParser").checkableP;
var nameP = A.optional(A.lift(identifierP, A.token("::"), function (identifier, sep) {
return identifier;
}), "");
var actionP = A.lift(nameP, A.token("->"), checkableP, function (name, arrow, result) {
return {
name: name,
context: {},
params: [],
rest: undefined,
result: result,
};
});
var typesetP = A.sepBy(polyP, "|");
var contextDefP = A.lift(identifierP, A.token(":"), typesetP, function (name, sep, typeset) {
// console.log("contextDefP", name, typeset);
return {
name: name,
typeset: typeset,
};
});
var contextP = A.optional(A.lift(A.sepBy(contextDefP, ","), A.token("=>"), function (defs, arrow) {
return defs.reduce(function (context, def) {
context[def.name] = def.typeset;
return context;
}, {});
}), {});
var paramsP = A.many(A.lift(checkableP, A.token("->"), function (param, arrow) {
return param;
}));
var restP = A.optional(A.lift(A.optional(checkableP), A.token("..."), A.token("->"), function (type, ellipsis, arrow) {
// console.log("restP", type, ellipsis, arrow);
return type || { type: "any" };
}));
var functionTypeP1 = A.lift(nameP, contextP, paramsP, restP, checkableP, function (name, context, params, rest, result) {
// console.log("functionTypeP1", name, context, params, rest, result);
return {
name: name,
context: context,
params: params,
rest: rest,
result: result,
};
});
var functionTypeP = A.or(actionP, functionTypeP1);
module.exports = {
functionP: functionTypeP,
};
},{"./aparser.js":1,"./checkableParser":5}],7:[function(require,module,exports){
"use strict";
// Type predicates
var toString = Object.prototype.toString;
// :: any -> boolean
function isBoolean(val) {
return typeof val === "boolean";
}
// :: any -> boolean
function isNumber(val) {
return typeof val === "number";
}
// :: any -> boolean
function isInteger(val) {
return val === (val|0);
}
// :: any -> boolean
function isPositive(val) {
return typeof val === "number" && val > 0;
}
// :: any -> boolean
function isNonNegative(val) {
return typeof val === "number" && val >= 0;
}
// :: any -> boolean
function isFinite(val) {
return typeof val === "number" && val !== Infinity && val !== -Infinity && val === +val;
}
// :: any -> boolean
function isString(val) {
return typeof val === "string";
}
// :: any -> boolean
function isFunction(val) {
return typeof val === "function";
}
// :: any -> boolean
function isDate(val) {
return toString.call(val) === "[object Date]";
}
// :: any -> boolean
function isRegExp(val) {
return toString.call(val) === "[object RegExp]";
}
// :: any -> boolean
function isArray(val) {
return Array.isArray(val);
}
// :: any -> boolean
function isObject(val) {
return Object(val) === val;
}
// :: any -> boolean
function isArguments(val) {
return val && isObject(arguments) && isInteger(val.length) && Object.prototype.toString.call(val) === "[object Arguments]" || false;
}
// :: *... -> true
function constTrue() {
return true;
}
module.exports = {
isBoolean: isBoolean,
isNumber: isNumber,
isInteger: isInteger,
isPositive: isPositive,
isNonNegative: isNonNegative,
isFinite: isFinite,
isString: isString,
isFunction: isFunction,
isDate: isDate,
isRegExp: isRegExp,
isArray: isArray,
isObject: isObject,
isArguments: isArguments,
constTrue: constTrue,
};
},{}],8:[function(require,module,exports){
"use strict";
var utils = require("./utils.js");
// :: boolean -> string -> string
function parensS(guard, str) {
return guard ? "(" + str + ")" : str;
}
// Forward declaration
var showCheckableTypePrecedence;
// :: checkableLiteral -> string
function showLiteral(type) {
if (typeof type.value === "string") {
return "'" + type.value + "'";
} else {
return "" + type.value;
}
}
// :: checkableRecord -> string
function showRecord(type) {
var pairs = [];
for (var t in type.fields) {
pairs.push(t + ": " + showCheckableTypePrecedence(0, type.fields[t]));
}
return "{" + pairs.join(", ") + "}";
}
// :: nat -> checkable -> string
function showCheckableTypePrecedence(precedence, type) {
switch (type.type) {
case "any": return "*";
case "literal": return showLiteral(type);
case "var": return type.name;
case "record":
return showRecord(type);
case "alt":
return parensS(precedence > 0,
utils.map(type.options, showCheckableTypePrecedence.bind(undefined, 0)).join("|"));
case "and":
return parensS(precedence > 1,
utils.map(type.options, showCheckableTypePrecedence.bind(undefined, 1)).join("&"));
case "poly":
return parensS(precedence > 2,
type.name + " " + utils.map(type.args, showCheckableTypePrecedence.bind(undefined, 3)).join(" "));
case "opt":
return parensS(precedence > 3,
showCheckableTypePrecedence(3, type.term) + "?");
}
}
// :: checkable -> string
function showCheckableType(type) {
return showCheckableTypePrecedence(0, type);
}
// :: map (array checkable) -> string
function showContext(context) {
var res = "";
for (var name in context) {
res += name + " : " + utils.map(context[name], showCheckableTypePrecedence.bind(undefined, 1)).join(" | ");
}
return res;
}
module.exports = {
checkable: showCheckableType,
context: showContext,
};
},{"./utils.js":10}],9:[function(require,module,exports){
/*
* typify
* https://github.com/phadej/typify
*
* Copyright (c) 2013 Oleg Grenrus
* Licensed under the MIT license.
*/
"use strict";
var VERSION = [0, 2, 9];
var utils = require("./utils.js");
var p = require("./predicates.js");
var A = require("./aparser.js");
var c = require("./checkableCompiler.js");
var cons = require("./checkableConstructors.js");
var show = require("./show.js");
var parseCheckableType = require("./checkableParser").parse;
var compileCheckableType = c.compile;
var compileCheckableTypeRecursive = c.compileRecursive;
var functionP = require("./functionParser.js").functionP;
// Few almost predicates
// :: *... -> *
function throwAlways() {
throw new Error("this shouldn't been called");
}
var functionTypeCheckRe = /^([a-zA-Z_][a-zA-Z0-9_]*|"[^"]*"|'[^']*'|[0-9]+|\*|\?|\||&|\(|\)|\{|\}|::|:|,|=>|->|\.\.\.|\s+)*$/;
var functionTypeTokenRe = /([a-zA-Z_][a-zA-Z0-9_]*|"[^"]*"|'[^']*'|[0-9]+|\*|\?|\||&|\(|\)|\{|\}|::|:|,|=>|->|\.\.\.)/g;
// Function type parsing, checks pre-compiling & pretty-printing
// :: checkable -> *... -> boolean
function optional(parsed) {
if (parsed.type === "any") { return true; }
if (parsed.type === "opt") { return true; }
if (parsed.type === "alt") { return parsed.options.some(optional); }
return false;
}
// :: functionType -> nat | infinity
function maxParamsF(parsed) {
return parsed.rest === undefined ? parsed.params.length : Infinity;
}
// :: functionType -> nat
function minParamsF(parsed) {
var result = parsed.params.length;
for (var i = result - 1; i >= 0; i--) {
if (!optional(parsed.params[i])) {
break;
}
result = i;
}
return result;
}
// :: Environment -> map (array checkable) -> map (array fn)
function compileContext(environment, context) {
return utils.mapValues(context, function (v) {
return utils.map(v, compileCheckableType.bind(undefined, environment, context));
});
}
// :: string -> functionType
function parseFunctionType(type) {
if (!functionTypeCheckRe.test(type)) { throw new TypeError("invalid function type: " + type); }
var tokens = type.match(functionTypeTokenRe);
var parsed = A.parse(functionP, tokens);
if (parsed === undefined) { throw new TypeError("invalid function type: " + type); }
return parsed;
}
// :: Environment -> functionType -> *
function compileFunctionType(environment, parsed) {
return {
name: parsed.name,
context: compileContext(environment, parsed.context),
params: utils.map(parsed.params, compileCheckableType.bind(undefined, environment, parsed.context)),
rest: parsed.rest && compileCheckableType(environment, parsed.context, parsed.rest),
result: compileCheckableType(environment, parsed.context, parsed.result),
minParams: minParamsF(parsed),
maxParams: maxParamsF(parsed),
};
}
// :: map (array fn) -> fn -> string -> *... -> boolean
function contextCheckGeneric(context, compiled, varname) {
var options = context[varname];
var args = utils.slice(arguments, 3);
for (var i = 0; i < options.length; i++) {
var option = options[i];
var res = option.apply(undefined, [compiled].concat(args));
if (res) {
context[varname] = [option];
return true;
}
}
return false;
}
// Decorate function with type-signature check
function decorate(environment, type, method) {
var parsed = parseFunctionType(type);
var compiled = compileFunctionType(environment, parsed);
return function() {
// check there are enough parameters
if (arguments.length < compiled.minParams || arguments.length > compiled.maxParams) {
if (compiled.minParams === compiled.maxParams) {
throw new TypeError("function " + compiled.name + " expects " + compiled.maxParams + " arguments, " + arguments.length + " given");
} else {
throw new TypeError("function " + compiled.name + " expects " + compiled.minParams + "-" + compiled.maxParams + " arguments, " + arguments.length + " given");
}
}
var contextCheckUn = contextCheckGeneric.bind(undefined, utils.copyObj(compiled.context));
var contextCheck = utils.y(contextCheckUn);
// check that parameters are of right type
for (var i = 0; i < arguments.length; i++) {
var argCheck = i < compiled.params.length ? compiled.params[i] : compiled.rest;
var argType = i < compiled.params.length ? parsed.params[i] : parsed.rest;
if (!argCheck(contextCheck, arguments[i])) {
// TODO: str checkable type
throw new TypeError("type of " + parsed.name + " " + (i+1) + ". parameter is not `" + show.checkable(argType) + "` in context `" + show.context(parsed.context) + "` -- " + JSON.stringify(arguments[i]));
}
}
// call original function
var r = method.apply(this, arguments);
// check type of return value
if (!compiled.result(contextCheck, r)) {
// TODO: str checkable type
throw new TypeError("type of `" + parsed.name + "` return value is not `" + show.checkable(parsed.result) + "` in context `" + show.context(parsed.context) + "` -- " + r);
}
// return
return r;
};
}
// typify: type checkableEmbedded = string | fn | array checkableEmbedded | map checkableEmbedded
// :: checkableEmbedded -> boolean? -> checkable
function parse(definition, closed) {
if (p.isString(definition)) {
return parseCheckableType(definition);
} else if (p.isFunction(definition)) {
return cons.user(definition);
} else if (p.isArray(definition)) {
var options = utils.map(definition, parse);
return cons.alt(options);
} else /* if (p.isObject(definition)) */ {
var fields = utils.mapValues(definition, parse);
return cons.record(fields, closed);
}
}
// Check checkable type
function check(environment, type, variable) {
if (arguments.length !== 2 && arguments.length !== 3) {
throw new TypeError("check takes 1 or 2 arguments, " + (arguments.length-1) + " provided");
}
var parsed = parseCheckableType(type);
// console.log(parsed);
// console.log(JSON.stringify(parsed, null));
var compiled = compileCheckableType(environment, {}, parsed); // using empty context
switch (arguments.length) {
case 2: return function (variable1) {
return compiled(throwAlways, variable1) === true;
};
case 3:
return compiled(throwAlways, variable) === true;
}
}
function assert(environment, type, variable) {
if (arguments.length !== 2 && arguments.length !== 3) {
throw new TypeError("assert takes 1 or 2 arguments, " + (arguments.length-1) + " provided");
}
var parsed = parseCheckableType(type);
// console.log(parsed);
// console.log(JSON.stringify(parsed, null));
var compiled = compileCheckableType(environment, {}, parsed); // using empty context
switch (arguments.length) {
case 2: return function (variable1) {
var result1 = compiled(throwAlways, variable1);
if (result1 !== true) {
throw new TypeError(result1);
}
};
case 3:
var result = compiled(throwAlways, variable);
if (result !== true) {
throw new TypeError(result);
}
}
}
// Add single parsable type
// :: Environment -> map checkable -> undefined
function addParsedTypes(environment, parsed, closed) {
var names = Object.keys(parsed);
names.forEach(function (name) {
if (environment.has(name)) { throw new Error(name + " is already defined"); }
});
var compiled = utils.mapValues(parsed, compileCheckableTypeRecursive.bind(undefined, environment, {}, names));
var checks = utils.mapValues(compiled, function (check) {
return check.bind(undefined, compiled, throwAlways);
});
environment.add(checks);
}
function addType(environment, name, definition, closed) {
var parsed = {};
parsed[name] = parse(definition, closed);
return addParsedTypes(environment, parsed);
}
// Or many simultanouslty
function mutual(environment, definitions) {
var parsed = utils.mapValues(definitions, parse);
return addParsedTypes(environment, parsed);
}
function adt(environment, name, definitions) {
if (utils.has(definitions, name)) {
throw new Error("adt and it's constructor cannot has the same name");
}
var constructors = Object.keys(definitions);
var parsed = utils.mapValues(definitions, parse);
parsed[name] = parse(constructors);
return addParsedTypes(environment, parsed);
}
function instance(environment, name, cls) {
return addType(environment, name, function (arg) {
return arg instanceof cls;
});
}
function wrap(environment, module, signatures) {
for (var fn in signatures) {
module[fn] = decorate(environment, fn + " :: " + signatures[fn], module[fn]);
}
return module;
}
var buildInTypes = require("./builtin.js");
// typify: instance Environment
// :: -> undefined
function Environment() {
this.types = {};
}
Environment.prototype.has = function environmentHas(type) {
return utils.has(this.types, type) || utils.has(buildInTypes, type);
};
Environment.prototype.get = function environmentGet(type) {
return this.types[type] || buildInTypes[type];
};
Environment.prototype.add = function environmentAdd(checks) {
Object.keys(checks).forEach(function (type) {
this.types[type] = checks[type];
}, this);
};
// typify public API type signatures
var TYPE_SIGNATURES = {
// TODO: change fn to multi type and deprecate alias & record
type: "string -> fn -> *",
// TODO: support alternative function signatures
// TODO: support specifying required but "any" parameter
// check: "string -> * -> boolean",
alias: "string -> string -> *",
record: "string -> map string -> boolean? -> *",
mutual: "map string -> *",
instance: "string -> fn -> *",
wrap: "* -> map string -> *",
adt: "string -> map string -> *",
};
// Create typify
// We could want use prototype-style, instead of closure, but we cannot make callable objects.
// TODO: add reference
function create() {
var env = new Environment();
var typify = decorate.bind(undefined, env);
typify.type = addType.bind(undefined, env);
typify.alias = addType.bind(undefined, env);
typify.record = addType.bind(undefined, env);
typify.mutual = mutual.bind(undefined, env);
typify.adt = adt.bind(undefined, env);
typify.instance = instance.bind(undefined, env);
typify.check = check.bind(undefined, env);
typify.assert = assert.bind(undefined, env);
typify.wrap = wrap.bind(undefined, env);
typify.version = VERSION;
// also add recursive create
// make recursive environments or just possible to merge types from old?
typify.create = create;
return typify.wrap(typify, TYPE_SIGNATURES);
}
// Export stuff
module.exports = create();
},{"./aparser.js":1,"./builtin.js":2,"./checkableCompiler.js":3,"./checkableConstructors.js":4,"./checkableParser":5,"./functionParser.js":6,"./predicates.js":7,"./show.js":8,"./utils.js":10}],10:[function(require,module,exports){
"use strict";
// Does the object contain given key? http://underscorejs.org/#has
// :: map -> string -> boolean
function has(object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
}
// :: array -> any -> boolean
function contains(array, element) {
return array.indexOf(element) !== -1;
}
// Create a shallow-copied clone of the object. http://underscorejs.org/#clone
// :: map -> map
function copyObj(obj) {
var res = {};
for (var k in obj) {
res[k] = obj[k];
}
return res;
}
// Returns values of the object
// :: map -> array
function values(obj) {
var res = [];
for (var k in obj) {
if (has(obj, k)) {
res.push(obj[k]);
}
}
return res;
}
// :: map -> fn -> map
function mapValues(obj, f) {
var res = {};
for (var k in obj) {
if (has(obj, k)) {
res[k] = f(obj[k]);
}
}
return res;
}
// :: array|arguments -> integer? -> integer? -> array
function slice(array, n, m) {
return Array.prototype.slice.call(array, n, m);
}
// :: array -> fn -> array
function map(array, f) {
return array.map(function (x) {
return f(x);
});
}
// This has different semantics than Array#every
// utils.every([1, 2, 3], function (x) { return x; }); // 3
// [1, 2, 3].every(function (x) { return x; }); // true
// :: array -> fn -> *
function every(array, f) {
var acc = true;
for (var i = 0; i < array.length; i++) {
acc = acc && f(array[i]);
if (!acc) {
return acc;
}
}
return acc;
}
// :: fn -> fn
function y(f) {
// :: fn -> fn
function p(h) {
return function() {
var args = Array.prototype.slice.call(arguments);
return f.apply(undefined, [h(h)].concat(args));
};
}
return p(p);
}
// try { throw new Error(); } catch (e) { console.log(e.stack); }
module.exports = {
has: has,
contains: contains,
copyObj: copyObj,
values: values,
mapValues: mapValues,
slice: slice,
map: map,
every: every,
y: y,
};
},{}],11:[function(require,module,exports){
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
//
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
//
// Originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// when used in node, this will actually load the util module we depend on
// versus loading the builtin util module as happens otherwise
// this is a bug in node module loading as far as I am concerned
var util = require('util/');
var pSlice = Array.prototype.slice;
var hasOwn = Object.prototype.hasOwnProperty;
// 1. The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.
var assert = module.exports = ok;
// 2. The AssertionError is defined in assert.
// new assert.AssertionError({ message: message,
// actual: actual,
// expected: expected })
assert.AssertionError = function AssertionError(options) {
this.name = 'AssertionError';
this.actual = options.actual;
this.expected = options.expected;
this.operator = options.operator;
if (options.message) {
this.message = options.message;
this.generatedMessage = false;
} else {
this.message = getMessage(this);
this.generatedMessage = true;
}
var stackStartFunction = options.stackStartFunction || fail;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, stackStartFunction);
}
else {
// non v8 browsers so we can have a stacktrace
var err = new Error();
if (err.stack) {
var out = err.stack;
// try to strip useless frames
var fn_name = stackStartFunction.name;
var idx = out.indexOf('\n' + fn_name);
if (idx >= 0) {
// once we have located the function frame
// we need to strip out everything before it (and its line)
var next_line = out.indexOf('\n', idx + 1);
out = out.substring(next_line + 1);
}
this.stack = out;
}
}
};
// assert.AssertionError instanceof Error
util.inherits(assert.AssertionError, Error);
function replacer(key, value) {
if (util.isUndefined(value)) {
return '' + value;
}
if (util.isNumber(value) && !isFinite(value)) {
return value.toString();
}
if (util.isFunction(value) || util.isRegExp(value)) {
return value.toString();
}
return value;
}
function truncate(s, n) {
if (util.isString(s)) {
return s.length < n ? s : s.slice(0, n);
} else {
return s;
}
}
function getMessage(self) {
return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' +
self.operator + ' ' +
truncate(JSON.stringify(self.expected, replacer), 128);
}
// At present only the three keys mentioned above are used and
// understood by the spec. Implementations or sub modules can pass
// other keys to the AssertionError's constructor - they will be
// ignored.
// 3. All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.
function fail(actual, expected, message, operator, stackStartFunction) {
throw new assert.AssertionError({
message: message,
actual: actual,
expected: expected,
operator: operator,
stackStartFunction: stackStartFunction
});
}
// EXTENSION! allows for well behaved errors defined elsewhere.
assert.fail = fail;
// 4. Pure assertion tests whether a value is truthy, as determined
// by !!guard.
// assert.ok(guard, message_opt);
// This statement is equivalent to assert.equal(true, !!guard,
// message_opt);. To test strictly for the value true, use
// assert.strictEqual(true, guard, message_opt);.
function ok(value, message) {
if (!value) fail(value, true, message, '==', assert.ok);
}
assert.ok = ok;
// 5. The equality assertion tests shallow, coercive equality with
// ==.
// assert.equal(actual, expected, message_opt);
assert.equal = function equal(actual, expected, message) {
if (actual != expected) fail(actual, expected, message, '==', assert.equal);
};
// 6. The non-equality assertion tests for whether two objects are not equal
// with != assert.notEqual(actual, expected, message_opt);
assert.notEqual = function notEqual(actual, expected, message) {
if (actual == expected) {
fail(actual, expected, message, '!=', assert.notEqual);
}
};
// 7. The equivalence assertion tests a deep equality relation.
// assert.deepEqual(actual, expected, message_opt);
assert.deepEqual = function deepEqual(actual, expected, message) {
if (!_deepEqual(actual, expected)) {
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
}
};
function _deepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
} else if (util.isBuffer(actual) && util.isBuffer(expected)) {
if (actual.length != expected.length) return false;
for (var i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) return false;
}
return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (util.isDate(actual) && util.isDate(expected)) {
return actual.getTime() === expected.getTime();
// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (util.isRegExp(actual) && util.isRegExp(expected)) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if (!util.isObject(actual) && !util.isObject(expected)) {
return actual == expected;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
}
}
function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}
function objEquiv(a, b) {
if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b))
return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
// if one is a primitive, the other must be same
if (util.isPrimitive(a) || util.isPrimitive(b)) {
return a === b;
}
var aIsArgs = isArguments(a),
bIsArgs = isArguments(b);
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
return false;
if (aIsArgs) {
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
var ka = objectKeys(a),
kb = objectKeys(b),
key, i;
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] != kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key])) return false;
}
return true;
}
// 8. The non-equivalence assertion tests for any deep inequality.
// assert.notDeepEqual(actual, expected, message_opt);
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (_deepEqual(actual, expected)) {
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
}
};
// 9. The strict equality assertion tests strict equality, as determined by ===.
// assert.strictEqual(actual, expected, message_opt);
assert.strictEqual = function strictEqual(actual, expected, message) {
if (actual !== expected) {
fail(actual, expected, message, '===', assert.strictEqual);
}
};
// 10. The strict non-equality assertion tests for strict inequality, as
// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (actual === expected) {
fail(actual, expected, message, '!==', assert.notStrictEqual);
}
};
function expectedException(actual, expected) {
if (!actual || !expected) {
return false;
}
if (Object.prototype.toString.call(expected) == '[object RegExp]') {
return expected.test(actual);
} else if (actual instanceof expected) {
return true;
} else if (expected.call({}, actual) === true) {
return true;
}
return false;
}
function _throws(shouldThrow, block, expected, message) {
var actual;
if (util.isString(expected)) {
message = expected;
expected = null;
}
try {
block();
} catch (e) {
actual = e;
}
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
(message ? ' ' + message : '.');
if (shouldThrow && !actual) {
fail(actual, expected, 'Missing expected exception' + message);
}
if (!shouldThrow && expectedException(actual, expected)) {
fail(actual, expected, 'Got unwanted exception' + message);
}
if ((shouldThrow && actual && expected &&
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
}
// 11. Expected to throw an error:
// assert.throws(block, Error_opt, message_opt);
assert.throws = function(block, /*optional*/error, /*optional*/message) {
_throws.apply(this, [true].concat(pSlice.call(arguments)));
};
// EXTENSION! This is annoying to write outside this module.
assert.doesNotThrow = function(block, /*optional*/message) {
_throws.apply(this, [false].concat(pSlice.call(arguments)));
};
assert.ifError = function(err) { if (err) {throw err;}};
var objectKeys = Object.keys || function (obj) {
var keys = [];
for (var key in obj) {
if (hasOwn.call(obj, key)) keys.push(key);
}
return keys;
};
},{"util/":15}],12:[function(require,module,exports){
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = supe