@cloudpss/expression 0.6.0-alpha.1 → 0.6.0-alpha.11

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