UNPKG

@jaryway/formula-engine

Version:

这个是一个公式引擎库

1,473 lines (1,457 loc) 85.2 kB
'use strict'; var chevrotain = require('chevrotain'); var Big = require('big.js'); var getIn = require('lodash/get'); var dayjs = require('dayjs'); var weekOfYear = require('dayjs/plugin/weekOfYear'); var isoWeek = require('dayjs/plugin/isoWeek'); var isoWeeksInYear = require('dayjs/plugin/isoWeeksInYear'); var isLeapYear = require('dayjs/plugin/isLeapYear'); /* 基本概念 运算符: 比较运算符 (Comparison operators) 等于、不等于、全等于、不全等于、大于、大于等于、小于、小于等于 算数运算符 (Arithmetic operators) 加、减、乘、除 位运算符 (Bitwise operators) 与、或 逻辑运算符 (Logical operators) 与、或 逗号运算符 (Comma operator) 一元运算符 (Unary operators) 自增(++i,i--)、自减(--i,i--)、一元负值、一元正值、非(!、!!) 操作数-字面量: 字符串 日期 布尔值 数字 空 未定义 NaN 运算符优先级 1 + !2 * 3 / 4 & 5 !== 6 && 1 * 2 + 3 () > 一元运算符 > 乘除 > 加减、位运算 > 比较运算符 > 逻辑运算符 */ var TokenName; (function (TokenName) { TokenName["LParen"] = "LParen"; TokenName["RParen"] = "RParen"; TokenName["LSquare"] = "LSquare"; TokenName["RSquare"] = "RSquare"; TokenName["WhiteSpace"] = "WhiteSpace"; TokenName["Comma"] = "Comma"; TokenName["Func"] = "Func"; TokenName["StringLiteral"] = "StringLiteral"; TokenName["DateLiteral"] = "DateLiteral"; TokenName["BooleanLiteral"] = "BooleanLiteral"; TokenName["NumberLiteral"] = "NumberLiteral"; TokenName["NullLiteral"] = "NullLiteral"; TokenName["UndefinedLiteral"] = "UndefinedLiteral"; TokenName["NaNLiteral"] = "NaNLiteral"; TokenName["Reference"] = "Reference"; TokenName["AdditionOperator"] = "AdditionOperator"; TokenName["Addition"] = "Addition"; TokenName["Subtraction"] = "Subtraction"; TokenName["MultiplicationOperator"] = "MultiplicationOperator"; TokenName["Multiplication"] = "Multiplication"; TokenName["Division"] = "Division"; // 等于、不等于、全等于、不全等于、大于、大于等于、小于、小于等于 TokenName["ComparisonOperator"] = "ComparisonOperator"; TokenName["EqualLoose"] = "EqualLoose"; TokenName["NotEqualLoose"] = "NotEqualLoose"; TokenName["EqualStrict"] = "EqualStrict"; TokenName["NotEqualStrict"] = "NotEqualStrict"; TokenName["Greater"] = "Greater"; TokenName["GreaterOrEqual"] = "GreaterOrEqual"; TokenName["Less"] = "Less"; TokenName["LessOrEqual"] = "LessOrEqual"; TokenName["BitwiseOperator"] = "BitwiseOperator"; TokenName["BitwiseAnd"] = "BitwiseAnd"; TokenName["BitwiseOr"] = "BitwiseOr"; TokenName["LogicalOperator"] = "LogicalOperator"; TokenName["LogicalAnd"] = "LogicalAnd"; TokenName["LogicalOr"] = "LogicalOr"; TokenName["UnaryOperator"] = "UnaryOperator"; TokenName["Increment"] = "Increment"; TokenName["Decrement"] = "Decrement"; TokenName["LogicalNot"] = "LogicalNot"; // Positive = 'Positive', // 正值 // Negative = 'Negative', // 负值 })(TokenName || (TokenName = {})); const AdditionOperator = chevrotain.createToken({ name: TokenName.AdditionOperator, pattern: chevrotain.Lexer.NA, }); const MultiplicationOperator = chevrotain.createToken({ name: TokenName.MultiplicationOperator, pattern: chevrotain.Lexer.NA, }); const ComparisonOperator = chevrotain.createToken({ name: TokenName.ComparisonOperator, pattern: chevrotain.Lexer.NA, }); const BitwiseOperator = chevrotain.createToken({ name: TokenName.BitwiseOperator, pattern: chevrotain.Lexer.NA, }); const LogicalOperator = chevrotain.createToken({ name: TokenName.LogicalOperator, pattern: chevrotain.Lexer.NA, }); const UnaryOperator = chevrotain.createToken({ name: TokenName.UnaryOperator, pattern: chevrotain.Lexer.NA, }); const LParen = chevrotain.createToken({ name: TokenName.LParen, pattern: /\(/ }); const RParen = chevrotain.createToken({ name: TokenName.RParen, pattern: /\)/ }); const LSquare = chevrotain.createToken({ name: TokenName.LSquare, pattern: /\[/ }); const RSquare = chevrotain.createToken({ name: TokenName.RSquare, pattern: /\]/ }); const WhiteSpace = chevrotain.createToken({ name: TokenName.WhiteSpace, pattern: /\s+/, group: chevrotain.Lexer.SKIPPED, }); const Comma = chevrotain.createToken({ name: TokenName.Comma, pattern: /,/ }); const Func = chevrotain.createToken({ name: TokenName.Func, pattern: /[A-Z0-9_]+/ }); const StringLiteral = chevrotain.createToken({ name: TokenName.StringLiteral, pattern: /("(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*')/, }); const DateLiteral = chevrotain.createToken({ name: TokenName.DateLiteral, pattern: /[\d-+]+T[\d:.]+Z/, }); const NumberLiteral = chevrotain.createToken({ name: TokenName.NumberLiteral, pattern: /[0-9]+(\.[0-9]+)?(e[+-]?[0-9]+)?/, }); const BooleanLiteral = chevrotain.createToken({ name: TokenName.BooleanLiteral, pattern: /(true|false)/, }); const NullLiteral = chevrotain.createToken({ name: TokenName.NullLiteral, pattern: /null/, }); const UndefinedLiteral = chevrotain.createToken({ name: TokenName.UndefinedLiteral, pattern: /undefined/, }); const NaNLiteral = chevrotain.createToken({ name: TokenName.NaNLiteral, pattern: /NaN/ }); const Reference = chevrotain.createToken({ name: TokenName.Reference, pattern: /\{[A-Za-z_0-9.]+\}/, }); const Addition = chevrotain.createToken({ name: TokenName.Addition, pattern: /\+/, categories: [AdditionOperator, UnaryOperator], }); const Subtraction = chevrotain.createToken({ name: TokenName.Subtraction, pattern: /-/, categories: [AdditionOperator, UnaryOperator], }); const Multiplication = chevrotain.createToken({ name: TokenName.Multiplication, pattern: /\*/, categories: MultiplicationOperator, }); const Division = chevrotain.createToken({ name: TokenName.Division, pattern: /\//, categories: MultiplicationOperator, }); const EqualLoose = chevrotain.createToken({ name: TokenName.EqualLoose, pattern: /==/, categories: [ComparisonOperator], }); const NotEqualLoose = chevrotain.createToken({ name: TokenName.NotEqualLoose, pattern: /!=/, categories: [ComparisonOperator], }); const EqualStrict = chevrotain.createToken({ name: TokenName.EqualStrict, pattern: /===/, categories: [ComparisonOperator], }); const NotEqualStrict = chevrotain.createToken({ name: TokenName.NotEqualStrict, pattern: /!==/, categories: [ComparisonOperator], }); const Greater = chevrotain.createToken({ name: TokenName.Greater, pattern: />/, categories: [ComparisonOperator], }); const GreaterOrEqual = chevrotain.createToken({ name: TokenName.GreaterOrEqual, pattern: />=/, categories: [ComparisonOperator], }); const Less = chevrotain.createToken({ name: TokenName.Less, pattern: /</, categories: [ComparisonOperator], }); const LessOrEqual = chevrotain.createToken({ name: TokenName.LessOrEqual, pattern: /<=/, categories: [ComparisonOperator], }); const BitwiseAnd = chevrotain.createToken({ name: TokenName.BitwiseAnd, pattern: /&/, categories: [BitwiseOperator, ComparisonOperator], }); const BitwiseOr = chevrotain.createToken({ name: TokenName.BitwiseOr, pattern: /\|/, categories: [BitwiseOperator, ComparisonOperator], }); const LogicalAnd = chevrotain.createToken({ name: TokenName.LogicalAnd, pattern: /&&/, categories: [LogicalOperator], }); const LogicalOr = chevrotain.createToken({ name: TokenName.LogicalOr, pattern: /\|\|/, categories: [LogicalOperator], }); const Increment = chevrotain.createToken({ name: TokenName.Increment, pattern: /\+\+/, categories: [UnaryOperator], }); const Decrement = chevrotain.createToken({ name: TokenName.Decrement, pattern: /--/, categories: [UnaryOperator], }); const LogicalNot = chevrotain.createToken({ name: TokenName.LogicalNot, pattern: /!/, categories: [UnaryOperator], }); const allTokens = [ WhiteSpace, Increment, Decrement, Addition, Subtraction, Multiplication, Division, EqualStrict, NotEqualStrict, EqualLoose, NotEqualLoose, GreaterOrEqual, Greater, LessOrEqual, Less, LParen, RParen, LSquare, RSquare, DateLiteral, StringLiteral, BooleanLiteral, NumberLiteral, UndefinedLiteral, NullLiteral, NaNLiteral, ComparisonOperator, AdditionOperator, MultiplicationOperator, UnaryOperator, LogicalOperator, LogicalAnd, LogicalOr, BitwiseAnd, BitwiseOr, LogicalNot, Func, Reference, Comma, ]; const FormulaLexer = new chevrotain.Lexer(allTokens, { ensureOptimizations: true }); const tokens = allTokens.reduce((acc, tokenType) => { acc[tokenType.name] = tokenType; return acc; }, {}); // console.log('tokens', allTokens, tokens); class FormulaParser extends chevrotain.CstParser { constructor() { super(tokens, { maxLookahead: 1 }); this.performSelfAnalysis(); } expression = this.RULE("expression", () => { this.SUBRULE(this.commaExpression); }); commaExpression = this.RULE("commaExpression", () => { this.SUBRULE(this.logicalExpression); this.MANY(() => { this.CONSUME(tokens.Comma); this.SUBRULE2(this.logicalExpression); }); }); logicalExpression = this.RULE("logicalExpression", () => { this.SUBRULE(this.comparisonExpression, { LABEL: "lhs" }); this.OPTION(() => { this.MANY(() => { // console.log('tokens.LogicalOperator',tokens.LogicalOperator); this.CONSUME(tokens.LogicalOperator); this.SUBRULE1(this.comparisonExpression, { LABEL: "rhs" }); }); }); }); comparisonExpression = this.RULE("comparisonExpression", () => { this.SUBRULE(this.additionExpression, { LABEL: "lhs" }); this.OPTION(() => { this.MANY(() => { this.CONSUME(tokens.ComparisonOperator); this.SUBRULE1(this.additionExpression, { LABEL: "rhs" }); }); }); }); // comparisonExpression = this.RULE('comparisonExpression', () => { // this.SUBRULE(this.additionExpression, { LABEL: 'lhs' }); // this.OPTION(() => { // this.CONSUME(tokens.ComparisonOperator); // this.SUBRULE1(this.func); // this.SUBRULE1(this.additionExpression, { LABEL: 'rhs' }); // }); // }); additionExpression = this.RULE("additionExpression", () => { // + - & | this.SUBRULE(this.multiplicationExpression, { LABEL: "lhs" }); this.MANY(() => { this.CONSUME(tokens.AdditionOperator); this.SUBRULE1(this.multiplicationExpression, { LABEL: "rhs" }); }); }); multiplicationExpression = this.RULE("multiplicationExpression", () => { this.SUBRULE(this.atomicExpression, { LABEL: "lhs" }); this.MANY(() => { this.CONSUME(tokens.MultiplicationOperator); this.SUBRULE1(this.atomicExpression, { LABEL: "rhs" }); }); }); unaryExpression = this.RULE("unaryExpression", () => { this.CONSUME(tokens.UnaryOperator); this.SUBRULE(this.atomicExpression); }); atomicExpression = this.RULE("atomicExpression", () => { this.OR([ { ALT: () => this.SUBRULE(this.array) }, { ALT: () => this.SUBRULE(this.func) }, { ALT: () => this.SUBRULE(this.group) }, { ALT: () => this.SUBRULE(this.base) }, { ALT: () => this.SUBRULE(this.reference) }, { ALT: () => this.SUBRULE(this.unaryExpression) }, // { // ALT: () => { // this.CONSUME(tokens.UnaryOperator); // this.SUBRULE(this.atomicExpression); // }, // }, ]); this.MANY(() => { this.CONSUME(tokens.LSquare); this.SUBRULE(this.expression); this.CONSUME(tokens.RSquare); }); }); reference = this.RULE("reference", () => { this.CONSUME(tokens.Reference); }); array = this.RULE("array", () => { this.CONSUME(tokens.LSquare); this.MANY_SEP({ SEP: tokens.Comma, DEF: () => { this.SUBRULE(this.logicalExpression); }, }); this.CONSUME(tokens.RSquare); }); func = this.RULE("func", () => { this.CONSUME(tokens.Func); this.CONSUME(tokens.LParen); this.OPTION(() => { this.SUBRULE(this.logicalExpression, { LABEL: "args" }); this.MANY(() => { this.CONSUME(tokens.Comma); this.SUBRULE2(this.logicalExpression, { LABEL: "args" }); }); this.OPTION2(() => { this.CONSUME2(tokens.Comma); }); }); this.CONSUME(tokens.RParen); }); group = this.RULE("group", () => { this.CONSUME(tokens.LParen); this.AT_LEAST_ONE_SEP({ SEP: tokens.Comma, DEF: () => this.SUBRULE(this.expression), }); this.CONSUME(tokens.RParen); }); base = this.RULE("base", () => { this.OR([ { ALT: () => this.CONSUME(tokens.StringLiteral) }, { ALT: () => this.CONSUME(tokens.DateLiteral) }, { ALT: () => this.CONSUME(tokens.NumberLiteral) }, { ALT: () => this.CONSUME(tokens.BooleanLiteral) }, { ALT: () => this.CONSUME(tokens.UndefinedLiteral) }, { ALT: () => this.CONSUME(tokens.NullLiteral) }, { ALT: () => this.CONSUME(tokens.NaNLiteral) }, ]); }); } class FormulaError extends Error { details; constructor(message, details) { super(message); this.details = details; } } class ExecutionError extends FormulaError { } class FunctionError extends ExecutionError { } class NotImplementedException extends Error { constructor() { super('function not implemented'); } } const toTag = function (e) { return Object.prototype.toString.call(e); }; const isDate$1 = (e) => '[object Date]' === toTag(e); const isString$1 = (e) => '[object String]' === toTag(e); const isNumber$1 = (e) => '[object Number]' === toTag(e); const isBoolean = (e) => '[object Boolean]' === toTag(e); const isNumeric$1 = (e) => { const t = e && e.toString(); return !Array.isArray(e) && t - parseFloat(t) + 1 >= 0; }; const isNumberType$1 = (s) => s === 'number'; const isStringType$1 = (s) => s === 'string'; const throwUnknownOperatorError = (operator, ctx) => { throw new ExecutionError(`Unknown operator: ${operator.image} at ${operator.startOffset}`, { operator, context: ctx }); }; function createEvalVisitor(parser, functions, mode = 'eval') { const FormulaVisitorBase = parser.getBaseCstVisitorConstructorWithDefaults(); class InterpreterVisitor extends FormulaVisitorBase { constructor() { super(); this.validateVisitor(); } expression(ctx, state) { return this.visit(ctx.commaExpression, state); } commaExpression(ctx, state) { return this.visit(ctx.logicalExpression, state); } logicalExpression(ctx, state) { const left = this.visit(ctx.lhs, state); if (!ctx.rhs) return left; return ctx.rhs.reduce((prev, cur, i) => { const operator = ctx.LogicalOperator[i]; const value = this.visit(cur, state); if (chevrotain.tokenMatcher(operator, tokens.LogicalAnd)) return prev && value; if (chevrotain.tokenMatcher(operator, tokens.LogicalOr)) return prev || value; return prev; }, left || 0); } comparisonExpression(ctx, state) { const left = this.visit(ctx.lhs, state); if (!ctx.rhs) return left; // console.log('comparisonExpression', left) const reuslt = ctx.rhs.reduce((prev, cur, i) => { const operator = ctx.ComparisonOperator[i]; const value = this.visit(cur, state); if (chevrotain.tokenMatcher(operator, tokens.EqualLoose)) return prev == value; if (chevrotain.tokenMatcher(operator, tokens.NotEqualLoose)) return prev != value; if (chevrotain.tokenMatcher(operator, tokens.EqualStrict)) return prev === value; if (chevrotain.tokenMatcher(operator, tokens.NotEqualStrict)) return prev !== value; if (chevrotain.tokenMatcher(operator, tokens.Greater)) return prev > value; if (chevrotain.tokenMatcher(operator, tokens.GreaterOrEqual)) return prev >= value; if (chevrotain.tokenMatcher(operator, tokens.Less)) return prev < value; if (chevrotain.tokenMatcher(operator, tokens.LessOrEqual)) return prev <= value; if (chevrotain.tokenMatcher(operator, tokens.BitwiseAnd)) return prev & value; if (chevrotain.tokenMatcher(operator, tokens.BitwiseOr)) return prev | value; throwUnknownOperatorError(operator, ctx); return prev; }, left || 0); if (mode === 'check') { if (isDate$1(reuslt)) return 'date'; if (isString$1(reuslt)) return 'string'; if (isNumber$1(reuslt)) return 'number'; if (isBoolean(reuslt)) return 'boolean'; } return reuslt; } additionExpression(ctx, state) { const left = this.visit(ctx.lhs, state); if (!ctx.rhs) return left; if (mode === 'check') { if (!isNumberType$1(left) && !isStringType$1(left)) throw new ExecutionError('additionExpression: lhs is not a number or string', {}); return ctx.rhs.reduce((prev, cur, i) => { const operator = ctx.AdditionOperator[i]; const value = this.visit(cur, state); if (chevrotain.tokenMatcher(operator, tokens.Addition)) { if (!isNumberType$1(value) && !isStringType$1(value)) throw new ExecutionError('Addition: rhs is not a number or string', {}); if (isStringType$1(value) || isStringType$1(prev)) return 'string'; return 'number'; } if (chevrotain.tokenMatcher(operator, tokens.Subtraction)) { if (!isNumberType$1(prev)) throw new ExecutionError('Subtraction: lhs is not a number', {}); if (!isNumberType$1(value)) throw new ExecutionError('Subtraction: rhs is not a number', {}); return 'number'; } if (chevrotain.tokenMatcher(operator, tokens.BitwiseAnd)) { if (!isNumberType$1(value) && !isStringType$1(value)) throw new ExecutionError('BitwiseAnd: rhs is not a number or string', {}); return 'number'; } if (chevrotain.tokenMatcher(operator, tokens.BitwiseOr)) { if (!isNumberType$1(value) && !isStringType$1(value)) throw new ExecutionError('BitwiseOr: rhs is not a number or string', {}); return 'number'; } throwUnknownOperatorError(operator, ctx); return prev; }, left); } return ctx.rhs.reduce((prev, cur, i) => { const operator = ctx.AdditionOperator[i]; const value = this.visit(cur, state); if (chevrotain.tokenMatcher(operator, tokens.Addition)) return prev + value; if (chevrotain.tokenMatcher(operator, tokens.Subtraction)) return prev - value; if (chevrotain.tokenMatcher(operator, tokens.BitwiseAnd)) return prev & value; if (chevrotain.tokenMatcher(operator, tokens.BitwiseOr)) return prev | value; throwUnknownOperatorError(operator, ctx); return prev; }, left || 0); } multiplicationExpression(ctx, state) { const left = this.visit(ctx.lhs, state); if (!ctx.rhs) return left; if (mode === 'check') { if (!isNumberType$1(left)) throw new ExecutionError('multiplicationExpression: lhs is not a number', {}); return ctx.rhs.reduce((prev, cur, i) => { const operator = ctx.MultiplicationOperator[i]; const value = this.visit(cur, state); if (!isNumberType$1(value)) throw new ExecutionError('multiplicationExpression: rhs is not a number', {}); if (chevrotain.tokenMatcher(operator, tokens.Multiplication)) return 'number'; if (chevrotain.tokenMatcher(operator, tokens.Division)) return 'number'; throwUnknownOperatorError(operator, ctx); return prev; }, left); } return ctx.rhs .reduce((prev, cur, i) => { const operator = ctx.MultiplicationOperator[i]; let value = this.visit(cur, state); if (!isNumeric$1(value)) value = 0; if (chevrotain.tokenMatcher(operator, tokens.Multiplication)) return prev.times(value); if (chevrotain.tokenMatcher(operator, tokens.Division)) return prev.div(value); throwUnknownOperatorError(operator, ctx); return prev; }, Big(left || 0)) .toNumber(); } atomicExpression(ctx, state) { if (ctx.array) return this.visit(ctx.array, state); if (ctx.func) return this.visit(ctx.func, state); if (ctx.group) return this.visit(ctx.group, state); if (ctx.base) return this.visit(ctx.base, state); if (ctx.unaryExpression) return this.visit(ctx.unaryExpression, state); if (ctx.reference) return this.visit(ctx.reference, state); } unaryExpression(ctx, state) { const right = this.visit(ctx.atomicExpression, state); if (ctx.UnaryOperator) { const operator = ctx.UnaryOperator[0]; if (chevrotain.tokenMatcher(operator, tokens.LogicalNot)) return !right; if (chevrotain.tokenMatcher(operator, tokens.Addition)) return +right; if (chevrotain.tokenMatcher(operator, tokens.Subtraction)) return -right; if (chevrotain.tokenMatcher(operator, tokens.Increment)) return right + 1; if (chevrotain.tokenMatcher(operator, tokens.Decrement)) return right - 1; throwUnknownOperatorError(operator, ctx); } throw new ExecutionError(`一元表达式应该由一个操作符和一个操作数组成`, { context: ctx }); } reference(ctx, state) { const name = ctx.Reference[0].image.slice(1, -1); return getIn(state.variables, name); } arguments(ctx, state) { if (!ctx.additionExpression) return []; const result = ctx.additionExpression.map((arg) => this.visit(arg, state)); return result; } array(ctx, state) { return (ctx.logicalExpression || []).map((m) => this.visit(m, state)); } func(ctx, state) { const funcName = ctx.Func[0].image; const func = functions[funcName]; if (!func) { throw new ExecutionError(`Unknown function: ${funcName} at ${ctx.Func[0].startOffset}`, { funcName, context: ctx }); } const args = (ctx.args || []).map((m) => this.visit(m, state)); // console.log('funcfuncfunc', args) try { return func.apply({}, args); } catch (err) { const loc = ctx.Func[0].startOffset; const stack = err.stack; const msg = `Function ${funcName} at ${loc} thrown an error: ${err}, stacktrace: ${stack}`; throw new FunctionError(msg, { originalError: err, funcName, function: ctx.Func[0], context: ctx }); } } group(ctx, state) { return this.visit(ctx.expression, state); } base(ctx) { // console.log('base', ctx) if (mode === 'check') { if (ctx.StringLiteral) { // 分为单引号,双引号字符串 const t = ctx.StringLiteral[0].image.slice(1, -1); return /^\$\{[a-z]+\}$/.test(t) ? t.match(/^\$\{([a-z]+)\}$/)[1] : 'string'; } // if (ctx.DateLiteral) return new Date(ctx.DateLiteral[0].image); if (ctx.BooleanLiteral) return 'boolean'; if (ctx.NumberLiteral) return 'number'; if (ctx.NullLiteral) return 'null'; if (ctx.UndefinedLiteral) return 'undefined'; if (ctx.NaNLiteral) return 'number'; return undefined; } if (ctx.StringLiteral) { // 分为单引号,双引号字符串 return ctx.StringLiteral[0].image.slice(1, -1); //.replace(/''/, "'"); // return ctx.DateLiteral[0].image.slice(1, -1).replace(/""/, '"'); } if (ctx.DateLiteral) return new Date(ctx.DateLiteral[0].image); if (ctx.BooleanLiteral) return ctx.BooleanLiteral[0].image === 'true' ? true : false; if (ctx.NumberLiteral) return Number(ctx.NumberLiteral[0].image.replace(/,/g, '')); if (ctx.NullLiteral) return null; if (ctx.UndefinedLiteral) return undefined; if (ctx.NaNLiteral) return NaN; return undefined; } } return new InterpreterVisitor(); } const toString = Object.prototype.toString; const isType = (type) => { return (obj) => { return getType(obj) === `[object ${type}]`; }; }; const isUndefined$1 = (s) => s === undefined; const isNull$1 = (s) => s === null; const isNullUndefined$1 = (s) => isUndefined$1(s) || isNull$1(s); const getType = (obj) => toString.call(obj); const isDate = isType('Date'); const isString = isType('String'); const isObject = isType('Object'); const isNumber = isType('Number'); const isNumeric = (e) => { const t = e && e.toString(); return !Array.isArray(e) && t - parseFloat(t) + 1 >= 0; }; const parseNumber = function (e) { const num = parseFloat(e); return !isNaN(num) && isFinite(num) ? num : null; }; /* 根据角度求弧度 */ const radians = function (a) { const angle = parseNumber(a) || 0; return Number(Big(angle * Math.PI).div(180)); }; function uuid(match) { if (match) return (match ^ ((16 * Math.random()) >> (match / 4))).toString(16); return [1e7, 1e3, 4e3, 8e3, 1e11].join('-').replace(/[018]/g, uuid); } function numberToChinese(num, t = 0) { const configs = { 0: { ch: '〇一二三四五六七八九', ch_u: '个十百千万亿', ch_f: '负', ch_d: '点', m_u: '', m_z: '' }, 1: { ch: '零壹贰叁肆伍陆柒捌玖', ch_u: '个拾佰仟万亿', ch_f: '负', ch_d: '点', m_u: '', m_z: '' }, 2: { ch: '零壹贰叁肆伍陆柒捌玖', ch_u: '个拾佰仟万亿', ch_f: '负', ch_d: '', m_u: '元角分厘', m_z: '整' } }; // 0、转成中文小写,123.4567 显示为一百二十三点四五六七; // 1、转成中文大写,123.4567 显示为壹佰贰十叁点肆伍陆柒; // 2、转成中文金额大写,金额大写只能显示小数点后2位,123.4567 显示为壹佰贰十叁元肆角伍分; const config = configs[t]; const chineseNums = config.ch; const chineseUnits = config.ch_u; const moneyUnits = config.m_u; const isMoney = config.m_u.length > 0; const decimals = (isMoney ? Math.abs(num).toFixed(2) : Math.abs(num)).toString().split('.'); const [integerPart, decimalPart] = decimals; const zero = config.ch[0]; const reg1 = new RegExp(`${zero}+`, 'g'); const reg2 = new RegExp(`${zero}*$`); const negativeChar = num < 0 ? config.ch_f : ''; const arr = ['', '', '']; for (let i = 0; i < integerPart.length; i++) { const cur = integerPart[integerPart.length - 1 - i]; const idx = 3 - Math.ceil((i + 1) / 4); const safeIdx = idx < 0 ? 0 : idx; const prev = arr[safeIdx]; arr[safeIdx] = cur + prev; } let result = arr .map((item) => { let res = ''; if (item === '0000') return res; for (let i = 0; i < item.length; i++) { const temp = item.length - i - 1; const unit = temp ? chineseUnits[temp] : ''; const digit = Number(item[i]); const cdigit = chineseNums[digit]; res += digit === 0 ? cdigit : cdigit + unit; } res = res.replace(reg1, zero).replace(reg2, ''); return res; }) .reduce((prev, cur, i) => { if (!cur) return prev; if (i === 2) return prev + cur; if (i === 1) return prev + cur + chineseUnits[4]; return prev + cur + chineseUnits[5]; }, ''); result = result || zero; const decimalPart1 = (decimalPart || '').replace(/0*$/, ''); let result1 = ''; for (let i = 0; i < decimalPart1.length; i++) { const digit = parseInt(decimalPart1[i]); result1 += [chineseNums[digit], moneyUnits[i + 1]].join(''); } result1 = result1.replace(new RegExp(`${zero}*$`), ''); if (!result1) return negativeChar + [result, config.m_u[0], config.m_z].join(''); result += [config.m_u[0], config.ch_d].join(''); return negativeChar + result + result1; } const makeArray = (s) => { if (isNullUndefined$1(s)) return []; if (Array.isArray(s)) return s; return [s]; }; /** * 用于计算两个定位之间的距离,单位为米。 * @param args */ const DISTANCE$1 = function (..._args) { throw new NotImplementedException(); }; /** * 函数用于获取当前用户的昵称。 * @param args */ const GETUSERNAME = function (..._args) { throw new NotImplementedException(); }; const INDEX$1 = function (...args) { return args[0][args[1]]; }; const MAPX = function (..._args) { throw new NotImplementedException(); }; const RECNO = function (..._args) { throw new NotImplementedException(); }; /** * 获取部门名称和部门编号。 * @param args */ const TEXTDEPT$1 = function (..._args) { throw new NotImplementedException(); }; const TEXTUSER$1 = function (..._args) { throw new NotImplementedException(); }; const TEXTLOCATION$1 = function (..._args) { throw new NotImplementedException(); }; const TEXTPHONE$1 = function (..._args) { throw new NotImplementedException(); }; const UUID$1 = function () { return uuid(); }; dayjs.extend(weekOfYear); dayjs.extend(isoWeeksInYear); dayjs.extend(isLeapYear); dayjs.extend(isoWeek); const DATE_COMMON_FORMAT$1 = 'ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'; // const DATE_YMD_FORMAT = 'YYYY-MM-DD' /* 转换为日期 */ const DATE$1 = function (...args) { if (args.length === 1) { if (isNumber(args[0]) || isString(args[0])) { return dayjs(args[0].valueOf()).format(DATE_COMMON_FORMAT$1); } } if (args.length >= 3 && args.length <= 6) { const [year, month, ...rest] = args; return dayjs(new Date(year, month - 1, ...rest)).format(DATE_COMMON_FORMAT$1); } }; const DAY$1 = function (...args) { return dayjs(args[0].valueOf()).date().toString(); }; /* 天数差 */ const DAYS$1 = function (...args) { return dayjs(args[0].valueOf()).diff(dayjs(args[1].valueOf()), 'days').toString(); }; // TODO const DAYS360$1 = function (..._args) { throw new NotImplementedException(); }; const DATEDIF$1 = function (...args) { return dayjs(args[0].valueOf()) .diff(dayjs(args[1].valueOf()), args[2] || 'days') .toString(); }; const DATEDELTA$1 = function (...args) { return dayjs(args[0].valueOf()).add(args[1], 'days').format(DATE_COMMON_FORMAT$1); }; const HOUR$1 = function (...args) { return dayjs(args[0].valueOf()).hour().toString(); }; const ISOWEEKNUM$1 = function (date) { return dayjs(date).isoWeek(); }; const MINUTE$1 = function (...args) { return dayjs(args[0].valueOf()).minute().toString(); }; const MONTH$1 = function (...args) { return (dayjs(args[0].valueOf()).month() + 1).toString(); }; // TODO const NETWORKDAYS$1 = function (..._args) { throw new NotImplementedException(); }; const NOW$1 = function () { return dayjs().format(DATE_COMMON_FORMAT$1); }; // TODO const SYSTIME = function (..._args) { throw new NotImplementedException(); }; const SECOND$1 = function (...args) { return dayjs(args[0].valueOf()).second().toString(); }; const TIMESTAMP$1 = function (...args) { return dayjs(args[0]).format(DATE_COMMON_FORMAT$1); }; // TODO const TIME$1 = function (hour, minute, second) { const hdays = (~~hour < 0 ? 0 : ~~hour) / 24; const mdays = (~~minute < 0 ? 0 : ~~minute) / 24 / 60; const sdays = (~~second < 0 ? 0 : ~~second) / 24 / 60 / 60; return hdays + mdays + sdays; }; const TODAY$1 = function () { return dayjs().format(DATE_COMMON_FORMAT$1); }; const WEEKDAY$1 = function (...args) { return dayjs(args[0].valueOf()).day().toString(); }; const WEEKNUM$1 = function (timestamp, returnType = 1) { const returnTypeArr = { 1: 0, 2: 1, 11: 1, 12: 2, 13: 3, 14: 4, 15: 5, 16: 6, 17: 0 }; const returnTypeNum = returnTypeArr[returnType]; const timestampStart = dayjs(new Date(dayjs(timestamp).year(), 0, 1)).valueOf(); const n = (returnTypeNum + 7 - dayjs(timestampStart).day()) % 7; // 当周第几天 const currentWeekCount = n > 0 ? 1 : 0; // 时间戳当前算不算新的一周 const c = timestampStart + 24 * n * 60 * 60 * 1e3; return Math.floor((timestamp - c) / (24 * 60 * 60 * 1e3) / 7 + 1) + currentWeekCount; }; /** * 计算在某日期(起始日期)之前或之后、与该日期相隔指定工作日的某一日期的日期值。 工作日不包括周末和专门指定的假日。 * @param start_timestamp * @param days 所需工作天数 * @param holidays 节假日 * @returns 日期值 */ const WORKDAY$1 = function (start_timestamp, days, holidays) { const start = dayjs(start_timestamp); const safeDays = days || 0; let i = 0; while (i < safeDays) { const date = dayjs(start).add(i, 'day'); if ([0, 6].includes(date.day())) continue; //排除周末 if ((holidays || []).some((m) => date.diff(m, 'day') === 0)) continue; // 排除节假日 i++; } return dayjs(start).add(i).format(DATE_COMMON_FORMAT$1); }; const YEAR$1 = function (...args) { return dayjs(args[0].valueOf()).year().toString(); }; function AND$1(...args) { return args.reduce((prev, cur) => { return prev && !!cur; }, true); } function OR$1(...args) { return args.reduce((prev, cur) => { return prev || !!cur; }, false); } function FALSE() { return false; } function TRUE$1() { return true; } const IF$1 = (condition, valueIfTrue, valueIfFalse) => { return condition ? valueIfTrue : valueIfFalse; }; const IFS$1 = (...args) => { for (let index = 0; index < args.length; index += 2) { if (args[index]) { const a = args[index + 1]; return a; } } }; const NOT$1 = (logical) => { return !logical; }; /** * 异或运算 * @param args boolean[] * @returns 如果两个逻辑值相同,返回 false, 如果两个逻辑值不同,返回 true */ const XOR$1 = function (...args) { if (args.length < 2) return true; return args.reduce((prev, cur, i) => { if (i === 0) return prev; return prev !== cur; }, args[0]); }; function flatten(arr) { return arr.reduce((prev, cur) => { return Array.isArray(cur) ? [...prev, ...flatten(cur)] : [...prev, cur]; }, []); } const ABS$1 = function (e) { return isNumber(e) ? Math.abs(e.valueOf()) : 0; }; const AVERAGE$1 = function (...args) { // console.log('AVERAGE') const arr = flatten(args); const numArray = arr.filter((e) => isNumber(e)); const [sum, count] = numArray.reduce(([sum, count], cur) => { sum = sum.plus(Big(parseNumber(cur) || 0)); count += 1; return [sum, count]; }, [Big(0), 0]); if (count === 0) return 0; return sum.div(count).toNumber(); }; /* CEILING 将参数 number 向上/向下舍入为最接近的指定基数的倍数 */ const CEILING$1 = function (number, significance) { let mode = 0; significance = significance === undefined ? 1 : significance; if (significance === 0) { return 0; } if (number < 0 && significance < 0) { mode = 1; } significance = Math.abs(significance); if (number >= 0) { return Math.ceil(number / significance) * significance; } else { if (mode === 0) { return -1 * Math.floor(Math.abs(number) / significance) * significance; } else { return -1 * Math.ceil(Math.abs(number) / significance) * significance; } } }; /* 余弦函数 */ const COS$1 = function (a) { const angle = parseNumber(a) || 0; return Number(Math.cos(radians(angle)).toFixed(8)); }; /* 余切 */ const COT$1 = function (a) { const angle = parseNumber(a) || 0; return Number((1 / Math.tan(radians(angle))).toFixed(8)); }; const COUNT$1 = function (...args) { // 如果是多个参数直接返回参数长度 if (args.length > 1) { return args.length; } // 否则将数组打平 return flatten(args).length; }; const COUNTIF$1 = function (range, criteria) { if (!/[<>=!]/.test(criteria)) { criteria = '=="' + criteria + '"'; } let matches = 0; for (let i = 0; i < range.length; i++) { if (typeof range[i] !== 'string') { if (eval(range[i] + criteria)) { matches++; } } else { if (eval('"' + range[i] + '"' + criteria)) { matches++; } } } return matches; }; /* FLOOR 将参数 number 向下舍入为最接近的 significance 的倍数。 */ const FLOOR$1 = function (number, significance) { let mode = 0; significance = significance === undefined ? 1 : significance; number = parseNumber(number); significance = parseNumber(significance); if (significance === 0) { return 0; } if (number < 0 && significance < 0) { mode = 1; } significance = Math.abs(significance); if (number >= 0) { return Math.floor(number / significance) * significance; } else { if (mode === 0) { return -1 * Math.ceil(Math.abs(number) / significance) * significance; } else { return -1 * Math.floor(Math.abs(number) / significance) * significance; } } }; /* 将数字舍入到指定的小数位数,以十进制数格式对该数进行格式设置,并以文本形式返回结果。 number: 必需。 要进行舍入并转换为文本的数字。 decimals: 可选。 小数点右边的位数。 */ const FIXED$1 = function (number, decimals) { const r = parseNumber(number); const s = decimals === undefined ? 0 : decimals; return isNull$1(r) ? NaN : new Big(r || 0).toFixed(s).toString(); }; /* 将数字向下舍入到最接近的整数 */ const INT$1 = function (e) { return isNumber(e) ? Math.floor(e.valueOf()) : 0; }; /* 第n个最大数 */ const LARGE$1 = function (arr, index) { const arrFiltered = arr.filter((item) => isNumber(item)); arrFiltered.sort((a, b) => b - a); const indexWithin = (index - 1) % arr.length; return arrFiltered[indexWithin]; }; const LOG$1 = function (number, base) { number = parseNumber(number); base = base === undefined ? 10 : parseNumber(base); return Math.log(number) / Math.log(base); }; /** * 求余数 * @param dividend 要计算余数的被除数 * @param divisor 除数 * @returns 返回两数相除的余数 */ const MOD$1 = function (dividend, divisor) { const num1 = parseNumber(dividend); const num2 = parseNumber(divisor); if (num2 === 0) { return NaN; } const num = Math.abs(num1 % num2); return num2 > 0 ? num : -num; }; const MAX$1 = function (...args) { const nums = args.filter((item) => isNumber(item)); return 0 === nums.length ? 0 : Math.max(...nums); }; const MIN$1 = function (...args) { const nums = args.filter((item) => isNumber(item)); return 0 === nums.length ? 0 : Math.min(...nums); }; const POWER$1 = function (number, power) { number = parseNumber(number); power = parseNumber(power); const result = Math.pow(number, power); return result; }; /* 求乘积 */ const PRODUCT$1 = function (...args) { const arrFiltered = flatten(args).filter((m) => isNumber(m)); // console.log('arrFiltered', args, arrFiltered); if (!arrFiltered.length) return 0; const result = arrFiltered.reduce((prev, curr) => { return prev * curr; }, 1); return result; }; const RADIANS$1 = function (a) { return Number(radians(a).toFixed(8)); }; /* 0~1随机数 */ const RAND$1 = function () { return Math.random(); }; /* 将数字a四舍五入到指定的位数b */ const ROUND$1 = function (a, b) { const num = parseNumber(a) || 0; const digits = parseNumber(b) || 0; return Math.round(num * Math.pow(10, digits)) / Math.pow(10, digits); }; /* 正弦函数 */ const SIN$1 = function (a) { const angle = parseNumber(a) || 0; return Number(Math.sin(radians(angle)).toFixed(8)); }; /* 第n个最小数 */ const SMALL$1 = function (arr, index) { const arrFiltered = arr.filter((item) => isNumber(item)); arrFiltered.sort((a, b) => a - b); const indexWithin = (index - 1) % arr.length; return arrFiltered[indexWithin]; }; /* 正平方根 */ const SQRT$1 = function (a) { const num = parseNumber(a) || 0; return num < 0 ? 0 : Math.sqrt(num); }; /* 求和 */ const SUM$1 = function (...args) { // console.log('SUMSUMSUMSUM', args) const res = flatten(args) .filter((item) => { return isNumeric(item); }) .reduce((prev, cur) => { return prev.plus(Big(parseFloat(cur))); }, Big(0)); return res.toNumber(); }; /** * 用于计算子表单中满足某一条件的数字相加并返回和 * @param values 子表单某列的值 * @param criteria * @param sum_range * @returns */ const SUMIF$1 = function (range, criteria, sum_range) { // SUMIF(入库明细.产品类型,"水果",入库明细.数量) // SUMIF(["水果","蔬菜","面食"],"水果",[30,25,90]) const _sum_range = isNullUndefined$1(sum_range) ? range : sum_range; const final_range = makeArray(range); const final_sum_range = makeArray(_sum_range); if (final_range.length !== final_sum_range.length) return null; if (isNullUndefined$1(criteria) || criteria === '') return 0; return final_range.reduce((prev, cur, index) => { const sum_value = final_sum_range[index]; // 条件成立并且 if (cur === criteria) return prev + ~~sum_value; return prev; }, 0); }; const SUMIFS$1 = function (range, ...args) { // SUMIFS(入库明细.数量,入库明细.水果名称,"苹果",入库明细.水果种类,"红富士") // SUMIFS([1, 2, 3, 4], ['苹果', '葡萄', '苹果', '香蕉'], '苹果', ['红富士', '红星', '红富士', '夏黑'], '红富士') function getConditions() { const result = []; let index = 0; while (index < args.length) { result.push([args[index], args[index + 1]]); index = index + 2; } return result; } const conditionsList = getConditions(); return (range || []).reduce((prev, cur, index) => { const result = conditionsList.every((m) => { const [conditions, criteria] = m; return conditions[index] === criteria; }); if (result) return prev + ~~cur; return prev; }, 0); }; /* 数组对应位置元素的乘积之和 */ const SUMPRODUCT$1 = function (...args) { const max = Math.max(...args.map((m) => m.length)); let result = 0; for (let i = 0; i < max; i++) { const product = args.reduce((prev, cur) => { const num = cur[i]; if (isNullUndefined$1(num) || !isNumber(num)) return prev; if (prev === undefined) return num; return prev * num; }, undefined); result += product === undefined ? 0 : product; } return result; }; /* 正切 */ const TAN$1 = function (a) { const angle = parseNumber(a) || 0; return Number(Math.tan(radians(angle)).toFixed(8)); }; const DATE_COMMON_FORMAT = 'ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'; function CONCATENATE$1(...args) { return args .filter((m) => !isNullUndefined$1(m)) // .map((m) => (m === true ? 'TRUE' : m === false ? 'FALSE' : String(m))) .join(''); } const CHAR$1 = (...args) => { // 函数可将计算机其他类型的数字代码转换为字符 const e = args[0]; return 9 === e || 10 === e || 34 === e || 39 === e || 92 === e ? String.fromCharCode(e) : ''; }; /** * 比较两个字符串是否完全相同(区分大小写) * @param args * @returns */ const EXACT$1 = (text1, text2) => { return text1 === text2; }; const IP = (..._args) => { throw new NotImplementedException(); }; const ISEMPTY$1 = (value) => { if (isNullUndefined$1(value)) return true; // undefined null if (Array.isArray(value)) return value.length === 0; // [] if (isObject(value)) return Object.keys(value).length === 0; // {} if (isString(value)) return value === ''; // 空字符串 return false; }; const JOIN$1 = function (arrToJoin, joiner) { return Array.isArray(arrToJoin) && isString(joiner) ? arrToJoin.join(joiner.valueOf()) : ''; }; const LEFT$1 = (...args) => { const r = isNull$1(args[1]) ? 1 : args[0]; return args[0] ? args[0].substring(0, r) : ''; }; const LEN$1 = function (...args) { const s = args[0]; if (isString(s)) return s.length; if (s && s.length) return s.length; return 0; }; const LOWER$1 = function (...args) { const s = args[0]; return isString(s) ? s.toLowerCase() : ''; }; /** * * MID(text, start_num, num_chars) * 返回文本字符串中从指定位置开始的特定数目的字符,该数目由用户指定。 */ const MID$1 = function (...args) { const [text, start, len] = args; return (text || '').slice(start, start + len); }; const REPLACE$1 = function (...args) { // REPLACE(old_text, start_num, num_chars, new_text) const [old_text, start_num, num_chars, new_text] = args; return old_text.slice(0, start_num) + new_text + old_text.slice(start_num + num_chars); }; /** * 将文本重复一定次数。 * @param String text * @param Number number_times * @returns */ const REPT$1 = function (...args) { // const [text, times] = args; return new Array(times).fill(text).join(''); }; const RIGHT$1 = function (...args) { const t = isNullUndefined$1(args[1]) ? 1 : args[1]; return args[0] ? args[0].substring(args[0].length - t) : ''; }; /* 数字转人民币大写 */ const RMBCAP$1 = function (inputNumber) { return numberToChinese(inputNumber, 2); }; /** * SEARCH(find_text,within_text,[start_num]) * 返回第一个文本字符串在第二个文本字符串中第一次出现的位置序号,从左到右查找,忽略英文字母的大小写;返回 0 则表示未查找到。 * @param args */ const SEARCH$1 = function (find_text, within_text, start_num) { // const [find_text, within_text, start_num] = args if (!isString(find_text) || !isString(within_text)) return 0; const res = find_text .slice(start_num || 0) .toLowerCase() .indexOf(within_text); return res === -1 ? 0 : res; }; const SPLIT$1 = function (...args) { const [text, separator] = args; return (text || '').split(separator); }; const TRIM$1 = function (...args) { return args[0].trim(); }; const TEXT$1 = function (numOrDate, format) { if (isNullUndefined$1(numOrDate)) return ''; if (isDate(numOrDate) || dayjs.isDayjs(numOrDate)) { if (isNullUndefined$1(format)) return dayjs(numOrDate).format(DATE_COMMON_FORMAT); return dayjs(numOrDate).format(format.replace(/y/g, 'Y')); } if (format === '[Num0]') return numOrDate; if (format === '[Num1]') return numberToChinese(numOrDate, 0); // 中文小写 if (format === '[Num2]') return numberToChinese(numOrDate, 1); // 中文大写 if (format === '[Num3]') return numberToChinese(numOrDate, 2); // 中文金额大写 if (!format) return String(numOrDate); const getConf = (format) => { const [integerPart, decimalPart] = format.split('.'); const match = decimalPart.match(/^([#|0]*)(.*)?/); const precision = match[1].length; const suffix = match[2]; const prefix = integerPart.match(/[^(#|0)]*/); const needSep = integerPart.includes(','); return { precision, suffix, prefix, needSep }; }; const match = format.match(/[#0]+,?[#0]*\.?[#0]*%?/); if (match && match.length > 0) { const { precision, suffix, prefix, needSep } = getConf(format); const numStr = Number(numOrDate).toFixed(precision).toString(); let [part1, part2] = numStr.split('.'); if (needSep) { part1 = part1.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); part2 = part2.replace(/(\d{3}(?!$))/g, '$1,'); } return [prefix, part1, '.', part2, suffix].join(''); }