@cloudpss/expression 0.6.0-alpha.2 → 0.6.0-alpha.20

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 (169) 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 +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-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 +28 -0
  24. package/dist/definitions/utils.d.ts.map +1 -0
  25. package/dist/definitions/utils.js +209 -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 +6 -6
  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 +149 -147
  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 +45 -25
  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 +298 -201
  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/function.d.ts +6 -0
  69. package/dist/migrator/function.d.ts.map +1 -0
  70. package/dist/migrator/function.js +25 -0
  71. package/dist/migrator/function.js.map +1 -0
  72. package/dist/migrator/interface.d.ts +3 -1
  73. package/dist/migrator/interface.d.ts.map +1 -1
  74. package/dist/migrator/interface.js.map +1 -1
  75. package/dist/migrator/node.d.ts.map +1 -1
  76. package/dist/migrator/node.js +35 -6
  77. package/dist/migrator/node.js.map +1 -1
  78. package/dist/migrator/operator.d.ts.map +1 -1
  79. package/dist/migrator/operator.js +107 -60
  80. package/dist/migrator/operator.js.map +1 -1
  81. package/dist/migrator/serialize.d.ts +4 -0
  82. package/dist/migrator/serialize.d.ts.map +1 -0
  83. package/dist/migrator/serialize.js +21 -0
  84. package/dist/migrator/serialize.js.map +1 -0
  85. package/dist/migrator/special.d.ts.map +1 -1
  86. package/dist/migrator/special.js +31 -0
  87. package/dist/migrator/special.js.map +1 -1
  88. package/dist/migrator/state.d.ts +2 -2
  89. package/dist/migrator/state.d.ts.map +1 -1
  90. package/dist/migrator/state.js +30 -33
  91. package/dist/migrator/state.js.map +1 -1
  92. package/dist/migrator/symbol.d.ts.map +1 -1
  93. package/dist/migrator/symbol.js +23 -9
  94. package/dist/migrator/symbol.js.map +1 -1
  95. package/dist/migrator/to-type.d.ts.map +1 -1
  96. package/dist/migrator/to-type.js +21 -4
  97. package/dist/migrator/to-type.js.map +1 -1
  98. package/dist/migrator/utils.d.ts +6 -0
  99. package/dist/migrator/utils.d.ts.map +1 -1
  100. package/dist/migrator/utils.js +35 -0
  101. package/dist/migrator/utils.js.map +1 -1
  102. package/dist/parser.d.ts +2 -2
  103. package/dist/parser.d.ts.map +1 -1
  104. package/dist/parser.js +27 -8
  105. package/dist/parser.js.map +1 -1
  106. package/dist/re-exports.d.ts +4 -0
  107. package/dist/re-exports.d.ts.map +1 -0
  108. package/dist/re-exports.js +3 -0
  109. package/dist/re-exports.js.map +1 -0
  110. package/dist/scope.d.ts +17 -18
  111. package/dist/scope.d.ts.map +1 -1
  112. package/dist/scope.js +84 -53
  113. package/dist/scope.js.map +1 -1
  114. package/dist/type.d.ts +27 -6
  115. package/dist/type.d.ts.map +1 -1
  116. package/dist/type.js +34 -15
  117. package/dist/type.js.map +1 -1
  118. package/package.json +8 -5
  119. package/src/analyze.ts +77 -37
  120. package/src/definitions/argument.ts +3 -13
  121. package/src/definitions/constraint.ts +3 -3
  122. package/src/definitions/parameter-decoration.ts +3 -9
  123. package/src/definitions/parameter-group.ts +4 -19
  124. package/src/definitions/parameter.ts +70 -62
  125. package/src/definitions/utils.ts +224 -0
  126. package/src/definitions/variable.ts +1 -6
  127. package/src/definitions.ts +1 -0
  128. package/src/eval.ts +16 -25
  129. package/src/expression.ts +16 -8
  130. package/src/index.ts +3 -1
  131. package/src/interface.ts +35 -0
  132. package/src/main.ts +217 -199
  133. package/src/migrate.ts +30 -6
  134. package/src/migrator/access.ts +50 -33
  135. package/src/migrator/call.ts +287 -190
  136. package/src/migrator/concat.ts +15 -2
  137. package/src/migrator/function.ts +27 -0
  138. package/src/migrator/interface.ts +3 -1
  139. package/src/migrator/node.ts +36 -5
  140. package/src/migrator/operator.ts +110 -64
  141. package/src/migrator/serialize.ts +21 -0
  142. package/src/migrator/special.ts +31 -0
  143. package/src/migrator/state.ts +30 -33
  144. package/src/migrator/symbol.ts +23 -9
  145. package/src/migrator/to-type.ts +23 -4
  146. package/src/migrator/utils.ts +38 -0
  147. package/src/parser.ts +33 -8
  148. package/src/re-exports.ts +47 -0
  149. package/src/scope.ts +101 -65
  150. package/src/type.ts +52 -18
  151. package/tests/analyze.ts +40 -6
  152. package/tests/compile.ts +65 -0
  153. package/tests/condition.ts +33 -17
  154. package/tests/definition.ts +237 -18
  155. package/tests/eval-complex.ts +19 -11
  156. package/tests/eval.ts +59 -12
  157. package/tests/import.ts +21 -7
  158. package/tests/main.ts +58 -0
  159. package/tests/migrate.ts +308 -0
  160. package/tests/scope.ts +3 -3
  161. package/tests/template.ts +36 -0
  162. package/dist/context.d.ts +0 -41
  163. package/dist/context.d.ts.map +0 -1
  164. package/dist/context.js +0 -18
  165. package/dist/context.js.map +0 -1
  166. package/jest.config.js +0 -3
  167. package/src/context.ts +0 -54
  168. package/tests/tsconfig.json +0 -3
  169. package/tsconfig.json +0 -3
@@ -0,0 +1,35 @@
1
+ /** 日志 */
2
+ export interface Logger {
3
+ /**
4
+ * error
5
+ */
6
+ error(...args: unknown[]): void;
7
+ /**
8
+ * warning
9
+ */
10
+ warn(...args: unknown[]): void;
11
+ /**
12
+ * info
13
+ */
14
+ info(...args: unknown[]): void;
15
+ /**
16
+ * debug
17
+ */
18
+ debug(...args: unknown[]): void;
19
+ }
20
+
21
+ /** 选项 */
22
+ export interface Options {
23
+ /** 日志 */
24
+ logger: Logger;
25
+ /** 表达式缓存 */
26
+ expressionCacheSize: number;
27
+ /** 模板缓存 */
28
+ templateCacheSize: number;
29
+ }
30
+
31
+ export const defaultOptions: Options = {
32
+ logger: console,
33
+ expressionCacheSize: 50,
34
+ templateCacheSize: 50,
35
+ };
package/src/main.ts CHANGED
@@ -1,211 +1,283 @@
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';
1
+ import { isVmFunction, wrapToVmValue, type VmValue } from '@mirascript/mirascript';
2
+ import { serialize } from '@mirascript/mirascript/subtle';
3
+ import { defaultOptions, type Logger, type Options } from './interface.js';
6
4
  import { parse } from './parser.js';
7
- import { analyze } from './analyze.js';
5
+ import { analyze, type AccessChain } from './analyze.js';
8
6
  import {
9
7
  type CompiledExpression,
10
- Expression,
8
+ type Expression,
9
+ type ExpressionFunction,
11
10
  type ExpressionOrValue,
11
+ type ExpressionSource,
12
12
  ExpressionTag,
13
13
  isExpression,
14
14
  } from './expression.js';
15
15
  import { evaluate } from './eval.js';
16
16
  import { TypeInfo } from './type.js';
17
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,
18
+ import {
19
+ argumentConverter,
20
+ choiceParameterType,
21
+ fillArgumentMap,
22
+ type ArgumentMap,
23
+ type ArgumentValue,
24
+ type Choice,
25
+ type ChoiceParameterType,
26
+ type ConditionExpression,
27
+ type Parameter,
28
+ type ParameterGroup,
29
+ type ParameterMap,
28
30
  } from './definitions.js';
29
31
  const { isArray } = Array;
30
- const { to_number, to_string } = lib;
32
+ const { defineProperty } = Object;
33
+
34
+ /** 选项类型 */
35
+ export type EvaluatedChoices<T extends ChoiceParameterType> = {
36
+ [K in T]: ParameterMap[K] extends { choices?: infer C } ? (C extends readonly unknown[] ? C : never) : never;
37
+ }[T];
31
38
 
32
39
  /**
33
40
  * 表达式求值
34
41
  */
35
42
  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;
43
+ constructor(options?: Partial<Options> | null) {
44
+ this.options = options ?? defaultOptions;
42
45
  }
43
46
 
44
- /** 上下文 */
45
- private readonly context: Context;
47
+ /** Logger */
48
+ get logger(): Logger {
49
+ return this.options.logger ?? console;
50
+ }
51
+ /** 选项 */
52
+ readonly options: Readonly<Partial<Options>>;
46
53
 
47
54
  /** 默认执行环境 */
48
- private readonly scope = new Scope(undefined, false, '<evaluator>');
55
+ private readonly scope = new Scope(null, true, '<evaluator>');
49
56
 
50
- /** 导入到执行环境 */
57
+ private readonly __imported = new Map<string, VmValue>();
58
+ /** 执行环境 */
59
+ get imported(): ReadonlyMap<string, VmValue> {
60
+ return this.__imported;
61
+ }
62
+ /**
63
+ * 导入到执行环境
64
+ *
65
+ * 使用 `undefined` 删除已导入的变量
66
+ */
51
67
  import(values: Record<string, unknown>): void {
52
68
  for (const [key, value] of Object.entries(values)) {
53
69
  if (value === undefined) {
54
- this.context.imported.delete(key);
70
+ this.__imported.delete(key);
55
71
  } else {
56
- this.context.imported.set(key, wrapToVmValue(value, null));
72
+ this.__imported.set(key, wrapToVmValue(value, null));
57
73
  }
58
74
  }
59
75
  }
60
76
 
61
- /**
62
- * 编译表达式
63
- */
77
+ /** 编译表达式 */
64
78
  compile<T extends VmValue>(expression: Expression<T>, throws = true): CompiledExpression<T> {
65
- const value = parse(this.context, expression.source, throws);
79
+ const value = parse(this, expression.source, throws, false);
66
80
  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;
81
+ const type = tag ? TypeInfo.parse(tag) : '';
82
+ let compiled: ExpressionFunction<T>;
83
+ if (!type) {
84
+ compiled = (scope, evaluator) => evaluate<T>(evaluator, value, scope);
85
+ } else {
86
+ const to = TypeInfo.converter(type);
87
+ compiled = (scope, evaluator) => {
88
+ const ret = evaluate<T>(evaluator, value, scope);
89
+ if (ret == null) return null;
90
+ return to(ret) as T;
91
+ };
92
+ }
93
+ defineProperty(compiled, ExpressionTag, { value: type, enumerable: true });
94
+ defineProperty(compiled, 'source', { value: value.source, enumerable: true });
95
+ return compiled as CompiledExpression<T>;
75
96
  }
76
97
 
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,
98
+ /** 执行 MiraScript 模板 */
99
+ template(template: string | null | undefined, scope: Scope | undefined): string {
100
+ if (template == null) return '';
101
+ const throws = scope?.throws ?? true;
102
+ const value = parse(this, template, throws, true);
103
+ const result = evaluate<string>(this, value, scope ?? this.scope) ?? template;
104
+ if (typeof result == 'string') return result;
105
+ return TypeInfo.toString(result);
106
+ }
107
+
108
+ /** 求值 */
109
+ evaluate<T extends VmValue>(
110
+ expression: ExpressionOrValue<T> | ExpressionFunction<T> | null | undefined,
111
+ scope?: Scope | null,
112
+ ): T | null;
113
+ /** 求值 */
114
+ evaluate<T extends VmValue, const D = NonNullable<T>>(
115
+ expression: ExpressionOrValue<T> | ExpressionFunction<T> | null | undefined,
116
+ scope: Scope | null | undefined,
87
117
  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 {
118
+ ): NonNullable<T> | D;
119
+ /** 求值 */
120
+ evaluate<T>(expression: ExpressionFunction<T> | T | null | undefined, scope?: Scope | null): T | null;
121
+ /** 求值 */
122
+ evaluate<T, const D = NonNullable<T>>(
123
+ expression: ExpressionFunction<T> | T | null | undefined,
124
+ scope: Scope | null | undefined,
125
+ defaults: D,
126
+ ): NonNullable<T> | D;
127
+
128
+ /** 求值 */
129
+ evaluate<T extends VmValue, const D = NonNullable<T>>(
130
+ expression: ExpressionOrValue<T> | ExpressionFunction<T> | null | undefined,
131
+ scope: Scope | null | undefined,
132
+ defaults: D | null = null,
133
+ ): T | D | null {
97
134
  if (expression == null) return defaults;
98
- if (!isExpression(expression)) return expression;
99
- const throws = scope?.throws ?? true;
135
+
136
+ if (typeof expression == 'function' && !isVmFunction(expression)) {
137
+ try {
138
+ const ret = expression(scope ?? this.scope, this);
139
+ if (ret == null) return defaults;
140
+ return ret;
141
+ } catch (ex) {
142
+ if (scope?.throws ?? true) {
143
+ throw ex;
144
+ }
145
+ return defaults;
146
+ }
147
+ }
148
+
149
+ if (!isExpression<T>(expression)) {
150
+ return expression;
151
+ }
152
+
153
+ const result = this.evaluateSource(expression.source, scope);
154
+ if (result == null) return defaults;
155
+
100
156
  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);
157
+ if (!tag) return result;
158
+ return TypeInfo.to(result, tag) as T;
159
+ }
160
+
161
+ /** 求值 */
162
+ evaluateSource<T extends VmValue>(
163
+ expression: ExpressionSource<T> | T | null | undefined,
164
+ scope: Scope | null | undefined,
165
+ ): T | null;
166
+ /** 求值 */
167
+ evaluateSource<T extends VmValue, const D = NonNullable<T>>(
168
+ expression: ExpressionSource<T> | T | null | undefined,
169
+ scope: Scope | null | undefined,
170
+ defaults: D,
171
+ ): NonNullable<T> | D;
172
+
173
+ /** 求值 */
174
+ evaluateSource<T extends VmValue, const D = NonNullable<T>>(
175
+ expression: ExpressionSource<T> | T | null | undefined,
176
+ scope: Scope | null | undefined,
177
+ defaults: D | null = null,
178
+ ): NonNullable<T> | D | null {
179
+ if (expression == null) return defaults;
180
+ if (typeof expression != 'string') return expression;
181
+
182
+ scope ??= this.scope;
183
+ const { throws } = scope;
184
+ const value = parse(this, expression, throws, false);
185
+ const result = evaluate<T>(this, value, scope);
186
+ if (result == null) return defaults;
187
+
104
188
  return result;
105
189
  }
106
190
 
107
191
  /** 求条件 */
108
- evaluateCondition(condition: ConditionExpression | undefined, scope: Scope | undefined, defaults = true): boolean {
109
- if (typeof condition == 'boolean') return condition;
192
+ evaluateCondition(
193
+ condition: ConditionExpression | null | undefined,
194
+ scope: Scope | null | undefined,
195
+ defaults = true,
196
+ ): boolean {
110
197
  if (condition == null) return defaults;
111
- condition = (
112
- typeof condition == 'string' ? condition : lib.to_string(condition)
113
- ).trim() as ConditionExpression & string;
198
+ if (typeof condition == 'boolean') return condition;
114
199
  if (condition === '') return defaults;
115
200
  if (condition === 'true') return true;
116
201
  if (condition === 'false') return false;
117
202
  try {
118
- return this.evaluate(Expression(condition, 'boolean'), scope, defaults);
203
+ const result = this.evaluateSource(condition, scope, defaults);
204
+ if (typeof result == 'boolean') return result;
205
+ throw new Error(`Condition evaluates to \`${serialize(result)}\``);
119
206
  } catch (ex) {
120
207
  if (scope?.throws) {
121
208
  throw ex;
122
209
  }
123
- this.options.logger.warn(`Invalid condition`, ex);
210
+ this.logger.warn(`Invalid condition`, ex);
124
211
  return defaults;
125
212
  }
126
213
  }
127
214
 
128
215
  /** 求选项列表 */
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`);
216
+ evaluateChoices<T extends ChoiceParameterType>(
217
+ definition: Pick<ParameterMap[T], 'type' | 'choices' | 'value'>,
218
+ scope: Scope | null | undefined,
219
+ ): EvaluatedChoices<T> {
220
+ const ret: Choice[] = [];
221
+ const { type, choices } = definition;
222
+ const isLogical = type === 'logical';
223
+ const choiceType = choiceParameterType(definition);
224
+ const fallbackKeys = choiceType === 'boolean' ? [false, true] : choiceType === 'number' ? [0, 1] : ['0', '1'];
225
+ if (choices != null) {
226
+ const evaluatedChoices = this.evaluate(choices, scope);
227
+ if (evaluatedChoices == null) {
228
+ // do nothing
229
+ } else if (!isArray(evaluatedChoices as unknown[])) {
230
+ if (scope?.throws ?? true) {
231
+ throw new Error(`Choices is not an array`);
232
+ }
233
+ } else if (evaluatedChoices.length > 0) {
234
+ const to = TypeInfo.converter(choiceType);
235
+ let len = evaluatedChoices.length;
236
+ if (isLogical && len > 2) len = 2;
237
+ for (let i = 0; i < len; i++) {
238
+ const item = evaluatedChoices[i];
239
+ const key = to(item?.key ?? fallbackKeys[i] ?? i);
240
+ const name = item?.name ?? (isLogical ? '' : String(key));
241
+ const description = item?.description ?? '';
242
+ const condition = item?.condition || undefined;
243
+ ret[i] = { key, name, description, condition };
244
+ }
139
245
  }
140
- return [];
141
246
  }
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;
247
+ if (isLogical) {
248
+ while (ret.length < 2) {
249
+ ret.push({
250
+ key: fallbackKeys[ret.length]!,
251
+ name: '',
252
+ description: '',
253
+ condition: undefined,
254
+ });
183
255
  }
184
- case 'grouped':
185
- case 'record':
186
- default:
187
- return value as V;
188
256
  }
257
+ return ret as EvaluatedChoices<T>;
189
258
  }
190
259
 
191
260
  /** 求参数值 */
192
261
  evaluateArg<V extends ArgumentValue>(
193
- expression: ExpressionOrValue<V> | undefined,
194
- scope: Scope | undefined,
262
+ expression: ExpressionOrValue<V> | null | undefined,
263
+ scope: Scope | null | undefined,
195
264
  definition: Parameter,
196
265
  ): 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);
266
+ // 默认值有可能为表达式,因此需要先填充再求值
267
+ expression ??= definition.value as ExpressionOrValue<V>;
268
+ // 求值失败时返回默认值
269
+ const result = this.evaluate<V>(expression, scope, definition.value as V);
270
+ const converter = argumentConverter(definition);
271
+ // 转换失败时返回转换前的值
272
+ return converter(result, result) as V;
201
273
  }
202
274
  /** 求参数表的所有参数值 */
203
275
  evaluateArgs<T extends Record<string, ArgumentValue>>(
204
- args: ArgumentMap<T> | undefined,
205
- scope: Scope | undefined,
276
+ args: ArgumentMap<T> | null | undefined,
277
+ scope: Scope | null | undefined,
206
278
  definition: ReadonlyArray<Pick<ParameterGroup, 'items'>>,
207
279
  ): T {
208
- const a = this.defaultArgs(definition, args);
280
+ const a = fillArgumentMap(definition, args);
209
281
  const ret: Record<string, ArgumentValue> = {};
210
282
  for (const group of definition) {
211
283
  for (const param of group.items) {
@@ -217,63 +289,6 @@ export class Evaluator {
217
289
  }
218
290
  return ret as T;
219
291
  }
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
292
  /** 解析表达式 */
278
293
  validate<T extends VmValue>(expression: Expression<T>): void {
279
294
  if (!isExpression(expression) || !expression.source) {
@@ -281,15 +296,18 @@ export class Evaluator {
281
296
  }
282
297
  const tag = expression[ExpressionTag];
283
298
  if (tag) TypeInfo.parse(tag);
284
- parse(this.context, expression.source, true);
299
+ parse(this, expression.source, true, false);
285
300
  }
286
301
 
287
302
  /** 分析表达式 */
288
- analyze<T extends VmValue>(expression: Expression<T>): string[][] {
303
+ analyze<T extends VmValue>(expression: Expression<T>, scope?: Scope | null): readonly AccessChain[] {
289
304
  if (!isExpression(expression) || !expression.source) {
290
305
  throw new Error(`${expression.source || String(expression)} is not a valid expression`);
291
306
  }
292
- const exp = parse(this.context, expression.source, true);
293
- return analyze(this.context, exp);
307
+ scope ??= this.scope;
308
+ const exp = parse(this, expression.source, scope.throws, false);
309
+ // 如果 throws=true,则 parse 会抛出错误,此处有 error 一定是 throws=false 的情况
310
+ if (exp.error) return [];
311
+ return analyze(exp, this, scope);
294
312
  }
295
313
  }
package/src/migrate.ts CHANGED
@@ -1,12 +1,16 @@
1
- import type { VmContext, VmValue } from '@mirascript/mirascript';
2
- import { BaseState } from './migrator/state.js';
1
+ import type { VmValue } from '@mirascript/mirascript';
2
+ import type { Scope } from './scope.js';
3
3
  import type { ExpressionSource } from './expression.js';
4
+ import { BaseState } from './migrator/state.js';
5
+ import { Evaluator } from './main.js';
6
+ import { DefaultVmContext } from '@mirascript/mirascript/subtle';
4
7
 
8
+ const evaluator = new Evaluator();
5
9
  /** MathJs 表达式转为 MiraScript 表达式 */
6
10
  export function migrateMathJs<const T extends VmValue>(
7
11
  expr: ExpressionSource<T>,
8
12
  condition: boolean,
9
- context?: VmContext,
13
+ context: Scope | undefined,
10
14
  ): ExpressionSource<T> {
11
15
  // 检查输入
12
16
  if (!expr) return '' as ExpressionSource<T>;
@@ -15,11 +19,31 @@ export function migrateMathJs<const T extends VmValue>(
15
19
  if (expr === 'null') return 'nil' as ExpressionSource<T>;
16
20
  if (expr === 'true') return 'true' as ExpressionSource<T>;
17
21
  if (expr === 'false') return 'false' as ExpressionSource<T>;
22
+ if (condition) {
23
+ // 兼容旧的 evaluateCondition 的宽容处理
24
+ if (expr === 'True' || expr === 'TRUE' || expr === '1' || expr === '"1"' || expr === "'1'")
25
+ return 'true' as ExpressionSource<T>;
26
+ if (
27
+ expr === 'False' ||
28
+ expr === 'FALSE' ||
29
+ expr === '0' ||
30
+ expr === '"0"' ||
31
+ expr === "'0'" ||
32
+ expr === '""' ||
33
+ expr === "''"
34
+ )
35
+ return 'false' as ExpressionSource<T>;
36
+ }
18
37
  if (/^[-+]?(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)$/.test(expr)) {
19
38
  return expr;
20
39
  }
21
40
 
22
- const state = new BaseState(expr, condition, context);
23
- state.migrate();
24
- return state.result() as ExpressionSource<T>;
41
+ const dispose = context?.reset(evaluator);
42
+ try {
43
+ const state = new BaseState(expr, condition, context ? context.proxy : DefaultVmContext);
44
+ state.migrate();
45
+ return state.result() as ExpressionSource<T>;
46
+ } finally {
47
+ dispose?.();
48
+ }
25
49
  }