@cloudpss/expression 0.6.0-alpha.9 → 0.6.0-beta.1

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.
Files changed (157) hide show
  1. package/dist/analyze.d.ts +6 -3
  2. package/dist/analyze.d.ts.map +1 -1
  3. package/dist/analyze.js +13 -33
  4. package/dist/analyze.js.map +1 -1
  5. package/dist/definitions/argument.d.ts +3 -11
  6. package/dist/definitions/argument.d.ts.map +1 -1
  7. package/dist/definitions/constraint.d.ts +2 -2
  8. package/dist/definitions/constraint.d.ts.map +1 -1
  9. package/dist/definitions/constraint.js +1 -1
  10. package/dist/definitions/constraint.js.map +1 -1
  11. package/dist/definitions/parameter-group.js +4 -4
  12. package/dist/definitions/parameter-group.js.map +1 -1
  13. package/dist/definitions/parameter.d.ts +22 -14
  14. package/dist/definitions/parameter.d.ts.map +1 -1
  15. package/dist/definitions/parameter.js +10 -1
  16. package/dist/definitions/parameter.js.map +1 -1
  17. package/dist/definitions/utils.d.ts +28 -0
  18. package/dist/definitions/utils.d.ts.map +1 -0
  19. package/dist/definitions/utils.js +272 -0
  20. package/dist/definitions/utils.js.map +1 -0
  21. package/dist/definitions/variable.js +1 -1
  22. package/dist/definitions/variable.js.map +1 -1
  23. package/dist/definitions.d.ts +1 -2
  24. package/dist/definitions.d.ts.map +1 -1
  25. package/dist/definitions.js +1 -1
  26. package/dist/definitions.js.map +1 -1
  27. package/dist/eval.d.ts +3 -5
  28. package/dist/eval.d.ts.map +1 -1
  29. package/dist/eval.js +12 -20
  30. package/dist/eval.js.map +1 -1
  31. package/dist/expression.d.ts +10 -4
  32. package/dist/expression.d.ts.map +1 -1
  33. package/dist/expression.js +6 -6
  34. package/dist/expression.js.map +1 -1
  35. package/dist/index.d.ts +3 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/interface.d.ts +30 -0
  40. package/dist/interface.d.ts.map +1 -0
  41. package/dist/interface.js +6 -0
  42. package/dist/interface.js.map +1 -0
  43. package/dist/main.d.ts +41 -28
  44. package/dist/main.d.ts.map +1 -1
  45. package/dist/main.js +165 -149
  46. package/dist/main.js.map +1 -1
  47. package/dist/migrate.d.ts +1 -1
  48. package/dist/migrate.d.ts.map +1 -1
  49. package/dist/migrate.js +25 -4
  50. package/dist/migrate.js.map +1 -1
  51. package/dist/migrator/access.d.ts.map +1 -1
  52. package/dist/migrator/access.js +64 -29
  53. package/dist/migrator/access.js.map +1 -1
  54. package/dist/migrator/call.d.ts.map +1 -1
  55. package/dist/migrator/call.js +298 -201
  56. package/dist/migrator/call.js.map +1 -1
  57. package/dist/migrator/concat.d.ts.map +1 -1
  58. package/dist/migrator/concat.js +15 -2
  59. package/dist/migrator/concat.js.map +1 -1
  60. package/dist/migrator/function.d.ts +6 -0
  61. package/dist/migrator/function.d.ts.map +1 -0
  62. package/dist/migrator/function.js +25 -0
  63. package/dist/migrator/function.js.map +1 -0
  64. package/dist/migrator/interface.d.ts +3 -1
  65. package/dist/migrator/interface.d.ts.map +1 -1
  66. package/dist/migrator/interface.js.map +1 -1
  67. package/dist/migrator/node.d.ts.map +1 -1
  68. package/dist/migrator/node.js +55 -7
  69. package/dist/migrator/node.js.map +1 -1
  70. package/dist/migrator/operator.d.ts.map +1 -1
  71. package/dist/migrator/operator.js +107 -60
  72. package/dist/migrator/operator.js.map +1 -1
  73. package/dist/migrator/serialize.d.ts +4 -0
  74. package/dist/migrator/serialize.d.ts.map +1 -0
  75. package/dist/migrator/serialize.js +21 -0
  76. package/dist/migrator/serialize.js.map +1 -0
  77. package/dist/migrator/special.d.ts.map +1 -1
  78. package/dist/migrator/special.js +31 -0
  79. package/dist/migrator/special.js.map +1 -1
  80. package/dist/migrator/state.d.ts +4 -4
  81. package/dist/migrator/state.d.ts.map +1 -1
  82. package/dist/migrator/state.js +29 -31
  83. package/dist/migrator/state.js.map +1 -1
  84. package/dist/migrator/symbol.d.ts.map +1 -1
  85. package/dist/migrator/symbol.js +34 -12
  86. package/dist/migrator/symbol.js.map +1 -1
  87. package/dist/migrator/to-type.d.ts.map +1 -1
  88. package/dist/migrator/to-type.js +21 -4
  89. package/dist/migrator/to-type.js.map +1 -1
  90. package/dist/migrator/utils.d.ts +6 -0
  91. package/dist/migrator/utils.d.ts.map +1 -1
  92. package/dist/migrator/utils.js +49 -1
  93. package/dist/migrator/utils.js.map +1 -1
  94. package/dist/parser.d.ts +2 -2
  95. package/dist/parser.d.ts.map +1 -1
  96. package/dist/parser.js +27 -8
  97. package/dist/parser.js.map +1 -1
  98. package/dist/re-exports.d.ts +4 -0
  99. package/dist/re-exports.d.ts.map +1 -0
  100. package/dist/re-exports.js +3 -0
  101. package/dist/re-exports.js.map +1 -0
  102. package/dist/scope.d.ts +16 -17
  103. package/dist/scope.d.ts.map +1 -1
  104. package/dist/scope.js +84 -53
  105. package/dist/scope.js.map +1 -1
  106. package/dist/type.d.ts +22 -10
  107. package/dist/type.d.ts.map +1 -1
  108. package/dist/type.js +21 -24
  109. package/dist/type.js.map +1 -1
  110. package/package.json +8 -5
  111. package/src/analyze.ts +20 -39
  112. package/src/definitions/argument.ts +3 -13
  113. package/src/definitions/constraint.ts +3 -3
  114. package/src/definitions/parameter-group.ts +4 -4
  115. package/src/definitions/parameter.ts +47 -24
  116. package/src/definitions/utils.ts +288 -0
  117. package/src/definitions/variable.ts +1 -1
  118. package/src/definitions.ts +1 -28
  119. package/src/eval.ts +16 -25
  120. package/src/expression.ts +16 -8
  121. package/src/index.ts +3 -1
  122. package/src/interface.ts +35 -0
  123. package/src/main.ts +232 -200
  124. package/src/migrate.ts +27 -4
  125. package/src/migrator/access.ts +67 -37
  126. package/src/migrator/call.ts +287 -190
  127. package/src/migrator/concat.ts +15 -2
  128. package/src/migrator/function.ts +27 -0
  129. package/src/migrator/interface.ts +3 -1
  130. package/src/migrator/node.ts +56 -6
  131. package/src/migrator/operator.ts +110 -64
  132. package/src/migrator/serialize.ts +21 -0
  133. package/src/migrator/special.ts +31 -0
  134. package/src/migrator/state.ts +31 -34
  135. package/src/migrator/symbol.ts +33 -12
  136. package/src/migrator/to-type.ts +23 -4
  137. package/src/migrator/utils.ts +49 -1
  138. package/src/parser.ts +33 -8
  139. package/src/re-exports.ts +47 -0
  140. package/src/scope.ts +98 -62
  141. package/src/type.ts +40 -25
  142. package/tests/analyze.ts +45 -6
  143. package/tests/compile.ts +65 -0
  144. package/tests/condition.ts +33 -17
  145. package/tests/definition.ts +237 -18
  146. package/tests/eval-complex.ts +19 -11
  147. package/tests/eval.ts +59 -12
  148. package/tests/import.ts +21 -7
  149. package/tests/main.ts +58 -0
  150. package/tests/migrate.ts +317 -0
  151. package/tests/scope.ts +3 -3
  152. package/tests/template.ts +36 -0
  153. package/dist/context.d.ts +0 -41
  154. package/dist/context.d.ts.map +0 -1
  155. package/dist/context.js +0 -18
  156. package/dist/context.js.map +0 -1
  157. package/src/context.ts +0 -54
@@ -6,14 +6,14 @@ import {
6
6
  isObjectNode,
7
7
  type FunctionNode,
8
8
  type MathNode,
9
- isOperatorNode,
10
9
  } from 'mathjs';
11
10
  import type { Options, Result } from './interface.js';
12
11
  import type { State } from './state.js';
13
- import { constantValue, equalText, symbolName } from './utils.js';
12
+ import { constantValue, equalText, globalFnName, len, symbolName } from './utils.js';
14
13
  import { migrateAtomic, migrateExpr, migrateNode } from './node.js';
15
14
  import { concat } from './concat.js';
16
15
  import { toNumber, toString } from './to-type.js';
16
+ import { isVmArray, isVmPrimitive } from '@mirascript/mirascript';
17
17
 
18
18
  const EXACT_UNARY_FUNCTION = new Map<string, Result['type']>([
19
19
  // 只支持标量的 mathjs 函数
@@ -42,6 +42,9 @@ const EXACT_UNARY_FUNCTION = new Map<string, Result['type']>([
42
42
  ['service', 'extern'],
43
43
  ]);
44
44
 
45
+ /** 只接受一个参数,返回数字的函数 */
46
+ const ENTRYWISE_UNARY_MATH_FUNCTION = new Set<string>(['abs', 'ceil', 'floor', 'round', 'sign']);
47
+
45
48
  /** 兜底 */
46
49
  function call(state: State, node: FunctionNode<MathNode>, done: (fn: Result, args: readonly Result[]) => void): Result {
47
50
  const { fn, args } = node;
@@ -53,215 +56,305 @@ function call(state: State, node: FunctionNode<MathNode>, done: (fn: Result, arg
53
56
  };
54
57
  }
55
58
 
56
- /** 转换 AST */
57
- export function migrateCall(state: State, node: FunctionNode<MathNode>, options: Options): Result {
58
- const { fn, args } = node;
59
-
60
- const fnName = symbolName(fn);
61
- if (fnName && !state.locals.has(fnName)) {
62
- const openE = options.format !== 'no-paren' ? '(' : '';
63
- const closeE = options.format !== 'no-paren' ? ')' : '';
59
+ /** 函数调用 */
60
+ function migrateFunctionCall(
61
+ state: State,
62
+ node: FunctionNode<MathNode>,
63
+ fnName: string,
64
+ args: readonly MathNode[],
65
+ options: Options,
66
+ ): Result {
67
+ const openE = options.format !== 'no-paren' ? '(' : '';
68
+ const closeE = options.format !== 'no-paren' ? ')' : '';
69
+ const g = (fnName: string) => globalFnName(state, fnName);
64
70
 
65
- if (EXACT_UNARY_FUNCTION.has(fnName) && args.length === 1) {
66
- const arg = migrateAtomic(state, args[0]!);
67
- return {
68
- type: EXACT_UNARY_FUNCTION.get(fnName)!,
69
- code: `${fnName}(${arg.code})`,
70
- };
71
- } else if (fnName === 're' || fnName === 'im') {
72
- const m = migrateAtomic(state, args[0]!);
73
- state.err(`不支持复数`);
71
+ if (EXACT_UNARY_FUNCTION.has(fnName) && args.length === 1) {
72
+ const arg = migrateAtomic(state, args[0]!);
73
+ return {
74
+ type: EXACT_UNARY_FUNCTION.get(fnName)!,
75
+ code: `${fnName}(${arg.code})`,
76
+ };
77
+ } else if (fnName === 'random' && args.length === 0) {
78
+ return {
79
+ type: 'number',
80
+ code: `${g('random')}()`,
81
+ };
82
+ } else if (ENTRYWISE_UNARY_MATH_FUNCTION.has(fnName) && args.length === 1) {
83
+ const arg = migrateAtomic(state, args[0]!);
84
+ if (arg.type === 'number' || arg.type === 'boolean' || arg.type === 'string') {
74
85
  return {
75
86
  type: 'number',
76
- code: `/* ${fnName} */(${m.code})`,
87
+ code: `${g(fnName)}(${arg.code})`,
77
88
  };
78
- } else if (fnName === 'date' && args.length === 1) {
79
- return {
80
- type: 'number',
81
- code: `to_timestamp(${migrateAtomic(state, args[0]!).code})`,
82
- };
83
- } else if (fnName === 'inv' && args.length === 1) {
84
- const arg = migrateAtomic(state, args[0]!);
85
- return {
86
- type: arg.type,
87
- code: `matrix.invert(${arg.code})`,
88
- };
89
- } else if (fnName === 'transpose' && args.length === 1) {
89
+ }
90
+ return {
91
+ type: arg.type,
92
+ code: `${g('matrix')}.entrywise(${arg.code}, nil, ${g(fnName)})`,
93
+ };
94
+ } else if (fnName === 'max' || fnName === 'min') {
95
+ if (args.length === 1) {
90
96
  const arg = migrateAtomic(state, args[0]!);
91
97
  return {
92
- type: arg.type,
93
- code: `matrix.transpose(${arg.code})`,
98
+ type: 'number',
99
+ code: `${g(fnName)}(${arg.code})`,
94
100
  };
95
- } else if (fnName === 'diag' && (args.length === 1 || args.length === 2)) {
96
- const a = args.map((a) => migrateAtomic(state, a).code).join(', ');
101
+ } else {
102
+ const argList = args.map((a) => migrateAtomic(state, a));
103
+ if (!argList.every((a) => a.type === 'number' || a.type === 'boolean' || a.type === 'string')) {
104
+ state.warn(`函数行为可能不一致: ${fnName}`);
105
+ }
97
106
  return {
98
- type: 'array',
99
- code: `matrix.diagonal(${a})`,
107
+ type: 'number',
108
+ code: `${g(fnName)}(${argList.map((a) => a.code).join(', ')})`,
100
109
  };
101
- } else if (fnName === 'zeros' || fnName === 'ones' || fnName === 'identity') {
110
+ }
111
+ } else if (fnName === 're' || fnName === 'im') {
112
+ const m = migrateAtomic(state, args[0]!);
113
+ state.err(`不支持复数`);
114
+ return {
115
+ type: 'number',
116
+ code: `/* ${fnName} */(${m.code})`,
117
+ };
118
+ } else if (fnName === 'date' && args.length === 1) {
119
+ return {
120
+ type: 'number',
121
+ code: `${g('to_timestamp')}(${migrateAtomic(state, args[0]!).code})`,
122
+ };
123
+ } else if (fnName === 'size' && args.length === 1) {
124
+ const arg = migrateAtomic(state, args[0]!);
125
+ return {
126
+ type: arg.type,
127
+ code: `${g('matrix')}.size(${arg.code})`,
128
+ };
129
+ } else if (fnName === 'inv' && args.length === 1) {
130
+ const arg = migrateAtomic(state, args[0]!);
131
+ return {
132
+ type: arg.type,
133
+ code: `${g('matrix')}.invert(${arg.code})`,
134
+ };
135
+ } else if (fnName === 'count' && args.length === 1) {
136
+ const arg = migrateAtomic(state, args[0]!);
137
+ if (arg.type !== 'string') {
138
+ state.warn(`函数行为可能不一致: count`);
139
+ }
140
+ return len(state, arg);
141
+ } else if (fnName === 'transpose' && args.length === 1) {
142
+ const arg = migrateAtomic(state, args[0]!);
143
+ return {
144
+ type: arg.type,
145
+ code: `${g('matrix')}.transpose(${arg.code})`,
146
+ };
147
+ } else if (fnName === 'diag' && (args.length === 1 || args.length === 2)) {
148
+ const a = args.map((a) => migrateAtomic(state, a).code).join(', ');
149
+ return {
150
+ type: 'array',
151
+ code: `${g('matrix')}.diagonal(${a})`,
152
+ };
153
+ } else if (fnName === 'zeros' || fnName === 'ones' || fnName === 'identity') {
154
+ return {
155
+ type: 'array',
156
+ code: `${g('matrix')}.${fnName}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
157
+ };
158
+ } else if (fnName === 'concat') {
159
+ const results = args.map((a) => migrateAtomic(state, a));
160
+ if (results.some((r) => r.type === 'string')) return concat(state, results);
161
+ if (results.some((r) => r.type === 'array')) {
162
+ state.warn(`矩阵连接时结果可能不一致`);
102
163
  return {
103
164
  type: 'array',
104
- code: `matrix.${fnName}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
165
+ code: `${g('flatten')}([` + results.map((r) => r.code).join(', ') + `])`,
105
166
  };
106
- } else if (fnName === 'concat') {
107
- const results = args.map((a) => migrateAtomic(state, a));
108
- if (results.some((r) => r.type === 'string')) return concat(state, results);
109
- } else if (fnName === 'numeric' && args.length === 1) {
110
- return toNumber(state, args[0]!);
111
- } else if (fnName === 'flatten' && args.length === 1) {
167
+ }
168
+ } else if (fnName === 'numeric' && args.length === 1) {
169
+ return toNumber(state, args[0]!);
170
+ } else if (fnName === 'flatten' && args.length === 1) {
171
+ return {
172
+ type: 'array',
173
+ code: `${g('flatten')}(${migrateAtomic(state, args[0]!).code})`,
174
+ };
175
+ } else if (fnName === 'norm' && args.length === 1) {
176
+ if (isArrayNode(args[0])) {
112
177
  return {
113
- type: 'array',
114
- code: `flatten(${migrateAtomic(state, args[0]!).code})`,
178
+ type: 'number',
179
+ code: `${g('hypot')}(${args[0].items.map((a) => migrateAtomic(state, a).code).join(', ')})`,
115
180
  };
116
- } else if (fnName === 'norm' && args.length === 1) {
117
- if (isArrayNode(args[0])) {
181
+ }
182
+ return {
183
+ type: 'number',
184
+ code: `${g('hypot')}(${migrateAtomic(state, args[0]!).code})`,
185
+ };
186
+ } else if (fnName === 'equalText' && args.length === 2) {
187
+ const p = equalText(state, '==', args[0]!, args[1]!);
188
+ return {
189
+ type: p.type,
190
+ code: `${openE}${p.code}${closeE}`,
191
+ };
192
+ } else if (fnName === 'toJson' && args.length === 1) {
193
+ return {
194
+ type: 'string',
195
+ code: `${g('to_json')}(${migrateAtomic(state, args[0]!).code})`,
196
+ };
197
+ } else if (fnName === 'fromJson' && args.length === 1) {
198
+ return {
199
+ code: `${g('from_json')}(${migrateAtomic(state, args[0]!).code})`,
200
+ };
201
+ } else if (fnName === 'is' && args.length === 2 && isConstantNode(args[1])) {
202
+ const t = String((args[1] as ConstantNode<string | number>).value);
203
+ switch (t) {
204
+ case 'number':
205
+ case 'string':
206
+ case 'boolean':
118
207
  return {
119
- type: 'number',
120
- code: `hypot(${args[0].items.map((a) => migrateAtomic(state, a).code).join(', ')})`,
208
+ type: 'boolean',
209
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == '${t}'${closeE}`,
121
210
  };
122
- }
123
- return {
124
- type: 'number',
125
- code: `hypot(${migrateAtomic(state, args[0]!).code})`,
126
- };
127
- } else if (fnName === 'equalText' && args.length === 2) {
128
- const p = equalText(state, '==', args[0]!, args[1]!);
129
- return {
130
- type: p.type,
131
- code: `${openE}${p.code}${closeE}`,
132
- };
133
- } else if (fnName === 'toJson' && args.length === 1) {
134
- return {
135
- type: 'string',
136
- code: `to_json(${migrateAtomic(state, args[0]!).code})`,
137
- };
138
- } else if (fnName === 'fromJson' && args.length === 1) {
139
- return {
140
- code: `from_json(${migrateAtomic(state, args[0]!).code})`,
141
- };
142
- } else if (fnName === 'is' && args.length === 2 && isConstantNode(args[1])) {
143
- const t = String((args[1] as ConstantNode<string | number>).value);
144
- switch (t) {
145
- case 'number':
146
- case 'string':
147
- case 'boolean':
148
- return {
149
- type: 'boolean',
150
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == '${t}'${closeE}`,
151
- };
152
- case 'null':
153
- case 'undefined':
154
- return {
155
- type: 'boolean',
156
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'nil'${closeE}`,
157
- };
158
- case 'Array':
159
- case 'array':
160
- state.warn(`'type' 与 'is' 结果可能不同`);
161
- return {
162
- type: 'boolean',
163
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'array'${closeE}`,
164
- };
165
- case 'Object':
166
- case 'object':
167
- state.warn(`'type' 与 'is' 结果可能不同`);
211
+ case 'null':
212
+ case 'undefined':
213
+ return {
214
+ type: 'boolean',
215
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'nil'${closeE}`,
216
+ };
217
+ case 'Array':
218
+ case 'array':
219
+ state.warn(`'type' 与 'is' 结果可能不同`);
220
+ return {
221
+ type: 'boolean',
222
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'array'${closeE}`,
223
+ };
224
+ case 'Object':
225
+ case 'object': {
226
+ const arg0 = migrateAtomic(state, args[0]!);
227
+ if (arg0.code.endsWith('.value.rt_state')) {
168
228
  return {
169
229
  type: 'boolean',
170
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'record'${closeE}`,
230
+ code: `${openE}!!${arg0.code}${closeE}`,
171
231
  };
172
- case 'Function':
173
- case 'function': {
174
- if (symbolName(args[0]!) === 'service' && !state.locals.has('service')) {
175
- return {
176
- type: 'boolean',
177
- code: `${openE}type(service) == 'extern'${closeE}`,
178
- };
179
- }
180
- state.warn(`'type' 'is' 结果可能不同`);
232
+ }
233
+ state.warn(`'type' 'is' 结果可能不同`);
234
+ return {
235
+ type: 'boolean',
236
+ code: `${openE}type(${arg0.code}) == 'record'${closeE}`,
237
+ };
238
+ }
239
+ case 'Function':
240
+ case 'function': {
241
+ if (symbolName(args[0]!) === 'service' && !state.locals.has('service')) {
181
242
  return {
182
243
  type: 'boolean',
183
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'function'${closeE}`,
244
+ code: `${openE}type(service) == 'extern'${closeE}`,
184
245
  };
185
246
  }
247
+ state.warn(`'type' 与 'is' 结果可能不同`);
248
+ return {
249
+ type: 'boolean',
250
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'function'${closeE}`,
251
+ };
186
252
  }
187
- } else if (fnName === 'typeOf' && args.length === 1) {
188
- state.warn(`'type' 'typeOf' 结果可能不同`);
189
- return {
190
- type: 'string',
191
- code: `type(${migrateAtomic(state, args[0]!).code})`,
192
- };
193
- } else if (fnName === 'format' && args.length === 1) {
194
- return toString(state, args[0]!);
195
- } else if (fnName === 'print' && args.length === 1) {
196
- state.loose();
197
- return toString(state, args[0]!);
198
- } else if (
199
- fnName === 'print' &&
200
- args.length === 2 &&
201
- isConstantNode(args[0]) &&
202
- (isObjectNode(args[1]) || isArrayNode(args[1]))
203
- ) {
204
- const template = String(args[0].value);
205
- const values = isObjectNode(args[1])
206
- ? args[1].properties
207
- : (args[1].items as unknown as Record<string, MathNode>);
208
- return {
209
- type: 'string',
210
- code:
211
- '`' +
212
- template.replaceAll(/`|\$([\w.]+)/g, (original, key: string) => {
213
- if (original === '`') return '\\`';
214
- const keys = key.split('.').map((part) => {
215
- const nPart = Number.parseInt(part);
216
- if (!Number.isNaN(nPart) && part.length > 0 && nPart >= 1) {
217
- return nPart - 1;
218
- } else {
219
- return part;
220
- }
221
- });
222
- const value = values[keys.shift()!];
223
- if (value == null) return original.replaceAll('$', String.raw`\$`);
224
- if (!keys.length) {
225
- return `$(${migrateAtomic(state, value).code})`;
253
+ }
254
+ } else if (fnName === 'typeOf' && args.length === 1) {
255
+ state.warn(`'type' 与 'typeOf' 结果可能不同`);
256
+ return {
257
+ type: 'string',
258
+ code: `type(${migrateAtomic(state, args[0]!).code})`,
259
+ };
260
+ } else if (fnName === 'format' && args.length === 1) {
261
+ return toString(state, args[0]!);
262
+ } else if (fnName === 'print' && args.length === 1) {
263
+ state.loose();
264
+ return toString(state, args[0]!);
265
+ } else if (
266
+ fnName === 'print' &&
267
+ args.length === 2 &&
268
+ isConstantNode(args[0]) &&
269
+ (isObjectNode(args[1]) || isArrayNode(args[1]))
270
+ ) {
271
+ const template = String(args[0].value);
272
+ const values = isObjectNode(args[1])
273
+ ? args[1].properties
274
+ : (args[1].items as unknown as Record<string, MathNode>);
275
+ return {
276
+ type: 'string',
277
+ code:
278
+ '`' +
279
+ template.replaceAll(/`|\$([\w.]+)/g, (original, key: string) => {
280
+ if (original === '`') return '\\`';
281
+ const keys = key.split('.').map((part) => {
282
+ const nPart = Number.parseInt(part);
283
+ if (!Number.isNaN(nPart) && part.length > 0 && nPart >= 1) {
284
+ return nPart - 1;
285
+ } else {
286
+ return part;
226
287
  }
227
- return `$(${migrateNode(state, value, { format: 'paren' }).code}.${keys.join('.')})`;
228
- }) +
229
- '`',
230
- };
231
- } else if (fnName === 'string' && args.length === 1) {
232
- const inner = migrateAtomic(state, args[0]!);
288
+ });
289
+ const value = values[keys.shift()!];
290
+ if (value == null) return original.replaceAll('$', String.raw`\$`);
291
+ if (!keys.length) {
292
+ return `$(${migrateAtomic(state, value).code})`;
293
+ }
294
+ return `$(${migrateNode(state, value, { format: 'paren' }).code}.${keys.join('.')})`;
295
+ }) +
296
+ '`',
297
+ };
298
+ } else if (fnName === 'string' && args.length === 1) {
299
+ const inner = migrateAtomic(state, args[0]!);
300
+ return {
301
+ type: 'string',
302
+ code: `${g('to_string')}(${inner.code})`,
303
+ };
304
+ } else if (fnName === 'sum' || fnName === 'prod') {
305
+ const newFnName = fnName === 'sum' ? 'sum' : 'product';
306
+ if (args.length !== 1) {
233
307
  return {
234
- type: 'string',
235
- code: `to_string(${inner.code})`,
308
+ type: 'number',
309
+ code: `${g(newFnName)}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
236
310
  };
237
- } else if (
238
- fnName === 'sum' &&
239
- args.length === 1 &&
240
- isArrayNode(args[0]) &&
241
- args[0].items.length === 1 &&
242
- isOperatorNode(args[0].items[0]) &&
243
- ['>=', '>', '<=', '<', '==', '!='].includes(args[0].items[0].op) &&
244
- typeof constantValue(args[0].items[0].args[1]!) === 'number'
245
- ) {
246
- // sum([a > v])
247
- const a = migrateAtomic(state, args[0].items[0].args[0]!);
248
- const v = migrateAtomic(state, args[0].items[0].args[1]!);
311
+ }
312
+ const arg = migrateAtomic(state, args[0]!);
313
+ if (arg.literal) {
314
+ if (isVmPrimitive(arg.literal)) {
315
+ return {
316
+ type: 'number',
317
+ code: `${g(newFnName)}(${arg.code})`,
318
+ };
319
+ }
320
+ if (isVmArray(arg.literal) && arg.literal.every((v) => isVmPrimitive(v))) {
321
+ return {
322
+ type: 'number',
323
+ code: `${g(newFnName)}(${arg.code})`,
324
+ };
325
+ }
326
+ }
327
+ if (arg.type === 'array' || !arg.type) {
249
328
  return {
250
329
  type: 'number',
251
- code: `sum(map(${a.code}, fn { it ${args[0].items[0].op} ${v.code} }))`,
330
+ code: `${g(newFnName)}(${arg.code}::${g('flatten')}())`,
252
331
  };
253
332
  }
333
+ return {
334
+ type: 'number',
335
+ code: `${g(newFnName)}(${arg.code})`,
336
+ };
337
+ }
254
338
 
255
- return call(state, node, (fn, args) => {
256
- const fun = state.globals.get(fnName);
257
- if (typeof fun === 'function') {
258
- if (args.some((a) => a.type !== 'number' && a.type !== 'boolean' && a.type !== 'string')) {
259
- state.warn(`函数行为可能不一致: ${fnName}`);
260
- }
261
- } else {
262
- state.err(`不支持的函数: ${fnName}`);
339
+ return call(state, node, (fn, args) => {
340
+ const fun = state.globals.has(fnName) ? state.globals.get(fnName) : undefined;
341
+ if (typeof fun === 'function') {
342
+ if (args.some((a) => a.type !== 'number' && a.type !== 'boolean' && a.type !== 'string')) {
343
+ state.warn(`函数行为可能不一致: ${fnName}`);
263
344
  }
264
- });
345
+ } else {
346
+ state.err(`不支持的函数: ${fnName}`);
347
+ }
348
+ });
349
+ }
350
+
351
+ /** 转换 AST */
352
+ export function migrateCall(state: State, node: FunctionNode<MathNode>, options: Options): Result {
353
+ const { fn, args } = node;
354
+
355
+ const fnName = symbolName(fn);
356
+ if (fnName && !state.locals.has(fnName)) {
357
+ return migrateFunctionCall(state, node, fnName, args, options);
265
358
  }
266
359
 
267
360
  if (
@@ -272,10 +365,11 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
272
365
  ) {
273
366
  const thisArg = migrateExpr(state, fn.object);
274
367
  const fnName = String(fn.index.dimensions[0].value);
368
+ const g = (fnName: string) => globalFnName(state, fnName);
275
369
  if (fnName === 'toString' && args.length === 0) {
276
370
  return {
277
371
  type: 'string',
278
- code: `${thisArg.code}::to_string()`,
372
+ code: `${thisArg.code}::${g('to_string')}()`,
279
373
  };
280
374
  } else if (fnName === 'includes' && args.length === 1) {
281
375
  if (thisArg.type === 'array') {
@@ -287,7 +381,7 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
287
381
  if (thisArg.type === 'string') {
288
382
  return {
289
383
  type: 'boolean',
290
- code: `${thisArg.code}::contains(${migrateAtomic(state, args[0]!).code})`,
384
+ code: `${thisArg.code}::${g('contains')}(${migrateAtomic(state, args[0]!).code})`,
291
385
  };
292
386
  }
293
387
  state.helper(
@@ -300,26 +394,26 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
300
394
  } else if ((fnName === 'map' || fnName === 'filter') && args.length === 1) {
301
395
  return {
302
396
  type: 'array',
303
- code: `${thisArg.code}::${fnName}(${migrateAtomic(state, args[0]!).code})`,
397
+ code: `${thisArg.code}::${g(fnName)}(${migrateAtomic(state, args[0]!).code})`,
304
398
  };
305
399
  } else if (fnName === 'find' && args.length === 1) {
306
400
  return {
307
- code: `${thisArg.code}::find(${migrateAtomic(state, args[0]!).code}).1`,
401
+ code: `${thisArg.code}::${g('find')}(${migrateAtomic(state, args[0]!).code}).1`,
308
402
  };
309
403
  } else if (fnName === 'findIndex' && args.length === 1) {
310
404
  return {
311
405
  type: 'number',
312
- code: `(${thisArg.code}::find(${migrateAtomic(state, args[0]!).code}).0 ?? -1)`,
406
+ code: `(${thisArg.code}::${g('find')}(${migrateAtomic(state, args[0]!).code}).0 ?? -1)`,
313
407
  };
314
408
  } else if (fnName === 'flatMap' && args.length === 1) {
315
409
  return {
316
410
  type: 'array',
317
- code: `${thisArg.code}::map(${migrateAtomic(state, args[0]!).code})::flatten()`,
411
+ code: `${thisArg.code}::${g('map')}(${migrateAtomic(state, args[0]!).code})::flatten()`,
318
412
  };
319
413
  } else if (fnName === 'reverse' && args.length === 0) {
320
414
  return {
321
415
  type: 'array',
322
- code: `${thisArg.code}::reverse()`,
416
+ code: `${thisArg.code}::${g('reverse')}()`,
323
417
  };
324
418
  } else if ((fnName === 'replaceAll' || fnName === 'replace') && args.length === 2) {
325
419
  if (fnName === 'replace') {
@@ -327,12 +421,12 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
327
421
  }
328
422
  return {
329
423
  type: 'string',
330
- code: `${thisArg.code}::replace(${migrateAtomic(state, args[0]!).code}, ${migrateAtomic(state, args[1]!).code})`,
424
+ code: `${thisArg.code}::${g('replace')}(${migrateAtomic(state, args[0]!).code}, ${migrateAtomic(state, args[1]!).code})`,
331
425
  };
332
426
  } else if (fnName === 'split' && args.length === 1) {
333
427
  return {
334
428
  type: 'array',
335
- code: `${thisArg.code}::split(${migrateAtomic(state, args[0]!).code})`,
429
+ code: `${thisArg.code}::${g('split')}(${migrateAtomic(state, args[0]!).code})`,
336
430
  };
337
431
  } else if (fnName === 'concat') {
338
432
  if (thisArg.type === 'string') {
@@ -344,7 +438,10 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
344
438
  }
345
439
  return {
346
440
  type: 'array',
347
- code: `flatten([` + [fn.object, ...args].map((a) => migrateAtomic(state, a).code).join(', ') + `])`,
441
+ code:
442
+ `${g('flatten')}([` +
443
+ [fn.object, ...args].map((a) => migrateAtomic(state, a).code).join(', ') +
444
+ `])`,
348
445
  };
349
446
  } else if (
350
447
  fnName === 'toFixed' &&
@@ -353,7 +450,7 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
353
450
  const digits = constantValue(args[0]!) ?? 0;
354
451
  return {
355
452
  type: 'string',
356
- code: `${thisArg.code}::format('.${digits}')`,
453
+ code: `${thisArg.code}::${g('format')}('.${digits}')`,
357
454
  };
358
455
  } else if (isArrayNode(fn.object) && fnName === 'join' && args.length <= 1) {
359
456
  let sep = ',';
@@ -382,7 +479,7 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
382
479
  } else if (thisArg.type === 'extern') {
383
480
  const argList = args.map((a) => migrateAtomic(state, a).code);
384
481
  return {
385
- code: `${thisArg.code}(${argList.join(', ')})`,
482
+ code: `${thisArg.code}.${fnName}(${argList.join(', ')})`,
386
483
  };
387
484
  }
388
485
 
@@ -3,18 +3,31 @@ import type { State } from './state.js';
3
3
  import { migrateNode } from './node.js';
4
4
  import { isResult, type Result } from './interface.js';
5
5
 
6
+ /** 生成转义序列 */
7
+ function escapeString(str: string | number): string {
8
+ return String(str)
9
+ .replaceAll('\\', String.raw`\\`)
10
+ .replaceAll('`', '\\`')
11
+ .replaceAll('$', String.raw`\$`)
12
+ .replaceAll('\n', String.raw`\n`)
13
+ .replaceAll('\r', String.raw`\r`)
14
+ .replaceAll('\t', String.raw`\t`);
15
+ }
16
+
6
17
  /** 连接字符串 */
7
18
  export function concat(state: State, args: ReadonlyArray<MathNode | string | number | Result>): Result {
8
19
  const parts: string[] = [];
9
20
  for (const arg of args) {
10
21
  if (typeof arg != 'object') {
11
- parts.push(String(arg).replaceAll('`', '\\`'));
22
+ parts.push(escapeString(arg));
12
23
  } else if (isConstantNode(arg)) {
13
- const value = String(arg.value).replaceAll('`', '\\`');
24
+ const value = escapeString(String(arg.value));
14
25
  parts.push(value);
15
26
  } else if (isResult(arg)) {
16
27
  if (arg.literal && typeof arg.literal != 'object') {
17
28
  parts.push(String(arg.literal).replaceAll('`', '\\`'));
29
+ } else if (arg.type === 'string' && arg.code.startsWith('to_string(') && arg.code.endsWith(')')) {
30
+ parts.push(`$(${arg.code.slice(10, -1)})`);
18
31
  } else {
19
32
  parts.push(`$(${arg.code})`);
20
33
  }