UNPKG

krl-stdlib

Version:
323 lines (284 loc) 7.5 kB
/** * This krl module is all the core utilities for working with the KRL language at runtime */ import * as _ from "lodash"; import * as SelectWhenModule from "select-when"; import { aggregateEvent as aggregateEventFn } from "./aggregateEvent"; import { KrlCtx } from "./KrlCtx"; export const SelectWhen = SelectWhenModule; // exposed for the krl-compiler export const aggregateEvent = aggregateEventFn; // exposed for the krl-compiler export interface Module { [name: string]: (ctx: KrlCtx, args?: any) => any; } function krlNamedArgs(paramOrder: string[]) { return function (args: any) { const params: any[] = []; if (_.isArray(args)) { for (let i = 0; i < Math.min(paramOrder.length, args.length); i++) { params.push(args[i]); } } else { for (let i = 0; i < paramOrder.length; i++) { const paramName = paramOrder[i]; if (_.has(args, paramName)) { while (params.length < i) { params.push(void 0); } params.push(args[paramName]); } else if (_.has(args, i)) { while (params.length < i) { params.push(void 0); } params.push(args[i]); } } } return params; }; } function krlFunctionMaker(krlTypeName: string) { return function ( paramOrder: string[], fn: (this: KrlCtx, ...args: any[]) => any ) { const fixArgs = krlNamedArgs(paramOrder); const wrapped = function (ctx: KrlCtx, args: any) { return fn.apply(ctx, fixArgs(args)); }; // add this for the compiler to know the type (wrapped as any)["$krl_" + krlTypeName] = true; return wrapped; }; } export const Function = krlFunctionMaker("function"); export const Action = krlFunctionMaker("action"); export const Postlude = krlFunctionMaker("postlude"); export function ActionFunction( paramOrder: string[], fn: (this: KrlCtx, ...args: any[]) => any ) { const act = Action(paramOrder, fn); (act as any)["$krl_function"] = true; return act; } export function Property(fn: (this: KrlCtx) => any) { const wrapped = function (ctx: KrlCtx) { return fn.apply(ctx); }; // add this for the compiler to know the type (wrapped as any)["$krl_property"] = true; return wrapped; } /** * used by `foreach` in krl * Array's use index numbers, maps use key strings */ export function toPairs(v: any) { if (isArray(v)) { var pairs = []; var i; for (i = 0; i < v.length; i++) { pairs.push([i, v[i]]); } return pairs; } return _.toPairs(v); } export function isNull(val: any): boolean { return val === null || val === void 0 || _.isNaN(val); } export function isNumber(val: any): boolean { return _.isFinite(val); } export function isString(val: any): boolean { return typeof val === "string"; } export function isHex(val: any): boolean { return typeof val === "string" && /^0x[A-F0-9]+$/i.test(val) } export function isBoolean(val: any): boolean { return val === true || val === false; } export function isRegExp(val: any): boolean { return _.isRegExp(val); } export function isArray(val: any): boolean { return _.isArray(val); } export function isMap(val: any): boolean { // Can't use _.isPlainObject b/c it's to restrictive on what is a "plain" object // especially when accepting values from other libraries outside of KRL return isArrayOrMap(val) && !_.isArray(val); } export function isArrayOrMap(val: any): boolean { return ( _.isObject(val) && !_.isFunction(val) && !_.isRegExp(val) && !_.isString(val) && !_.isNumber(val) ); } export function isFunction(val: any): boolean { return _.isFunction(val) && (val as any)["$krl_function"] === true; } export function assertFunction(val: any) { if (isFunction(val)) { return val; } throw new TypeError(toString(val) + " is not a function"); } export function isAction(val: any): boolean { return _.isFunction(val) && (val as any)["$krl_action"] === true; } export function assertAction(val: any) { if (isAction(val)) { return val; } throw new TypeError(toString(val) + " is not an action"); } export function isPostlude(val: any): boolean { return _.isFunction(val) && (val as any)["$krl_postlude"] === true; } export function typeOf(val: any): string { if (isNull(val)) { return "Null"; } else if (isBoolean(val)) { return "Boolean"; } else if (isHex(val)) { return "Hex"; } else if (isString(val)) { return "String"; } else if (isNumber(val)) { return "Number"; } else if (isRegExp(val)) { return "RegExp"; } else if (isArray(val)) { return "Array"; } else if (isAction(val) && isFunction(val)) { return "ActionFunction"; } else if (isFunction(val)) { return "Function"; } else if (isAction(val)) { return "Action"; } else if (isMap(val)) { return "Map"; } return "JSObject"; } export function toString(val: any): string { var valType = typeOf(val); switch (typeOf(val)) { case "String": return val; case "Hex": return val; case "Null": return "null"; case "Boolean": return val ? "true" : "false"; case "Number": return val + ""; case "RegExp": // NOTE: val.flags doesn't work on old versions of JS var flags = ""; if (val.global) { flags += "g"; } if (val.ignoreCase) { flags += "i"; } return "re#" + val.source + "#" + flags; } return "[" + valType + "]"; } export function toNumberOrNull(val: any): number | null { switch (typeOf(val)) { case "Null": return 0; case "Boolean": return val ? 1 : 0; case "Hex": return parseInt(val, 16); case "String": var n = _.toNumber(val); return isNumber(n) ? n : null; case "Number": return val; case "Array": case "Map": return _.size(val); case "RegExp": case "Function": case "Action": } return null; } export function toHexOrNull(val: any): string | null { switch (typeOf(val)) { case "Null": case "Boolean": case "Hex": case "String": case "Number": return val.toString(16); case "Array": case "Map": case "RegExp": case "Function": case "Action": } return null; } export function isEqual(left: any, right: any): boolean { left = cleanNulls(left); right = cleanNulls(right); return _.isEqual(left, right); } // returns a clone of val with void 0 and NaN values converted to null export function cleanNulls(val: any) { if (isNull(val)) { return null; } if (isArray(val)) { return deepClean(val, _.map); } if (isMap(val)) { return deepClean(val, _.mapValues); } return val; } function deepClean(val: any, mapFn: any) { return mapFn(val, function (v: any) { return cleanNulls(v); }); } export function decode(val: any) { if (!isString(val)) { return val; } try { return JSON.parse(val); } catch (e) { return val; } } export function encode(val: any, indent?: any) { indent = _.parseInt(indent, 10) || 0; // default to 0 (no indent) return JSON.stringify( val, function (k, v) { switch (typeOf(v)) { case "Null": return null; case "JSObject": case "RegExp": case "Function": case "Action": return toString(v); } return v; }, indent ); }