sh-tools 2.3.6 → 2.3.7

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,9 +1,10 @@
1
1
  {
2
2
  "name": "sh-tools",
3
- "version": "2.3.6",
3
+ "version": "2.3.7",
4
4
  "description": "基于fetch和xe-utils二次封装,支持宏公式计算",
5
5
  "main": "packages/index.js",
6
6
  "scripts": {
7
+ "serve": "vue-cli-service serve",
7
8
  "lib": "vue-cli-service build --target lib packages/index.js --name sh-tools --dest lib"
8
9
  },
9
10
  "keywords": [
@@ -12,7 +13,6 @@
12
13
  "author": "神秘的sh",
13
14
  "license": "ISC",
14
15
  "dependencies": {
15
- "hot-formula-parser": "^4.0.0",
16
16
  "xe-utils": "^3.8.2"
17
17
  },
18
18
  "devDependencies": {
@@ -0,0 +1,516 @@
1
+ import utilsModule from '../utils'
2
+
3
+ /**
4
+ * 常量定义:分类管理内置方法名
5
+ */
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
+ }
66
+
67
+ // ========== 核心修复1:正则新增字符串字面量匹配(优先识别) ==========
68
+ // 改动点:
69
+ // 1. 最前面新增 ("[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*') 匹配单/双引号字符串
70
+ // 2. 处理转义字符(如 \' / \"),兼容带转义的字符串
71
+ const TOKEN_REG = /\s*((?:"[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*')|(if|else)|[a-zA-Z_$][a-zA-Z0-9_$]*|[\d.]+|,|==|===|!=|!==|<=|>=|<<|>>|>>>|\|\||&&|\*\*|[-+*/%^&|<>!=?:()\[\]])\s*/g
72
+
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 }
79
+
80
+ /**
81
+ * 表达式求值器(最终完整版:支持if-else+换行+字符串字面量)
82
+ */
83
+ class ExprEvaluator {
84
+ constructor() {
85
+ this.functions = {}
86
+ this.operators = {}
87
+ this._isInited = false
88
+ this._initParseFunctions()
89
+ }
90
+
91
+ /**
92
+ * 懒加载初始化(原有逻辑不变)
93
+ */
94
+ _lazyInit() {
95
+ if (this._isInited) return
96
+ const utils = utilsModule.__esModule ? utilsModule.default : utilsModule
97
+
98
+ const funcs = {}
99
+ for (const method of MATH_METHODS) funcs[method] = Math[method]
100
+
101
+ // 提前扁平化方法列表(轻量化优化)
102
+ const ALL_UTIL_METHODS = Object.values(UTIL_METHODS).flat()
103
+ for (const method of ALL_UTIL_METHODS) {
104
+ 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
+ }
116
+ } else if (process.env.NODE_ENV === 'development') {
117
+ console.warn(`[ExprEvaluator] utils方法不存在: ${method}`)
118
+ }
119
+ }
120
+ this.functions = funcs
121
+
122
+ // 初始化运算符(原有逻辑不变)
123
+ this.operators = {
124
+ '?:': { precedence: 1, associativity: 'right', ternary: true, fn: (c, a, b) => (c ? a : b) },
125
+ '||': { precedence: 1, associativity: 'left', fn: (a, b) => a || b },
126
+ '&&': { precedence: 2, associativity: 'left', fn: (a, b) => a && b },
127
+ '|': { precedence: 3, associativity: 'left', fn: (a, b) => a | b },
128
+ '^': { precedence: 4, associativity: 'left', fn: (a, b) => a ^ b },
129
+ '&': { precedence: 5, associativity: 'left', fn: (a, b) => a & b },
130
+ '==': { precedence: 6, associativity: 'left', fn: (a, b) => a == b },
131
+ '!=': { precedence: 6, associativity: 'left', fn: (a, b) => a != b },
132
+ '===': { precedence: 6, associativity: 'left', fn: (a, b) => a === b },
133
+ '!==': { 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 },
141
+ '+': { precedence: 9, associativity: 'left', fn: utils.add },
142
+ '-': { precedence: 9, associativity: 'left', fn: utils.subtract },
143
+ '*': { precedence: 10, associativity: 'left', fn: utils.multiply },
144
+ '/': { precedence: 10, associativity: 'left', fn: utils.divide },
145
+ '%': { 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
+ '!': { precedence: 12, associativity: 'right', prefix: true, fn: a => !a },
150
+ '~': { precedence: 12, associativity: 'right', prefix: true, fn: a => ~a }
151
+ }
152
+
153
+ this._isInited = true
154
+ }
155
+
156
+ /**
157
+ * 初始化解析函数(原有逻辑不变,仅扩展parsePrimary支持字符串)
158
+ */
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
+ }
175
+ }
176
+
177
+ if (tokens[pos] !== closeChar) {
178
+ throw new Error(`函数参数未闭合:缺少 ${closeChar}(当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
179
+ }
180
+ pos++ // 跳过右括号 ) 或 ]
181
+ return args
182
+ }
183
+
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
+ }
197
+ }
198
+
199
+ if (tokens[pos] !== ']') {
200
+ throw new Error(`数组未闭合:在位置 ${startPos} 开始的数组缺少 ](当前Token:${tokens[pos] || '空'}, 位置:${pos})`)
201
+ }
202
+ pos++ // 跳过右中括号 ]
203
+ return arr
204
+ }
205
+
206
+ // ========== 核心修复2:扩展parsePrimary支持字符串字面量 ==========
207
+ const parsePrimary = () => {
208
+ if (pos >= tokens.length) throw new Error('表达式意外结束')
209
+ const currentToken = tokens[pos]
210
+
211
+ // 新增:字符串字面量解析(单/双引号)
212
+ if (typeof currentToken === 'string' && (currentToken.startsWith('"') || currentToken.startsWith("'"))) {
213
+ const strValue = currentToken.slice(1, -1) // 去掉首尾引号
214
+ pos++
215
+ return strValue // 返回纯字符串内容
216
+ }
217
+
218
+ // 数组字面量
219
+ if (currentToken === '[') {
220
+ return parseArrayLiteral()
221
+ }
222
+
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
+ }
233
+
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}(仅支持函数调用)`)
254
+ }
255
+
256
+ // 数字
257
+ if (typeof currentToken === 'number') {
258
+ const num = currentToken
259
+ pos++
260
+ return num
261
+ }
262
+
263
+ throw new Error(`无效Token: ${currentToken}(位置:${pos})`)
264
+ }
265
+
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()
275
+ }
276
+
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
+ }
286
+
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
296
+ }
297
+
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
307
+ }
308
+
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
318
+ }
319
+
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
329
+ }
330
+
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
340
+ }
341
+
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
350
+ }
351
+
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
360
+ }
361
+
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
370
+ }
371
+
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
380
+ }
381
+
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
390
+ }
391
+
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()
425
+
426
+ // 第七步:返回最终结果(条件为true返回thenValue,否则返回elseValue)
427
+ return condition ? thenValue : elseValue
428
+ }
429
+
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
442
+ }
443
+
444
+ // 三元运算符入口(不变)
445
+ const parseTernary = () => {
446
+ // 先解析if-else,再解析三元运算符
447
+ return parseIfElse()
448
+ }
449
+
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
+ }
460
+ }
461
+
462
+ /**
463
+ * 分词器(扩展支持字符串字面量,原有逻辑不变)
464
+ */
465
+ _tokenize(expr) {
466
+ const tokens = []
467
+ TOKEN_REG.lastIndex = 0
468
+ let match
469
+ const utils = utilsModule.__esModule ? utilsModule.default : utilsModule
470
+
471
+ while ((match = TOKEN_REG.exec(expr)) !== null) {
472
+ const token = match[1]
473
+ // 1. 字符串字面量:直接保留(后续在parsePrimary中去掉引号)
474
+ if (token.startsWith('"') || token.startsWith("'")) {
475
+ 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 {
483
+ tokens.push(token)
484
+ }
485
+ }
486
+
487
+ if (tokens.length === 0) throw new Error('表达式为空')
488
+ return tokens
489
+ }
490
+
491
+ /**
492
+ * 对外核心方法(兼容换行符,原有逻辑不变)
493
+ */
494
+ evaluate(expr) {
495
+ this._lazyInit()
496
+
497
+ if (typeof expr !== 'string') throw new Error('表达式必须是字符串')
498
+
499
+ // 替换所有换行符(\n/\r/\r\n)为空格,兼容换行
500
+ const normalizedExpr = expr.replace(/[\n\r]/g, ' ')
501
+ const trimmed = normalizedExpr.trim()
502
+
503
+ if (!trimmed) throw new Error('表达式不能为空')
504
+
505
+ try {
506
+ const tokens = this._tokenize(trimmed)
507
+ return this._parseEntry(tokens)
508
+ } catch (error) {
509
+ throw new Error(`表达式计算失败: ${error.message}(原表达式:${expr})`)
510
+ }
511
+ }
512
+ }
513
+
514
+ // 单例导出
515
+ const evaluator = new ExprEvaluator()
516
+ export default evaluator
package/packages/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from './api'
2
2
  import utils from './utils'
3
- import parser from './parser'
3
+ import expr from './expr'
4
4
 
5
- export { http, utils, parser }
5
+ export default { http, utils, expr }
@@ -24,4 +24,6 @@ XEUtils.mixin({
24
24
  ...pattern
25
25
  })
26
26
 
27
+ window.$vUtils = XEUtils
28
+
27
29
  export default XEUtils
@@ -1,6 +1,6 @@
1
1
  import XEUtils from 'xe-utils'
2
2
  import stringUtil from './string'
3
- import formulaParser from '../parser'
3
+ import formulaParser from '../expr'
4
4
 
5
5
  export default {
6
6
  truncate(num, digits) {
@@ -17,7 +17,6 @@ export default {
17
17
  if (data && XEUtils.isPlainObject(data)) {
18
18
  formula = stringUtil.format(formula, data, true)
19
19
  }
20
- let { result } = formulaParser.parse(formula)
21
- return result
20
+ return formulaParser.evaluate(formula)
22
21
  }
23
22
  }
@@ -1,4 +0,0 @@
1
- import { Parser } from 'hot-formula-parser'
2
- const formulaParser = new Parser()
3
-
4
- export default formulaParser