build-function
Version:
The way to describe and build simple functions using JSON
1,006 lines (817 loc) • 22.8 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('object-hash')) :
typeof define === 'function' && define.amd ? define(['exports', 'object-hash'], factory) :
(global = global || self, factory(global.buildFunc = {}, global.objectHash));
}(this, function (exports, objectHash) { 'use strict';
function returning(value) {
return function () {
return value;
};
}
var hasOwn = Object.prototype.hasOwnProperty;
var pre = "0$_";
function createEnv(parent, lib) {
var env = {
parent: parent
};
if (lib) {
for (var id in lib) {
if (hasOwn.call(lib, id)) {
setInEnv(env, id, lib[id]);
}
}
}
return env;
}
function findInEnv(env, id, topOnly) {
var tid = pre + id;
var current = env;
while (current) {
if (hasOwn.call(current, tid)) {
return {
env: current,
id: tid
};
}
if (topOnly) {
return;
}
current = current.parent;
}
}
function setInEnv(env, id, value) {
env[pre + id] = value;
}
function error(msg) {
return new Error(msg);
}
function errorFmt(template) {
return function () {
for (var _len = arguments.length, params = new Array(_len), _key = 0; _key < _len; _key++) {
params[_key] = arguments[_key];
}
return error(template.replace(/\$(\d+)/g, function (_, index) {
return params[index];
}));
};
}
var msgInvalid = '"$0" is not a valid $1';
var errorInvalid = errorFmt(msgInvalid);
var errorInvalidType = errorFmt(msgInvalid + " type");
var errorNotInEnv = errorFmt("\"$0\" can't be found in this environment");
var msgRequired = '"$0" is required in a "$1"';
var errorExpReq = errorFmt(msgRequired + " expression");
var errorStmnReq = errorFmt(msgRequired + " statement");
var hash;
if (objectHash) {
hash = function hash(object) {
for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
others[_key - 1] = arguments[_key];
}
return others.reduce(function (main, other) {
return main + objectHash.MD5(other);
}, objectHash.sha1(object));
};
}
var isArray = Array.isArray;
function isObj(param) {
return typeof param === "object";
}
var paramTable = {
rest: function rest(index) {
return function (input) {
var arg = [];
var len = input.length;
for (var i = index; i < len; i++) {
arg.push(input[i]);
}
return arg;
};
},
param: function param(index) {
return function (input) {
return input[index];
};
}
};
var specialOperTable = {
"||": function _(resolvers) {
var len = resolvers.length;
return function (env) {
var result;
for (var i = 0; i < len; i++) {
result = resolvers[i](env);
if (result) {
break;
}
}
return result;
};
},
"&&": function _(resolvers) {
var len = resolvers.length;
return function (env) {
var result;
for (var i = 0; i < len; i++) {
result = resolvers[i](env);
if (!result) {
break;
}
}
return result;
};
},
"**": function _(resolvers) {
var resolveLast = resolvers.pop();
return function (env) {
var result = resolveLast(env);
var i = resolvers.length - 1;
while (i >= 0) {
result = Math.pow(resolvers[i](env), result);
i--;
}
return result;
};
}
};
var operTable = {
"+": function _(total, value) {
return total + value;
},
"-": function _(total, value) {
return total - value;
},
"*": function _(total, value) {
return total * value;
},
"/": function _(total, value) {
return total / value;
},
"%": function _(total, value) {
return total % value;
},
"&": function _(total, value) {
return total & value;
},
"|": function _(total, value) {
return total | value;
},
"^": function _(total, value) {
return total ^ value;
},
"<<": function _(total, value) {
return total << value;
},
">>": function _(total, value) {
return total >> value;
},
">>>": function _(total, value) {
return total >>> value;
},
"==": function _(total, value) {
return total == value;
},
"===": function _(total, value) {
return total === value;
},
"!=": function _(total, value) {
return total != value;
},
"!==": function _(total, value) {
return total !== value;
},
">": function _(total, value) {
return total > value;
},
"<": function _(total, value) {
return total < value;
},
">=": function _(total, value) {
return total >= value;
},
"<=": function _(total, value) {
return total <= value;
}
};
var transTable = {
"!": function _(value) {
return !value;
},
"!!": function _(value) {
return !!value;
},
"~": function _(value) {
return ~value;
}
};
var expTable = {
literal: function literal(exp) {
if (!hasOwn.call(exp, "value")) {
throw errorExpReq("value", "literal");
}
var value = exp.value;
var valueType = typeof value;
if (valueType === "string" || valueType === "number" || valueType === "boolean") {
return returning(value);
}
var serialized = JSON.stringify(value);
return function () {
return JSON.parse(serialized);
};
},
get: function get(exp, cache, safe) {
if (!hasOwn.call(exp, "id")) {
throw errorExpReq("id", "get");
}
if (typeof exp.id !== "string") {
throw error('A "get" expression "id" must be a string');
}
var id = exp.id;
return function (env) {
var result = findInEnv(env, id);
if (!result) {
if (!safe) {
throw errorNotInEnv(id);
}
return;
}
return result.env[result.id];
};
},
set: function set(exp, cache) {
if (!hasOwn.call(exp, "id")) {
throw errorExpReq("id", "set");
}
if (!hasOwn.call(exp, "value")) {
throw errorExpReq("value", "set");
}
if (typeof exp.id !== "string") {
throw error('A "set" expression "id" must be a string');
}
var id = exp.id;
var resolveValue = compileExp(exp.value, cache);
return function (env) {
var result = findInEnv(env, id);
if (!result) {
throw errorNotInEnv(id);
}
return result.env[result.id] = resolveValue(env);
};
},
ternary: function ternary(exp, cache) {
if (!hasOwn.call(exp, "condition")) {
throw errorExpReq("condition", "ternary");
}
if (!hasOwn.call(exp, "then")) {
throw errorExpReq("then", "ternary");
}
if (!hasOwn.call(exp, "otherwise")) {
throw errorExpReq("otherwise", "ternary");
}
var resolveCondition = compileExp(exp.condition, cache);
var resolveThen = compileExp(exp.then, cache);
var resolveOtherwise = compileExp(exp.otherwise, cache);
return function (env) {
return resolveCondition(env) ? resolveThen(env) : resolveOtherwise(env);
};
},
oper: function oper(exp, cache) {
if (!hasOwn.call(exp, "oper")) {
throw errorExpReq("oper", "oper");
}
if (!hasOwn.call(exp, "exp")) {
throw errorExpReq("exp", "oper");
}
var operExps = exp.exp,
oper = exp.oper;
if (operExps.length < 2) {
throw error("not enought operands");
}
var resolvers = compileExp(operExps, cache);
var reduceResolvers = specialOperTable[oper];
if (reduceResolvers) {
return reduceResolvers(resolvers);
}
var reduce = operTable[oper];
if (!reduce) {
throw errorInvalidType(oper, "operation");
}
var resolveFirst = resolvers.shift();
var len = resolvers.length;
return function (env) {
var result = resolveFirst(env);
for (var i = 0; i < len; i++) {
result = reduce(result, resolvers[i](env));
}
return result;
};
},
trans: function trans(exp, cache) {
if (!hasOwn.call(exp, "oper")) {
throw errorExpReq("oper", "trans");
}
if (!hasOwn.call(exp, "exp")) {
throw errorExpReq("exp", "trans");
}
if (exp.oper === "typeof") {
var resolveSafe = compileExp(exp.exp, cache, true);
return function (env) {
var value = resolveSafe(env);
return typeof value;
};
}
var transform = transTable[exp.oper];
if (!transform) {
throw errorInvalidType(exp.oper, "transform operation");
}
var resolve = compileExp(exp.exp, cache);
return function (env) {
return transform(resolve(env));
};
},
func: function func(exp, cache) {
return compileFunc(exp, cache);
},
call: function call(exp, cache) {
if (!hasOwn.call(exp, "func")) {
throw errorExpReq("func", "call");
}
var args = exp.args;
var resolveFunc = compileExp(exp.func, cache);
var resolveArgs = args && compileSpread(args, cache);
return function (env) {
var func = resolveFunc(env);
if (!resolveArgs) {
return func();
}
return func.apply(null, resolveArgs(env, []));
};
}
};
var stepTable = {
declare: function declare(step, cache) {
if (!hasOwn.call(step, "set")) {
throw errorStmnReq("set", "declare");
}
var resolve = compileDecl(step.set, cache);
if (!resolve) {
return returning();
}
return function (env) {
resolve(env);
};
},
"let": function _let(step, cache) {
if (!hasOwn.call(step, "declare")) {
throw errorStmnReq("declare", "let");
}
var resolve = compileDecl(step.declare, cache);
if (!resolve) {
return returning();
}
return function (env) {
resolve(env);
};
},
"if": function _if(step, cache, breakable) {
if (!hasOwn.call(step, "condition")) {
throw errorStmnReq("condition", "if");
}
var then = step.then,
otherwise = step.otherwise;
var resolveCondition = compileExp(step.condition, cache);
var resolveThen = then && compileStep(then, cache, breakable);
var resolveOtherwise = otherwise && compileStep(otherwise, cache, breakable);
if (!resolveThen && !resolveOtherwise) {
return returning();
}
return function (env) {
var resolveSteps = resolveCondition(env) ? resolveThen : resolveOtherwise;
if (resolveSteps) {
return resolveSteps(createEnv(env));
}
};
},
"for": function _for(step, cache) {
if (!hasOwn.call(step, "target")) {
throw errorStmnReq("target", "for");
}
var body = step.body;
var resolveBody = body && compileStep(body, cache, true);
if (!resolveBody) {
return returning();
}
var index = step.index,
value = step.value;
var resolveTarget = compileExp(step.target, cache);
return function (env) {
var array = resolveTarget(env);
var len = array.length;
var i = 0;
while (i < len) {
var lib = {};
if (index) {
lib[index] = i;
}
if (value) {
lib[value] = array[i];
}
var result = resolveBody(createEnv(env, lib));
if (result) {
if (result === "break") {
return;
}
return result;
}
i++;
}
};
},
"break": function _break(step, cache, breakable) {
if (!breakable) {
throw error('"break" is not allowed outside loops');
}
return returning(step.type);
},
"return": function _return(step, cache) {
if (!hasOwn.call(step, "value")) {
throw errorStmnReq("value", "return");
}
var value = step.value,
type = step.type;
var resolveValue = compileExp(value, cache);
return function (env) {
return {
type: type,
value: resolveValue(env)
};
};
},
"try": function _try(step, cache) {
var body = step.body,
errorId = step.error,
catchSteps = step["catch"];
var resolveBody = body && compileStep(body, cache);
var resolveCatch = resolveBody && catchSteps && compileStep(catchSteps, cache);
if (!resolveBody) {
return returning();
}
return function (env) {
try {
var result = resolveBody(createEnv(env));
if (result && result.type === "throw") {
throw result.msg;
}
return result;
} catch (err) {
if (resolveCatch) {
var lib = {};
if (errorId) {
lib[errorId] = "" + (err.message || err);
}
return resolveCatch(createEnv(env, lib));
}
}
};
},
"throw": function _throw(step, cache) {
if (!hasOwn.call(step, "msg")) {
throw errorStmnReq("msg", "throw");
}
var type = step.type,
msg = step.msg;
var resolveMessage = isObj(msg) && compileExp(msg, cache);
return function (env) {
return {
type: type,
msg: resolveMessage ? resolveMessage(env) : "" + msg
};
};
}
};
function compileFunc(options, cache, name) {
var params = options.params,
body = options.body;
var parseArgs = params && compileParam(params, cache);
var resolveFuncBody = body && compileStep(body, cache);
return function (env) {
if (!resolveFuncBody) {
return returning();
}
var func = function func() {
var lib = {};
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (parseArgs) {
lib = parseArgs(args, lib);
}
lib.arguments = args;
var result = resolveFuncBody(createEnv(outerScope, lib));
if (result) {
if (result.type === "throw") {
throw result.msg;
}
return result.value;
}
};
var outerScope = env;
if (name) {
var lib = {};
lib[name] = func;
outerScope = createEnv(outerScope, lib);
}
return func;
};
}
function compileParam(params, cache) {
function normalize(single) {
return isObj(single) ? single : {
id: single,
type: "param"
};
}
function compileSingle(single, index) {
var type = single.type,
id = single.id;
var compileGetter = paramTable[type];
if (!compileGetter) {
throw errorInvalidType(type, "parameter");
}
var getValue = compileGetter(index);
return function (input, lib) {
lib[id] = getValue(input);
return lib;
};
}
function compileMulti() {
return function (input, lib) {
for (var i = 0; i < len; i++) {
populators[i](input, lib);
}
return lib;
};
}
var db = cache.param || (cache.param = {});
function compileCached(single, index) {
var id = single.id;
if (typeof id !== "string") {
throw errorInvalid(id, "parameter id");
}
if (id === "arguments") {
throw error("\"arguments\" can't be used as parameter id");
}
if (!hash) {
return compileSingle(single, index);
}
var key = hash(single, single.type, index);
var cached = db[key];
if (cached) {
return cached;
}
return db[key] = compileSingle(single, index);
}
if (!isArray(params)) {
return compileCached(normalize(params), 0);
}
var len = params.length;
if (!len) {
return null;
}
var norm = params.map(normalize);
var populators = norm.map(compileCached);
if (len === 1) {
return populators[0];
}
if (!hash) {
return compileMulti();
}
var mkey = hash(norm, norm.length);
var mcached = db[mkey];
if (mcached) {
return mcached;
}
return db[mkey] = compileMulti();
}
function compileDecl(decl, cache) {
function normalize(single) {
var obj = isObj(single) ? {
id: single.id,
value: single.value
} : {
id: single
};
if (hasOwn.call(obj, "value") && typeof obj.value === "undefined") {
delete obj.value;
}
return obj;
}
function compileSingle(single) {
var id = single.id,
value = single.value;
var resolveValue;
if (value) {
resolveValue = compileExp(value, cache);
}
return function (env) {
if (findInEnv(env, id, true)) {
throw error("\"" + id + "\" has already been declared in this environment");
}
setInEnv(env, id, resolveValue && resolveValue(env));
};
}
function compileMulti() {
return function (env) {
for (var i = 0; i < len; i++) {
resolvers[i](env);
}
};
}
var db = cache["let"] || (cache["let"] = {});
function compileCached(single) {
if (!hash) {
return compileSingle(single);
}
var key = hash(single, single.id);
var cached = db[key];
if (cached) {
return cached;
}
return db[key] = compileSingle(single);
}
if (!isArray(decl)) {
return compileCached(normalize(decl));
}
var len = decl.length;
if (!len) {
return null;
}
var norm = decl.map(normalize);
var resolvers = norm.map(compileCached);
if (len === 1) {
return resolvers[0];
}
if (!hash) {
return compileMulti();
}
var mkey = hash(norm, norm.length);
var mcached = db[mkey];
if (mcached) {
return mcached;
}
return db[mkey] = compileMulti();
}
function compileSpread(exp, cache) {
function compileSingle(single) {
if (single.type === "spread") {
var resolveArray = compileExp(single.exp, cache);
return function (env, resolved) {
resolved.push.apply(resolved, resolveArray(env));
return resolved;
};
}
var resolveParam = compileExp(single, cache);
return function (env, resolved) {
resolved.push(resolveParam(env));
return resolved;
};
}
function compileMulti() {
return function (env, input) {
for (var i = 0; i < len; i++) {
populators[i](env, input);
}
return input;
};
}
var db = cache.spread || (cache.spread = {});
function compileCached(single) {
if (!hash) {
return compileSingle(single);
}
var key = hash(single, single.type);
var cached = db[key];
if (cached) {
return cached;
}
return db[key] = compileSingle(single);
}
if (!isArray(exp)) {
return compileCached(exp);
}
var len = exp.length;
if (!len) {
return null;
}
var populators = exp.map(compileCached);
if (len === 1) {
return populators[0];
}
if (!hash) {
return compileMulti();
}
var mkey = hash(exp, exp.length);
var mcached = db[mkey];
if (mcached) {
return mcached;
}
return db[mkey] = compileMulti();
}
function compileExp(exp, cache, safe) {
function compileSingle(single) {
var type = single.type;
var compile = expTable[type];
if (!compile) {
throw errorInvalidType(type, "expression");
}
return compile(single, cache, safe);
}
var db = cache.exp || (cache.exp = {});
function compileCached(single) {
if (!single || !isObj(single)) {
throw errorInvalid(single, "expression");
}
if (!hash) {
return compileSingle(single);
}
var key = hash(single, single.type);
var cached = db[key];
if (cached) {
return cached;
}
return db[key] = compileSingle(single);
}
if (!isArray(exp)) {
return compileCached(exp);
}
return exp.map(compileCached);
}
function compileStep(steps, cache, breakable) {
function compileSingle(single) {
var compile = stepTable[single.type];
if (compile) {
return compile(single, cache, breakable);
}
var resolveExp = compileExp(single, cache);
return function (env) {
resolveExp(env);
};
}
function compileMulti() {
return function (env) {
for (var i = 0; i < len; i++) {
var resolveStep = resolvers[i];
var result = resolveStep(env);
if (result) {
return result;
}
}
};
}
var db = cache.step || (cache.step = {});
function compileCached(single) {
if (!single || !isObj(single)) {
throw errorInvalid(single, "step");
}
if (!hash) {
return compileSingle(single);
}
var key = hash(single, single.type);
var cached = db[key];
if (cached) {
return cached;
}
return db[key] = compileSingle(single);
}
if (!isArray(steps)) {
return compileCached(steps);
}
var len = steps.length;
if (!len) {
return returning();
}
var resolvers = steps.map(compileCached);
if (len === 1) {
return resolvers[0];
}
if (!hash) {
return compileMulti();
}
var mkey = hash(steps, steps.length);
var mcached = db[mkey];
if (mcached) {
return mcached;
}
return db[mkey] = compileMulti();
}
function build(options, env) {
return compileFunc(options, {}, options.name)(env || createEnv(null));
}
exports.build = build;
exports.compileExp = compileExp;
exports.compileStep = compileStep;
exports.createEnv = createEnv;
exports.findInEnv = findInEnv;
exports.setInEnv = setInEnv;
}));
//# sourceMappingURL=build.umd.js.map