@cloudpss/expression 0.6.0-alpha.14 → 0.6.0-alpha.16
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/access.d.ts.map +1 -1
- package/dist/migrator/access.js +3 -3
- package/dist/migrator/access.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 +23 -3
- 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/state.d.ts.map +1 -1
- package/dist/migrator/state.js +20 -5
- package/dist/migrator/state.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/re-exports.d.ts +3 -3
- package/dist/re-exports.d.ts.map +1 -1
- package/dist/re-exports.js +2 -2
- package/dist/re-exports.js.map +1 -1
- package/dist/scope.d.ts +0 -2
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +3 -3
- package/dist/scope.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/access.ts +3 -3
- package/src/migrator/call.ts +37 -15
- package/src/migrator/node.ts +24 -3
- package/src/migrator/operator.ts +55 -26
- package/src/migrator/state.ts +19 -4
- package/src/migrator/symbol.ts +2 -2
- package/src/migrator/to-type.ts +2 -2
- package/src/re-exports.ts +20 -1
- package/src/scope.ts +3 -3
- 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 +52 -9
- package/tests/scope.ts +3 -3
package/src/migrator/node.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { serializeRecordKey } from '@mirascript/mirascript/subtle';
|
|
2
2
|
import {
|
|
3
3
|
type MathNode,
|
|
4
4
|
isAccessorNode,
|
|
@@ -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 {
|
|
@@ -82,7 +83,7 @@ export function migrateNode(state: State, node: MathNode, options: Options): Res
|
|
|
82
83
|
} else {
|
|
83
84
|
literals[key] = item.literal;
|
|
84
85
|
}
|
|
85
|
-
code.push(`${
|
|
86
|
+
code.push(`${serializeRecordKey(key)}: ${item.code}`);
|
|
86
87
|
}
|
|
87
88
|
return {
|
|
88
89
|
type: 'record',
|
|
@@ -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/state.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AssignmentNode,
|
|
5
5
|
isAssignmentNode,
|
|
6
6
|
isBlockNode,
|
|
7
|
+
isConstantNode,
|
|
7
8
|
isFunctionAssignmentNode,
|
|
8
9
|
isSymbolNode,
|
|
9
10
|
type MathNode,
|
|
@@ -109,7 +110,7 @@ export class BaseState {
|
|
|
109
110
|
const r = isFunc ? migrateFunctionAssignment(this, node) : migrator(this, node);
|
|
110
111
|
let { code } = r;
|
|
111
112
|
if (stmt && !isFunc && !code.endsWith(';')) code += ';';
|
|
112
|
-
if (node.comment) code += ' //' + node.comment;
|
|
113
|
+
if (node.comment) code += ' //' + node.comment.slice(1);
|
|
113
114
|
this.ret.push(code);
|
|
114
115
|
}
|
|
115
116
|
/** 转换 */
|
|
@@ -142,6 +143,8 @@ export class BaseState {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
return;
|
|
146
|
+
} else if (isConstantNode(ast) && ast.value === undefined && ast.comment) {
|
|
147
|
+
this.ret.push(`//${ast.comment.slice(1)}`);
|
|
145
148
|
} else {
|
|
146
149
|
this.migrateStmt(migrateLast, false, ast);
|
|
147
150
|
return;
|
|
@@ -160,21 +163,33 @@ export class BaseState {
|
|
|
160
163
|
// 写入原始 math.js 作为注释
|
|
161
164
|
const lines = this.expr.split('\n').map((line) => `// ${line}`);
|
|
162
165
|
lines.unshift(`// # 原始 math.js 表达式`);
|
|
166
|
+
const addComment = (prefix: string, item: string) => {
|
|
167
|
+
if (!item.includes('\n')) {
|
|
168
|
+
lines.unshift(`// ${prefix}: ${item}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const l = item.split('\n');
|
|
172
|
+
const indent = ' '.repeat(prefix.length + 2);
|
|
173
|
+
for (let i = l.length - 1; i >= 1; i--) {
|
|
174
|
+
lines.unshift(`// ${indent}${l[i]}`);
|
|
175
|
+
}
|
|
176
|
+
lines.unshift(`// ${prefix}: ${l[0]}`);
|
|
177
|
+
};
|
|
163
178
|
if (this.errors.size || this.warnings.size || this.critical.size) {
|
|
164
179
|
const warnings = [...this.warnings];
|
|
165
180
|
for (let i = warnings.length - 1; i >= 0; i--) {
|
|
166
181
|
const warn = warnings[i]!;
|
|
167
|
-
|
|
182
|
+
addComment('- W', warn);
|
|
168
183
|
}
|
|
169
184
|
const errors = [...this.errors];
|
|
170
185
|
for (let i = errors.length - 1; i >= 0; i--) {
|
|
171
186
|
const err = errors[i]!;
|
|
172
|
-
|
|
187
|
+
addComment('- E', err);
|
|
173
188
|
}
|
|
174
189
|
const critical = [...this.critical];
|
|
175
190
|
for (let i = critical.length - 1; i >= 0; i--) {
|
|
176
191
|
const crit = critical[i]!;
|
|
177
|
-
|
|
192
|
+
addComment('- C', crit);
|
|
178
193
|
}
|
|
179
194
|
lines.unshift(`// # 转换日志`);
|
|
180
195
|
}
|
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/re-exports.ts
CHANGED
|
@@ -9,13 +9,22 @@ export {
|
|
|
9
9
|
isVmModule,
|
|
10
10
|
isVmPrimitive,
|
|
11
11
|
isVmRecord,
|
|
12
|
+
isVmArrayLikeRecord,
|
|
13
|
+
isVmArrayLikeRecordByEntires,
|
|
14
|
+
isVmArrayLikeRecordByKeys,
|
|
15
|
+
isVmCallable,
|
|
16
|
+
isVmScript,
|
|
17
|
+
isVmWrapper,
|
|
12
18
|
isVmValue,
|
|
13
19
|
VmExtern,
|
|
14
20
|
VmFunction,
|
|
15
21
|
VmModule,
|
|
22
|
+
VmError,
|
|
23
|
+
defineVmContextValue,
|
|
16
24
|
} from '@mirascript/mirascript';
|
|
17
|
-
export { serialize, serializePropName, serializeString, lib, operations } from '@mirascript/mirascript/subtle';
|
|
18
25
|
export type {
|
|
26
|
+
VmScript,
|
|
27
|
+
VmValueMap,
|
|
19
28
|
VmAny,
|
|
20
29
|
VmArray,
|
|
21
30
|
VmConst,
|
|
@@ -26,3 +35,13 @@ export type {
|
|
|
26
35
|
VmUninitialized,
|
|
27
36
|
VmValue,
|
|
28
37
|
} from '@mirascript/mirascript';
|
|
38
|
+
export {
|
|
39
|
+
serialize,
|
|
40
|
+
serializeRecordKey,
|
|
41
|
+
serializeString,
|
|
42
|
+
type SerializeOptions,
|
|
43
|
+
constants,
|
|
44
|
+
DefaultVmContext,
|
|
45
|
+
lib,
|
|
46
|
+
operations,
|
|
47
|
+
} from '@mirascript/mirascript/subtle';
|
package/src/scope.ts
CHANGED
|
@@ -80,10 +80,10 @@ class ScopeProxyHandler implements ProxyHandler<object> {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** 最大求值计数 */
|
|
84
|
+
const MAX_RECURSION = 500;
|
|
83
85
|
/** 函数的执行环境 */
|
|
84
86
|
export class Scope {
|
|
85
|
-
/** 最大求值计数 */
|
|
86
|
-
static readonly MAX_RECURSION = 500;
|
|
87
87
|
constructor(
|
|
88
88
|
/** 生成环境的工厂函数 */
|
|
89
89
|
readonly scope:
|
|
@@ -193,7 +193,7 @@ export class Scope {
|
|
|
193
193
|
|
|
194
194
|
/** 表达式递归求值 */
|
|
195
195
|
eval(expression: Expression | CompiledExpression, path: readonly PropertyKey[]): VmValue {
|
|
196
|
-
if (this.evalCounter >=
|
|
196
|
+
if (this.evalCounter >= MAX_RECURSION) {
|
|
197
197
|
this.evalCounter = 0;
|
|
198
198
|
throw new Error(`Execution recursion exceeds limit`);
|
|
199
199
|
}
|
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);
|
package/tests/definition.ts
CHANGED
|
@@ -85,8 +85,10 @@ describe('definitions', () => {
|
|
|
85
85
|
null,
|
|
86
86
|
);
|
|
87
87
|
// @ts-expect-error 测试 undefined 输入
|
|
88
|
-
expect(def.toArgumentValue(undefined, { type: 'real', key: '', name: '', description: '', value: 1 })).toBe(
|
|
89
|
-
|
|
88
|
+
expect(def.toArgumentValue(undefined, { type: 'real', key: '', name: '', description: '', value: 1 })).toBe(
|
|
89
|
+
null,
|
|
90
|
+
);
|
|
91
|
+
expect(def.toArgumentValue(null, { type: 'real', key: '', name: '', description: '', value: 1 })).toBe(null);
|
|
90
92
|
expect(def.toArgumentValue('12', { type: 'real', key: '', name: '', description: '', value: 1 })).toBe(12);
|
|
91
93
|
expect(def.toArgumentValue(null, { type: 'text', key: '', name: '', description: '', value: '1' })).toBe('');
|
|
92
94
|
expect(def.toArgumentValue(12, { type: 'text', key: '', name: '', description: '', value: '1' })).toBe('12');
|
|
@@ -96,7 +98,7 @@ describe('definitions', () => {
|
|
|
96
98
|
|
|
97
99
|
expect(
|
|
98
100
|
def.toArgumentValue(null, { type: 'choice', key: '', name: '', description: '', value: 1, choices: [] }),
|
|
99
|
-
).toBe(
|
|
101
|
+
).toBe(null);
|
|
100
102
|
expect(
|
|
101
103
|
def.toArgumentValue(null, {
|
|
102
104
|
type: 'choice',
|
|
@@ -120,7 +122,7 @@ describe('definitions', () => {
|
|
|
120
122
|
],
|
|
121
123
|
}),
|
|
122
124
|
).toBe('0');
|
|
123
|
-
expect(def.toArgumentValue(null, { type: 'logical', key: '', name: '', description: '', value: 1 })).toBe(
|
|
125
|
+
expect(def.toArgumentValue(null, { type: 'logical', key: '', name: '', description: '', value: 1 })).toBe(null);
|
|
124
126
|
expect(
|
|
125
127
|
def.toArgumentValue(null, {
|
|
126
128
|
type: 'logical',
|
|
@@ -142,7 +144,7 @@ describe('definitions', () => {
|
|
|
142
144
|
description: '',
|
|
143
145
|
value: true,
|
|
144
146
|
}),
|
|
145
|
-
).toBe(
|
|
147
|
+
).toBe(null);
|
|
146
148
|
expect(
|
|
147
149
|
def.toArgumentValue(null, {
|
|
148
150
|
type: 'logical',
|
|
@@ -194,7 +196,7 @@ describe('definitions', () => {
|
|
|
194
196
|
value: [1],
|
|
195
197
|
choices: [],
|
|
196
198
|
}),
|
|
197
|
-
).toStrictEqual([
|
|
199
|
+
).toStrictEqual([]);
|
|
198
200
|
expect(
|
|
199
201
|
def.toArgumentValue('', {
|
|
200
202
|
type: 'multiSelect',
|
|
@@ -206,7 +208,7 @@ describe('definitions', () => {
|
|
|
206
208
|
}),
|
|
207
209
|
).toStrictEqual(['']);
|
|
208
210
|
expect(
|
|
209
|
-
def.toArgumentValue(['', '1'], {
|
|
211
|
+
def.toArgumentValue(['', '1', ' 2 '], {
|
|
210
212
|
type: 'multiSelect',
|
|
211
213
|
key: '',
|
|
212
214
|
name: '',
|
|
@@ -214,9 +216,9 @@ describe('definitions', () => {
|
|
|
214
216
|
value: [1],
|
|
215
217
|
choices: [],
|
|
216
218
|
}),
|
|
217
|
-
).toStrictEqual([
|
|
219
|
+
).toStrictEqual([1, 2]);
|
|
218
220
|
expect(
|
|
219
|
-
def.toArgumentValue(['', '1'], {
|
|
221
|
+
def.toArgumentValue(['', '1', ' 2 '], {
|
|
220
222
|
type: 'multiSelect',
|
|
221
223
|
key: '',
|
|
222
224
|
name: '',
|
|
@@ -224,6 +226,6 @@ describe('definitions', () => {
|
|
|
224
226
|
value: [1],
|
|
225
227
|
choices: Expression(''),
|
|
226
228
|
}),
|
|
227
|
-
).toStrictEqual(['', '1']);
|
|
229
|
+
).toStrictEqual(['', '1', ' 2 ']);
|
|
228
230
|
});
|
|
229
231
|
});
|
package/tests/import.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VmFunction } from '@mirascript/mirascript';
|
|
2
2
|
import { Evaluator, Expression, Scope } from '../dist/index.js';
|
|
3
|
-
import { lib } from '@mirascript/mirascript/subtle';
|
|
3
|
+
import { convert, lib } from '@mirascript/mirascript/subtle';
|
|
4
4
|
|
|
5
5
|
const e = new Evaluator({
|
|
6
6
|
logger: {
|
|
@@ -26,7 +26,7 @@ describe('Import should work correctly', () => {
|
|
|
26
26
|
expect(e.evaluate(Expression('add(1,2)'), s)).toBe(3);
|
|
27
27
|
expect(e.evaluate(Expression('add::type()'), s)).toBe('extern');
|
|
28
28
|
expect(e2.evaluate(Expression('add(1,2)'), s)).toBe(null);
|
|
29
|
-
expect(e2.evaluate(Expression('add::type()'), s)).toBe(
|
|
29
|
+
expect(e2.evaluate(Expression('add::type()'), s)).toBe(null);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it('can remove modules', () => {
|
|
@@ -39,7 +39,7 @@ describe('Import should work correctly', () => {
|
|
|
39
39
|
it('can replace modules', () => {
|
|
40
40
|
e.import({ add: (x: number, y: number) => x + y });
|
|
41
41
|
expect(e.evaluate(Expression('add::type()'), s)).toBe('extern');
|
|
42
|
-
e.import({ add: VmFunction((x, y) =>
|
|
42
|
+
e.import({ add: VmFunction((x, y) => convert.toNumber(x, 0) + convert.toNumber(y, 0)) });
|
|
43
43
|
expect(e.evaluate(Expression('add::type()'), s)).toBe('function');
|
|
44
44
|
});
|
|
45
45
|
});
|