Skip to content

Instantly share code, notes, and snippets.

@fakedrake
Last active June 20, 2016 11:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fakedrake/3aa756cbff346255b0a7942f4a89d057 to your computer and use it in GitHub Desktop.
Save fakedrake/3aa756cbff346255b0a7942f4a89d057 to your computer and use it in GitHub Desktop.
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 80085):
* <cperivol@csail.mit.edu> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return
*
* Chris Perivolaropoulos.
* ----------------------------------------------------------------------------
*/
function mkChecker(expected, check) {
return function (obj) {
if (!check(obj)) {
return {error: "Expected " + expected + " got " + obj};
}
return {ok: true};
};
}
function isSerializable (obj, dontCheckRecursive) {
// Is a class/function
if (obj === null) return true;
if (typeof obj === 'function') return false;
if (typeof obj !== 'object') return true;
if (obj instanceof Array) return !obj.some(function (x) {
return !isSerializable(x);
});
// Is a class instance
if (obj.__proto__ && obj.__proto__.__proto__) return false;
// Is recursive
if (!dontCheckRecursive)
try {JSON.stringify(obj);} catch(c) {return false;}
// Check the children
return !Object.getOwnPropertyNames(obj).some(function (k) {
return !isSerializable(obj[k], true);});
}
var _callback = mkChecker('function', function (o) {
return typeof o === 'function';});
var checks = {
callback: _callback,
function: _callback,
object: mkChecker('object', function (o) {return o instanceof Object;}),
arraybuffer: mkChecker('arraybuffer', function (o) {
return o instanceof ArrayBuffer;}),
array: mkChecker('array', function (o) {return o instanceof Array;}),
number: mkChecker('number', function (o) {return typeof o === 'number';}),
string: mkChecker('string', function (o) {return typeof o === 'string';}),
bool: mkChecker('string', function (o) {return typeof o === 'boolean';}),
boolean: mkChecker('string', function (o) {return typeof o === 'boolean';}),
json: mkChecker('json', isSerializable),
any: function () {return {ok: true};}
};
function hasKey(obj, key) {
return !Object.getOwnPropertyNames(obj).some(function (k) {
return key === k;
});
}
function getCheck(checker) {
// Hardcoded checkers
var chk = checks[checker];
if (typeof chk === 'function') return chk;
// Instance of a class
if (typeof checker === 'function' && checker.prototype)
return mkChecker(checker.name || 'class', function (obj) {
return obj instanceof checker;
});
// Array with checked arguments
if (checker instanceof Array)
return function(arr) {return match (arr, checker);};
// Object with checked values
if (typeof checker === 'object') return function (obj) {
var ret;
Object.getOwnPropertyNames(checker).some(function (k) {
if (typeof obj !== 'object') {
ret = {error: "Expected object, got " + obj};
return true;
}
ret = getCheck(checker[k])(obj[k]);
if (ret.error) ret.error = "{" + k + ": " + ret.error + "}";
return !!ret.error;
});
return ret;
};
throw Error('Unknown type checker:' + checker);
}
function match(args, checkers, index, cb) {
// In haskell-like
// match [] [] = True
// match [] [VarAny] = True
// match [] x = False
// match x [] = False
// match as [VarAny] = True
// match a:as VarAny:f:fs = (match a:as f:fs) || (match as VarAny:b:bs)
// match a:as b:bs = f a && match as fs
if (args.length === 0 && checkers.length === 0) {
return {ok: true};
}
if (args.length === 0 && checkers.length === 1 && checkers[0] === 'varany')
return {ok: true};
if (args.length === 0 || checkers.length === 0) {
if (checkers[0] === 'varany') return {
error: "Last args should check with " + checkers.slice(1) +
" but couldn't."
};
cb && cb(args);
return {
error: "Wrong num of arguments: " + (index + args.length) +
" (expected " + (index + checkers.length) + ")"
};
}
var checker = checkers[0], m;
if (checker === 'varany') {
if (checkers.length === 1) return {ok: true};
m = match(args, checkers.slice(1), index, cb);
if (m.ok) return m;
return match(args.slice(1), checkers, index + 1, cb);
}
m = getCheck(checker)(args[0]);
if (!m.ok) {
cb && cb(checker, args[0]);
return {error: "Argument #" + index + ": " + m.error};
};
return match(args.slice(1), checkers.slice(1), index + 1, cb);
}
var PRODUCTION =
typeof global.it !== 'function' &&
typeof global.describe !== 'function' &&
typeof global.process === 'undefined';
var settings = require('./settings.js').settings;
function typecheck (args, checkers, callback) {
if (PRODUCTION && !settings.get('typecheck')) return;
var m = match([].slice.call(args), checkers, 0, callback);
if (m.ok) return;
throw Error(m.error);
}
function typechecked(fn, argtypes) {
return function () {
typecheck(arguments, argtypes, console.log.bind(console, "Typechecked:"));
return fn.apply(null, arguments);
};
}
module.exports.typechecked = typechecked;
module.exports.typecheck = typecheck;
module.exports.isSerializable = isSerializable;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment