@cloudpss/expression 0.6.0-alpha.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 (165) hide show
  1. package/README.md +3 -0
  2. package/dist/analyze.d.ts +7 -0
  3. package/dist/analyze.d.ts.map +1 -0
  4. package/dist/analyze.js +38 -0
  5. package/dist/analyze.js.map +1 -0
  6. package/dist/context.d.ts +41 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +18 -0
  9. package/dist/context.js.map +1 -0
  10. package/dist/definitions/argument.d.ts +33 -0
  11. package/dist/definitions/argument.d.ts.map +1 -0
  12. package/dist/definitions/argument.js +2 -0
  13. package/dist/definitions/argument.js.map +1 -0
  14. package/dist/definitions/constraint.d.ts +32 -0
  15. package/dist/definitions/constraint.d.ts.map +1 -0
  16. package/dist/definitions/constraint.js +20 -0
  17. package/dist/definitions/constraint.js.map +1 -0
  18. package/dist/definitions/parameter-decoration.d.ts +27 -0
  19. package/dist/definitions/parameter-decoration.d.ts.map +1 -0
  20. package/dist/definitions/parameter-decoration.js +17 -0
  21. package/dist/definitions/parameter-decoration.js.map +1 -0
  22. package/dist/definitions/parameter-group.d.ts +38 -0
  23. package/dist/definitions/parameter-group.d.ts.map +1 -0
  24. package/dist/definitions/parameter-group.js +31 -0
  25. package/dist/definitions/parameter-group.js.map +1 -0
  26. package/dist/definitions/parameter.d.ts +214 -0
  27. package/dist/definitions/parameter.d.ts.map +1 -0
  28. package/dist/definitions/parameter.js +44 -0
  29. package/dist/definitions/parameter.js.map +1 -0
  30. package/dist/definitions/variable.d.ts +14 -0
  31. package/dist/definitions/variable.d.ts.map +1 -0
  32. package/dist/definitions/variable.js +14 -0
  33. package/dist/definitions/variable.js.map +1 -0
  34. package/dist/definitions.d.ts +7 -0
  35. package/dist/definitions.d.ts.map +1 -0
  36. package/dist/definitions.js +7 -0
  37. package/dist/definitions.js.map +1 -0
  38. package/dist/eval.d.ts +15 -0
  39. package/dist/eval.d.ts.map +1 -0
  40. package/dist/eval.js +54 -0
  41. package/dist/eval.js.map +1 -0
  42. package/dist/expression.d.ts +37 -0
  43. package/dist/expression.d.ts.map +1 -0
  44. package/dist/expression.js +18 -0
  45. package/dist/expression.js.map +1 -0
  46. package/dist/index.d.ts +6 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +5 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/main.d.ts +49 -0
  51. package/dist/main.d.ts.map +1 -0
  52. package/dist/main.js +238 -0
  53. package/dist/main.js.map +1 -0
  54. package/dist/migrate.d.ts +5 -0
  55. package/dist/migrate.d.ts.map +1 -0
  56. package/dist/migrate.js +23 -0
  57. package/dist/migrate.js.map +1 -0
  58. package/dist/migrator/access.d.ts +6 -0
  59. package/dist/migrator/access.d.ts.map +1 -0
  60. package/dist/migrator/access.js +176 -0
  61. package/dist/migrator/access.js.map +1 -0
  62. package/dist/migrator/call.d.ts +6 -0
  63. package/dist/migrator/call.d.ts.map +1 -0
  64. package/dist/migrator/call.js +405 -0
  65. package/dist/migrator/call.js.map +1 -0
  66. package/dist/migrator/concat.d.ts +6 -0
  67. package/dist/migrator/concat.d.ts.map +1 -0
  68. package/dist/migrator/concat.js +32 -0
  69. package/dist/migrator/concat.js.map +1 -0
  70. package/dist/migrator/condition.d.ts +6 -0
  71. package/dist/migrator/condition.d.ts.map +1 -0
  72. package/dist/migrator/condition.js +110 -0
  73. package/dist/migrator/condition.js.map +1 -0
  74. package/dist/migrator/interface.d.ts +24 -0
  75. package/dist/migrator/interface.d.ts.map +1 -0
  76. package/dist/migrator/interface.js +8 -0
  77. package/dist/migrator/interface.js.map +1 -0
  78. package/dist/migrator/node.d.ts +12 -0
  79. package/dist/migrator/node.d.ts.map +1 -0
  80. package/dist/migrator/node.js +119 -0
  81. package/dist/migrator/node.js.map +1 -0
  82. package/dist/migrator/operator.d.ts +6 -0
  83. package/dist/migrator/operator.d.ts.map +1 -0
  84. package/dist/migrator/operator.js +299 -0
  85. package/dist/migrator/operator.js.map +1 -0
  86. package/dist/migrator/parser.d.ts +3 -0
  87. package/dist/migrator/parser.d.ts.map +1 -0
  88. package/dist/migrator/parser.js +76 -0
  89. package/dist/migrator/parser.js.map +1 -0
  90. package/dist/migrator/special.d.ts +4 -0
  91. package/dist/migrator/special.d.ts.map +1 -0
  92. package/dist/migrator/special.js +26 -0
  93. package/dist/migrator/special.js.map +1 -0
  94. package/dist/migrator/state.d.ts +64 -0
  95. package/dist/migrator/state.d.ts.map +1 -0
  96. package/dist/migrator/state.js +251 -0
  97. package/dist/migrator/state.js.map +1 -0
  98. package/dist/migrator/symbol.d.ts +6 -0
  99. package/dist/migrator/symbol.d.ts.map +1 -0
  100. package/dist/migrator/symbol.js +50 -0
  101. package/dist/migrator/symbol.js.map +1 -0
  102. package/dist/migrator/to-type.d.ts +10 -0
  103. package/dist/migrator/to-type.d.ts.map +1 -0
  104. package/dist/migrator/to-type.js +89 -0
  105. package/dist/migrator/to-type.js.map +1 -0
  106. package/dist/migrator/utils.d.ts +14 -0
  107. package/dist/migrator/utils.d.ts.map +1 -0
  108. package/dist/migrator/utils.js +64 -0
  109. package/dist/migrator/utils.js.map +1 -0
  110. package/dist/parser.d.ts +41 -0
  111. package/dist/parser.d.ts.map +1 -0
  112. package/dist/parser.js +36 -0
  113. package/dist/parser.js.map +1 -0
  114. package/dist/scope.d.ts +43 -0
  115. package/dist/scope.d.ts.map +1 -0
  116. package/dist/scope.js +204 -0
  117. package/dist/scope.js.map +1 -0
  118. package/dist/type.d.ts +21 -0
  119. package/dist/type.d.ts.map +1 -0
  120. package/dist/type.js +50 -0
  121. package/dist/type.js.map +1 -0
  122. package/jest.config.js +3 -0
  123. package/package.json +26 -0
  124. package/src/analyze.ts +44 -0
  125. package/src/context.ts +54 -0
  126. package/src/definitions/argument.ts +40 -0
  127. package/src/definitions/constraint.ts +49 -0
  128. package/src/definitions/parameter-decoration.ts +42 -0
  129. package/src/definitions/parameter-group.ts +62 -0
  130. package/src/definitions/parameter.ts +276 -0
  131. package/src/definitions/variable.ts +23 -0
  132. package/src/definitions.ts +6 -0
  133. package/src/eval.ts +64 -0
  134. package/src/expression.ts +62 -0
  135. package/src/index.ts +5 -0
  136. package/src/main.ts +295 -0
  137. package/src/migrate.ts +25 -0
  138. package/src/migrator/access.ts +201 -0
  139. package/src/migrator/call.ts +397 -0
  140. package/src/migrator/concat.ts +28 -0
  141. package/src/migrator/condition.ts +120 -0
  142. package/src/migrator/interface.ts +30 -0
  143. package/src/migrator/node.ts +131 -0
  144. package/src/migrator/operator.ts +339 -0
  145. package/src/migrator/parser.ts +106 -0
  146. package/src/migrator/special.ts +30 -0
  147. package/src/migrator/state.ts +262 -0
  148. package/src/migrator/symbol.ts +53 -0
  149. package/src/migrator/to-type.ts +93 -0
  150. package/src/migrator/utils.ts +67 -0
  151. package/src/parser.ts +69 -0
  152. package/src/scope.ts +208 -0
  153. package/src/type.ts +66 -0
  154. package/tests/analyze.ts +15 -0
  155. package/tests/condition.ts +49 -0
  156. package/tests/definition.ts +42 -0
  157. package/tests/eval-complex.ts +150 -0
  158. package/tests/eval.ts +42 -0
  159. package/tests/functions.ts +22 -0
  160. package/tests/import.ts +31 -0
  161. package/tests/scope.ts +33 -0
  162. package/tests/setup.ts +41 -0
  163. package/tests/tsconfig.json +3 -0
  164. package/tests/unwrap.ts +46 -0
  165. package/tsconfig.json +3 -0
@@ -0,0 +1,62 @@
1
+ import type { VmValue } from '@mirascript/mirascript';
2
+ import type { Tagged } from 'type-fest';
3
+ import type { LegacyType, TsTypeOf, TypeInfo } from './type.js';
4
+ import type { Scope } from './scope.js';
5
+
6
+ /** 表达式的标记属性 */
7
+ export const ExpressionTag = 'ɵexp' as const;
8
+
9
+ // 使用 `type` 确保 Expression 是一个 VmRecord
10
+ /** 表示一个表达式 */
11
+ export type Expression<T extends VmValue = VmValue> = {
12
+ /** 标记,表示求值结果的类型,"" 表示无需转换类型 */
13
+ readonly [ExpressionTag]: TypeInfo | LegacyType | '';
14
+ /** 表达式内容 */
15
+ readonly source: ExpressionSource<T>;
16
+ };
17
+
18
+ /**
19
+ * 编译后的表达式
20
+ */
21
+ export interface CompiledExpression<T extends VmValue = VmValue> extends Expression<T> {
22
+ (scope: Scope, defaults: T): T;
23
+ (scope?: Scope): T | undefined;
24
+ }
25
+
26
+ /** 表示一个表达式内容 */
27
+ export type ExpressionSource<T extends VmValue = VmValue> = Tagged<string, 'ExpressionResult', T>;
28
+
29
+ /** 表示表达式或值 */
30
+ export type ExpressionOrValue<T extends VmValue = VmValue> = Expression<T> | T;
31
+ /** 创建一个指定类型的表达式 */
32
+ export function Expression<S extends TypeInfo | LegacyType>(
33
+ source: string | Expression<TsTypeOf<S>>,
34
+ returnType: S,
35
+ ): Expression<TsTypeOf<S>>;
36
+ /** 创建一个不转换结果类型的表达式 */
37
+ export function Expression<T extends VmValue = VmValue>(source: string | Expression<T>): Expression<T>;
38
+
39
+ /** 创建一个表达式 */
40
+ export function Expression<T extends VmValue = VmValue>(
41
+ source: string | Expression<T>,
42
+ returnType: TypeInfo | LegacyType | '' = '',
43
+ ): Expression<T> {
44
+ if (!source) throw new Error(`Invalid expression ${String(source)}`);
45
+ const s = isExpression(source) ? source.source : source;
46
+ if (!s || typeof s != 'string') throw new Error(`Invalid expression ${String(s)}`);
47
+ return { [ExpressionTag]: returnType, source: s as ExpressionSource<T> };
48
+ }
49
+
50
+ /** 检查是否为表达式 */
51
+ export function isExpression<T extends VmValue>(value: unknown): value is Expression<T> | CompiledExpression<T> {
52
+ if (value == null || (typeof value != 'object' && typeof value != 'function')) return false;
53
+ return ExpressionTag in value;
54
+ }
55
+
56
+ /** 表示一个表达式求值的结果 */
57
+ export type ExpressionResult<T> = T extends Expression<infer R> ? R : T;
58
+
59
+ /** 表示一个含表达式对象求值的结果 */
60
+ export type ExpressionResultObject<T> = {
61
+ [K in keyof T]: ExpressionResult<T[K]>;
62
+ };
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type { Options, Context } from './context.js';
2
+ export { Scope } from './scope.js';
3
+ export { Evaluator } from './main.js';
4
+ export * from './expression.js';
5
+ export * from './type.js';
package/src/main.ts ADDED
@@ -0,0 +1,295 @@
1
+ import type { LiteralUnion, SetFieldType } from 'type-fest';
2
+ import { isVmArray, wrapToVmValue, type VmValue } from '@mirascript/mirascript';
3
+ import { lib } from '@mirascript/mirascript/subtle';
4
+ import { klona } from 'klona';
5
+ import { Context, type Options } from './context.js';
6
+ import { parse } from './parser.js';
7
+ import { analyze } from './analyze.js';
8
+ import {
9
+ type CompiledExpression,
10
+ Expression,
11
+ type ExpressionOrValue,
12
+ ExpressionTag,
13
+ isExpression,
14
+ } from './expression.js';
15
+ import { evaluate } from './eval.js';
16
+ import { TypeInfo } from './type.js';
17
+ import { Scope } from './scope.js';
18
+ import type {
19
+ ArgumentMap,
20
+ ArgumentValue,
21
+ Choice,
22
+ ChoiceArgumentValue,
23
+ ConditionExpression,
24
+ Parameter,
25
+ ParameterFunction,
26
+ ParameterGroup,
27
+ ParameterMap,
28
+ } from './definitions.js';
29
+ const { isArray } = Array;
30
+ const { to_number, to_string } = lib;
31
+
32
+ /**
33
+ * 表达式求值
34
+ */
35
+ export class Evaluator {
36
+ constructor(options?: Partial<Options>) {
37
+ this.context = new Context(options);
38
+ }
39
+ /** 选项 */
40
+ get options(): Options {
41
+ return this.context.options;
42
+ }
43
+
44
+ /** 上下文 */
45
+ private readonly context: Context;
46
+
47
+ /** 默认执行环境 */
48
+ private readonly scope = new Scope(undefined, false, '<evaluator>');
49
+
50
+ /** 导入到执行环境 */
51
+ import(values: Record<string, unknown>): void {
52
+ for (const [key, value] of Object.entries(values)) {
53
+ if (value === undefined) {
54
+ this.context.imported.delete(key);
55
+ } else {
56
+ this.context.imported.set(key, wrapToVmValue(value, null));
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 编译表达式
63
+ */
64
+ compile<T extends VmValue>(expression: Expression<T>, throws = true): CompiledExpression<T> {
65
+ const value = parse(this.context, expression.source, throws);
66
+ const tag = expression[ExpressionTag];
67
+ const type = tag ? TypeInfo.parse(tag) : null;
68
+ const compiled = ((scope, defaults) => {
69
+ const ret = evaluate(this.context, value, type, scope, defaults);
70
+ return ret;
71
+ }) as CompiledExpression<T>;
72
+ Object.defineProperty(compiled, ExpressionTag, { value: type, enumerable: true });
73
+ Object.defineProperty(compiled, 'source', { value: value.source, enumerable: true });
74
+ return compiled;
75
+ }
76
+
77
+ /**
78
+ * 求值
79
+ */
80
+ evaluate<T extends VmValue>(expression: ExpressionOrValue<T> | undefined, scope?: Scope): T | undefined;
81
+ /**
82
+ * 求值
83
+ */
84
+ evaluate<T extends VmValue, D = T>(
85
+ expression: ExpressionOrValue<T> | undefined,
86
+ scope: Scope | undefined,
87
+ defaults: D,
88
+ ): T | D;
89
+ /**
90
+ * 求值
91
+ */
92
+ evaluate<T extends VmValue, D = T>(
93
+ expression: ExpressionOrValue<T> | undefined,
94
+ scope: Scope | undefined,
95
+ defaults?: D,
96
+ ): T | D | undefined {
97
+ if (expression == null) return defaults;
98
+ if (!isExpression(expression)) return expression;
99
+ const throws = scope?.throws ?? true;
100
+ const tag = expression[ExpressionTag];
101
+ const type = tag ? TypeInfo.parse(tag) : null;
102
+ const value = parse(this.context, expression.source, throws);
103
+ const result = evaluate<T, D | undefined>(this.context, value, type, scope ?? this.scope, defaults);
104
+ return result;
105
+ }
106
+
107
+ /** 求条件 */
108
+ evaluateCondition(condition: ConditionExpression | undefined, scope: Scope | undefined, defaults = true): boolean {
109
+ if (typeof condition == 'boolean') return condition;
110
+ if (condition == null) return defaults;
111
+ condition = (
112
+ typeof condition == 'string' ? condition : lib.to_string(condition)
113
+ ).trim() as ConditionExpression & string;
114
+ if (condition === '') return defaults;
115
+ if (condition === 'true') return true;
116
+ if (condition === 'false') return false;
117
+ try {
118
+ return this.evaluate(Expression(condition, 'boolean'), scope, defaults);
119
+ } catch (ex) {
120
+ if (scope?.throws) {
121
+ throw ex;
122
+ }
123
+ this.options.logger.warn(`Invalid condition`, ex);
124
+ return defaults;
125
+ }
126
+ }
127
+
128
+ /** 求选项列表 */
129
+ evaluateChoices<V extends ChoiceArgumentValue>(
130
+ choices: ExpressionOrValue<Array<Choice<V>>> | ParameterFunction<Array<Choice<V>>> | undefined,
131
+ scope: Scope | undefined,
132
+ ): ReadonlyArray<SetFieldType<Choice<V>, 'key', V>> {
133
+ if (choices == null) return [];
134
+ const result =
135
+ typeof choices == 'function' ? choices(scope ?? this.scope, this) : this.evaluate(choices, scope, []);
136
+ if (!isArray(result)) {
137
+ if (scope?.throws) {
138
+ throw new Error(`Choices is not an array`);
139
+ }
140
+ return [];
141
+ }
142
+ return result.map((item, index): SetFieldType<Choice<V>, 'key', V> => {
143
+ if (item == null) {
144
+ return { key: index as V, name: String(index), description: '' };
145
+ }
146
+ if (typeof item == 'string' || typeof item == 'number' || typeof item == 'boolean') {
147
+ return { key: item as V, name: String(item), description: '' };
148
+ }
149
+ return {
150
+ key: (item.key ?? index) as V,
151
+ name: item.name ?? String(item.key ?? index),
152
+ description: item.description ?? '',
153
+ condition: item.condition,
154
+ };
155
+ });
156
+ }
157
+
158
+ /** 转换参数值的类型 */
159
+ toArg<V extends ArgumentValue>(value: VmValue, definition: Parameter): V {
160
+ switch (definition.type as LiteralUnion<keyof ParameterMap, string>) {
161
+ case 'real':
162
+ case 'integer':
163
+ return to_number(value) as V;
164
+ case 'file':
165
+ case 'text':
166
+ case 'code':
167
+ case 'pinLike':
168
+ case 'cssColor':
169
+ case 'cssBackground':
170
+ case 'resourceId':
171
+ return to_string(value) as V;
172
+
173
+ case 'choice':
174
+ case 'logical':
175
+ return value as V;
176
+
177
+ case 'multiSelect':
178
+ case 'table': {
179
+ if (isVmArray(value)) {
180
+ return value as V;
181
+ }
182
+ return (value != null ? [value] : []) as ArgumentValue as V;
183
+ }
184
+ case 'grouped':
185
+ case 'record':
186
+ default:
187
+ return value as V;
188
+ }
189
+ }
190
+
191
+ /** 求参数值 */
192
+ evaluateArg<V extends ArgumentValue>(
193
+ expression: ExpressionOrValue<V> | undefined,
194
+ scope: Scope | undefined,
195
+ definition: Parameter,
196
+ ): V {
197
+ const result =
198
+ this.evaluate<V>(expression ?? (definition.value as ExpressionOrValue<V>), scope) ??
199
+ (definition.value as V);
200
+ return this.toArg<V>(result, definition);
201
+ }
202
+ /** 求参数表的所有参数值 */
203
+ evaluateArgs<T extends Record<string, ArgumentValue>>(
204
+ args: ArgumentMap<T> | undefined,
205
+ scope: Scope | undefined,
206
+ definition: ReadonlyArray<Pick<ParameterGroup, 'items'>>,
207
+ ): T {
208
+ const a = this.defaultArgs(definition, args);
209
+ const ret: Record<string, ArgumentValue> = {};
210
+ for (const group of definition) {
211
+ for (const param of group.items) {
212
+ const { key } = param;
213
+ if (!key) continue;
214
+ const arg = a[key];
215
+ ret[key] = this.evaluateArg(arg, scope, param);
216
+ }
217
+ }
218
+ return ret as T;
219
+ }
220
+ /** 获取参数默认值,修复现有的参数,并填充缺失的参数 */
221
+ defaultArgs<T extends Record<string, ArgumentValue>>(
222
+ definition: ReadonlyArray<Pick<ParameterGroup, 'items'>> | undefined,
223
+ args: ArgumentMap<T> = {} as ArgumentMap<T>,
224
+ ): ArgumentMap<T> {
225
+ if (definition == null || !isArray(definition as unknown[])) {
226
+ return args;
227
+ }
228
+ /** 参数键的类型 */
229
+ type K = keyof T & string;
230
+ /** 参数值的类型 */
231
+ type V = NonNullable<T[K]>;
232
+ const keys = new Set(Object.keys(args) as K[]);
233
+
234
+ // 根据定义填充或修复参数
235
+ for (const group of definition) {
236
+ for (const param of group.items) {
237
+ const key = param.key as K;
238
+ if (!key) continue;
239
+ const currentValue = keys.has(key) ? args[key] : undefined;
240
+ if (currentValue == null) {
241
+ // 使用默认值填充
242
+ const defaultValue = param.value;
243
+ if (isExpression(defaultValue)) {
244
+ args[key] = { [ExpressionTag]: '', source: defaultValue.source } as Expression<V>;
245
+ } else {
246
+ const value =
247
+ defaultValue != null && typeof defaultValue == 'object'
248
+ ? klona(defaultValue)
249
+ : defaultValue;
250
+ args[key] = this.toArg<V>(value, param);
251
+ }
252
+ } else {
253
+ // 已存在的参数值
254
+ if (isExpression(currentValue)) {
255
+ // 保留表达式
256
+ } else {
257
+ // 修复现有的参数值
258
+ const castedValue = this.toArg<V>(currentValue, param);
259
+ if (castedValue != null && castedValue !== currentValue) {
260
+ args[key] = castedValue;
261
+ }
262
+ }
263
+ }
264
+ keys.delete(key);
265
+ }
266
+ }
267
+
268
+ // 删除多余的参数
269
+ for (const key of keys) {
270
+ // 保留特殊参数和私有字段
271
+ if (key.startsWith('_') || key.startsWith('$') || key.startsWith('@')) continue;
272
+ delete args[key];
273
+ }
274
+ return args;
275
+ }
276
+
277
+ /** 解析表达式 */
278
+ validate<T extends VmValue>(expression: Expression<T>): void {
279
+ if (!isExpression(expression) || !expression.source) {
280
+ throw new Error(`${expression.source || String(expression)} is not a valid expression`);
281
+ }
282
+ const tag = expression[ExpressionTag];
283
+ if (tag) TypeInfo.parse(tag);
284
+ parse(this.context, expression.source, true);
285
+ }
286
+
287
+ /** 分析表达式 */
288
+ analyze<T extends VmValue>(expression: Expression<T>): string[][] {
289
+ if (!isExpression(expression) || !expression.source) {
290
+ throw new Error(`${expression.source || String(expression)} is not a valid expression`);
291
+ }
292
+ const exp = parse(this.context, expression.source, true);
293
+ return analyze(this.context, exp);
294
+ }
295
+ }
package/src/migrate.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { VmContext, VmValue } from '@mirascript/mirascript';
2
+ import { BaseState } from './migrator/state.js';
3
+ import type { ExpressionSource } from './expression.js';
4
+
5
+ /** MathJs 表达式转为 MiraScript 表达式 */
6
+ export function migrateMathJs<const T extends VmValue>(
7
+ expr: ExpressionSource<T>,
8
+ condition: boolean,
9
+ context?: VmContext,
10
+ ): ExpressionSource<T> {
11
+ // 检查输入
12
+ if (!expr) return '' as ExpressionSource<T>;
13
+ expr = expr.trim() as ExpressionSource<T>;
14
+ if (!expr || expr === 'undefined') return '' as ExpressionSource<T>;
15
+ if (expr === 'null') return 'nil' as ExpressionSource<T>;
16
+ if (expr === 'true') return 'true' as ExpressionSource<T>;
17
+ if (expr === 'false') return 'false' as ExpressionSource<T>;
18
+ if (/^[-+]?(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)$/.test(expr)) {
19
+ return expr;
20
+ }
21
+
22
+ const state = new BaseState(expr, condition, context);
23
+ state.migrate();
24
+ return state.result() as ExpressionSource<T>;
25
+ }
@@ -0,0 +1,201 @@
1
+ import {
2
+ type ConstantNode,
3
+ isConstantNode,
4
+ isRangeNode,
5
+ type AccessorNode,
6
+ type MathNode,
7
+ isFunctionNode,
8
+ isSymbolNode,
9
+ isAccessorNode,
10
+ isAssignmentNode,
11
+ type AssignmentNode,
12
+ } from 'mathjs';
13
+ import type { Result } from './interface.js';
14
+ import { migrateAtomic, migrateExpr } from './node.js';
15
+ import type { State } from './state.js';
16
+ import { unsupportedNode } from './utils.js';
17
+ import { serialize, serializePropName } from '@mirascript/mirascript/subtle';
18
+
19
+ /** 常量访问 */
20
+ function constIndexing(node: ConstantNode): Result {
21
+ const { value } = node as ConstantNode<string | number>;
22
+ if (typeof value == 'string') {
23
+ return { type: 'string', literal: value, code: serialize(value) };
24
+ } else if (typeof value == 'number' && Number.isInteger(value) && value > 0) {
25
+ return { type: 'number', literal: value - 1, code: serialize(value - 1) };
26
+ } else {
27
+ return { code: serialize(value) };
28
+ }
29
+ }
30
+
31
+ /** 字符串转为数组 */
32
+ function stringToArray(state: State, ret: Result): Result {
33
+ if (ret.type === 'string') {
34
+ return {
35
+ type: 'array',
36
+ code: `chars(${ret.code})`,
37
+ };
38
+ }
39
+ if (ret.type === 'array') {
40
+ return ret;
41
+ }
42
+ state.helper(`fn @@maybe_chars(x) { if type(x) == 'string' { chars(x) } else { x } }`);
43
+ return {
44
+ type: 'array',
45
+ code: `@@maybe_chars(${ret.code})`,
46
+ };
47
+ }
48
+
49
+ /** 访问节点 */
50
+ function accessChain(
51
+ state: State,
52
+ object: MathNode,
53
+ index: ReadonlyArray<string | number>,
54
+ assignment: boolean,
55
+ ): Result {
56
+ const obj = migrateExpr(state, object);
57
+ const chain = index.map((i) => {
58
+ if (typeof i == 'number') return `[${i}]`;
59
+ const s = serializePropName(i);
60
+ if (s !== i) return `[${s}]`;
61
+ return `.${s}`;
62
+ });
63
+ if (!assignment && typeof index.at(-1) == 'number') {
64
+ const prev = chain.slice(0, -1).join('');
65
+ return {
66
+ code: `${
67
+ stringToArray(state, {
68
+ code: `${obj.code}${prev}`,
69
+ }).code
70
+ }${chain.at(-1)}`,
71
+ };
72
+ }
73
+ return {
74
+ code: `${migrateExpr(state, object).code}${chain.join('')}`,
75
+ };
76
+ }
77
+
78
+ /** 访问节点 */
79
+ function access(
80
+ state: State,
81
+ node: MathNode,
82
+ assignment: boolean,
83
+ ): Result | [object: MathNode, index: Array<string | number>] {
84
+ if (!isAccessorNode(node) && !isAssignmentNode(node)) {
85
+ return [node, []];
86
+ }
87
+ const { object, index } = node;
88
+ if (!index || index.dimensions.length !== 1 || !index.dimensions[0]) {
89
+ return unsupportedNode(state, node);
90
+ }
91
+ const dim0 = index.dimensions[0];
92
+
93
+ if (isRangeNode(dim0)) {
94
+ const obj = migrateExpr(state, object);
95
+ if (obj.type === 'array' && !dim0.step && isConstantNode(dim0.start) && isConstantNode(dim0.end)) {
96
+ const start = constIndexing(dim0.start);
97
+ const end = constIndexing(dim0.end);
98
+ if (start.type === 'number' && end.type === 'number') {
99
+ return {
100
+ type: 'array',
101
+ code: `${obj.code}[${start.code}..${end.code}]`,
102
+ };
103
+ }
104
+ }
105
+ const d = unsupportedNode(state, dim0);
106
+ return {
107
+ code: `${obj.code}[${d.code}]`,
108
+ };
109
+ }
110
+ if (index.dotNotation) {
111
+ if (!isConstantNode(dim0)) return unsupportedNode(state, node);
112
+ const { value } = dim0 as ConstantNode<string | number>;
113
+ if (typeof value != 'string') return unsupportedNode(state, node);
114
+ if (!assignment && value === 'length') {
115
+ const obj = migrateExpr(state, object);
116
+ if (obj.type === 'string')
117
+ return {
118
+ type: 'number',
119
+ code: `len(chars(${obj.code}))`,
120
+ };
121
+ if (obj.type === 'array')
122
+ return {
123
+ type: 'number',
124
+ code: `len(${obj.code})`,
125
+ };
126
+ state.helper(
127
+ `fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } } else { x.length } }`,
128
+ );
129
+ return {
130
+ code: `@@length(${obj.code})`,
131
+ };
132
+ }
133
+ const obj = access(state, object, assignment);
134
+ if (Array.isArray(obj)) {
135
+ return [obj[0], [...obj[1], value]];
136
+ }
137
+ return {
138
+ code: `${obj.code}.${value}`,
139
+ };
140
+ }
141
+ if (isConstantNode(dim0)) {
142
+ const index = constIndexing(dim0);
143
+ if (index.type === 'string') {
144
+ const obj = access(state, object, assignment);
145
+ if (Array.isArray(obj)) {
146
+ return [obj[0], [...obj[1], index.literal as string]];
147
+ }
148
+ return { code: `${obj.code}[${index.code}]` };
149
+ } else if (index.type === 'number') {
150
+ if (
151
+ !assignment &&
152
+ index.literal === 0 &&
153
+ isFunctionNode(object) &&
154
+ isSymbolNode(object.fn) &&
155
+ !state.locals.has('size') &&
156
+ !('size' in state.globals) &&
157
+ object.fn.name === 'size' &&
158
+ object.args.length === 1
159
+ ) {
160
+ const el = stringToArray(state, migrateAtomic(state, object.args[0]!));
161
+ return {
162
+ type: 'number',
163
+ code: `len(${el.code})`,
164
+ };
165
+ }
166
+ const obj = access(state, object, assignment);
167
+ if (Array.isArray(obj)) {
168
+ return [obj[0], [...obj[1], index.literal as number]];
169
+ }
170
+ return { code: `${stringToArray(state, obj).code}[${index.code}]` };
171
+ } else {
172
+ const obj = migrateExpr(state, object);
173
+ state.err(`不支持的索引: ${node.toString()}`);
174
+ return { code: `${obj.code}[${index.code}]` };
175
+ }
176
+ }
177
+ const d0 = migrateAtomic(state, dim0);
178
+ const obj = migrateExpr(state, object);
179
+ if (obj.literal && obj.type === 'record') {
180
+ return {
181
+ code: `${obj.code}[${d0.code}]`,
182
+ };
183
+ }
184
+ if (obj.literal && obj.type === 'array') {
185
+ return {
186
+ code: `${obj.code}[${d0.code} - 1]`,
187
+ };
188
+ }
189
+ state.helper(`fn @@index(index) { if type(index) == 'number' { index - 1 } else { index } }`);
190
+ return { code: `${obj.code}[@@index(${d0.code})]` };
191
+ }
192
+
193
+ /** 访问节点 */
194
+ export function migrateAccess(state: State, node: AccessorNode | AssignmentNode): Result {
195
+ const assignment = isAssignmentNode(node);
196
+ const ret = access(state, node, assignment);
197
+ if (Array.isArray(ret)) {
198
+ return accessChain(state, ret[0], ret[1], assignment);
199
+ }
200
+ return ret;
201
+ }