@cloudpss/expression 0.6.0-alpha.15 → 0.6.0-alpha.17
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.
- package/dist/definitions/argument.d.ts +1 -1
- package/dist/definitions/argument.d.ts.map +1 -1
- package/dist/definitions/parameter.d.ts.map +1 -1
- package/dist/definitions/parameter.js.map +1 -1
- package/dist/definitions/utils.d.ts +2 -1
- package/dist/definitions/utils.d.ts.map +1 -1
- package/dist/definitions/utils.js +24 -7
- package/dist/definitions/utils.js.map +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +25 -17
- package/dist/main.js.map +1 -1
- package/dist/migrator/call.d.ts.map +1 -1
- package/dist/migrator/call.js +39 -13
- package/dist/migrator/call.js.map +1 -1
- package/dist/migrator/node.d.ts.map +1 -1
- package/dist/migrator/node.js +21 -1
- package/dist/migrator/node.js.map +1 -1
- package/dist/migrator/operator.d.ts.map +1 -1
- package/dist/migrator/operator.js +56 -26
- package/dist/migrator/operator.js.map +1 -1
- package/dist/migrator/symbol.js +2 -2
- package/dist/migrator/symbol.js.map +1 -1
- package/dist/migrator/to-type.js +2 -2
- package/dist/migrator/to-type.js.map +1 -1
- package/dist/type.d.ts +22 -10
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +19 -26
- package/dist/type.js.map +1 -1
- package/package.json +3 -3
- package/src/definitions/argument.ts +1 -1
- package/src/definitions/parameter.ts +15 -8
- package/src/definitions/utils.ts +29 -13
- package/src/main.ts +21 -14
- package/src/migrator/call.ts +37 -15
- package/src/migrator/node.ts +22 -1
- package/src/migrator/operator.ts +55 -26
- package/src/migrator/symbol.ts +2 -2
- package/src/migrator/to-type.ts +2 -2
- package/src/type.ts +36 -27
- package/tests/condition.ts +20 -12
- package/tests/definition.ts +12 -10
- package/tests/import.ts +3 -3
- package/tests/migrate.ts +49 -9
- package/tests/scope.ts +3 -3
package/src/definitions/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { klona } from 'klona';
|
|
2
|
-
import { isVmArray, type VmPrimitive, type VmValue } from '@mirascript/mirascript';
|
|
3
|
-
import { Expression, isExpression } from '../expression.js';
|
|
2
|
+
import { isVmArray, isVmRecord, type VmPrimitive, type VmValue } from '@mirascript/mirascript';
|
|
3
|
+
import { Expression, isExpression, type ExpressionOrValue } from '../expression.js';
|
|
4
4
|
import type { ChoiceParameterType, Parameter, ParameterMap } from './parameter.js';
|
|
5
5
|
import { TypeInfo } from '../type.js';
|
|
6
6
|
import type { ArgumentMap, ArgumentValue } from './argument.js';
|
|
@@ -11,7 +11,9 @@ const { isArray } = Array;
|
|
|
11
11
|
export function enumType(definition: Pick<ParameterMap[ChoiceParameterType], 'type' | 'choices'>): TypeInfo | null {
|
|
12
12
|
const { choices } = definition;
|
|
13
13
|
if (typeof choices == 'function' || isExpression(choices)) return null;
|
|
14
|
+
// 首先根据参数类型确定默认类型
|
|
14
15
|
let defaultType: TypeInfo = definition.type === 'logical' ? 'boolean' : 'number';
|
|
16
|
+
// 然后根据默认值调整默认类型
|
|
15
17
|
if ('value' in definition) {
|
|
16
18
|
const { value } = definition;
|
|
17
19
|
if (typeof value == 'boolean') defaultType = 'boolean';
|
|
@@ -23,6 +25,7 @@ export function enumType(definition: Pick<ParameterMap[ChoiceParameterType], 'ty
|
|
|
23
25
|
let type: TypeInfo | null = null;
|
|
24
26
|
for (const choice of choices) {
|
|
25
27
|
const v = choice?.key;
|
|
28
|
+
// 选项值为空时,使用默认类型
|
|
26
29
|
const t = v == null ? defaultType : typeof v;
|
|
27
30
|
if (t === 'string') {
|
|
28
31
|
if (type == null) type = 'string';
|
|
@@ -42,15 +45,18 @@ export function enumType(definition: Pick<ParameterMap[ChoiceParameterType], 'ty
|
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
47
|
* 转换参数值的类型,不回退到 {@link Parameter.value} 提供的默认值
|
|
48
|
+
* 转换失败时返回 null
|
|
45
49
|
*/
|
|
46
|
-
export function toArgumentValue<V extends ArgumentValue>(
|
|
50
|
+
export function toArgumentValue<V extends ArgumentValue>(
|
|
51
|
+
value: VmValue | null | undefined,
|
|
52
|
+
definition: Parameter,
|
|
53
|
+
): V | null {
|
|
47
54
|
// 避免无效的 VmValue 导致错误
|
|
48
55
|
value ??= null;
|
|
49
|
-
|
|
50
56
|
switch (definition.type) {
|
|
51
57
|
case 'real':
|
|
52
58
|
case 'integer':
|
|
53
|
-
return TypeInfo.toNumber(value) as V;
|
|
59
|
+
return TypeInfo.toNumber(value, null) as V;
|
|
54
60
|
case 'text':
|
|
55
61
|
case 'file':
|
|
56
62
|
case 'code':
|
|
@@ -58,16 +64,16 @@ export function toArgumentValue<V extends ArgumentValue>(value: VmValue, definit
|
|
|
58
64
|
case 'cssColor' as never:
|
|
59
65
|
case 'cssBackground' as never:
|
|
60
66
|
case 'resourceId' as never:
|
|
61
|
-
return TypeInfo.toString(value) as V;
|
|
67
|
+
return TypeInfo.toString(value, null) as V;
|
|
62
68
|
|
|
63
69
|
case 'choice':
|
|
64
70
|
case 'logical': {
|
|
65
71
|
if (value == null && Array.isArray(definition.choices) && definition.choices.length > 0) {
|
|
66
|
-
value = definition.choices[0]?.key;
|
|
72
|
+
value = definition.choices[0]?.key as V;
|
|
67
73
|
}
|
|
68
74
|
const type = enumType(definition);
|
|
69
75
|
if (!type) return value as V;
|
|
70
|
-
return TypeInfo.to(value, type) as V;
|
|
76
|
+
return TypeInfo.to(value, type, null) as V;
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
case 'multiSelect': {
|
|
@@ -75,7 +81,13 @@ export function toArgumentValue<V extends ArgumentValue>(value: VmValue, definit
|
|
|
75
81
|
if (v.length === 0) return v as V;
|
|
76
82
|
const type = enumType(definition);
|
|
77
83
|
if (!type) return v as V;
|
|
78
|
-
|
|
84
|
+
const to = TypeInfo.converter(type);
|
|
85
|
+
const ret: VmPrimitive[] = [];
|
|
86
|
+
for (const x of v) {
|
|
87
|
+
const r = to(x, null);
|
|
88
|
+
if (r != null) ret.push(r);
|
|
89
|
+
}
|
|
90
|
+
return ret as readonly VmPrimitive[] as V;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
case 'table': {
|
|
@@ -84,7 +96,10 @@ export function toArgumentValue<V extends ArgumentValue>(value: VmValue, definit
|
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
case 'grouped':
|
|
87
|
-
case 'record':
|
|
99
|
+
case 'record': {
|
|
100
|
+
if (!isVmRecord(value)) return null;
|
|
101
|
+
return value as V;
|
|
102
|
+
}
|
|
88
103
|
default:
|
|
89
104
|
return value as V;
|
|
90
105
|
}
|
|
@@ -127,7 +142,7 @@ export function fillArgumentMap<T extends Record<string, ArgumentValue>>(
|
|
|
127
142
|
if (!checkParameterDefinitions(definition)) return args;
|
|
128
143
|
|
|
129
144
|
/** 参数键的类型 */
|
|
130
|
-
type K = keyof T
|
|
145
|
+
type K = keyof T;
|
|
131
146
|
/** 参数值的类型 */
|
|
132
147
|
type V = NonNullable<T[K]>;
|
|
133
148
|
const keys = new Set(Object.keys(args) as K[]);
|
|
@@ -140,13 +155,13 @@ export function fillArgumentMap<T extends Record<string, ArgumentValue>>(
|
|
|
140
155
|
const currentValue = keys.has(key) ? args[key] : undefined;
|
|
141
156
|
if (currentValue == null) {
|
|
142
157
|
// 使用默认值填充
|
|
143
|
-
const defaultValue = param.value
|
|
158
|
+
const defaultValue = param.value as ExpressionOrValue<V>;
|
|
144
159
|
if (isExpression(defaultValue)) {
|
|
145
160
|
args[key] = Expression<V>(defaultValue.source);
|
|
146
161
|
} else {
|
|
147
162
|
const value =
|
|
148
163
|
defaultValue != null && typeof defaultValue == 'object' ? klona(defaultValue) : defaultValue;
|
|
149
|
-
args[key] = toArgumentValue<V>(value, param);
|
|
164
|
+
args[key] = toArgumentValue<V>(value, param) ?? value;
|
|
150
165
|
}
|
|
151
166
|
} else {
|
|
152
167
|
// 已存在的参数值
|
|
@@ -167,6 +182,7 @@ export function fillArgumentMap<T extends Record<string, ArgumentValue>>(
|
|
|
167
182
|
// 删除多余的参数
|
|
168
183
|
for (const key of keys) {
|
|
169
184
|
// 保留特殊参数和私有字段
|
|
185
|
+
if (typeof key != 'string') continue;
|
|
170
186
|
const k0 = key[0];
|
|
171
187
|
if (k0 === '_' || k0 === '$' || k0 === '@' || k0 === 'ɵ') continue;
|
|
172
188
|
delete args[key];
|
package/src/main.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isVmFunction, isVmPrimitive, wrapToVmValue, type VmValue } from '@mirascript/mirascript';
|
|
2
|
-
import {
|
|
2
|
+
import { serialize } from '@mirascript/mirascript/subtle';
|
|
3
3
|
import { defaultOptions, type Logger, type Options } from './interface.js';
|
|
4
4
|
import { parse } from './parser.js';
|
|
5
5
|
import { analyze, type AccessChain } from './analyze.js';
|
|
6
6
|
import {
|
|
7
7
|
type CompiledExpression,
|
|
8
|
-
Expression,
|
|
8
|
+
type Expression,
|
|
9
9
|
type ExpressionFunction,
|
|
10
10
|
type ExpressionOrValue,
|
|
11
11
|
type ExpressionSource,
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
} from './definitions.js';
|
|
31
31
|
const { isArray } = Array;
|
|
32
32
|
const { defineProperty } = Object;
|
|
33
|
-
const { to_string } = lib;
|
|
34
33
|
|
|
35
34
|
/** 选项类型 */
|
|
36
35
|
export type EvaluatedChoices<T extends ChoiceParameterType> = {
|
|
@@ -80,12 +79,17 @@ export class Evaluator {
|
|
|
80
79
|
const value = parse(this, expression.source, throws, false);
|
|
81
80
|
const tag = expression[ExpressionTag];
|
|
82
81
|
const type = tag ? TypeInfo.parse(tag) : '';
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
93
|
defineProperty(compiled, ExpressionTag, { value: type, enumerable: true });
|
|
90
94
|
defineProperty(compiled, 'source', { value: value.source, enumerable: true });
|
|
91
95
|
return compiled as CompiledExpression<T>;
|
|
@@ -98,7 +102,7 @@ export class Evaluator {
|
|
|
98
102
|
const value = parse(this, template, throws, true);
|
|
99
103
|
const result = evaluate<string>(this, value, scope ?? this.scope) ?? template;
|
|
100
104
|
if (typeof result == 'string') return result;
|
|
101
|
-
return
|
|
105
|
+
return TypeInfo.toString(result);
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
/** 求值 */
|
|
@@ -190,14 +194,15 @@ export class Evaluator {
|
|
|
190
194
|
scope: Scope | null | undefined,
|
|
191
195
|
defaults = true,
|
|
192
196
|
): boolean {
|
|
193
|
-
if (typeof condition == 'boolean') return condition;
|
|
194
197
|
if (condition == null) return defaults;
|
|
195
|
-
|
|
198
|
+
if (typeof condition == 'boolean') return condition;
|
|
196
199
|
if (condition === '') return defaults;
|
|
197
200
|
if (condition === 'true') return true;
|
|
198
201
|
if (condition === 'false') return false;
|
|
199
202
|
try {
|
|
200
|
-
|
|
203
|
+
const result = this.evaluateSource(condition, scope, defaults);
|
|
204
|
+
if (typeof result == 'boolean') return result;
|
|
205
|
+
throw new Error(`Condition evaluates to \`${serialize(result)}\``);
|
|
201
206
|
} catch (ex) {
|
|
202
207
|
if (scope?.throws) {
|
|
203
208
|
throw ex;
|
|
@@ -269,8 +274,10 @@ export class Evaluator {
|
|
|
269
274
|
): V {
|
|
270
275
|
// 默认值有可能为表达式,因此需要先填充再求值
|
|
271
276
|
expression ??= definition.value as ExpressionOrValue<V>;
|
|
277
|
+
// 求值失败时返回默认值
|
|
272
278
|
const result = this.evaluate<V>(expression, scope, definition.value as V);
|
|
273
|
-
|
|
279
|
+
// 转换失败时返回转换前的值
|
|
280
|
+
return toArgumentValue<V>(result, definition) ?? result;
|
|
274
281
|
}
|
|
275
282
|
/** 求参数表的所有参数值 */
|
|
276
283
|
evaluateArgs<T extends Record<string, ArgumentValue>>(
|
package/src/migrator/call.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
isObjectNode,
|
|
7
7
|
type FunctionNode,
|
|
8
8
|
type MathNode,
|
|
9
|
-
isOperatorNode,
|
|
10
9
|
} from 'mathjs';
|
|
11
10
|
import type { Options, Result } from './interface.js';
|
|
12
11
|
import type { State } from './state.js';
|
|
@@ -14,6 +13,7 @@ import { constantValue, equalText, globalFnName, len, symbolName } from './utils
|
|
|
14
13
|
import { migrateAtomic, migrateExpr, migrateNode } from './node.js';
|
|
15
14
|
import { concat } from './concat.js';
|
|
16
15
|
import { toNumber, toString } from './to-type.js';
|
|
16
|
+
import { isVmArray, isVmPrimitive } from '@mirascript/mirascript';
|
|
17
17
|
|
|
18
18
|
const EXACT_UNARY_FUNCTION = new Map<string, Result['type']>([
|
|
19
19
|
// 只支持标量的 mathjs 函数
|
|
@@ -71,6 +71,11 @@ function migrateFunctionCall(
|
|
|
71
71
|
type: EXACT_UNARY_FUNCTION.get(fnName)!,
|
|
72
72
|
code: `${fnName}(${arg.code})`,
|
|
73
73
|
};
|
|
74
|
+
} else if (fnName === 'random' && args.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
type: 'number',
|
|
77
|
+
code: `${globalFnName(state, 'random')}()`,
|
|
78
|
+
};
|
|
74
79
|
} else if (fnName === 're' || fnName === 'im') {
|
|
75
80
|
const m = migrateAtomic(state, args[0]!);
|
|
76
81
|
state.err(`不支持复数`);
|
|
@@ -264,26 +269,43 @@ function migrateFunctionCall(
|
|
|
264
269
|
type: 'string',
|
|
265
270
|
code: `${g('to_string')}(${inner.code})`,
|
|
266
271
|
};
|
|
267
|
-
} else if (
|
|
268
|
-
fnName === 'sum'
|
|
269
|
-
args.length
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
272
|
+
} else if (fnName === 'sum' || fnName === 'prod') {
|
|
273
|
+
const newFnName = fnName === 'sum' ? 'sum' : 'product';
|
|
274
|
+
if (args.length !== 1) {
|
|
275
|
+
return {
|
|
276
|
+
type: 'number',
|
|
277
|
+
code: `${g(newFnName)}(${args.map((a) => migrateAtomic(state, a).code).join(', ')})`,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const arg = migrateAtomic(state, args[0]!);
|
|
281
|
+
if (arg.literal) {
|
|
282
|
+
if (isVmPrimitive(arg.literal)) {
|
|
283
|
+
return {
|
|
284
|
+
type: 'number',
|
|
285
|
+
code: `${g(newFnName)}(${arg.code})`,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (isVmArray(arg.literal) && arg.literal.every((v) => isVmPrimitive(v))) {
|
|
289
|
+
return {
|
|
290
|
+
type: 'number',
|
|
291
|
+
code: `${g(newFnName)}(${arg.code})`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (arg.type === 'array' || !arg.type) {
|
|
296
|
+
return {
|
|
297
|
+
type: 'number',
|
|
298
|
+
code: `${g(newFnName)}(${arg.code}::${g('flatten')}())`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
279
301
|
return {
|
|
280
302
|
type: 'number',
|
|
281
|
-
code: `${g(
|
|
303
|
+
code: `${g(newFnName)}(${arg.code})`,
|
|
282
304
|
};
|
|
283
305
|
}
|
|
284
306
|
|
|
285
307
|
return call(state, node, (fn, args) => {
|
|
286
|
-
const fun = state.globals.get(fnName);
|
|
308
|
+
const fun = state.globals.has(fnName) ? state.globals.get(fnName) : undefined;
|
|
287
309
|
if (typeof fun === 'function') {
|
|
288
310
|
if (args.some((a) => a.type !== 'number' && a.type !== 'boolean' && a.type !== 'string')) {
|
|
289
311
|
state.warn(`函数行为可能不一致: ${fnName}`);
|
package/src/migrator/node.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import type { State } from './state.js';
|
|
18
18
|
import type { Options, Result } from './interface.js';
|
|
19
19
|
import type { VmConst } from '@mirascript/mirascript';
|
|
20
|
-
import { unsupportedNode } from './utils.js';
|
|
20
|
+
import { constantValue, unsupportedNode } from './utils.js';
|
|
21
21
|
import { migrateAccess } from './access.js';
|
|
22
22
|
import { migrateCall } from './call.js';
|
|
23
23
|
import { migrateOperator } from './operator.js';
|
|
@@ -25,6 +25,7 @@ import { migrateSymbol } from './symbol.js';
|
|
|
25
25
|
import { migrateCondition } from './condition.js';
|
|
26
26
|
import { migrateFunctionAssignment } from './function.js';
|
|
27
27
|
import { serialize } from './serialize.js';
|
|
28
|
+
import { toNumber } from './to-type.js';
|
|
28
29
|
|
|
29
30
|
/** 转换 AST */
|
|
30
31
|
export function migrateAtomic(state: State, node: MathNode): Result {
|
|
@@ -113,6 +114,26 @@ export function migrateNode(state: State, node: MathNode, options: Options): Res
|
|
|
113
114
|
}
|
|
114
115
|
if (isRelationalNode(node)) {
|
|
115
116
|
const { conditionals, params } = node;
|
|
117
|
+
// 优化范围判断 a <= b <= c
|
|
118
|
+
if (
|
|
119
|
+
conditionals.length === 2 &&
|
|
120
|
+
params.length === 3 &&
|
|
121
|
+
conditionals[0] === 'smallerEq' &&
|
|
122
|
+
conditionals[1] === 'smallerEq'
|
|
123
|
+
) {
|
|
124
|
+
// 模式匹配只支持常量
|
|
125
|
+
const l = constantValue(params[0]!);
|
|
126
|
+
const r = constantValue(params[2]!);
|
|
127
|
+
if (typeof l == 'number' && typeof r == 'number' && l < r) {
|
|
128
|
+
const v = toNumber(state, migrateNode(state, params[1]!, options));
|
|
129
|
+
let code = `${v.code} is ${l}..${r}`;
|
|
130
|
+
if (options.format !== 'no-paren') code = `(${code})`;
|
|
131
|
+
return {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
code,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
116
137
|
const exprs = [];
|
|
117
138
|
for (let i = 0; i < conditionals.length; i++) {
|
|
118
139
|
const fn = conditionals[i]! as OperatorNodeFn;
|
package/src/migrator/operator.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isOperatorNode,
|
|
9
9
|
isNode,
|
|
10
10
|
} from 'mathjs';
|
|
11
|
+
import { isVmArray, type TypeName, type VmAny } from '@mirascript/mirascript';
|
|
11
12
|
import { operations } from '@mirascript/mirascript/subtle';
|
|
12
13
|
import { symbolName, constantValue, equalText, scalar, globalFnName } from './utils.js';
|
|
13
14
|
import type { State } from './state.js';
|
|
@@ -91,10 +92,12 @@ const BIT_OPS_TO_BOOL_OPS = {
|
|
|
91
92
|
} as const;
|
|
92
93
|
|
|
93
94
|
const COMPARE_OPERATORS = {
|
|
94
|
-
smaller: '<',
|
|
95
|
-
smallerEq: '<=',
|
|
96
|
-
larger: '>',
|
|
97
|
-
largerEq: '>=',
|
|
95
|
+
smaller: ['<'],
|
|
96
|
+
smallerEq: ['<='],
|
|
97
|
+
larger: ['>'],
|
|
98
|
+
largerEq: ['>='],
|
|
99
|
+
equal: ['=~', '=='],
|
|
100
|
+
unequal: ['!~', '!='],
|
|
98
101
|
} as const;
|
|
99
102
|
|
|
100
103
|
/** 转换为 boolean */
|
|
@@ -103,6 +106,21 @@ function b(op: string, state: State, node: MathNode): string {
|
|
|
103
106
|
return toBoolean(state, scalar(op, state, re)).code;
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
/** 数组元素类型 */
|
|
110
|
+
function elementType(lit: VmAny): TypeName | undefined {
|
|
111
|
+
if (!isVmArray(lit) || !lit.length) return undefined;
|
|
112
|
+
let type: TypeName | undefined = undefined;
|
|
113
|
+
for (const e of lit) {
|
|
114
|
+
const t = operations.$Type(e as VmAny);
|
|
115
|
+
if (!type) {
|
|
116
|
+
type = t;
|
|
117
|
+
} else if (type !== t) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return type;
|
|
122
|
+
}
|
|
123
|
+
|
|
106
124
|
/** 二元操作 */
|
|
107
125
|
function binary(
|
|
108
126
|
state: State,
|
|
@@ -115,23 +133,27 @@ function binary(
|
|
|
115
133
|
return op(state, l, r);
|
|
116
134
|
}
|
|
117
135
|
if (Array.isArray(l.literal) && l.literal.every((e) => !Array.isArray(e))) {
|
|
136
|
+
const it = { code: 'it', type: elementType(l.literal) };
|
|
118
137
|
return {
|
|
119
138
|
type: 'array',
|
|
120
|
-
code: `${l.code}::${globalFnName(state, 'map')}(fn { ${op(state,
|
|
139
|
+
code: `${l.code}::${globalFnName(state, 'map')}(fn { ${op(state, it, r).code} })`,
|
|
121
140
|
};
|
|
122
141
|
}
|
|
123
142
|
if (Array.isArray(r.literal) && r.literal.every((e) => !Array.isArray(e))) {
|
|
143
|
+
const it = { code: 'it', type: elementType(r.literal) };
|
|
124
144
|
return {
|
|
125
145
|
type: 'array',
|
|
126
|
-
code: `${r.code}::${globalFnName(state, 'map')}(fn { ${op(state, l,
|
|
146
|
+
code: `${r.code}::${globalFnName(state, 'map')}(fn { ${op(state, l, it).code} })`,
|
|
127
147
|
};
|
|
128
148
|
}
|
|
129
149
|
if (alt) {
|
|
130
150
|
return alt(state, l, r);
|
|
131
151
|
}
|
|
152
|
+
const a = { code: 'a', type: l.type === 'array' ? elementType(l.literal) : l.type };
|
|
153
|
+
const b = { code: 'b', type: r.type === 'array' ? elementType(r.literal) : r.type };
|
|
132
154
|
return {
|
|
133
155
|
type: l.type === 'array' || r.type === 'array' ? 'array' : undefined,
|
|
134
|
-
code: `${globalFnName(state, 'matrix')}.entrywise(${l.code}, ${r.code}, fn (a, b) { ${op(state,
|
|
156
|
+
code: `${globalFnName(state, 'matrix')}.entrywise(${l.code}, ${r.code}, fn (a, b) { ${op(state, a, b).code} })`,
|
|
135
157
|
};
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -141,9 +163,10 @@ function unary(state: State, v: Result, op: (state: State, v: Result) => Result)
|
|
|
141
163
|
return op(state, v);
|
|
142
164
|
}
|
|
143
165
|
if (Array.isArray(v.literal) && v.literal.every((e) => !Array.isArray(e))) {
|
|
166
|
+
const it = { code: 'it', type: elementType(v.literal) };
|
|
144
167
|
return {
|
|
145
168
|
type: 'array',
|
|
146
|
-
code: `${v.code}::${globalFnName(state, 'map')}(fn { ${op(state,
|
|
169
|
+
code: `${v.code}::${globalFnName(state, 'map')}(fn { ${op(state, it).code} })`,
|
|
147
170
|
};
|
|
148
171
|
}
|
|
149
172
|
return {
|
|
@@ -288,17 +311,21 @@ export function migrateOperator(
|
|
|
288
311
|
}
|
|
289
312
|
|
|
290
313
|
case 'equal':
|
|
291
|
-
case 'unequal':
|
|
292
|
-
|
|
314
|
+
case 'unequal':
|
|
315
|
+
case 'smaller':
|
|
316
|
+
case 'smallerEq':
|
|
317
|
+
case 'larger':
|
|
318
|
+
case 'largerEq': {
|
|
319
|
+
const [op, eqOp] = COMPARE_OPERATORS[fn];
|
|
293
320
|
const c0 = constantValue(a0);
|
|
294
321
|
const c1 = constantValue(a1);
|
|
295
|
-
if (c0 === null || c1 === null) {
|
|
296
|
-
|
|
297
|
-
const p0 = c0 === null ? { code: `nil` } :
|
|
298
|
-
const p1 = c1 === null ? { code: `nil` } :
|
|
322
|
+
if (eqOp && (c0 === null || c1 === null)) {
|
|
323
|
+
// Mathjs 只支持标量与 null 比较
|
|
324
|
+
const p0 = c0 === null ? { code: `nil` } : migrateExpr(state, a0);
|
|
325
|
+
const p1 = c1 === null ? { code: `nil` } : migrateExpr(state, a1);
|
|
299
326
|
return {
|
|
300
327
|
type: 'boolean',
|
|
301
|
-
code: `${open}${p0.code} ${
|
|
328
|
+
code: `${open}${p0.code} ${eqOp} ${p1.code}${close}`,
|
|
302
329
|
};
|
|
303
330
|
}
|
|
304
331
|
// PI, E 与常量比较时,不进行转换
|
|
@@ -317,7 +344,19 @@ export function migrateOperator(
|
|
|
317
344
|
};
|
|
318
345
|
|
|
319
346
|
return binary(state, a(a0), a(a1), (state, l, r) => {
|
|
320
|
-
if (l.
|
|
347
|
+
if (l.literal !== undefined) {
|
|
348
|
+
return {
|
|
349
|
+
type: 'boolean',
|
|
350
|
+
code: `${toNumber(state, l).code} ${op} ${r.code}`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
if (r.literal !== undefined) {
|
|
354
|
+
return {
|
|
355
|
+
type: 'boolean',
|
|
356
|
+
code: `${l.code} ${op} ${toNumber(state, r).code}`,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (l.type === 'number' || r.type === 'number') {
|
|
321
360
|
return {
|
|
322
361
|
type: 'boolean',
|
|
323
362
|
code: `${l.code} ${op} ${r.code}`,
|
|
@@ -329,16 +368,6 @@ export function migrateOperator(
|
|
|
329
368
|
};
|
|
330
369
|
});
|
|
331
370
|
}
|
|
332
|
-
case 'smaller':
|
|
333
|
-
case 'smallerEq':
|
|
334
|
-
case 'larger':
|
|
335
|
-
case 'largerEq': {
|
|
336
|
-
const op = COMPARE_OPERATORS[fn];
|
|
337
|
-
return {
|
|
338
|
-
type: 'boolean',
|
|
339
|
-
code: `${open}${scalar(op, state, migrateExpr(state, a0)).code} ${op} ${scalar(op, state, migrateExpr(state, a1)).code}${close}`,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
371
|
|
|
343
372
|
default: {
|
|
344
373
|
state.err(`不支持的运算符: ${fn}`);
|
package/src/migrator/symbol.ts
CHANGED
|
@@ -14,8 +14,8 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
|
|
|
14
14
|
code: migrateSymbolName(name, true),
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (state.globals.has(name)) {
|
|
18
|
+
const global = state.globals.get(name);
|
|
19
19
|
return {
|
|
20
20
|
type: operations.$Type(global),
|
|
21
21
|
code: migrateSymbolName(name, false),
|
package/src/migrator/to-type.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { globalFnName } from './utils.js';
|
|
|
9
9
|
export function toBoolean(state: State, node: MathNode | Result): Result {
|
|
10
10
|
const helper = (): void => {
|
|
11
11
|
state.helper(
|
|
12
|
-
"fn @@to_boolean { if it is nil {
|
|
12
|
+
"fn @@to_boolean { if it is nil { nil } else { it != '' && it != '0' && it != 0 && it is not nan && !!it } }",
|
|
13
13
|
);
|
|
14
14
|
};
|
|
15
15
|
if (!isNode(node)) {
|
|
@@ -85,7 +85,7 @@ export function toNumber(state: State, node: MathNode | Result): Result {
|
|
|
85
85
|
code: node.as_number,
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
-
if (node.type === 'string' && node.literal !== undefined) {
|
|
88
|
+
if ((node.type === 'string' || node.type === 'boolean') && node.literal !== undefined) {
|
|
89
89
|
const lit = Number(node.literal);
|
|
90
90
|
return {
|
|
91
91
|
type: 'number',
|
package/src/type.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { TypeName, VmValue, VmValueMap } from '@mirascript/mirascript';
|
|
2
|
-
import {
|
|
3
|
-
const { to_string, to_number, to_boolean } = lib;
|
|
2
|
+
import { convert } from '@mirascript/mirascript/subtle';
|
|
4
3
|
|
|
5
4
|
/** 类型信息 */
|
|
6
5
|
export type TypeInfo = 'string' | 'number' | 'boolean';
|
|
@@ -13,10 +12,11 @@ type TypeMap = VmValueMap & {
|
|
|
13
12
|
s: string;
|
|
14
13
|
f: number;
|
|
15
14
|
b: boolean;
|
|
15
|
+
'': VmValue;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/** Ts 类型 */
|
|
19
|
-
export type TsTypeOf<T extends TypeInfo | LegacyType> = TypeMap[T];
|
|
19
|
+
export type TsTypeOf<T extends TypeInfo | LegacyType | '', I extends VmValue = VmValue> = T extends '' ? I : TypeMap[T];
|
|
20
20
|
|
|
21
21
|
/** 兼容的旧类型 */
|
|
22
22
|
export type LegacyType = 's' | 'f' | 'b';
|
|
@@ -49,39 +49,48 @@ export namespace TypeInfo {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/** 转换类型 */
|
|
52
|
-
export function to<T extends TypeInfo | LegacyType
|
|
53
|
-
|
|
54
|
-
type
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
export function to<T extends TypeInfo | LegacyType | '', I extends VmValue = VmValue, F = undefined>(
|
|
53
|
+
value: I | undefined,
|
|
54
|
+
type: T,
|
|
55
|
+
fallback?: F,
|
|
56
|
+
): TsTypeOf<T> | Exclude<F, undefined> {
|
|
57
|
+
const c = converter(type);
|
|
58
|
+
return (c as typeof convert.toNumber)(value, fallback) as TsTypeOf<T>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 不转换 */
|
|
62
|
+
function identity<T extends VmValue>(value: T | undefined): T | null {
|
|
63
|
+
return value ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 获取转换器 */
|
|
67
|
+
type Converters = {
|
|
68
|
+
string: typeof convert.toString;
|
|
69
|
+
s: typeof convert.toString;
|
|
70
|
+
number: typeof convert.toNumber;
|
|
71
|
+
f: typeof convert.toNumber;
|
|
72
|
+
boolean: typeof convert.toBoolean;
|
|
73
|
+
b: typeof convert.toBoolean;
|
|
74
|
+
'': typeof identity;
|
|
75
|
+
};
|
|
76
|
+
/** 获取转换器 */
|
|
77
|
+
export function converter<T extends TypeInfo | LegacyType | ''>(type: T): Converters[T] {
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
57
79
|
switch (type) {
|
|
58
80
|
case 'b':
|
|
59
81
|
case 'boolean':
|
|
60
|
-
return toBoolean
|
|
82
|
+
return toBoolean as Converters[T];
|
|
61
83
|
case 'f':
|
|
62
84
|
case 'number':
|
|
63
|
-
return toNumber
|
|
85
|
+
return toNumber as Converters[T];
|
|
64
86
|
case 's':
|
|
65
87
|
case 'string':
|
|
66
|
-
return toString
|
|
88
|
+
return toString as Converters[T];
|
|
67
89
|
default:
|
|
68
|
-
(type) satisfies
|
|
69
|
-
return
|
|
90
|
+
(type) satisfies '';
|
|
91
|
+
return identity as Converters[T];
|
|
70
92
|
}
|
|
71
93
|
}
|
|
72
94
|
|
|
73
|
-
|
|
74
|
-
export function toString(value: VmValue | undefined): string {
|
|
75
|
-
return to_string(value ?? null);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** 转换为数字 */
|
|
79
|
-
export function toNumber(value: VmValue | undefined): number {
|
|
80
|
-
return to_number(value ?? null);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** 转换为布尔值 */
|
|
84
|
-
export function toBoolean(value: VmValue | undefined): boolean {
|
|
85
|
-
return to_boolean(value ?? null);
|
|
86
|
-
}
|
|
95
|
+
export const { toBoolean, toFormat, toNumber, toString } = convert;
|
|
87
96
|
}
|
package/tests/condition.ts
CHANGED
|
@@ -27,20 +27,28 @@ describe('Evaluate conditions', () => {
|
|
|
27
27
|
expect(e.evaluateCondition('true' as ExpressionSource<boolean>, s)).toBe(true);
|
|
28
28
|
expect(e.evaluateCondition('false' as ExpressionSource<boolean>, s)).toBe(false);
|
|
29
29
|
});
|
|
30
|
-
it('can
|
|
31
|
-
expect(e.evaluateCondition('""' as ExpressionSource<boolean>, s)).toBe(true);
|
|
32
|
-
expect(e.evaluateCondition('nil' as ExpressionSource<boolean>, s)).toBe(true);
|
|
33
|
-
expect(e.evaluateCondition('undefined' as ExpressionSource<boolean>, s)).toBe(true);
|
|
34
|
-
expect(e.evaluateCondition('0' as ExpressionSource<boolean>, s)).toBe(true);
|
|
35
|
-
expect(e.evaluateCondition('nan' as ExpressionSource<boolean>, s)).toBe(true);
|
|
30
|
+
it('can evaluate to boolean', () => {
|
|
36
31
|
expect(e.evaluateCondition('(false)' as ExpressionSource<boolean>, s)).toBe(false);
|
|
37
|
-
|
|
38
|
-
expect(e.evaluateCondition('""' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
39
|
-
expect(e.evaluateCondition('nil' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
40
|
-
expect(e.evaluateCondition('undefined' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
41
|
-
expect(e.evaluateCondition('0' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
42
|
-
expect(e.evaluateCondition('nan' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
32
|
+
expect(e.evaluateCondition('(true)' as ExpressionSource<boolean>, s)).toBe(true);
|
|
43
33
|
expect(e.evaluateCondition('(false)' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
34
|
+
expect(e.evaluateCondition('(true)' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('can evaluate to nil', () => {
|
|
37
|
+
expect(e.evaluateCondition('{}' as ExpressionSource<boolean>, s)).toBe(true);
|
|
38
|
+
expect(e.evaluateCondition('{}' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('do not convert to boolean', () => {
|
|
41
|
+
expect(en.evaluateCondition('""' as ExpressionSource<boolean>, s)).toBe(true);
|
|
42
|
+
expect(en.evaluateCondition('nil' as ExpressionSource<boolean>, s)).toBe(true);
|
|
43
|
+
expect(en.evaluateCondition('undefined' as ExpressionSource<boolean>, s)).toBe(true);
|
|
44
|
+
expect(en.evaluateCondition('0' as ExpressionSource<boolean>, s)).toBe(true);
|
|
45
|
+
expect(en.evaluateCondition('nan' as ExpressionSource<boolean>, s)).toBe(true);
|
|
46
|
+
|
|
47
|
+
expect(en.evaluateCondition('""' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
48
|
+
expect(en.evaluateCondition('nil' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
49
|
+
expect(en.evaluateCondition('undefined' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
50
|
+
expect(en.evaluateCondition('0' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
51
|
+
expect(en.evaluateCondition('nan' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
44
52
|
});
|
|
45
53
|
it('can evaluate complex', () => {
|
|
46
54
|
expect(e.evaluateCondition('12>3' as ExpressionSource<boolean>, s)).toBe(true);
|