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

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 (150) 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 +12 -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 +22 -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 +235 -209
  64. package/dist/migrator/call.js.map +1 -1
  65. package/dist/migrator/interface.d.ts +3 -1
  66. package/dist/migrator/interface.d.ts.map +1 -1
  67. package/dist/migrator/interface.js.map +1 -1
  68. package/dist/migrator/node.js +1 -1
  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 +19 -5
  72. package/dist/migrator/operator.js.map +1 -1
  73. package/dist/migrator/state.d.ts +2 -2
  74. package/dist/migrator/state.d.ts.map +1 -1
  75. package/dist/migrator/state.js +1 -2
  76. package/dist/migrator/state.js.map +1 -1
  77. package/dist/migrator/symbol.d.ts.map +1 -1
  78. package/dist/migrator/symbol.js +13 -0
  79. package/dist/migrator/symbol.js.map +1 -1
  80. package/dist/migrator/to-type.d.ts.map +1 -1
  81. package/dist/migrator/to-type.js +16 -0
  82. package/dist/migrator/to-type.js.map +1 -1
  83. package/dist/migrator/utils.d.ts +2 -0
  84. package/dist/migrator/utils.d.ts.map +1 -1
  85. package/dist/migrator/utils.js +18 -0
  86. package/dist/migrator/utils.js.map +1 -1
  87. package/dist/parser.d.ts +2 -2
  88. package/dist/parser.d.ts.map +1 -1
  89. package/dist/parser.js +25 -8
  90. package/dist/parser.js.map +1 -1
  91. package/dist/re-exports.d.ts +4 -0
  92. package/dist/re-exports.d.ts.map +1 -0
  93. package/dist/re-exports.js +3 -0
  94. package/dist/re-exports.js.map +1 -0
  95. package/dist/scope.d.ts +13 -16
  96. package/dist/scope.d.ts.map +1 -1
  97. package/dist/scope.js +56 -49
  98. package/dist/scope.js.map +1 -1
  99. package/dist/type.d.ts +14 -5
  100. package/dist/type.d.ts.map +1 -1
  101. package/dist/type.js +35 -11
  102. package/dist/type.js.map +1 -1
  103. package/package.json +4 -4
  104. package/src/analyze.ts +77 -37
  105. package/src/definitions/argument.ts +2 -12
  106. package/src/definitions/constraint.ts +3 -3
  107. package/src/definitions/parameter-decoration.ts +3 -9
  108. package/src/definitions/parameter-group.ts +4 -19
  109. package/src/definitions/parameter.ts +62 -61
  110. package/src/definitions/utils.ts +175 -0
  111. package/src/definitions/variable.ts +1 -6
  112. package/src/definitions.ts +1 -0
  113. package/src/eval.ts +13 -26
  114. package/src/expression.ts +14 -6
  115. package/src/index.ts +3 -1
  116. package/src/interface.ts +35 -0
  117. package/src/main.ts +213 -194
  118. package/src/migrate.ts +15 -6
  119. package/src/migrator/access.ts +21 -26
  120. package/src/migrator/call.ts +225 -194
  121. package/src/migrator/interface.ts +3 -1
  122. package/src/migrator/node.ts +1 -1
  123. package/src/migrator/operator.ts +19 -5
  124. package/src/migrator/state.ts +2 -2
  125. package/src/migrator/symbol.ts +13 -0
  126. package/src/migrator/to-type.ts +16 -0
  127. package/src/migrator/utils.ts +21 -0
  128. package/src/parser.ts +27 -8
  129. package/src/re-exports.ts +28 -0
  130. package/src/scope.ts +75 -61
  131. package/src/type.ts +32 -11
  132. package/tests/analyze.ts +40 -6
  133. package/tests/compile.ts +65 -0
  134. package/tests/condition.ts +13 -5
  135. package/tests/definition.ts +205 -18
  136. package/tests/eval-complex.ts +7 -10
  137. package/tests/eval.ts +59 -12
  138. package/tests/import.ts +18 -4
  139. package/tests/main.ts +9 -0
  140. package/tests/migrate.ts +77 -0
  141. package/tests/scope.ts +3 -3
  142. package/tests/template.ts +36 -0
  143. package/dist/context.d.ts +0 -41
  144. package/dist/context.d.ts.map +0 -1
  145. package/dist/context.js +0 -18
  146. package/dist/context.js.map +0 -1
  147. package/jest.config.js +0 -3
  148. package/src/context.ts +0 -54
  149. package/tests/tsconfig.json +0 -3
  150. 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, 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,246 @@ 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' ? ')' : '';
59
66
 
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) {
67
+ if (EXACT_UNARY_FUNCTION.has(fnName) && args.length === 1) {
68
+ const arg = migrateAtomic(state, args[0]!);
69
+ return {
70
+ type: EXACT_UNARY_FUNCTION.get(fnName)!,
71
+ code: `${fnName}(${arg.code})`,
72
+ };
73
+ } else if (fnName === 're' || fnName === 'im') {
74
+ const m = migrateAtomic(state, args[0]!);
75
+ state.err(`不支持复数`);
76
+ return {
77
+ type: 'number',
78
+ code: `/* ${fnName} */(${m.code})`,
79
+ };
80
+ } else if (fnName === 'date' && args.length === 1) {
81
+ return {
82
+ type: 'number',
83
+ code: `to_timestamp(${migrateAtomic(state, args[0]!).code})`,
84
+ };
85
+ } else if (fnName === 'size' && args.length === 1) {
86
+ const arg = migrateAtomic(state, args[0]!);
87
+ return {
88
+ type: arg.type,
89
+ code: `matrix.size(${arg.code})`,
90
+ };
91
+ } else if (fnName === 'inv' && args.length === 1) {
92
+ const arg = migrateAtomic(state, args[0]!);
93
+ return {
94
+ type: arg.type,
95
+ code: `matrix.invert(${arg.code})`,
96
+ };
97
+ } else if (fnName === 'count' && args.length === 1) {
98
+ const arg = migrateAtomic(state, args[0]!);
99
+ if (arg.type !== 'string') {
100
+ state.warn(`函数行为可能不一致: count`);
101
+ }
102
+ return len(state, arg);
103
+ } else if (fnName === 'transpose' && args.length === 1) {
104
+ const arg = migrateAtomic(state, args[0]!);
105
+ return {
106
+ type: arg.type,
107
+ code: `matrix.transpose(${arg.code})`,
108
+ };
109
+ } else if (fnName === 'diag' && (args.length === 1 || args.length === 2)) {
110
+ const a = args.map((a) => migrateAtomic(state, a).code).join(', ');
111
+ return {
112
+ type: 'array',
113
+ code: `matrix.diagonal(${a})`,
114
+ };
115
+ } else if (fnName === 'zeros' || fnName === 'ones' || fnName === 'identity') {
116
+ return {
117
+ type: 'array',
118
+ code: `matrix.${fnName}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
119
+ };
120
+ } else if (fnName === 'concat') {
121
+ const results = args.map((a) => migrateAtomic(state, a));
122
+ if (results.some((r) => r.type === 'string')) return concat(state, results);
123
+ } else if (fnName === 'numeric' && args.length === 1) {
124
+ return toNumber(state, args[0]!);
125
+ } else if (fnName === 'flatten' && args.length === 1) {
126
+ return {
127
+ type: 'array',
128
+ code: `flatten(${migrateAtomic(state, args[0]!).code})`,
129
+ };
130
+ } else if (fnName === 'norm' && args.length === 1) {
131
+ if (isArrayNode(args[0])) {
79
132
  return {
80
133
  type: 'number',
81
- code: `to_timestamp(${migrateAtomic(state, args[0]!).code})`,
134
+ code: `hypot(${args[0].items.map((a) => migrateAtomic(state, a).code).join(', ')})`,
82
135
  };
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') {
102
- return {
103
- type: 'array',
104
- code: `matrix.${fnName}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
105
- };
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) {
112
- return {
113
- type: 'array',
114
- code: `flatten(${migrateAtomic(state, args[0]!).code})`,
115
- };
116
- } else if (fnName === 'norm' && args.length === 1) {
117
- if (isArrayNode(args[0])) {
136
+ }
137
+ return {
138
+ type: 'number',
139
+ code: `hypot(${migrateAtomic(state, args[0]!).code})`,
140
+ };
141
+ } else if (fnName === 'equalText' && args.length === 2) {
142
+ const p = equalText(state, '==', args[0]!, args[1]!);
143
+ return {
144
+ type: p.type,
145
+ code: `${openE}${p.code}${closeE}`,
146
+ };
147
+ } else if (fnName === 'toJson' && args.length === 1) {
148
+ return {
149
+ type: 'string',
150
+ code: `to_json(${migrateAtomic(state, args[0]!).code})`,
151
+ };
152
+ } else if (fnName === 'fromJson' && args.length === 1) {
153
+ return {
154
+ code: `from_json(${migrateAtomic(state, args[0]!).code})`,
155
+ };
156
+ } else if (fnName === 'is' && args.length === 2 && isConstantNode(args[1])) {
157
+ const t = String((args[1] as ConstantNode<string | number>).value);
158
+ switch (t) {
159
+ case 'number':
160
+ case 'string':
161
+ case 'boolean':
118
162
  return {
119
- type: 'number',
120
- code: `hypot(${args[0].items.map((a) => migrateAtomic(state, a).code).join(', ')})`,
163
+ type: 'boolean',
164
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == '${t}'${closeE}`,
121
165
  };
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' 结果可能不同`);
166
+ case 'null':
167
+ case 'undefined':
168
+ return {
169
+ type: 'boolean',
170
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'nil'${closeE}`,
171
+ };
172
+ case 'Array':
173
+ case 'array':
174
+ state.warn(`'type' 与 'is' 结果可能不同`);
175
+ return {
176
+ type: 'boolean',
177
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'array'${closeE}`,
178
+ };
179
+ case 'Object':
180
+ case 'object': {
181
+ const arg0 = migrateAtomic(state, args[0]!);
182
+ if (arg0.code.endsWith('.value.rt_state')) {
168
183
  return {
169
184
  type: 'boolean',
170
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'record'${closeE}`,
185
+ code: `${openE}!!${arg0.code}${closeE}`,
171
186
  };
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' 结果可能不同`);
187
+ }
188
+ state.warn(`'type' 'is' 结果可能不同`);
189
+ return {
190
+ type: 'boolean',
191
+ code: `${openE}type(${arg0.code}) == 'record'${closeE}`,
192
+ };
193
+ }
194
+ case 'Function':
195
+ case 'function': {
196
+ if (symbolName(args[0]!) === 'service' && !state.locals.has('service')) {
181
197
  return {
182
198
  type: 'boolean',
183
- code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'function'${closeE}`,
199
+ code: `${openE}type(service) == 'extern'${closeE}`,
184
200
  };
185
201
  }
202
+ state.warn(`'type' 与 'is' 结果可能不同`);
203
+ return {
204
+ type: 'boolean',
205
+ code: `${openE}type(${migrateAtomic(state, args[0]!).code}) == 'function'${closeE}`,
206
+ };
186
207
  }
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
208
  }
209
+ } else if (fnName === 'typeOf' && args.length === 1) {
210
+ state.warn(`'type' 与 'typeOf' 结果可能不同`);
211
+ return {
212
+ type: 'string',
213
+ code: `type(${migrateAtomic(state, args[0]!).code})`,
214
+ };
215
+ } else if (fnName === 'format' && args.length === 1) {
216
+ return toString(state, args[0]!);
217
+ } else if (fnName === 'print' && args.length === 1) {
218
+ state.loose();
219
+ return toString(state, args[0]!);
220
+ } else if (
221
+ fnName === 'print' &&
222
+ args.length === 2 &&
223
+ isConstantNode(args[0]) &&
224
+ (isObjectNode(args[1]) || isArrayNode(args[1]))
225
+ ) {
226
+ const template = String(args[0].value);
227
+ const values = isObjectNode(args[1])
228
+ ? args[1].properties
229
+ : (args[1].items as unknown as Record<string, MathNode>);
230
+ return {
231
+ type: 'string',
232
+ code:
233
+ '`' +
234
+ template.replaceAll(/`|\$([\w.]+)/g, (original, key: string) => {
235
+ if (original === '`') return '\\`';
236
+ const keys = key.split('.').map((part) => {
237
+ const nPart = Number.parseInt(part);
238
+ if (!Number.isNaN(nPart) && part.length > 0 && nPart >= 1) {
239
+ return nPart - 1;
240
+ } else {
241
+ return part;
242
+ }
243
+ });
244
+ const value = values[keys.shift()!];
245
+ if (value == null) return original.replaceAll('$', String.raw`\$`);
246
+ if (!keys.length) {
247
+ return `$(${migrateAtomic(state, value).code})`;
248
+ }
249
+ return `$(${migrateNode(state, value, { format: 'paren' }).code}.${keys.join('.')})`;
250
+ }) +
251
+ '`',
252
+ };
253
+ } else if (fnName === 'string' && args.length === 1) {
254
+ const inner = migrateAtomic(state, args[0]!);
255
+ return {
256
+ type: 'string',
257
+ code: `to_string(${inner.code})`,
258
+ };
259
+ } else if (
260
+ fnName === 'sum' &&
261
+ args.length === 1 &&
262
+ isArrayNode(args[0]) &&
263
+ args[0].items.length === 1 &&
264
+ isOperatorNode(args[0].items[0]) &&
265
+ ['>=', '>', '<=', '<', '==', '!='].includes(args[0].items[0].op) &&
266
+ typeof constantValue(args[0].items[0].args[1]!) === 'number'
267
+ ) {
268
+ // sum([a > v])
269
+ const a = migrateAtomic(state, args[0].items[0].args[0]!);
270
+ const v = migrateAtomic(state, args[0].items[0].args[1]!);
271
+ return {
272
+ type: 'number',
273
+ code: `sum(map(${a.code}, fn { it ${args[0].items[0].op} ${v.code} }))`,
274
+ };
275
+ }
254
276
 
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}`);
277
+ return call(state, node, (fn, args) => {
278
+ const fun = state.globals.get(fnName);
279
+ if (typeof fun === 'function') {
280
+ if (args.some((a) => a.type !== 'number' && a.type !== 'boolean' && a.type !== 'string')) {
281
+ state.warn(`函数行为可能不一致: ${fnName}`);
263
282
  }
264
- });
283
+ } else {
284
+ state.err(`不支持的函数: ${fnName}`);
285
+ }
286
+ });
287
+ }
288
+
289
+ /** 转换 AST */
290
+ export function migrateCall(state: State, node: FunctionNode<MathNode>, options: Options): Result {
291
+ const { fn, args } = node;
292
+
293
+ const fnName = symbolName(fn);
294
+ if (fnName && !state.locals.has(fnName)) {
295
+ return migrateFunctionCall(state, node, fnName, args, options);
265
296
  }
266
297
 
267
298
  if (
@@ -382,7 +413,7 @@ export function migrateCall(state: State, node: FunctionNode<MathNode>, options:
382
413
  } else if (thisArg.type === 'extern') {
383
414
  const argList = args.map((a) => migrateAtomic(state, a).code);
384
415
  return {
385
- code: `${thisArg.code}(${argList.join(', ')})`,
416
+ code: `${thisArg.code}.${fnName}(${argList.join(', ')})`,
386
417
  };
387
418
  }
388
419
 
@@ -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',
@@ -47,10 +47,18 @@ const BINARY_MATH_OPERATORS = {
47
47
  ],
48
48
  divide: [
49
49
  ' / ',
50
- (l, r) => ({
51
- type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
52
- code: `matrix.multiply(${l.code}, matrix.invert(${r.code}))`,
53
- }),
50
+ (l, r) => {
51
+ if (r.type === 'array' || !r.type) {
52
+ return {
53
+ type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
54
+ code: `matrix.multiply(${l.code}, matrix.invert(${r.code}))`,
55
+ };
56
+ }
57
+ return {
58
+ type: l.type === 'array' ? 'array' : l.type ? 'number' : undefined,
59
+ code: `matrix.entrywise_divide(${l.code}, ${r.code})`,
60
+ };
61
+ },
54
62
  ],
55
63
  dotDivide: [
56
64
  ' / ',
@@ -187,9 +195,9 @@ export function migrateOperator(
187
195
  case 'unaryMinus':
188
196
  case 'unaryPlus': {
189
197
  const op = fn === 'unaryMinus' ? '-' : '+';
190
- const f = fn === 'unaryMinus' ? operations.$Neg : operations.$Pos;
191
198
  const exp = migrateExpr(state, a0);
192
199
  if (typeof exp.literal == 'number') {
200
+ const f = fn === 'unaryMinus' ? operations.$Neg : operations.$Pos;
193
201
  const v = f(exp.literal);
194
202
  return {
195
203
  type: 'number',
@@ -308,6 +316,12 @@ export function migrateOperator(
308
316
  };
309
317
 
310
318
  return binary(state, a(a0), a(a1), (l, r) => {
319
+ if (l.type === 'boolean' && r.type === 'boolean') {
320
+ return {
321
+ type: 'boolean',
322
+ code: `${l.code} ${op} ${r.code}`,
323
+ };
324
+ }
311
325
  return {
312
326
  type: 'boolean',
313
327
  code: `${toNumber(state, l).code} ${op} ${toNumber(state, r).code}`,
@@ -1,4 +1,4 @@
1
- import { createVmContext, type VmContext } from '@mirascript/mirascript';
1
+ import type { VmContext } from '@mirascript/mirascript';
2
2
  import { parse } from './parser.js';
3
3
  import {
4
4
  type AssignmentNode,
@@ -87,7 +87,7 @@ export class BaseState {
87
87
  private readonly expr: string,
88
88
  readonly condition: boolean,
89
89
  /** 可识别的全局环境 */
90
- readonly globals: VmContext = createVmContext(),
90
+ readonly globals: VmContext,
91
91
  ) {}
92
92
  /** 帮助函数 */
93
93
  private readonly helpers = new Set<string>();
@@ -18,6 +18,7 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
18
18
  return {
19
19
  type: operations.$Type(global),
20
20
  code: name,
21
+ global,
21
22
  };
22
23
  }
23
24
  if (name.startsWith('$') || name.startsWith('@')) {
@@ -45,6 +46,18 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
45
46
  code: `@e`,
46
47
  };
47
48
  }
49
+ if (name === '__PREVIEW') {
50
+ return {
51
+ type: 'boolean',
52
+ code: '@PREVIEW',
53
+ };
54
+ }
55
+ if (name === '__ICON') {
56
+ return {
57
+ type: 'boolean',
58
+ code: '@ICON',
59
+ };
60
+ }
48
61
  }
49
62
  state.warn(`符号 '${name}' 未定义`);
50
63
  return {
@@ -46,6 +46,14 @@ export function toString(state: State, node: MathNode | Result): Result {
46
46
  code: node.as_string,
47
47
  };
48
48
  }
49
+ if (node.type === 'number' && node.literal !== undefined) {
50
+ const lit = String(node.literal);
51
+ return {
52
+ type: 'string',
53
+ literal: lit,
54
+ code: serialize(lit),
55
+ };
56
+ }
49
57
  return {
50
58
  type: 'string',
51
59
  code: `to_string(${node.code})`,
@@ -74,6 +82,14 @@ export function toNumber(state: State, node: MathNode | Result): Result {
74
82
  code: node.as_number,
75
83
  };
76
84
  }
85
+ if (node.type === 'string' && node.literal !== undefined) {
86
+ const lit = Number(node.literal);
87
+ return {
88
+ type: 'number',
89
+ literal: lit,
90
+ code: serialize(lit),
91
+ };
92
+ }
77
93
  return {
78
94
  type: 'number',
79
95
  code: `to_number(${node.code})`,
@@ -65,3 +65,24 @@ export function equalText(state: State, op: '==' | '!=', l: MathNode, r: MathNod
65
65
  code: `${toString(state, lr).code} ${op} ${toString(state, rr).code}`,
66
66
  };
67
67
  }
68
+
69
+ /** 计算长度 */
70
+ export function len(state: State, obj: Result): Result {
71
+ if (obj.type === 'string')
72
+ return {
73
+ type: 'number',
74
+ code: `len(chars(${obj.code}))`,
75
+ };
76
+ if (obj.type === 'array')
77
+ return {
78
+ type: 'number',
79
+ code: `len(${obj.code})`,
80
+ };
81
+ state.helper(
82
+ `fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } } else { x.length } }`,
83
+ );
84
+ return {
85
+ type: 'number',
86
+ code: `@@length(${obj.code})`,
87
+ };
88
+ }