@cloudpss/expression 0.6.0-alpha.1 → 0.6.0-alpha.11
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/analyze.d.ts +6 -3
- package/dist/analyze.d.ts.map +1 -1
- package/dist/analyze.js +67 -33
- package/dist/analyze.js.map +1 -1
- package/dist/definitions/argument.d.ts +2 -10
- package/dist/definitions/argument.d.ts.map +1 -1
- package/dist/definitions/constraint.d.ts +2 -2
- package/dist/definitions/constraint.d.ts.map +1 -1
- package/dist/definitions/constraint.js +1 -1
- package/dist/definitions/constraint.js.map +1 -1
- package/dist/definitions/parameter-decoration.d.ts +3 -3
- package/dist/definitions/parameter-decoration.d.ts.map +1 -1
- package/dist/definitions/parameter-decoration.js +0 -7
- package/dist/definitions/parameter-decoration.js.map +1 -1
- package/dist/definitions/parameter-group.d.ts +0 -4
- package/dist/definitions/parameter-group.d.ts.map +1 -1
- package/dist/definitions/parameter-group.js +5 -17
- package/dist/definitions/parameter-group.js.map +1 -1
- package/dist/definitions/parameter.d.ts +47 -40
- package/dist/definitions/parameter.d.ts.map +1 -1
- package/dist/definitions/parameter.js +9 -21
- package/dist/definitions/parameter.js.map +1 -1
- package/dist/definitions/utils.d.ts +19 -0
- package/dist/definitions/utils.d.ts.map +1 -0
- package/dist/definitions/utils.js +177 -0
- package/dist/definitions/utils.js.map +1 -0
- package/dist/definitions/variable.d.ts +0 -2
- package/dist/definitions/variable.d.ts.map +1 -1
- package/dist/definitions/variable.js +1 -5
- package/dist/definitions/variable.js.map +1 -1
- package/dist/definitions.d.ts +1 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +1 -0
- package/dist/definitions.js.map +1 -1
- package/dist/eval.d.ts +3 -5
- package/dist/eval.d.ts.map +1 -1
- package/dist/eval.js +12 -20
- package/dist/eval.js.map +1 -1
- package/dist/expression.d.ts +10 -4
- package/dist/expression.d.ts.map +1 -1
- package/dist/expression.js +4 -4
- package/dist/expression.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interface.d.ts +30 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +6 -0
- package/dist/interface.js.map +1 -0
- package/dist/main.d.ts +41 -28
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +145 -141
- package/dist/main.js.map +1 -1
- package/dist/migrate.d.ts +3 -2
- package/dist/migrate.d.ts.map +1 -1
- package/dist/migrate.js +25 -3
- package/dist/migrate.js.map +1 -1
- package/dist/migrator/access.d.ts.map +1 -1
- package/dist/migrator/access.js +44 -24
- package/dist/migrator/access.js.map +1 -1
- package/dist/migrator/call.d.ts.map +1 -1
- package/dist/migrator/call.js +254 -217
- package/dist/migrator/call.js.map +1 -1
- package/dist/migrator/concat.d.ts.map +1 -1
- package/dist/migrator/concat.js +15 -2
- package/dist/migrator/concat.js.map +1 -1
- package/dist/migrator/interface.d.ts +3 -1
- package/dist/migrator/interface.d.ts.map +1 -1
- package/dist/migrator/interface.js.map +1 -1
- package/dist/migrator/node.js +1 -1
- package/dist/migrator/node.js.map +1 -1
- package/dist/migrator/operator.d.ts.map +1 -1
- package/dist/migrator/operator.js +43 -29
- package/dist/migrator/operator.js.map +1 -1
- package/dist/migrator/state.d.ts +2 -2
- package/dist/migrator/state.d.ts.map +1 -1
- package/dist/migrator/state.js +1 -2
- package/dist/migrator/state.js.map +1 -1
- package/dist/migrator/symbol.d.ts.map +1 -1
- package/dist/migrator/symbol.js +13 -0
- package/dist/migrator/symbol.js.map +1 -1
- package/dist/migrator/to-type.d.ts.map +1 -1
- package/dist/migrator/to-type.js +20 -3
- package/dist/migrator/to-type.js.map +1 -1
- package/dist/migrator/utils.d.ts +4 -0
- package/dist/migrator/utils.d.ts.map +1 -1
- package/dist/migrator/utils.js +25 -0
- package/dist/migrator/utils.js.map +1 -1
- package/dist/parser.d.ts +2 -2
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +25 -8
- package/dist/parser.js.map +1 -1
- package/dist/re-exports.d.ts +4 -0
- package/dist/re-exports.d.ts.map +1 -0
- package/dist/re-exports.js +3 -0
- package/dist/re-exports.js.map +1 -0
- package/dist/scope.d.ts +13 -16
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +56 -49
- package/dist/scope.js.map +1 -1
- package/dist/type.d.ts +14 -5
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +35 -11
- package/dist/type.js.map +1 -1
- package/package.json +7 -4
- package/src/analyze.ts +77 -37
- package/src/definitions/argument.ts +2 -12
- package/src/definitions/constraint.ts +3 -3
- package/src/definitions/parameter-decoration.ts +3 -9
- package/src/definitions/parameter-group.ts +4 -19
- package/src/definitions/parameter.ts +62 -61
- package/src/definitions/utils.ts +175 -0
- package/src/definitions/variable.ts +1 -6
- package/src/definitions.ts +1 -0
- package/src/eval.ts +13 -26
- package/src/expression.ts +14 -6
- package/src/index.ts +3 -1
- package/src/interface.ts +35 -0
- package/src/main.ts +213 -194
- package/src/migrate.ts +30 -6
- package/src/migrator/access.ts +49 -32
- package/src/migrator/call.ts +246 -203
- package/src/migrator/concat.ts +15 -2
- package/src/migrator/interface.ts +3 -1
- package/src/migrator/node.ts +1 -1
- package/src/migrator/operator.ts +47 -33
- package/src/migrator/state.ts +2 -2
- package/src/migrator/symbol.ts +13 -0
- package/src/migrator/to-type.ts +22 -3
- package/src/migrator/utils.ts +29 -0
- package/src/parser.ts +27 -8
- package/src/re-exports.ts +28 -0
- package/src/scope.ts +75 -61
- package/src/type.ts +32 -11
- package/tests/analyze.ts +40 -6
- package/tests/compile.ts +65 -0
- package/tests/condition.ts +13 -5
- package/tests/definition.ts +205 -18
- package/tests/eval-complex.ts +7 -10
- package/tests/eval.ts +59 -12
- package/tests/import.ts +18 -4
- package/tests/main.ts +9 -0
- package/tests/migrate.ts +202 -0
- package/tests/scope.ts +3 -3
- package/tests/template.ts +36 -0
- package/dist/context.d.ts +0 -41
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -18
- package/dist/context.js.map +0 -1
- package/jest.config.js +0 -3
- package/src/context.ts +0 -54
- package/tests/tsconfig.json +0 -3
- package/tsconfig.json +0 -3
package/src/migrator/operator.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
isOperatorNode,
|
|
9
9
|
isNode,
|
|
10
10
|
} from 'mathjs';
|
|
11
|
-
import { symbolName, constantValue, equalText, scalar } from './utils.js';
|
|
11
|
+
import { symbolName, constantValue, equalText, scalar, globalFnName } from './utils.js';
|
|
12
12
|
import type { State } from './state.js';
|
|
13
13
|
import type { Options, Result } from './interface.js';
|
|
14
14
|
import { migrateAtomic, migrateExpr } from './node.js';
|
|
@@ -19,50 +19,58 @@ import { operations, serialize } from '@mirascript/mirascript/subtle';
|
|
|
19
19
|
const BINARY_MATH_OPERATORS = {
|
|
20
20
|
add: [
|
|
21
21
|
' + ',
|
|
22
|
-
(l, r) => ({
|
|
22
|
+
(state, l, r) => ({
|
|
23
23
|
type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
|
|
24
|
-
code:
|
|
24
|
+
code: `${globalFnName(state, 'matrix')}.add(${l.code}, ${r.code})`,
|
|
25
25
|
}),
|
|
26
26
|
],
|
|
27
27
|
subtract: [
|
|
28
28
|
' - ',
|
|
29
|
-
(l, r) => ({
|
|
29
|
+
(state, l, r) => ({
|
|
30
30
|
type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
|
|
31
|
-
code:
|
|
31
|
+
code: `${globalFnName(state, 'matrix')}.subtract(${l.code}, ${r.code})`,
|
|
32
32
|
}),
|
|
33
33
|
],
|
|
34
34
|
multiply: [
|
|
35
35
|
' * ',
|
|
36
|
-
(l, r) => ({
|
|
36
|
+
(state, l, r) => ({
|
|
37
37
|
type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
|
|
38
|
-
code:
|
|
38
|
+
code: `${globalFnName(state, 'matrix')}.multiply(${l.code}, ${r.code})`,
|
|
39
39
|
}),
|
|
40
40
|
],
|
|
41
41
|
dotMultiply: [
|
|
42
42
|
' * ',
|
|
43
|
-
(l, r) => ({
|
|
43
|
+
(state, l, r) => ({
|
|
44
44
|
type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
|
|
45
|
-
code:
|
|
45
|
+
code: `${globalFnName(state, 'matrix')}.entrywise_multiply(${l.code}, ${r.code})`,
|
|
46
46
|
}),
|
|
47
47
|
],
|
|
48
48
|
divide: [
|
|
49
49
|
' / ',
|
|
50
|
-
(l, r) =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
(state, l, r) => {
|
|
51
|
+
if (r.type === 'array' || !r.type) {
|
|
52
|
+
return {
|
|
53
|
+
type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
|
|
54
|
+
code: `${globalFnName(state, 'matrix')}.multiply(${l.code}, ${globalFnName(state, 'matrix')}.invert(${r.code}))`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
type: l.type === 'array' ? 'array' : l.type ? 'number' : undefined,
|
|
59
|
+
code: `${globalFnName(state, 'matrix')}.entrywise_divide(${l.code}, ${r.code})`,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
54
62
|
],
|
|
55
63
|
dotDivide: [
|
|
56
64
|
' / ',
|
|
57
|
-
(l, r) => ({
|
|
65
|
+
(state, l, r) => ({
|
|
58
66
|
type: l.type === 'array' || r.type === 'array' ? 'array' : l.type && r.type ? 'number' : undefined,
|
|
59
|
-
code:
|
|
67
|
+
code: `${globalFnName(state, 'matrix')}.entrywise_divide(${l.code}, ${r.code})`,
|
|
60
68
|
}),
|
|
61
69
|
],
|
|
62
70
|
mod: [' % '],
|
|
63
71
|
pow: ['^', false],
|
|
64
72
|
dotPow: ['^'],
|
|
65
|
-
} satisfies Record<string, [op: string, alt?: ((l: Result, r: Result) => Result) | false]>;
|
|
73
|
+
} satisfies Record<string, [op: string, alt?: ((state: State, l: Result, r: Result) => Result) | false]>;
|
|
66
74
|
|
|
67
75
|
const MATH_FUNCTIONS = {
|
|
68
76
|
factorial: 'factorial',
|
|
@@ -99,47 +107,47 @@ function binary(
|
|
|
99
107
|
state: State,
|
|
100
108
|
l: Result,
|
|
101
109
|
r: Result,
|
|
102
|
-
op: (l: Result, r: Result) => Result,
|
|
103
|
-
alt?: (l: Result, r: Result) => Result,
|
|
110
|
+
op: (state: State, l: Result, r: Result) => Result,
|
|
111
|
+
alt?: (state: State, l: Result, r: Result) => Result,
|
|
104
112
|
): Result {
|
|
105
113
|
if (l.type && r.type && l.type !== 'array' && r.type !== 'array') {
|
|
106
|
-
return op(l, r);
|
|
114
|
+
return op(state, l, r);
|
|
107
115
|
}
|
|
108
116
|
if (Array.isArray(l.literal) && l.literal.every((e) => !Array.isArray(e))) {
|
|
109
117
|
return {
|
|
110
118
|
type: 'array',
|
|
111
|
-
code: `${l.code}
|
|
119
|
+
code: `${l.code}::${globalFnName(state, 'map')}(fn { ${op(state, { code: 'it' }, r).code} })`,
|
|
112
120
|
};
|
|
113
121
|
}
|
|
114
122
|
if (Array.isArray(r.literal) && r.literal.every((e) => !Array.isArray(e))) {
|
|
115
123
|
return {
|
|
116
124
|
type: 'array',
|
|
117
|
-
code: `${r.code}
|
|
125
|
+
code: `${r.code}::${globalFnName(state, 'map')}(fn { ${op(state, l, { code: 'it' }).code} })`,
|
|
118
126
|
};
|
|
119
127
|
}
|
|
120
128
|
if (alt) {
|
|
121
|
-
return alt(l, r);
|
|
129
|
+
return alt(state, l, r);
|
|
122
130
|
}
|
|
123
131
|
return {
|
|
124
132
|
type: l.type === 'array' || r.type === 'array' ? 'array' : undefined,
|
|
125
|
-
code:
|
|
133
|
+
code: `${globalFnName(state, 'matrix')}.entrywise(${l.code}, ${r.code}, fn (a, b) { ${op(state, { code: 'a' }, { code: 'b' }).code} })`,
|
|
126
134
|
};
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
/** 一元操作 */
|
|
130
|
-
function unary(state: State, v: Result, op: (v: Result) => Result): Result {
|
|
138
|
+
function unary(state: State, v: Result, op: (state: State, v: Result) => Result): Result {
|
|
131
139
|
if (v.type && v.type !== 'array') {
|
|
132
|
-
return op(v);
|
|
140
|
+
return op(state, v);
|
|
133
141
|
}
|
|
134
142
|
if (Array.isArray(v.literal) && v.literal.every((e) => !Array.isArray(e))) {
|
|
135
143
|
return {
|
|
136
144
|
type: 'array',
|
|
137
|
-
code: `${v.code}
|
|
145
|
+
code: `${v.code}::${globalFnName(state, 'map')}(fn { ${op(state, { code: 'it' }).code} })`,
|
|
138
146
|
};
|
|
139
147
|
}
|
|
140
148
|
return {
|
|
141
149
|
type: 'array',
|
|
142
|
-
code:
|
|
150
|
+
code: `${globalFnName(state, 'matrix')}.entrywise(${v.code}, nil, fn { ${op(state, { code: 'it' }).code} })`,
|
|
143
151
|
};
|
|
144
152
|
}
|
|
145
153
|
|
|
@@ -171,14 +179,14 @@ export function migrateOperator(
|
|
|
171
179
|
state,
|
|
172
180
|
migrateExpr(state, a0),
|
|
173
181
|
migrateExpr(state, a1),
|
|
174
|
-
(l, r) => ({
|
|
182
|
+
(state, l, r) => ({
|
|
175
183
|
type: 'number',
|
|
176
184
|
code: `${l.code}${op}${r.code}`,
|
|
177
185
|
}),
|
|
178
186
|
alt === false
|
|
179
|
-
? (l, r) => {
|
|
187
|
+
? (state, l, r) => {
|
|
180
188
|
state.warn(`'${op.trim()}' 不支持矩阵,计算结果可能不一致`);
|
|
181
|
-
return { code: `${l.code}
|
|
189
|
+
return { code: `${l.code}${op}${r.code}` };
|
|
182
190
|
}
|
|
183
191
|
: alt,
|
|
184
192
|
);
|
|
@@ -187,9 +195,9 @@ export function migrateOperator(
|
|
|
187
195
|
case 'unaryMinus':
|
|
188
196
|
case 'unaryPlus': {
|
|
189
197
|
const op = fn === 'unaryMinus' ? '-' : '+';
|
|
190
|
-
const f = fn === 'unaryMinus' ? operations.$Neg : operations.$Pos;
|
|
191
198
|
const exp = migrateExpr(state, a0);
|
|
192
199
|
if (typeof exp.literal == 'number') {
|
|
200
|
+
const f = fn === 'unaryMinus' ? operations.$Neg : operations.$Pos;
|
|
193
201
|
const v = f(exp.literal);
|
|
194
202
|
return {
|
|
195
203
|
type: 'number',
|
|
@@ -197,7 +205,7 @@ export function migrateOperator(
|
|
|
197
205
|
code: serialize(v),
|
|
198
206
|
};
|
|
199
207
|
}
|
|
200
|
-
return unary(state, migrateExpr(state, a0), (v) => ({
|
|
208
|
+
return unary(state, migrateExpr(state, a0), (state, v) => ({
|
|
201
209
|
type: 'number',
|
|
202
210
|
code: `${op}${v.code}`,
|
|
203
211
|
}));
|
|
@@ -307,7 +315,13 @@ export function migrateOperator(
|
|
|
307
315
|
return r;
|
|
308
316
|
};
|
|
309
317
|
|
|
310
|
-
return binary(state, a(a0), a(a1), (l, r) => {
|
|
318
|
+
return binary(state, a(a0), a(a1), (state, l, r) => {
|
|
319
|
+
if (l.type === 'boolean' && r.type === 'boolean') {
|
|
320
|
+
return {
|
|
321
|
+
type: 'boolean',
|
|
322
|
+
code: `${l.code} ${op} ${r.code}`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
311
325
|
return {
|
|
312
326
|
type: 'boolean',
|
|
313
327
|
code: `${toNumber(state, l).code} ${op} ${toNumber(state, r).code}`,
|
package/src/migrator/state.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { VmContext } from '@mirascript/mirascript';
|
|
2
2
|
import { parse } from './parser.js';
|
|
3
3
|
import {
|
|
4
4
|
type AssignmentNode,
|
|
@@ -87,7 +87,7 @@ export class BaseState {
|
|
|
87
87
|
private readonly expr: string,
|
|
88
88
|
readonly condition: boolean,
|
|
89
89
|
/** 可识别的全局环境 */
|
|
90
|
-
readonly globals: VmContext
|
|
90
|
+
readonly globals: VmContext,
|
|
91
91
|
) {}
|
|
92
92
|
/** 帮助函数 */
|
|
93
93
|
private readonly helpers = new Set<string>();
|
package/src/migrator/symbol.ts
CHANGED
|
@@ -18,6 +18,7 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
|
|
|
18
18
|
return {
|
|
19
19
|
type: operations.$Type(global),
|
|
20
20
|
code: name,
|
|
21
|
+
global,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
if (name.startsWith('$') || name.startsWith('@')) {
|
|
@@ -45,6 +46,18 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
|
|
|
45
46
|
code: `@e`,
|
|
46
47
|
};
|
|
47
48
|
}
|
|
49
|
+
if (name === '__PREVIEW') {
|
|
50
|
+
return {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
code: '@PREVIEW',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (name === '__ICON') {
|
|
56
|
+
return {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
code: '@ICON',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
state.warn(`符号 '${name}' 未定义`);
|
|
50
63
|
return {
|
package/src/migrator/to-type.ts
CHANGED
|
@@ -3,11 +3,14 @@ import type { Result } from './interface.js';
|
|
|
3
3
|
import { serialize } from '@mirascript/mirascript/subtle';
|
|
4
4
|
import type { State } from './state.js';
|
|
5
5
|
import { migrateNode } from './node.js';
|
|
6
|
+
import { globalFnName } from './utils.js';
|
|
6
7
|
|
|
7
8
|
/** 转换 AST */
|
|
8
9
|
export function toBoolean(state: State, node: MathNode | Result): Result {
|
|
9
10
|
const helper = (): void => {
|
|
10
|
-
state.helper(
|
|
11
|
+
state.helper(
|
|
12
|
+
"fn @@to_boolean { if it is nil { return nil; } it != '' && it != '0' && it != 0 && it is not nan && !!it }",
|
|
13
|
+
);
|
|
11
14
|
};
|
|
12
15
|
if (!isNode(node)) {
|
|
13
16
|
if (node.type === 'boolean') return node;
|
|
@@ -46,9 +49,17 @@ export function toString(state: State, node: MathNode | Result): Result {
|
|
|
46
49
|
code: node.as_string,
|
|
47
50
|
};
|
|
48
51
|
}
|
|
52
|
+
if (node.type === 'number' && node.literal !== undefined) {
|
|
53
|
+
const lit = String(node.literal);
|
|
54
|
+
return {
|
|
55
|
+
type: 'string',
|
|
56
|
+
literal: lit,
|
|
57
|
+
code: serialize(lit),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
49
60
|
return {
|
|
50
61
|
type: 'string',
|
|
51
|
-
code:
|
|
62
|
+
code: `${globalFnName(state, 'to_string')}(${node.code})`,
|
|
52
63
|
};
|
|
53
64
|
}
|
|
54
65
|
if (isConstantNode(node)) {
|
|
@@ -74,9 +85,17 @@ export function toNumber(state: State, node: MathNode | Result): Result {
|
|
|
74
85
|
code: node.as_number,
|
|
75
86
|
};
|
|
76
87
|
}
|
|
88
|
+
if (node.type === 'string' && node.literal !== undefined) {
|
|
89
|
+
const lit = Number(node.literal);
|
|
90
|
+
return {
|
|
91
|
+
type: 'number',
|
|
92
|
+
literal: lit,
|
|
93
|
+
code: serialize(lit),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
77
96
|
return {
|
|
78
97
|
type: 'number',
|
|
79
|
-
code:
|
|
98
|
+
code: `${globalFnName(state, 'to_number')}(${node.code})`,
|
|
80
99
|
};
|
|
81
100
|
}
|
|
82
101
|
if (isConstantNode(node)) {
|
package/src/migrator/utils.ts
CHANGED
|
@@ -4,6 +4,14 @@ import type { State } from './state.js';
|
|
|
4
4
|
import { toString } from './to-type.js';
|
|
5
5
|
import { migrateExpr } from './node.js';
|
|
6
6
|
|
|
7
|
+
/** 获取全局函数的名字 */
|
|
8
|
+
export function globalFnName(state: State, fnName: string): string {
|
|
9
|
+
if (state.locals.has(fnName)) {
|
|
10
|
+
return `global.${fnName}`;
|
|
11
|
+
}
|
|
12
|
+
return fnName;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
/** 获取 symbol name */
|
|
8
16
|
export function symbolName(node: MathNode): string | undefined {
|
|
9
17
|
if (!isSymbolNode(node)) return undefined;
|
|
@@ -65,3 +73,24 @@ export function equalText(state: State, op: '==' | '!=', l: MathNode, r: MathNod
|
|
|
65
73
|
code: `${toString(state, lr).code} ${op} ${toString(state, rr).code}`,
|
|
66
74
|
};
|
|
67
75
|
}
|
|
76
|
+
|
|
77
|
+
/** 计算长度 */
|
|
78
|
+
export function len(state: State, obj: Result): Result {
|
|
79
|
+
if (obj.type === 'string')
|
|
80
|
+
return {
|
|
81
|
+
type: 'number',
|
|
82
|
+
code: `len(chars(${obj.code}))`,
|
|
83
|
+
};
|
|
84
|
+
if (obj.type === 'array')
|
|
85
|
+
return {
|
|
86
|
+
type: 'number',
|
|
87
|
+
code: `len(${obj.code})`,
|
|
88
|
+
};
|
|
89
|
+
state.helper(
|
|
90
|
+
`fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } else { x.length } }`,
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
type: 'number',
|
|
94
|
+
code: `@@length(${obj.code})`,
|
|
95
|
+
};
|
|
96
|
+
}
|
package/src/parser.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { compileSync, type VmScript } from '@mirascript/mirascript';
|
|
2
|
-
import
|
|
2
|
+
import { LRUCache } from 'lru-cache';
|
|
3
|
+
import type { Evaluator } from './main.js';
|
|
4
|
+
|
|
3
5
|
/** 一般表达式 */
|
|
4
6
|
export interface EvalExpressionCache {
|
|
5
7
|
/**
|
|
@@ -38,12 +40,12 @@ export type ExpressionCache = ErrorExpressionCache | EvalExpressionCache;
|
|
|
38
40
|
/**
|
|
39
41
|
* 编译表达式
|
|
40
42
|
*/
|
|
41
|
-
function compile(expression: string): ExpressionCache {
|
|
43
|
+
function compile(expression: string, template: boolean): ExpressionCache {
|
|
42
44
|
const compiled = {
|
|
43
45
|
source: expression,
|
|
44
46
|
} as ExpressionCache;
|
|
45
47
|
try {
|
|
46
|
-
const script = compileSync(expression);
|
|
48
|
+
const script = compileSync(expression, { input_mode: template ? 'Template' : 'Script' });
|
|
47
49
|
compiled.func = script;
|
|
48
50
|
} catch (ex) {
|
|
49
51
|
compiled.error = ex as Error;
|
|
@@ -51,19 +53,36 @@ function compile(expression: string): ExpressionCache {
|
|
|
51
53
|
return compiled;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
const kTemplateCache = Symbol('cloudpss.expression.templateCache');
|
|
57
|
+
const kExpressionCache = Symbol('cloudpss.expression.expressionCache');
|
|
58
|
+
|
|
59
|
+
/** 获取编译缓存 */
|
|
60
|
+
function getCache(evaluator: Evaluator, template: boolean): LRUCache<string, EvalExpressionCache> {
|
|
61
|
+
const key = template ? kTemplateCache : kExpressionCache;
|
|
62
|
+
if (!(key in evaluator)) {
|
|
63
|
+
const size = template ? evaluator.options.templateCacheSize : evaluator.options.expressionCacheSize;
|
|
64
|
+
Object.defineProperty(evaluator, key, {
|
|
65
|
+
value: new LRUCache<string, EvalExpressionCache>({ max: size! || 50 }),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return (evaluator as Evaluator & Record<typeof key, LRUCache<string, EvalExpressionCache>>)[key];
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
/**
|
|
55
72
|
* 解析表达式
|
|
56
73
|
*/
|
|
57
|
-
export function parse(
|
|
74
|
+
export function parse(evaluator: Evaluator, expression: string, throws: boolean, template: boolean): ExpressionCache {
|
|
58
75
|
if (typeof expression != 'string') {
|
|
59
76
|
throw new TypeError(`${String(expression)} is not a valid expression`);
|
|
60
77
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
const cache = getCache(evaluator, template);
|
|
79
|
+
const cached = cache.get(expression);
|
|
80
|
+
if (cached != null) return cached;
|
|
81
|
+
const complied = compile(expression, template);
|
|
64
82
|
if (complied.error) {
|
|
65
83
|
if (throws) throw complied.error;
|
|
84
|
+
} else {
|
|
85
|
+
cache.set(expression, complied);
|
|
66
86
|
}
|
|
67
|
-
context.expressionCache.set(expression, complied);
|
|
68
87
|
return complied;
|
|
69
88
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
isVmAny,
|
|
3
|
+
isVmArray,
|
|
4
|
+
isVmConst,
|
|
5
|
+
isVmContext,
|
|
6
|
+
isVmExtern,
|
|
7
|
+
isVmFunction,
|
|
8
|
+
isVmImmutable,
|
|
9
|
+
isVmModule,
|
|
10
|
+
isVmPrimitive,
|
|
11
|
+
isVmRecord,
|
|
12
|
+
isVmValue,
|
|
13
|
+
VmExtern,
|
|
14
|
+
VmFunction,
|
|
15
|
+
VmModule,
|
|
16
|
+
} from '@mirascript/mirascript';
|
|
17
|
+
export { serialize, serializePropName, serializeString, lib, operations } from '@mirascript/mirascript/subtle';
|
|
18
|
+
export type {
|
|
19
|
+
VmAny,
|
|
20
|
+
VmArray,
|
|
21
|
+
VmConst,
|
|
22
|
+
VmContext,
|
|
23
|
+
VmImmutable,
|
|
24
|
+
VmPrimitive,
|
|
25
|
+
VmRecord,
|
|
26
|
+
VmUninitialized,
|
|
27
|
+
VmValue,
|
|
28
|
+
} from '@mirascript/mirascript';
|
package/src/scope.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createVmContext,
|
|
3
|
+
isVmExtern,
|
|
4
|
+
type VmAny,
|
|
5
|
+
type VmArray,
|
|
6
|
+
type VmContext,
|
|
7
|
+
type VmRecord,
|
|
8
|
+
type VmValue,
|
|
9
|
+
isVmModule,
|
|
10
|
+
} from '@mirascript/mirascript';
|
|
3
11
|
import {
|
|
4
12
|
type CompiledExpression,
|
|
5
13
|
type Expression,
|
|
@@ -7,16 +15,27 @@ import {
|
|
|
7
15
|
ExpressionTag,
|
|
8
16
|
isExpression,
|
|
9
17
|
} from './expression.js';
|
|
10
|
-
import {
|
|
18
|
+
import { evaluateEval } from './eval.js';
|
|
11
19
|
import { parse } from './parser.js';
|
|
12
20
|
import { TypeInfo } from './type.js';
|
|
21
|
+
import type { Evaluator } from './main.js';
|
|
13
22
|
const { hasOwn } = Object;
|
|
14
23
|
|
|
15
24
|
const RAW = Symbol.for('@private/expression:raw');
|
|
16
25
|
|
|
17
26
|
/** 是否需要创建代理对象 */
|
|
18
|
-
function needsProxy(value:
|
|
19
|
-
|
|
27
|
+
function needsProxy(value: ExpressionOrValue | undefined): value is Expression | VmArray | VmRecord {
|
|
28
|
+
// 先检查 isExpression,因为 'function' 也有可能是 Expression
|
|
29
|
+
if (isExpression(value)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (value == null || typeof value != 'object') {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (isVmExtern(value) || isVmModule(value)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
20
39
|
}
|
|
21
40
|
|
|
22
41
|
/** 创建代理对象 */
|
|
@@ -53,13 +72,10 @@ class ScopeProxyHandler implements ProxyHandler<object> {
|
|
|
53
72
|
if (p === RAW) {
|
|
54
73
|
return target;
|
|
55
74
|
}
|
|
56
|
-
const value = Reflect.get(target, p) as
|
|
57
|
-
if (!needsProxy(value)) {
|
|
75
|
+
const value = Reflect.get(target, p) as ExpressionOrValue;
|
|
76
|
+
if (isVmExtern(target) || !needsProxy(value)) {
|
|
58
77
|
return value;
|
|
59
78
|
}
|
|
60
|
-
if (isVmExtern(target)) {
|
|
61
|
-
return createProxy(value, this.scope, this.path);
|
|
62
|
-
}
|
|
63
79
|
return createProxy(value, this.scope, [...this.path, p]);
|
|
64
80
|
}
|
|
65
81
|
}
|
|
@@ -70,58 +86,61 @@ export class Scope {
|
|
|
70
86
|
static readonly MAX_RECURSION = 500;
|
|
71
87
|
constructor(
|
|
72
88
|
/** 生成环境的工厂函数 */
|
|
73
|
-
readonly scope
|
|
89
|
+
readonly scope:
|
|
74
90
|
| ((key: string) => ExpressionOrValue | undefined)
|
|
75
|
-
| Record<string, ExpressionOrValue | undefined
|
|
91
|
+
| Record<string, ExpressionOrValue | undefined>
|
|
92
|
+
| null = null,
|
|
76
93
|
/** 求值失败是否抛异常 */
|
|
77
94
|
readonly throws = true,
|
|
78
95
|
/** 执行环境名称 */
|
|
79
96
|
readonly name = '<anonymous>',
|
|
80
|
-
) {
|
|
81
|
-
if (scope == null) {
|
|
82
|
-
this.getter = (key) => this.context?.imported.get(key);
|
|
83
|
-
} else if (typeof scope == 'function') {
|
|
84
|
-
this.getter = (key) => {
|
|
85
|
-
const v = scope(key);
|
|
86
|
-
if (v === undefined) return this.context?.imported.get(key);
|
|
87
|
-
if (!needsProxy(v)) return v;
|
|
88
|
-
return createProxy(v, this, [key]) as VmValue;
|
|
89
|
-
};
|
|
90
|
-
} else {
|
|
91
|
-
this.getter = (key) => {
|
|
92
|
-
if (!hasOwn(scope, key)) return this.context?.imported.get(key);
|
|
93
|
-
const v = scope[key];
|
|
94
|
-
if (!needsProxy(v)) return v;
|
|
95
|
-
return createProxy(v, this, [key]) as VmValue;
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
97
|
+
) {}
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
protected context: Context | null = null;
|
|
99
|
+
/** 执行器上下文 */
|
|
100
|
+
protected evaluator: Evaluator | null = null;
|
|
103
101
|
/** 求值计数 */
|
|
104
102
|
protected evalCounter = 0;
|
|
105
|
-
/**
|
|
106
|
-
reset(
|
|
107
|
-
const
|
|
103
|
+
/** 重置执行器 */
|
|
104
|
+
reset(evaluator: Evaluator | null): () => void {
|
|
105
|
+
const oldEvaluator = this.evaluator;
|
|
108
106
|
const oldCounter = this.evalCounter;
|
|
109
|
-
this.
|
|
107
|
+
this.evaluator = evaluator;
|
|
110
108
|
this.evalCounter = 0;
|
|
111
109
|
return () => {
|
|
112
|
-
this.
|
|
110
|
+
this.evaluator = oldEvaluator;
|
|
113
111
|
this.evalCounter = oldCounter;
|
|
114
112
|
};
|
|
115
113
|
}
|
|
116
114
|
/** 函数的执行环境,惰性求值 */
|
|
117
|
-
protected _proxy
|
|
115
|
+
protected _proxy: VmContext | null = null;
|
|
118
116
|
/** 函数的执行环境,惰性求值 */
|
|
119
117
|
get proxy(): VmContext {
|
|
120
|
-
this._proxy
|
|
121
|
-
|
|
118
|
+
if (this._proxy != null) return this._proxy;
|
|
119
|
+
let getter: (key: string) => VmAny;
|
|
120
|
+
const { scope } = this;
|
|
121
|
+
if (scope == null) {
|
|
122
|
+
getter = (key) => this.evaluator?.imported.get(key);
|
|
123
|
+
} else if (typeof scope == 'function') {
|
|
124
|
+
getter = (key) => {
|
|
125
|
+
const v = scope(key);
|
|
126
|
+
if (v === undefined) return this.evaluator?.imported.get(key);
|
|
127
|
+
if (!needsProxy(v)) return v;
|
|
128
|
+
return createProxy(v, this, [key]) as VmValue;
|
|
129
|
+
};
|
|
130
|
+
} else {
|
|
131
|
+
getter = (key) => {
|
|
132
|
+
if (!hasOwn(scope, key)) return this.evaluator?.imported.get(key);
|
|
133
|
+
const v = scope[key];
|
|
134
|
+
if (!needsProxy(v)) return v;
|
|
135
|
+
return createProxy(v, this, [key]) as VmValue;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const proxy = createVmContext(getter);
|
|
139
|
+
this._proxy = proxy;
|
|
140
|
+
return proxy;
|
|
122
141
|
}
|
|
123
142
|
|
|
124
|
-
/**
|
|
143
|
+
/** 检查环境中是否有值,不包含 evaluator 和 default context 中的值 */
|
|
125
144
|
has(key: string): boolean {
|
|
126
145
|
const { scope } = this;
|
|
127
146
|
if (scope == null) return false;
|
|
@@ -134,8 +153,8 @@ export class Scope {
|
|
|
134
153
|
return v !== undefined;
|
|
135
154
|
}
|
|
136
155
|
|
|
137
|
-
/**
|
|
138
|
-
get(key: string):
|
|
156
|
+
/** 获取环境中的值,不包含 evaluator 和 default context 中的值 */
|
|
157
|
+
get<V extends VmValue>(key: string): V | undefined {
|
|
139
158
|
const { scope } = this;
|
|
140
159
|
if (scope == null) return undefined;
|
|
141
160
|
let v;
|
|
@@ -144,13 +163,11 @@ export class Scope {
|
|
|
144
163
|
} else if (hasOwn(scope, key)) {
|
|
145
164
|
v = scope[key];
|
|
146
165
|
}
|
|
147
|
-
if (!isExpression(v)) return v;
|
|
148
|
-
return this.eval(v, [key]);
|
|
166
|
+
if (!isExpression(v)) return v satisfies VmValue | undefined as V | undefined;
|
|
167
|
+
return this.eval(v, [key]) satisfies VmValue as V;
|
|
149
168
|
}
|
|
150
169
|
|
|
151
|
-
/**
|
|
152
|
-
* 表达式递归求值
|
|
153
|
-
*/
|
|
170
|
+
/** 表达式递归求值 */
|
|
154
171
|
eval(expression: Expression | CompiledExpression, path: readonly PropertyKey[]): VmValue {
|
|
155
172
|
if (this.evalCounter >= Scope.MAX_RECURSION) {
|
|
156
173
|
throw new Error(`Execution recursion exceeds limit`);
|
|
@@ -158,27 +175,24 @@ export class Scope {
|
|
|
158
175
|
|
|
159
176
|
this.evalCounter++;
|
|
160
177
|
try {
|
|
161
|
-
let result;
|
|
178
|
+
let result: VmValue | null = null;
|
|
162
179
|
if (typeof expression == 'function') {
|
|
163
|
-
result = expression(this)
|
|
180
|
+
result = expression(this, this.evaluator!);
|
|
164
181
|
} else {
|
|
165
|
-
const {
|
|
166
|
-
if (!
|
|
167
|
-
throw new Error(`Undefined
|
|
182
|
+
const { evaluator } = this;
|
|
183
|
+
if (!evaluator) {
|
|
184
|
+
throw new Error(`Undefined evaluator`);
|
|
168
185
|
}
|
|
169
|
-
const exp = parse(
|
|
186
|
+
const exp = parse(evaluator, expression.source, this.throws, false);
|
|
170
187
|
if (exp.func != null) {
|
|
171
|
-
result = evaluateEval(
|
|
188
|
+
result = evaluateEval(evaluator, exp, this);
|
|
172
189
|
} else if (exp.error != null) {
|
|
173
190
|
throw exp.error;
|
|
174
|
-
} else {
|
|
175
|
-
result = DEFAULTS;
|
|
176
191
|
}
|
|
177
192
|
}
|
|
178
|
-
if (result === DEFAULTS) return null;
|
|
179
193
|
const tag = expression[ExpressionTag];
|
|
180
194
|
if (!tag) return result;
|
|
181
|
-
return TypeInfo.to(result,
|
|
195
|
+
return TypeInfo.to(result, tag);
|
|
182
196
|
} catch (ex) {
|
|
183
197
|
throw new Error(`${(ex as Error).message || String(ex)}\nIn ${this.name}: @ ${path.join('/')}`);
|
|
184
198
|
}
|