sh-tools 2.3.7 → 2.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-tools",
3
- "version": "2.3.7",
3
+ "version": "2.3.9",
4
4
  "description": "基于fetch和xe-utils二次封装,支持宏公式计算",
5
5
  "main": "packages/index.js",
6
6
  "scripts": {
@@ -1,125 +1,137 @@
1
1
  import utilsModule from '../utils'
2
2
 
3
3
  /**
4
- * 常量定义:分类管理内置方法名
4
+ * 表达式求值器 - 支持JSON标记解析、常用数学/数组/对象运算
5
+ * @class ExprEvaluator
6
+ * @description 通过#JSON#双标记精准解析嵌套JSON,聚焦核心运算场景
5
7
  */
6
- const MATH_METHODS = ['abs', 'sin', 'cos', 'tan', 'log', 'exp', 'pow']
7
- const UTIL_METHODS = {
8
- base: [
9
- 'isNaN',
10
- 'isFinite',
11
- 'isArray',
12
- 'isFloat',
13
- 'isInteger',
14
- 'isBoolean',
15
- 'isString',
16
- 'isNumber',
17
- 'isPlainObject',
18
- 'isDate',
19
- 'isEmpty',
20
- 'isNull',
21
- 'isMatch',
22
- 'isEqual',
23
- 'getSize',
24
- 'first',
25
- 'last'
26
- ],
27
- object: ['has', 'get', 'assign', 'merge', 'pick', 'omit'],
28
- array: [
29
- 'slice',
30
- 'indexOf',
31
- 'includes',
32
- 'includeArrays',
33
- 'sum',
34
- 'mean',
35
- 'zip',
36
- 'unzip',
37
- 'uniq',
38
- 'union',
39
- 'flatten',
40
- 'pluck',
41
- 'invoke',
42
- 'orderBy',
43
- 'groupBy',
44
- 'countBy',
45
- 'toArrayTree',
46
- 'toTreeArray'
47
- ],
48
- date: [
49
- 'now',
50
- 'timestamp',
51
- 'toStringDate',
52
- 'toDateString',
53
- 'getWhatYear',
54
- 'getWhatMonth',
55
- 'getWhatWeek',
56
- 'getWhatDay',
57
- 'getDayOfYear',
58
- 'getYearDay',
59
- 'getYearWeek',
60
- 'getMonthWeek',
61
- 'getDayOfMonth'
62
- ],
63
- number: ['random', 'min', 'max', 'ceil', 'floor', 'round', 'truncate', 'toFixed', 'toNumber', 'toNumberString', 'toInteger', 'add', 'subtract', 'multiply', 'divide'],
64
- string: ['toValueString', 'trim', 'trimLeft', 'trimRight', 'camelCase', 'kebabCase', 'startsWith', 'endsWith']
65
- }
8
+ class ExprEvaluator {
9
+ // 静态常量 - 仅保留核心方法
10
+ static MATH_METHODS = ['abs', 'sin', 'cos', 'tan', 'log', 'exp', 'pow']
11
+ static UTIL_METHODS = {
12
+ base: [
13
+ 'isNaN',
14
+ 'isFinite',
15
+ 'isArray',
16
+ 'isFloat',
17
+ 'isInteger',
18
+ 'isBoolean',
19
+ 'isString',
20
+ 'isNumber',
21
+ 'isPlainObject',
22
+ 'isDate',
23
+ 'isEmpty',
24
+ 'isNull',
25
+ 'isMatch',
26
+ 'isEqual',
27
+ 'getSize',
28
+ 'first',
29
+ 'last'
30
+ ],
31
+ object: ['has', 'get', 'assign', 'merge', 'pick', 'omit'],
32
+ array: [
33
+ 'slice',
34
+ 'indexOf',
35
+ 'includes',
36
+ 'includeArrays',
37
+ 'sum',
38
+ 'mean',
39
+ 'zip',
40
+ 'unzip',
41
+ 'uniq',
42
+ 'union',
43
+ 'flatten',
44
+ 'pluck',
45
+ 'invoke',
46
+ 'orderBy',
47
+ 'groupBy',
48
+ 'countBy',
49
+ 'toArrayTree',
50
+ 'toTreeArray'
51
+ ],
52
+ date: ['toDateString', 'getYearDiff', 'getMonthDiff', 'getDayDiff', 'getHourDiff', 'getMinuteDiff', 'getSecondDiff', 'getYearDay', 'getYearWeek', 'getMonthWeek', 'getDayOfMonth'],
53
+ number: ['random', 'min', 'max', 'ceil', 'floor', 'round', 'truncate', 'toFixed', 'toNumber', 'toNumberString', 'toInteger', 'add', 'subtract', 'multiply', 'divide'],
54
+ string: ['toValueString', 'trim', 'trimLeft', 'trimRight', 'camelCase', 'kebabCase', 'startsWith', 'endsWith']
55
+ }
66
56
 
67
- // ========== 核心修复1:正则新增字符串字面量匹配(优先识别) ==========
68
- // 改动点:
69
- // 1. 最前面新增 ("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*') 匹配单/双引号字符串
70
- // 2. 处理转义字符(如 \' / \"),兼容带转义的字符串
71
- const TOKEN_REG = /\s*((?:"[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')|(if|else)|[a-zA-Z_$][a-zA-Z0-9_$]*|[\d.]+|,|==|===|!=|!==|<=|>=|<<|>>|>>>|\|\||&&|\*\*|[-+*/%^&|<>!=?:()\[\]])\s*/g
57
+ // TOKEN_REG匹配
58
+ static TOKEN_REG = /\s*(?:#JSON#([\s\S]*?)#JSON#|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|if|else|[a-zA-Z_$][a-zA-Z0-9_$]*|\d+\.?\d*|\.\d+|===|!==|==|!=|<=|>=|\|\||&&|[,.+\-*/%^&|<>!?()\[\]:])\s*/g
72
59
 
73
- // 运算符映射(原有逻辑不变)
74
- const UNARY_OP_MAP = { '+': 'unary +', '-': 'unary -', '!': '!', '~': '~' }
75
- const EQUALITY_OP_MAP = { '==': true, '!=': true, '===': true, '!==': true }
76
- const RELATIONAL_OP_MAP = { '<': true, '>': true, '<=': true, '>=': true }
77
- const SHIFT_OP_MAP = { '<<': true, '>>': true, '>>>': true }
78
- const ARITHMETIC_OP_MAP = { '+': true, '-': true, '*': true, '/': true, '%': true }
60
+ // 精简版OP_MAP:移除unary、shift,仅保留核心运算
61
+ static OP_MAP = {
62
+ equality: { '==': true, '!=': true, '===': true, '!==': true },
63
+ relational: { '<': true, '>': true, '<=': true, '>=': true },
64
+ arithmetic: { '+': true, '-': true, '*': true, '/': true, '%': true }
65
+ }
79
66
 
80
- /**
81
- * 表达式求值器(最终完整版:支持if-else+换行+字符串字面量)
82
- */
83
- class ExprEvaluator {
84
67
  constructor() {
85
- this.functions = {}
86
- this.operators = {}
87
- this._isInited = false
88
- this._initParseFunctions()
68
+ this.functions = {} // 工具函数映射
69
+ this.operators = {} // 运算符执行函数
70
+ this._isInited = false // 懒加载标记
71
+ this._cachedUtilMethods = null // 缓存扁平化的工具方法列表
72
+
73
+ // 绑定this
74
+ this.parsePrimary = this.parsePrimary.bind(this)
75
+ this.parseFunctionArgs = this.parseFunctionArgs.bind(this)
76
+ }
77
+
78
+ /**
79
+ * 类型归一化 - 为比较运算统一值类型(日期→数字→字符串)
80
+ * @private
81
+ * @param {any} value 待归一化的值
82
+ * @returns {number|string} 归一化后的值(日期转时间戳,数字串转数字,其余转字符串)
83
+ */
84
+ _normalizeCompareValue(value) {
85
+ // 1. 处理日期类型(日期对象/可解析的日期字符串)
86
+ if (value instanceof Date) {
87
+ return value.getTime() // 转时间戳(数字)
88
+ }
89
+ if (typeof value === 'string') {
90
+ // 尝试解析为日期(支持YYYY-MM-DD、YYYY/MM/DD、带时分秒的格式)
91
+ const date = new Date(value)
92
+ if (!isNaN(date.getTime())) {
93
+ return date.getTime()
94
+ }
95
+ // 尝试解析为数字(如"123"、"45.6")
96
+ const num = Number(value)
97
+ if (!isNaN(num)) {
98
+ return num
99
+ }
100
+ // 普通字符串(去空格后比较)
101
+ return value.trim()
102
+ }
103
+ // 2. 处理数字类型(含数字对象)
104
+ if (typeof value === 'number' || (value && typeof value === 'object' && value.constructor === Number)) {
105
+ return Number(value)
106
+ }
107
+ // 3. 其他类型(布尔值、null、undefined等)转字符串
108
+ return String(value)
89
109
  }
90
110
 
91
111
  /**
92
- * 懒加载初始化(原有逻辑不变)
112
+ * 懒加载初始化 - 仅首次调用evaluate时执行
113
+ * @private
93
114
  */
94
115
  _lazyInit() {
95
116
  if (this._isInited) return
96
117
  const utils = utilsModule.__esModule ? utilsModule.default : utilsModule
97
118
 
98
- const funcs = {}
99
- for (const method of MATH_METHODS) funcs[method] = Math[method]
119
+ // 1. 初始化数学函数(仅保留核心)
120
+ ExprEvaluator.MATH_METHODS.forEach(method => {
121
+ this.functions[method] = Math[method]
122
+ })
100
123
 
101
- // 提前扁平化方法列表(轻量化优化)
102
- const ALL_UTIL_METHODS = Object.values(UTIL_METHODS).flat()
103
- for (const method of ALL_UTIL_METHODS) {
124
+ // 2. 初始化工具函数
125
+ this._cachedUtilMethods = Object.values(ExprEvaluator.UTIL_METHODS).flat()
126
+ this._cachedUtilMethods.forEach(method => {
104
127
  if (typeof utils[method] === 'function') {
105
- // sum函数兼容包装(解决sum(1,2,3)返回0的问题)
106
- if (method === 'sum') {
107
- funcs[method] = (...args) => {
108
- if (args.length === 1 && Array.isArray(args[0])) {
109
- return utils.sum(args[0])
110
- }
111
- return utils.sum(args)
112
- }
113
- } else {
114
- funcs[method] = utils[method]
115
- }
128
+ this.functions[method] = utils[method]
116
129
  } else if (process.env.NODE_ENV === 'development') {
117
- console.warn(`[ExprEvaluator] utils方法不存在: ${method}`)
130
+ console.warn(`[ExprEvaluator] 工具方法不存在: ${method}`)
118
131
  }
119
- }
120
- this.functions = funcs
132
+ })
121
133
 
122
- // 初始化运算符(原有逻辑不变)
134
+ // 3. 初始化核心运算符
123
135
  this.operators = {
124
136
  '?:': { precedence: 1, associativity: 'right', ternary: true, fn: (c, a, b) => (c ? a : b) },
125
137
  '||': { precedence: 1, associativity: 'left', fn: (a, b) => a || b },
@@ -129,23 +141,17 @@ class ExprEvaluator {
129
141
  '&': { precedence: 5, associativity: 'left', fn: (a, b) => a & b },
130
142
  '==': { precedence: 6, associativity: 'left', fn: (a, b) => a == b },
131
143
  '!=': { precedence: 6, associativity: 'left', fn: (a, b) => a != b },
132
- '===': { precedence: 6, associativity: 'left', fn: (a, b) => a === b },
133
144
  '!==': { precedence: 6, associativity: 'left', fn: (a, b) => a !== b },
134
- '<': { precedence: 7, associativity: 'left', fn: (a, b) => a < b },
135
- '>': { precedence: 7, associativity: 'left', fn: (a, b) => a > b },
136
- '<=': { precedence: 7, associativity: 'left', fn: (a, b) => a <= b },
137
- '>=': { precedence: 7, associativity: 'left', fn: (a, b) => a >= b },
138
- '<<': { precedence: 8, associativity: 'left', fn: (a, b) => a << b },
139
- '>>': { precedence: 8, associativity: 'left', fn: (a, b) => a >> b },
140
- '>>>': { precedence: 8, associativity: 'left', fn: (a, b) => a >>> b },
145
+ '<': { precedence: 7, associativity: 'left', fn: (a, b) => this._normalizeCompareValue(a) < this._normalizeCompareValue(b) },
146
+ '>': { precedence: 7, associativity: 'left', fn: (a, b) => this._normalizeCompareValue(a) > this._normalizeCompareValue(b) },
147
+ '<=': { precedence: 7, associativity: 'left', fn: (a, b) => this._normalizeCompareValue(a) <= this._normalizeCompareValue(b) },
148
+ '>=': { precedence: 7, associativity: 'left', fn: (a, b) => this._normalizeCompareValue(a) >= this._normalizeCompareValue(b) },
149
+ '===': { precedence: 6, associativity: 'left', fn: utils.isEqual },
141
150
  '+': { precedence: 9, associativity: 'left', fn: utils.add },
142
151
  '-': { precedence: 9, associativity: 'left', fn: utils.subtract },
143
152
  '*': { precedence: 10, associativity: 'left', fn: utils.multiply },
144
153
  '/': { precedence: 10, associativity: 'left', fn: utils.divide },
145
154
  '%': { precedence: 10, associativity: 'left', fn: (a, b) => a % b },
146
- '**': { precedence: 11, associativity: 'right', fn: Math.pow },
147
- 'unary +': { precedence: 12, associativity: 'right', prefix: true, fn: a => +a },
148
- 'unary -': { precedence: 12, associativity: 'right', prefix: true, fn: a => -a },
149
155
  '!': { precedence: 12, associativity: 'right', prefix: true, fn: a => !a },
150
156
  '~': { precedence: 12, associativity: 'right', prefix: true, fn: a => ~a }
151
157
  }
@@ -154,332 +160,346 @@ class ExprEvaluator {
154
160
  }
155
161
 
156
162
  /**
157
- * 初始化解析函数(原有逻辑不变,仅扩展parsePrimary支持字符串)
163
+ * 解析函数参数
164
+ * @private
165
+ * @param {string} closeChar 闭合字符
166
+ * @returns {Array} 参数列表
158
167
  */
159
- _initParseFunctions() {
160
- let tokens, pos
161
- const self = this
162
-
163
- // 函数参数解析(不变)
164
- const parseFunctionArgs = closeChar => {
165
- const args = []
166
- pos++ // 跳过左括号 ( 或 [
167
-
168
- if (tokens[pos] !== closeChar) {
169
- args.push(parseTernary())
170
- while (tokens[pos] === ',') {
171
- pos++ // 跳过逗号
172
- if (tokens[pos] === closeChar) break // 处理多余逗号:sum(1,)
173
- args.push(parseTernary())
174
- }
168
+ parseFunctionArgs(closeChar) {
169
+ const args = []
170
+ this.pos++
171
+
172
+ if (this.tokens[this.pos] !== closeChar) {
173
+ args.push(this.parseTernary())
174
+ while (this.tokens[this.pos] === ',') {
175
+ this.pos++
176
+ if (this.tokens[this.pos] === closeChar) break
177
+ args.push(this.parseTernary())
175
178
  }
179
+ }
176
180
 
177
- if (tokens[pos] !== closeChar) {
178
- throw new Error(`函数参数未闭合:缺少 ${closeChar}(当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
179
- }
180
- pos++ // 跳过右括号 ) 或 ]
181
- return args
181
+ if (this.tokens[this.pos] !== closeChar) {
182
+ throw new Error(`函数参数未闭合:缺少 ${closeChar}(当前Token:${this.tokens[this.pos] || '空'}, 位置:${this.pos})`)
182
183
  }
184
+ this.pos++
185
+ return args
186
+ }
183
187
 
184
- // 数组字面量解析(不变)
185
- const parseArrayLiteral = () => {
186
- const startPos = pos
187
- pos++ // 跳过左中括号 [
188
- const arr = []
189
-
190
- if (tokens[pos] !== ']') {
191
- arr.push(parseTernary())
192
- while (tokens[pos] === ',') {
193
- pos++ // 跳过逗号
194
- if (tokens[pos] === ']') break // 处理多余逗号:[1,]
195
- arr.push(parseTernary())
196
- }
188
+ /**
189
+ * 解析数组字面量
190
+ * @private
191
+ * @returns {Array} 解析后的数组
192
+ */
193
+ parseArrayLiteral() {
194
+ const startPos = this.pos
195
+ this.pos++
196
+ const arr = []
197
+
198
+ if (this.tokens[this.pos] !== ']') {
199
+ arr.push(this.parseTernary())
200
+ while (this.tokens[this.pos] === ',') {
201
+ this.pos++
202
+ if (this.tokens[this.pos] === ']') break
203
+ arr.push(this.parseTernary())
197
204
  }
205
+ }
198
206
 
199
- if (tokens[pos] !== ']') {
200
- throw new Error(`数组未闭合:在位置 ${startPos} 开始的数组缺少 ](当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
201
- }
202
- pos++ // 跳过右中括号 ]
203
- return arr
207
+ if (this.tokens[this.pos] !== ']') {
208
+ throw new Error(`数组未闭合:在位置 ${startPos} 开始的数组缺少 ](当前Token:${this.tokens[this.pos] || '空'}, 位置:${this.pos})`)
204
209
  }
210
+ this.pos++
211
+ return arr
212
+ }
205
213
 
206
- // ========== 核心修复2:扩展parsePrimary支持字符串字面量 ==========
207
- const parsePrimary = () => {
208
- if (pos >= tokens.length) throw new Error('表达式意外结束')
209
- const currentToken = tokens[pos]
214
+ /**
215
+ * 校验JSON格式
216
+ * @private
217
+ * @param {string} jsonStr JSON字符串
218
+ * @returns {boolean} 是否有效
219
+ */
220
+ _isValidJson(jsonStr) {
221
+ if (!jsonStr) return false
222
+ try {
223
+ JSON.parse(jsonStr)
224
+ return true
225
+ } catch {
226
+ return false
227
+ }
228
+ }
210
229
 
211
- // 新增:字符串字面量解析(单/双引号)
212
- if (typeof currentToken === 'string' && (currentToken.startsWith('"') || currentToken.startsWith("'"))) {
213
- const strValue = currentToken.slice(1, -1) // 去掉首尾引号
214
- pos++
215
- return strValue // 返回纯字符串内容
230
+ /**
231
+ * 解析基础Token
232
+ * @private
233
+ * @returns {any} 解析后的值
234
+ */
235
+ parsePrimary() {
236
+ if (this.pos >= this.tokens.length) throw new Error('表达式意外结束')
237
+ const currentToken = this.tokens[this.pos]
238
+
239
+ // 解析双标记JSON
240
+ if (typeof currentToken === 'string' && currentToken.includes('#JSON#')) {
241
+ this.pos++
242
+ const jsonMatch = currentToken.match(/#JSON#([\s\S]*?)#JSON#/)
243
+ if (!jsonMatch || !this._isValidJson(jsonMatch[1])) {
244
+ throw new Error(`JSON解析失败:${jsonMatch?.[1] || currentToken}(原Token:${currentToken})`)
216
245
  }
246
+ return JSON.parse(jsonMatch[1])
247
+ }
217
248
 
218
- // 数组字面量
219
- if (currentToken === '[') {
220
- return parseArrayLiteral()
221
- }
249
+ // 解析字符串字面量
250
+ if (typeof currentToken === 'string' && (currentToken.startsWith('"') || currentToken.startsWith("'"))) {
251
+ this.pos++
252
+ return currentToken.slice(1, -1)
253
+ }
222
254
 
223
- // 括号表达式 (a + b)
224
- if (currentToken === '(') {
225
- pos++
226
- const res = parseTernary()
227
- if (tokens[pos] !== ')') {
228
- throw new Error(`括号未闭合:缺少 )(当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
229
- }
230
- pos++
231
- return res
232
- }
255
+ // 解析数组字面量
256
+ if (currentToken === '[') return this.parseArrayLiteral()
233
257
 
234
- // 函数调用(支持 () 和 [] 两种写法)
235
- if (typeof currentToken === 'string' && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(currentToken)) {
236
- const funcName = currentToken
237
- pos++
238
-
239
- // 圆括号调用:sum(1,2,3)
240
- if (tokens[pos] === '(') {
241
- const args = parseFunctionArgs(')')
242
- if (!self.functions[funcName]) throw new Error(`未定义函数: ${funcName}`)
243
- return self.functions[funcName](...args)
244
- }
245
-
246
- // 中括号调用:sum[1,2,3]
247
- if (tokens[pos] === '[') {
248
- const args = parseFunctionArgs(']')
249
- if (!self.functions[funcName]) throw new Error(`未定义函数: ${funcName}`)
250
- return self.functions[funcName](...args)
251
- }
252
-
253
- throw new Error(`不支持变量: ${funcName}(仅支持函数调用)`)
258
+ // 解析括号表达式
259
+ if (currentToken === '(') {
260
+ this.pos++
261
+ const res = this.parseTernary()
262
+ if (this.tokens[this.pos] !== ')') {
263
+ throw new Error(`括号未闭合:缺少 )(当前Token:${this.tokens[this.pos] || '空'}, 位置:${this.pos})`)
254
264
  }
265
+ this.pos++
266
+ return res
267
+ }
255
268
 
256
- // 数字
257
- if (typeof currentToken === 'number') {
258
- const num = currentToken
259
- pos++
260
- return num
269
+ // 解析函数调用
270
+ if (typeof currentToken === 'string' && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(currentToken)) {
271
+ const funcName = currentToken
272
+ this.pos++
273
+
274
+ const callType = this.tokens[this.pos]
275
+ if (callType === '(' || callType === '[') {
276
+ const args = this.parseFunctionArgs(callType === '(' ? ')' : ']')
277
+ if (!this.functions[funcName]) throw new Error(`未定义函数: ${funcName}`)
278
+ return this.functions[funcName](...args)
261
279
  }
262
280
 
263
- throw new Error(`无效Token: ${currentToken}(位置:${pos})`)
281
+ throw new Error(`不支持变量: ${funcName}(仅支持函数调用和字面量)`)
264
282
  }
265
283
 
266
- // 一元运算符解析(不变)
267
- const parseUnary = () => {
268
- const token = tokens[pos]
269
- if (UNARY_OP_MAP[token]) {
270
- const opName = UNARY_OP_MAP[token]
271
- pos++
272
- return self.operators[opName].fn(parsePrimary())
273
- }
274
- return parsePrimary()
284
+ // 解析数字
285
+ if (typeof currentToken === 'number' || /^\d+\.?\d*|\.\d+$/.test(currentToken)) {
286
+ this.pos++
287
+ return Number(currentToken)
275
288
  }
276
289
 
277
- // 幂运算解析(不变)
278
- const parseExponential = () => {
279
- let res = parseUnary()
280
- while (tokens[pos] === '**') {
281
- pos++
282
- res = self.operators['**'].fn(res, parseUnary())
283
- }
284
- return res
285
- }
290
+ throw new Error(`无效Token: ${currentToken}(位置:${this.pos})`)
291
+ }
286
292
 
287
- // 乘除取模解析(不变)
288
- const parseMultiplicative = () => {
289
- let res = parseExponential()
290
- while (ARITHMETIC_OP_MAP[tokens[pos]] && ['*', '/', '%'].includes(tokens[pos])) {
291
- const op = tokens[pos]
292
- pos++
293
- res = self.operators[op].fn(res, parseExponential())
294
- }
295
- return res
293
+ // ========== 精简后的解析链(仅保留核心运算) ==========
294
+ /**
295
+ * 解析乘/除/取模运算
296
+ * @private
297
+ * @returns {number} 运算结果
298
+ */
299
+ parseMultiplicative() {
300
+ let res = this.parsePrimary()
301
+ while (ExprEvaluator.OP_MAP.arithmetic[this.tokens[this.pos]] && ['*', '/', '%'].includes(this.tokens[this.pos])) {
302
+ const op = this.tokens[this.pos]
303
+ this.pos++
304
+ res = this.operators[op].fn(res, this.parsePrimary())
296
305
  }
306
+ return res
307
+ }
297
308
 
298
- // 加减解析(不变)
299
- const parseAdditive = () => {
300
- let res = parseMultiplicative()
301
- while (ARITHMETIC_OP_MAP[tokens[pos]] && ['+', '-'].includes(tokens[pos])) {
302
- const op = tokens[pos]
303
- pos++
304
- res = self.operators[op].fn(res, parseMultiplicative())
305
- }
306
- return res
309
+ /**
310
+ * 解析加/减运算
311
+ * @private
312
+ * @returns {number} 运算结果
313
+ */
314
+ parseAdditive() {
315
+ let res = this.parseMultiplicative()
316
+ while (ExprEvaluator.OP_MAP.arithmetic[this.tokens[this.pos]] && ['+', '-'].includes(this.tokens[this.pos])) {
317
+ const op = this.tokens[this.pos]
318
+ this.pos++
319
+ res = this.operators[op].fn(res, this.parseMultiplicative())
307
320
  }
321
+ return res
322
+ }
308
323
 
309
- // 位移解析(不变)
310
- const parseShift = () => {
311
- let res = parseAdditive()
312
- while (SHIFT_OP_MAP[tokens[pos]]) {
313
- const op = tokens[pos]
314
- pos++
315
- res = self.operators[op].fn(res, parseAdditive())
316
- }
317
- return res
324
+ /**
325
+ * 解析关系运算(<、>、<=、>=)
326
+ * @private
327
+ * @returns {boolean} 运算结果
328
+ */
329
+ parseRelational() {
330
+ let res = this.parseAdditive()
331
+ while (ExprEvaluator.OP_MAP.relational[this.tokens[this.pos]]) {
332
+ const op = this.tokens[this.pos]
333
+ this.pos++
334
+ res = this.operators[op].fn(res, this.parseAdditive())
318
335
  }
336
+ return res
337
+ }
319
338
 
320
- // 关系运算符解析(不变)
321
- const parseRelational = () => {
322
- let res = parseShift()
323
- while (RELATIONAL_OP_MAP[tokens[pos]]) {
324
- const op = tokens[pos]
325
- pos++
326
- res = self.operators[op].fn(res, parseShift())
327
- }
328
- return res
339
+ /**
340
+ * 解析相等运算(==、!=、===、!==)
341
+ * @private
342
+ * @returns {boolean} 运算结果
343
+ */
344
+ parseEquality() {
345
+ let res = this.parseRelational()
346
+ while (ExprEvaluator.OP_MAP.equality[this.tokens[this.pos]]) {
347
+ const op = this.tokens[this.pos]
348
+ this.pos++
349
+ res = this.operators[op].fn(res, this.parseRelational())
329
350
  }
351
+ return res
352
+ }
330
353
 
331
- // 相等性解析(不变)
332
- const parseEquality = () => {
333
- let res = parseRelational()
334
- while (EQUALITY_OP_MAP[tokens[pos]]) {
335
- const op = tokens[pos]
336
- pos++
337
- res = self.operators[op].fn(res, parseRelational())
338
- }
339
- return res
354
+ /**
355
+ * 解析按位与运算
356
+ * @private
357
+ * @returns {number} 运算结果
358
+ */
359
+ parseBitwiseAnd() {
360
+ let res = this.parseEquality()
361
+ while (this.tokens[this.pos] === '&') {
362
+ this.pos++
363
+ res = this.operators['&'].fn(res, this.parseEquality())
340
364
  }
365
+ return res
366
+ }
341
367
 
342
- // 按位与解析(不变)
343
- const parseBitwiseAnd = () => {
344
- let res = parseEquality()
345
- while (tokens[pos] === '&') {
346
- pos++
347
- res = self.operators['&'].fn(res, parseEquality())
348
- }
349
- return res
368
+ /**
369
+ * 解析按位异或运算
370
+ * @private
371
+ * @returns {number} 运算结果
372
+ */
373
+ parseBitwiseXor() {
374
+ let res = this.parseBitwiseAnd()
375
+ while (this.tokens[this.pos] === '^') {
376
+ this.pos++
377
+ res = this.operators['^'].fn(res, this.parseBitwiseAnd())
350
378
  }
379
+ return res
380
+ }
351
381
 
352
- // 按位异或解析(不变)
353
- const parseBitwiseXor = () => {
354
- let res = parseBitwiseAnd()
355
- while (tokens[pos] === '^') {
356
- pos++
357
- res = self.operators['^'].fn(res, parseBitwiseAnd())
358
- }
359
- return res
382
+ /**
383
+ * 解析按位或运算
384
+ * @private
385
+ * @returns {number} 运算结果
386
+ */
387
+ parseBitwiseOr() {
388
+ let res = this.parseBitwiseXor()
389
+ while (this.tokens[this.pos] === '|') {
390
+ this.pos++
391
+ res = this.operators['|'].fn(res, this.parseBitwiseXor())
360
392
  }
393
+ return res
394
+ }
361
395
 
362
- // 按位或解析(不变)
363
- const parseBitwiseOr = () => {
364
- let res = parseBitwiseXor()
365
- while (tokens[pos] === '|') {
366
- pos++
367
- res = self.operators['|'].fn(res, parseBitwiseXor())
368
- }
369
- return res
396
+ /**
397
+ * 解析逻辑与运算
398
+ * @private
399
+ * @returns {boolean} 运算结果
400
+ */
401
+ parseLogicalAnd() {
402
+ let res = this.parseBitwiseOr()
403
+ while (this.tokens[this.pos] === '&&') {
404
+ this.pos++
405
+ res = this.operators['&&'].fn(res, this.parseBitwiseOr())
370
406
  }
407
+ return res
408
+ }
371
409
 
372
- // 逻辑与解析(不变)
373
- const parseLogicalAnd = () => {
374
- let res = parseBitwiseOr()
375
- while (tokens[pos] === '&&') {
376
- pos++
377
- res = self.operators['&&'].fn(res, parseBitwiseOr())
378
- }
379
- return res
410
+ /**
411
+ * 解析逻辑或运算
412
+ * @private
413
+ * @returns {boolean} 运算结果
414
+ */
415
+ parseLogicalOr() {
416
+ let res = this.parseLogicalAnd()
417
+ while (this.tokens[this.pos] === '||') {
418
+ this.pos++
419
+ res = this.operators['||'].fn(res, this.parseLogicalAnd())
380
420
  }
421
+ return res
422
+ }
381
423
 
382
- // 逻辑或解析(不变)
383
- const parseLogicalOr = () => {
384
- let res = parseLogicalAnd()
385
- while (tokens[pos] === '||') {
386
- pos++
387
- res = self.operators['||'].fn(res, parseLogicalAnd())
388
- }
389
- return res
424
+ /**
425
+ * 解析三元运算核心逻辑
426
+ * @private
427
+ * @returns {any} 运算结果
428
+ */
429
+ parseTernaryCore() {
430
+ let res = this.parseLogicalOr()
431
+ if (this.tokens[this.pos] === '?') {
432
+ this.pos++
433
+ const a = this.parseTernaryCore()
434
+ if (this.tokens[this.pos] !== ':') throw new Error('三元运算符缺少冒号')
435
+ this.pos++
436
+ const b = this.parseTernaryCore()
437
+ res = this.operators['?:'].fn(res, a, b)
390
438
  }
439
+ return res
440
+ }
391
441
 
392
- // if-else 解析(不变)
393
- const parseIfElse = () => {
394
- // 第一步:判断当前Token是否是if
395
- if (tokens[pos] !== 'if') {
396
- // 不是if,走原有三元运算符解析逻辑
397
- return parseTernaryCore()
398
- }
399
-
400
- // 第二步:解析if关键字
401
- pos++ // 跳过if关键字
402
-
403
- // 第三步:解析if后面的括号条件,比如 if (2>1) 中的 (2>1)
404
- if (tokens[pos] !== '(') {
405
- throw new Error(`if语法错误:缺少左括号(当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
406
- }
407
- pos++ // 跳过左括号(
408
- const condition = parseTernaryCore() // 解析条件表达式(支持所有原有运算符)
409
- if (tokens[pos] !== ')') {
410
- throw new Error(`if语法错误:缺少右括号(当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
411
- }
412
- pos++ // 跳过右括号)
413
-
414
- // 第四步:解析if的结果(then分支),比如 if (2>1) 9 中的 9
415
- const thenValue = parseTernaryCore()
416
-
417
- // 第五步:解析else关键字
418
- if (tokens[pos] !== 'else') {
419
- throw new Error(`if语法错误:缺少else关键字(当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
420
- }
421
- pos++ // 跳过else关键字
422
-
423
- // 第六步:解析else的结果(else分支),比如 if (2>1)9 else10 中的10
424
- const elseValue = parseTernaryCore()
442
+ /**
443
+ * 解析if/else和三元运算
444
+ * @private
445
+ * @returns {any} 运算结果
446
+ */
447
+ parseIfElse() {
448
+ if (this.tokens[this.pos] !== 'if') return this.parseTernaryCore()
449
+ this.pos++
425
450
 
426
- // 第七步:返回最终结果(条件为true返回thenValue,否则返回elseValue)
427
- return condition ? thenValue : elseValue
451
+ if (this.tokens[this.pos] !== '(') {
452
+ throw new Error(`if语法错误:缺少左括号(当前Token:${this.tokens[this.pos] || '空'}, 位置:${this.pos})`)
428
453
  }
454
+ this.pos++
429
455
 
430
- // 三元运算符核心解析(不变)
431
- const parseTernaryCore = () => {
432
- let res = parseLogicalOr()
433
- if (tokens[pos] === '?') {
434
- pos++
435
- const a = parseTernaryCore()
436
- if (tokens[pos] !== ':') throw new Error('三元运算符缺少冒号')
437
- pos++
438
- const b = parseTernaryCore()
439
- res = self.operators['?:'].fn(res, a, b)
440
- }
441
- return res
456
+ const condition = this.parseTernaryCore()
457
+ if (this.tokens[this.pos] !== ')') {
458
+ throw new Error(`if语法错误:缺少右括号(当前Token:${this.tokens[this.pos] || '空'}, 位置:${this.pos})`)
442
459
  }
460
+ this.pos++
443
461
 
444
- // 三元运算符入口(不变)
445
- const parseTernary = () => {
446
- // 先解析if-else,再解析三元运算符
447
- return parseIfElse()
462
+ const thenValue = this.parseTernaryCore()
463
+ if (this.tokens[this.pos] !== 'else') {
464
+ throw new Error(`if语法错误:缺少else关键字(当前Token:${this.tokens[this.pos] || '空'}, 位置:${this.pos})`)
448
465
  }
466
+ this.pos++
449
467
 
450
- // 解析入口(不变)
451
- this._parseEntry = inputTokens => {
452
- tokens = inputTokens
453
- pos = 0
454
- const result = parseTernary()
455
- if (pos < tokens.length) {
456
- throw new Error(`表达式多余内容: ${tokens.slice(pos).join('')}(位置:${pos})`)
457
- }
458
- return result
459
- }
468
+ const elseValue = this.parseTernaryCore()
469
+ return condition ? thenValue : elseValue
470
+ }
471
+
472
+ /**
473
+ * 入口解析方法
474
+ * @private
475
+ * @returns {any} 运算结果
476
+ */
477
+ parseTernary() {
478
+ return this.parseIfElse()
460
479
  }
461
480
 
462
481
  /**
463
- * 分词器(扩展支持字符串字面量,原有逻辑不变)
482
+ * 分词处理
483
+ * @private
484
+ * @param {string} expr 表达式
485
+ * @returns {Array} 分词结果
464
486
  */
465
487
  _tokenize(expr) {
466
488
  const tokens = []
467
- TOKEN_REG.lastIndex = 0
489
+ const reg = new RegExp(ExprEvaluator.TOKEN_REG.source, 'g')
468
490
  let match
469
- const utils = utilsModule.__esModule ? utilsModule.default : utilsModule
470
491
 
471
- while ((match = TOKEN_REG.exec(expr)) !== null) {
472
- const token = match[1]
473
- // 1. 字符串字面量:直接保留(后续在parsePrimary中去掉引号)
474
- if (token.startsWith('"') || token.startsWith("'")) {
492
+ while ((match = reg.exec(expr)) !== null) {
493
+ const token = match[0].trim()
494
+ if (!token) continue
495
+
496
+ if (token.includes('#JSON#')) {
475
497
  tokens.push(token)
476
- }
477
- // 2. 数字转换(兼容整数/小数)
478
- else if (/^\d+(\.\d+)?$/.test(token)) {
479
- tokens.push(utils.toNumber(token))
480
- }
481
- // 3. 其他Token(关键字/运算符/标识符):原样保留
482
- else {
498
+ } else if (token.startsWith('"') || token.startsWith("'")) {
499
+ tokens.push(token)
500
+ } else if (/^\d+\.?\d*|\.\d+$/.test(token)) {
501
+ tokens.push(Number(token))
502
+ } else {
483
503
  tokens.push(token)
484
504
  }
485
505
  }
@@ -489,22 +509,28 @@ class ExprEvaluator {
489
509
  }
490
510
 
491
511
  /**
492
- * 对外核心方法(兼容换行符,原有逻辑不变)
512
+ * 对外求值方法
513
+ * @public
514
+ * @param {string} expr 表达式
515
+ * @returns {any} 结果
493
516
  */
494
517
  evaluate(expr) {
495
518
  this._lazyInit()
496
519
 
497
520
  if (typeof expr !== 'string') throw new Error('表达式必须是字符串')
521
+ const normalizedExpr = expr.replace(/[\n\r]/g, ' ').trim()
522
+ if (!normalizedExpr) throw new Error('表达式不能为空')
498
523
 
499
- // 替换所有换行符(\n/\r/\r\n)为空格,兼容换行
500
- const normalizedExpr = expr.replace(/[\n\r]/g, ' ')
501
- const trimmed = normalizedExpr.trim()
524
+ try {
525
+ this.tokens = this._tokenize(normalizedExpr)
526
+ this.pos = 0
527
+ const result = this.parseTernary()
502
528
 
503
- if (!trimmed) throw new Error('表达式不能为空')
529
+ if (this.pos < this.tokens.length) {
530
+ throw new Error(`表达式多余内容: ${this.tokens.slice(this.pos).join('')}(位置:${this.pos})`)
531
+ }
504
532
 
505
- try {
506
- const tokens = this._tokenize(trimmed)
507
- return this._parseEntry(tokens)
533
+ return result
508
534
  } catch (error) {
509
535
  throw new Error(`表达式计算失败: ${error.message}(原表达式:${expr})`)
510
536
  }
package/packages/index.js CHANGED
@@ -2,4 +2,6 @@ import http from './api'
2
2
  import utils from './utils'
3
3
  import expr from './expr'
4
4
 
5
+ export { http, utils, expr }
6
+
5
7
  export default { http, utils, expr }
@@ -6,6 +6,7 @@ import array from './array'
6
6
  import number from './number'
7
7
  import string from './string'
8
8
  import boolean from './boolean'
9
+ import time from './time'
9
10
  import color from './color'
10
11
  import dom from './dom'
11
12
  import other from './other'
@@ -17,6 +18,7 @@ XEUtils.mixin({
17
18
  ...number,
18
19
  ...string,
19
20
  ...boolean,
21
+ ...time,
20
22
  ...color,
21
23
  ...dom,
22
24
  ...other,
@@ -15,7 +15,7 @@ export default {
15
15
  calculate(formula, data) {
16
16
  if (!formula) return ''
17
17
  if (data && XEUtils.isPlainObject(data)) {
18
- formula = stringUtil.format(formula, data, true)
18
+ formula = stringUtil.format(formula, data)
19
19
  }
20
20
  return formulaParser.evaluate(formula)
21
21
  }
@@ -11,27 +11,6 @@ const endStrs = ['Id', '_id']
11
11
  const dateReplaceMap = { YYYY: 'yyyy', DD: 'dd', hh: 'HH' }
12
12
 
13
13
  export default {
14
- toDateDiffText(date) {
15
- let currDate = 1544407800000 // '2018-12-10 10:10:00'
16
- let dateDiff = XEUtils.getDateDiff(date, currDate)
17
- if (dateDiff.done) {
18
- if (dateDiff.time < 10000) {
19
- return '刚刚'
20
- } else if (dateDiff.time < 60000) {
21
- return `${dateDiff.ss}秒之前`
22
- } else if (dateDiff.time < 360000) {
23
- return `${dateDiff.mm}分钟之前`
24
- } else if (dateDiff.time < 86400000) {
25
- return `${dateDiff.HH}小时之前`
26
- } else if (dateDiff.time < 2592000000) {
27
- return `${dateDiff.dd}天之前`
28
- } else if (dateDiff.time < 31536000000) {
29
- return `${dateDiff.MM}个月之前`
30
- }
31
- return `${dateDiff.yyyy}年之前`
32
- }
33
- return '错误类型'
34
- },
35
14
  ioToFile(io, fileName = 'download', type = 'blob') {
36
15
  return new Promise((resolve, reject) => {
37
16
  try {
@@ -4,19 +4,26 @@ import XEUtils from 'xe-utils'
4
4
  function getFormatKeys(format) {
5
5
  let regR = new RegExp('({[a-zA-Z0-9_.]*})', 'ig')
6
6
  let formatStr = String(format)
7
- return (formatStr.match(regR) || []).map((key, keyIndex) => {
8
- return key.replace(/{|}/gi, '')
9
- })
7
+ return (formatStr.match(regR) || []).map((key, keyIndex) => key.replace(/{|}/gi, ''))
10
8
  }
11
9
 
12
10
  // 格式化数据结构
13
- function format(format, data, toNumber) {
11
+ function format(format, data) {
14
12
  let keys = getFormatKeys(format)
15
13
  let formatStr = String(format)
16
14
  keys.map((key, indexkey) => {
17
15
  let value = XEUtils.get(data, key) || ''
18
- if (toNumber) value = XEUtils.toNumber(value)
19
- formatStr = formatStr.replace(new RegExp('{' + key + '}', 'ig'), value)
16
+ let replaceValue = ''
17
+ if (value === undefined || value === null) {
18
+ replaceValue = '""'
19
+ } else if (typeof value === 'object' || Array.isArray(value)) {
20
+ replaceValue = `#JSON#${JSON.stringify(value)}#JSON#`
21
+ } else if (typeof value === 'string') {
22
+ replaceValue = `"${value}"`
23
+ } else {
24
+ replaceValue = value
25
+ }
26
+ formatStr = formatStr.replace(new RegExp('{' + key + '}', 'ig'), replaceValue)
20
27
  })
21
28
  return formatStr
22
29
  }
@@ -1 +1,85 @@
1
- export default {}
1
+ import XEUtils from 'xe-utils'
2
+
3
+ // 常量定义:各单位对应的毫秒数
4
+ const TIME_UNIT = {
5
+ second: 1000,
6
+ minute: 1000 * 60,
7
+ hour: 1000 * 60 * 60,
8
+ day: 1000 * 60 * 60 * 24
9
+ }
10
+
11
+ const getDataDiff = (date1, date2) => {
12
+ let datePre = ''
13
+ let dateDiff = XEUtils.getDateDiff(date1, date2)
14
+ if (!dateDiff.done) {
15
+ dateDiff = XEUtils.getDateDiff(date2, date1)
16
+ datePre = '-'
17
+ }
18
+ return { datePre, dateDiff }
19
+ }
20
+
21
+ export default {
22
+ toDateDiffText(date) {
23
+ let currDate = 1544407800000 // '2018-12-10 10:10:00'
24
+ let dateDiff = XEUtils.getDateDiff(date, currDate)
25
+ if (dateDiff.done) {
26
+ if (dateDiff.time < 10000) {
27
+ return '刚刚'
28
+ } else if (dateDiff.time < 60000) {
29
+ return `${dateDiff.ss}秒之前`
30
+ } else if (dateDiff.time < 360000) {
31
+ return `${dateDiff.mm}分钟之前`
32
+ } else if (dateDiff.time < 86400000) {
33
+ return `${dateDiff.HH}小时之前`
34
+ } else if (dateDiff.time < 2592000000) {
35
+ return `${dateDiff.dd}天之前`
36
+ } else if (dateDiff.time < 31536000000) {
37
+ return `${dateDiff.MM}个月之前`
38
+ }
39
+ return `${dateDiff.yyyy}年之前`
40
+ }
41
+ return '错误类型'
42
+ },
43
+ getYearDiff(date1, date2, isSupply) {
44
+ // isSupply: 为是否补充,即不足一年按整年算
45
+ let { datePre, dateDiff } = getDataDiff(date1, date2)
46
+ let dataValue = dateDiff.yyyy
47
+ if (isSupply && (dateDiff.MM || dateDiff.dd || dateDiff.HH || dateDiff.mm || dateDiff.ss)) {
48
+ dataValue += 1
49
+ }
50
+ return Number(`${datePre}${dataValue}`)
51
+ },
52
+ getMonthDiff(date1, date2, isSupply) {
53
+ // isSupply: 为是否补充,即不足一月按整月算
54
+ let { datePre, dateDiff } = getDataDiff(date1, date2)
55
+ let dataValue = XEUtils.multiply(dateDiff.yyyy, 12) + dateDiff.MM
56
+ if (isSupply && (dateDiff.dd || dateDiff.HH || dateDiff.mm || dateDiff.ss)) {
57
+ dataValue += 1
58
+ }
59
+ return Number(`${datePre}${dataValue}`)
60
+ },
61
+ getDayDiff(date1, date2, isSupply) {
62
+ // isSupply: 为是否补充,即不足一天按整天算
63
+ let { datePre, dateDiff } = getDataDiff(date1, date2)
64
+ let dataValue = XEUtils[isSupply ? 'ceil' : 'floor'](XEUtils.divide(dateDiff.time, TIME_UNIT.day))
65
+ return Number(`${datePre}${dataValue}`)
66
+ },
67
+ getHourDiff(date1, date2, isSupply) {
68
+ // isSupply: 为是否补充,即不足一小时按整小时算
69
+ let { datePre, dateDiff } = getDataDiff(date1, date2)
70
+ let dataValue = XEUtils[isSupply ? 'ceil' : 'floor'](XEUtils.divide(dateDiff.time, TIME_UNIT.hour))
71
+ return Number(`${datePre}${dataValue}`)
72
+ },
73
+ getMinuteDiff(date1, date2, isSupply) {
74
+ // isSupply: 为是否补充,即不足一分钟按整分钟算
75
+ let { datePre, dateDiff } = getDataDiff(date1, date2)
76
+ let dataValue = XEUtils[isSupply ? 'ceil' : 'floor'](XEUtils.divide(dateDiff.time, TIME_UNIT.minute))
77
+ return Number(`${datePre}${dataValue}`)
78
+ },
79
+ getSecondDiff(date1, date2, isSupply) {
80
+ // isSupply: 为是否补充,即不足一分钟按整分钟算
81
+ let { datePre, dateDiff } = getDataDiff(date1, date2)
82
+ let dataValue = XEUtils[isSupply ? 'ceil' : 'floor'](XEUtils.divide(dateDiff.time, TIME_UNIT.second))
83
+ return Number(`${datePre}${dataValue}`)
84
+ }
85
+ }