UNPKG

krl-stdlib

Version:
935 lines 31.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const _ = require("lodash"); const asyncSort_1 = require("./asyncSort"); const krl = require("./krl"); function ltEqGt(left, right) { let a = krl.toNumberOrNull(left); let b = krl.toNumberOrNull(right); if (a === null || b === null) { // fall back on string comparison a = krl.toString(left); b = krl.toString(right); } // at this point a and b are both numbers or both strings return a === b ? 0 : a > b ? 1 : -1; } function sprintfBase(val, template, specifier) { return _.join(_.map(template.split(/\\\\/g), function (v) { return v.replace(new RegExp("(^|[^\\\\])" + specifier, "g"), "$1" + val + ""); }), "\\").replace(new RegExp("\\\\" + specifier, "g"), specifier); } async function iterBase(val, iter) { if (!krl.isArrayOrMap(val)) { throw new TypeError("only works on collections"); } var shouldContinue; if (krl.isArray(val)) { var i; for (i = 0; i < val.length; i++) { shouldContinue = await iter(val[i], i, val); if (!shouldContinue) break; } } else { var key; for (key in val) { if (_.has(val, key)) { shouldContinue = await iter(val[key], key, val); if (!shouldContinue) break; } } } } const identity = krl.Function(["val"], function (val) { return val; }); // coerce the value into an array of key strings function toKeyPath(path) { if (!krl.isArray(path)) { path = [path]; } return _.map(path, krl.toString); } function isSafeArrayIndex(arr, key) { var index = _.parseInt(key, 10); if (_.isNaN(index)) { return false; } return index >= 0 && index <= arr.length; // equal too b/c it's ok to append } const stdlib = { ///////////////////////////////////////////////////////////////////////////// // Infix operators "+": krl.Function(["left", "right"], function (left, right) { if (arguments.length < 2) { return left; } // if we have two "numbers" then do plus if (krl.isNumber(left) && krl.isNumber(right)) { return left + right; } // else do concat return krl.toString(left) + krl.toString(right); }), "-": krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); if (arguments.length < 2) { if (leftNumber === null) { throw new TypeError("Cannot negate " + krl.toString(left)); } return -leftNumber; } const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(right) + " cannot be subtracted from " + krl.toString(left)); } return leftNumber - rightNumber; }), "*": krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be multiplied by " + krl.toString(right)); } return leftNumber * rightNumber; }), "/": krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be divided by " + krl.toString(right)); } if (rightNumber === 0) { this.log.debug("[DIVISION BY ZERO] " + leftNumber + " / 0"); return 0; } return leftNumber / rightNumber; }), "%": krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError("Cannot calculate " + krl.toString(left) + " modulo " + krl.toString(right)); } if (rightNumber === 0) { return 0; } return leftNumber % rightNumber; }), "==": krl.Function(["left", "right"], function (left, right) { return krl.isEqual(left, right); }), "!=": krl.Function(["left", "right"], function (left, right) { return !krl.isEqual(left, right); }), "<=>": krl.Function(["left", "right"], function (left, right) { return ltEqGt(left, right); }), "<": krl.Function(["left", "right"], function (left, right) { return ltEqGt(left, right) < 0; }), ">": krl.Function(["left", "right"], function (left, right) { return ltEqGt(left, right) > 0; }), "<=": krl.Function(["left", "right"], function (left, right) { return ltEqGt(left, right) <= 0; }), ">=": krl.Function(["left", "right"], function (left, right) { return ltEqGt(left, right) >= 0; }), "><": krl.Function(["left", "right"], function (left, right) { if (krl.isArray(left)) { return _.findIndex(left, _.partial(krl.isEqual, right)) >= 0; } else if (krl.isMap(left)) { return _.has(left, right); } else { return false; } // was throw new TypeError(">< only works with Array or Map"); }), like: krl.Function(["val", "regex"], function (val, regex) { if (!krl.isRegExp(regex)) { regex = new RegExp(krl.toString(regex)); } return regex.test(krl.toString(val)); }), cmp: krl.Function(["left", "right"], function (left, right) { left = krl.toString(left); right = krl.toString(right); return left === right ? 0 : left > right ? 1 : -1; }), ///////////////////////////////////////////////////////////////////////////// as: krl.Function(["val", "type"], function (val, type) { if (arguments.length < 2) { return val; } const valType = krl.typeOf(val); if (valType === type) { return val; } if (type === "Boolean") { if (val === "false") { return false; } if (valType === "Number") { return val !== 0; } return !!val; } if (type === "Hex") { return krl.toHexOrNull(val); } if (type === "String") { return krl.toString(val); } if (type === "Number") { return krl.toNumberOrNull(val); } if (type === "RegExp") { let regexSrc = krl.toString(val); if (valType !== "String" && /^\[[a-z]+\]$/i.test(regexSrc)) { regexSrc = regexSrc.replace(/^\[/, "\\[").replace(/\]$/, "\\]"); } return new RegExp(regexSrc); } throw new TypeError('Cannot use the .as("' + type + '") operator with ' + krl.toString(val) + " (type " + valType + ")"); }), isnull: krl.Function(["val"], function (val) { return krl.isNull(val); }), typeof: krl.Function(["val"], function (val) { return krl.typeOf(val); }), klog: krl.Function(["val", "message"], function (val, message) { this.log.klog(krl.isNull(message) ? "klog" : krl.toString(message), { val, }); return val; }), sprintf: krl.Function(["val", "template"], function (val, template) { if (krl.isNull(template)) { return ""; } template = krl.toString(template); if (krl.isNumber(val)) { return sprintfBase(val, template, "%d"); } if (krl.isString(val)) { return sprintfBase(val, template, "%s"); } return template; }), ///////////////////////////////////////////////////////////////////////////// defaultsTo: krl.Function(["val", "defaultVal", "message"], function (val, defaultVal, message) { if (!krl.isNull(val)) { return val; // not important whether defaultVal is missing } if (!krl.isNull(message)) { this.log.debug(`[DEFAULTSTO] ${krl.toString(message)}`); } return defaultVal; }), ///////////////////////////////////////////////////////////////////////////// // Boolean operators shiftRight: krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be shifted by " + krl.toString(right)); } return leftNumber >> rightNumber; }), shiftLeft: krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be shifted by " + krl.toString(right)); } return leftNumber << rightNumber; }), // 5.band(3) == 1 band: krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be bitwise anded " + krl.toString(right)); } return leftNumber & rightNumber; }), // 5.bor(3) == 7 bor: krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be bitwise anded " + krl.toString(right)); } return leftNumber | rightNumber; }), // 5.xor(3) == 6 bxor: krl.Function(["left", "right"], function (left, right) { const leftNumber = krl.toNumberOrNull(left); const rightNumber = krl.toNumberOrNull(right); if (leftNumber === null || rightNumber === null) { throw new TypeError(krl.toString(left) + " cannot be bitwise anded " + krl.toString(right)); } return leftNumber ^ rightNumber; }), // 5.bnot() == -6 // -3.bnot() == 2 bnot: krl.Function(["left"], function (left) { const leftNumber = krl.toNumberOrNull(left); if (leftNumber === null) { throw new TypeError(krl.toString(left) + " cannot be inverted "); } return ~leftNumber; }), ///////////////////////////////////////////////////////////////////////////// // Number operators chr: krl.Function(["val"], function (val) { const code = krl.toNumberOrNull(val); if (code === null) { return null; } return String.fromCharCode(code); }), range: krl.Function(["val", "end"], function (val, end) { if (arguments.length < 2) { return []; } const startNumber = krl.toNumberOrNull(val); const endNumber = krl.toNumberOrNull(end); if (startNumber === null || endNumber === null) { return []; } if (startNumber < endNumber) { return _.range(startNumber, endNumber + 1); } return _.range(startNumber, endNumber - 1); }), ///////////////////////////////////////////////////////////////////////////// // Number operators capitalize: krl.Function(["val"], function (val) { val = krl.toString(val); if (val.length === 0) { return ""; } return val[0].toUpperCase() + val.slice(1); }), decode: krl.Function(["val"], function (val) { return krl.decode(val); }), extract: krl.Function(["val", "regex"], function (val, regex) { if (arguments.length < 2) { return []; } val = krl.toString(val); if (!krl.isRegExp(regex)) { regex = new RegExp(krl.toString(regex)); } var r = val.match(regex); if (!r) { return []; } if (regex.global) { return r; } return r.slice(1); }), lc: krl.Function(["val"], function (val) { return krl.toString(val).toLowerCase(); }), uc: krl.Function(["val"], function (val) { return krl.toString(val).toUpperCase(); }), match: krl.Function(["val", "regex"], function (val, regex) { if (krl.isString(regex)) { regex = new RegExp(regex); } else if (!krl.isRegExp(regex)) { return false; } return regex.test(krl.toString(val)); }), ord: krl.Function(["val"], function (val) { val = krl.toString(val); var code = val.charCodeAt(0); return _.isNaN(code) ? null : code; }), split: krl.Function(["val", "splitOn"], function (val, splitOn) { val = krl.toString(val); if (!krl.isRegExp(splitOn)) { splitOn = krl.toString(splitOn); } return val.split(splitOn); }), substr: krl.Function(["val", "start", "len"], function (val, start, len) { val = krl.toString(val); start = krl.toNumberOrNull(start); len = krl.toNumberOrNull(len); if (start === null) { return val; } if (start > val.length) { return ""; } var end; if (len === null) { end = val.length; } else if (len > 0) { end = start + len; } else { end = val.length + len; } return val.substring(start, end); }), trimLeading: krl.Function(["val"], function (val) { return _.trimStart(krl.toString(val)); }), trimTrailing: krl.Function(["val"], function (val) { return _.trimEnd(krl.toString(val)); }), trim: krl.Function(["val"], function (val) { return krl.toString(val).trim(); }), startsWith: krl.Function(["val", "target"], function (val, target) { val = krl.toString(val); target = krl.toString(target); return _.startsWith(val, target); }), endsWith: krl.Function(["val", "target"], function (val, target) { val = krl.toString(val); target = krl.toString(target); return _.endsWith(val, target); }), contains: krl.Function(["val", "target"], function (val, target) { val = krl.toString(val); target = krl.toString(target); return _.includes(val, target); }), replace: krl.Function(["val", "regex", "replacement"], async function (val, regex, replacement) { if (arguments.length < 2) { return val; } val = krl.toString(val); if (!krl.isString(regex) && !krl.isRegExp(regex)) { regex = krl.toString(regex); } if (krl.isNull(replacement)) { return val.replace(regex, ""); } if (krl.isFunction(replacement)) { regex = stdlib.as(this, [regex, "RegExp"]); var out = ""; var lastI = 0; var m; while ((m = regex.exec(val))) { // eslint-disable-line no-cond-assign out += val.substring(lastI, m.index) + (await replacement(this, m.concat([m.index, val]))); lastI = m.index + m[0].length; if (!regex.global) { break; } } out += val.substring(lastI); return out; } return val.replace(regex, krl.toString(replacement)); }), ///////////////////////////////////////////////////////////////////////////// // Collection operators all: krl.Function(["val", "iter"], async function (val, iter) { let broke = false; await iterBase(val, async (v, k, obj) => { const r = await iter(this, [v, k, obj]); if (!r) { broke = true; return false; // stop } return true; }); return !broke; }), notall: krl.Function(["val", "iter"], async function (val, iter) { const b = await stdlib.all(this, [val, iter]); return !b; }), any: krl.Function(["val", "iter"], async function (val, iter) { var broke = false; await iterBase(val, async (v, k, obj) => { var r = await iter(this, [v, k, obj]); if (r) { broke = true; return false; // stop } return true; }); return broke; }), none: krl.Function(["val", "iter"], async function (val, iter) { const b = await stdlib.any(this, [val, iter]); return !b; }), append: krl.Function(["val", "a", "b", "c", "d", "e"], async function (...args) { return _.concat.apply(null, args); }), collect: krl.Function(["val", "iter"], async function (val, iter) { if (krl.isNull(iter)) { iter = identity; } const grouped = {}; await iterBase(val, async (v, k, obj) => { const r = await iter(this, [v, k, obj]); if (!grouped.hasOwnProperty(r)) { grouped[r] = []; } grouped[r].push(v); return true; }); return grouped; }), filter: krl.Function(["val", "iter"], async function (val, iter) { if (krl.isNull(iter)) { iter = identity; } var isArr = krl.isArray(val); var result = isArr ? [] : {}; await iterBase(val, async (v, k, obj) => { var r = await iter(this, [v, k, obj]); if (r) { if (isArr) { result.push(v); } else { result[k] = v; } } return true; }); return result; }), map: krl.Function(["val", "iter"], async function (val, iter) { if (krl.isNull(iter)) { iter = identity; } const isArr = krl.isArray(val); const result = isArr ? [] : {}; await iterBase(val, async (v, k, obj) => { const r = await iter(this, [v, k, obj]); if (isArr) { result.push(r); } else { result[k] = r; } return true; }); return result; }), head: krl.Function(["val"], function (val) { if (!krl.isArray(val)) { return val; // head is for arrays; pretend val is a one-value array } return val[0]; }), tail: krl.Function(["val"], function (val) { if (!krl.isArray(val)) { return []; } return _.tail(val); }), index: krl.Function(["val", "elm"], function (val, elm) { if (!krl.isArray(val)) { throw new TypeError("only works on Arrays"); } return _.findIndex(val, _.partial(krl.isEqual, elm)); }), join: krl.Function(["val", "str"], function (val, str) { if (!krl.isArray(val)) { return krl.toString(val); } val = _.map(val, krl.toString); if (krl.isNull(str)) { str = ","; } return _.join(val, krl.toString(str)); }), length: krl.Function(["val"], function (val) { if (krl.isArrayOrMap(val) || krl.isString(val)) { return _.size(val); } return 0; }), isEmpty: krl.Function(["val"], function (val) { return _.isEmpty(val); }), pairwise: krl.Function(["val", "iter"], async function (val, iter) { if (!krl.isArray(val)) { throw new TypeError("The .pairwise() operator cannot be called on " + krl.toString(val)); } if (val.length < 2) { throw new TypeError("The .pairwise() operator needs a longer array"); } if (!krl.isFunction(iter)) { throw new TypeError("The .pairwise() operator cannot use " + krl.toString(iter) + " as a function"); } val = _.map(val, function (v) { if (krl.isArray(v)) { return v; } return [v]; }); var maxLen = _.max(_.map(val, _.size)) || 0; var r = []; var i; var j; var args2; for (i = 0; i < maxLen; i++) { args2 = []; for (j = 0; j < val.length; j++) { args2.push(val[j][i]); } r.push(await iter(this, args2)); } return r; }), reduce: krl.Function(["val", "iter", "dflt"], async function (val, iter, dflt) { if (!krl.isArray(val)) { throw new TypeError("only works on Arrays"); } var noDefault = arguments.length < 3; if (val.length === 0) { return noDefault ? 0 : dflt; } if (!krl.isFunction(iter) && (noDefault || val.length > 1)) { throw new Error("The .reduce() operator cannot use " + krl.toString(iter) + " as a function"); } if (val.length === 1) { var head = val[0]; if (noDefault) { return head; } return iter(this, [dflt, head, 0, val]); } var acc = dflt; var isFirst = true; await iterBase(val, async (v, k, obj) => { if (isFirst && noDefault) { isFirst = false; acc = v; return true; // continue } acc = await iter(this, [acc, v, k, obj]); return true; // continue }); return acc; }), reverse: krl.Function(["val"], function (val) { if (!krl.isArray(val)) { throw new TypeError("only works on Arrays"); } return _.reverse(_.cloneDeep(val)); }), slice: krl.Function(["val", "start", "end"], function (val, start, end) { if (!krl.isArray(val)) { throw new TypeError("only works on Arrays"); } if (val.length === 0) { return []; } if (arguments.length === 1) { return val; } var firstIndex = krl.toNumberOrNull(start); if (firstIndex === null) { throw new TypeError("The .slice() operator cannot use " + krl.toString(start) + " as an index"); } if (arguments.length === 2) { if (firstIndex > val.length) { return []; } return _.slice(val, 0, firstIndex + 1); } var secondIndex = krl.toNumberOrNull(end); if (secondIndex === null) { throw new TypeError("The .slice() operator cannot use " + krl.toString(end) + " as the other index"); } if (firstIndex > secondIndex) { // this is why firstIndex isn't named startIndex var temp = firstIndex; firstIndex = secondIndex; secondIndex = temp; } if (firstIndex >= 0 && secondIndex < val.length) { return _.slice(val, firstIndex, secondIndex + 1); } return []; }), splice: krl.Function(["val", "start", "nElements", "value"], function (val, start, nElements, value) { if (!krl.isArray(val)) { throw new TypeError("only works on Arrays"); } if (val.length === 0) { return []; } var startIndex = krl.toNumberOrNull(start); if (startIndex === null) { throw new TypeError("The .splice() operator cannot use " + krl.toString(start) + "as an index"); } startIndex = Math.min(Math.max(startIndex, 0), val.length - 1); var nElm = krl.toNumberOrNull(nElements); if (nElm === null) { throw new TypeError("The .splice() operator cannot use " + krl.toString(nElements) + "as a number of elements"); } if (nElm < 0 || startIndex + nElm > val.length) { nElm = val.length - startIndex; } var part1 = _.slice(val, 0, startIndex); var part2 = _.slice(val, startIndex + nElm); if (arguments.length < 4) { return _.concat(part1, part2); } return _.concat(part1, value, part2); }), delete: krl.Function(["val", "path"], function (val, path) { path = toKeyPath(path); // TODO optimize var nVal = _.cloneDeep(val); _.unset(nVal, path); return nVal; }), encode: krl.Function(["val", "indent"], function (val, indent) { return krl.encode(val, indent); }), keys: krl.Function(["val", "path"], function (val, path) { if (!krl.isArrayOrMap(val)) { return []; } if (path) { path = toKeyPath(path); return _.keys(_.get(val, path)); } return _.keys(val); }), values: krl.Function(["val", "path"], function (val, path) { if (!krl.isArrayOrMap(val)) { return []; } if (path) { path = toKeyPath(path); return _.values(_.get(val, path)); } return _.values(val); }), intersection: krl.Function(["a", "b"], function (a, b) { if (arguments.length < 2) { return []; } if (!krl.isArray(a)) { a = [a]; } if (!krl.isArray(b)) { b = [b]; } return _.intersectionWith(a, b, krl.isEqual); }), union: krl.Function(["a", "b"], function (a, b) { if (arguments.length < 2) { return a; } if (!krl.isArray(a)) { a = [a]; } if (!krl.isArray(b)) { b = [b]; } return _.unionWith(a, b, krl.isEqual); }), difference: krl.Function(["a", "b"], function (a, b) { if (arguments.length < 2) { return a; } if (!krl.isArray(a)) { a = [a]; } if (!krl.isArray(b)) { b = [b]; } return _.differenceWith(a, b, krl.isEqual); }), has: krl.Function(["val", "other"], function (val, other) { if (!krl.isArray(val)) { val = [val]; } return stdlib.difference(this, [other, val]).length === 0; }), once: krl.Function(["val"], function (val) { if (!krl.isArray(val)) { return val; } // TODO optimize val = krl.cleanNulls(val); var r = []; _.each(_.groupBy(val), function (group) { if (group.length === 1) { r.push(group[0]); } }); return r; }), duplicates: krl.Function(["val"], function (val) { if (!krl.isArray(val)) { return []; } // TODO optimize val = krl.cleanNulls(val); var r = []; _.each(_.groupBy(val), function (group) { if (group.length > 1) { r.push(group[0]); } }); return r; }), unique: krl.Function(["val"], function (val) { if (!krl.isArray(val)) { return val; } return _.uniqWith(val, krl.isEqual); }), put: krl.Function(["val", "path", "toSet"], function (val, path, toSet) { if (!krl.isArrayOrMap(val) || arguments.length < 2) { return val; } if (arguments.length < 3) { toSet = path; path = []; } val = _.cloneDeep(val); path = toKeyPath(path); if (_.isEmpty(path)) { if (krl.isMap(toSet)) { if (krl.isMap(val)) { return _.assign({}, val, toSet); } } else if (krl.isArray(toSet)) { if (krl.isArray(val)) { return _.assign([], val, toSet); } } return toSet; } var nVal = val; var nested = nVal; var i, key; for (i = 0; i < path.length; i++) { key = path[i]; if (i === path.length - 1) { nested[key] = toSet; } else { if (krl.isMap(nested[key])) { // simply traverse down } else if (krl.isArray(nested[key])) { var nextKey = path[i + 1]; if (isSafeArrayIndex(nested[key], nextKey)) { // simply traverse down } else { // convert Array to Map b/c the key is not a safe index nested[key] = _.assign({}, nested[key]); } } else { // need to create a Map to continue nested[key] = {}; } nested = nested[key]; } } return nVal; }), sort: krl.Function(["val", "sortBy"], function (val, sortBy) { if (!krl.isArray(val)) { return val; } val = _.cloneDeep(val); var sorters = { default: (a, b) => { return stdlib.cmp(this, [a, b]); }, reverse: (a, b) => { return -stdlib.cmp(this, [a, b]); }, numeric: (a, b) => { return stdlib["<=>"](this, [a, b]); }, ciremun: (a, b) => { return -stdlib["<=>"](this, [a, b]); }, }; if (_.has(sorters, sortBy)) { return val.sort(sorters[sortBy]); } if (!krl.isFunction(sortBy)) { return val.sort(sorters["default"]); } return (0, asyncSort_1.asyncSort)(val, (a, b) => { return sortBy(this, [a, b]); }); }), ///////////////////////////////////////////////////////////////////////////// get: krl.Function(["obj", "path"], function (obj, path) { if (!krl.isArrayOrMap(obj)) { return null; } path = toKeyPath(path); return _.get(obj, path, null); }), set: krl.Function(["obj", "path", "val"], function (obj, path, val) { if (!krl.isArrayOrMap(obj)) { obj = {}; } path = toKeyPath(path); // TODO optimize obj = _.cloneDeep(obj); return _.set(obj, path, val); }), noop: krl.ActionFunction([], () => null), send_directive: krl.Action(["name", "options"], function (name, options) { return this.addDirective(name, options); }), }; exports.default = stdlib; //# sourceMappingURL=stdlib.js.map