UNPKG

build-function

Version:

The way to describe and build simple functions using JSON

995 lines (807 loc) 20.7 kB
import objectHash from 'object-hash'; 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)); } export { build, compileExp, compileStep, createEnv, findInEnv, setInEnv }; //# sourceMappingURL=build.es.js.map