@cloudpss/expression 0.6.0-alpha.9 → 0.6.0
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 +13 -33
- package/dist/analyze.js.map +1 -1
- package/dist/definitions/argument.d.ts +3 -11
- 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-group.js +4 -4
- package/dist/definitions/parameter-group.js.map +1 -1
- package/dist/definitions/parameter.d.ts +22 -14
- package/dist/definitions/parameter.d.ts.map +1 -1
- package/dist/definitions/parameter.js +10 -1
- package/dist/definitions/parameter.js.map +1 -1
- package/dist/definitions/utils.d.ts +28 -0
- package/dist/definitions/utils.d.ts.map +1 -0
- package/dist/definitions/utils.js +272 -0
- package/dist/definitions/utils.js.map +1 -0
- package/dist/definitions/variable.js +1 -1
- package/dist/definitions/variable.js.map +1 -1
- package/dist/definitions.d.ts +1 -2
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +1 -1
- 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 +6 -6
- 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 +165 -149
- package/dist/main.js.map +1 -1
- package/dist/migrate.d.ts +1 -1
- package/dist/migrate.d.ts.map +1 -1
- package/dist/migrate.js +25 -4
- package/dist/migrate.js.map +1 -1
- package/dist/migrator/access.d.ts.map +1 -1
- package/dist/migrator/access.js +64 -29
- package/dist/migrator/access.js.map +1 -1
- package/dist/migrator/call.d.ts.map +1 -1
- package/dist/migrator/call.js +298 -201
- 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/function.d.ts +6 -0
- package/dist/migrator/function.d.ts.map +1 -0
- package/dist/migrator/function.js +25 -0
- package/dist/migrator/function.js.map +1 -0
- 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.d.ts.map +1 -1
- package/dist/migrator/node.js +55 -7
- package/dist/migrator/node.js.map +1 -1
- package/dist/migrator/operator.d.ts.map +1 -1
- package/dist/migrator/operator.js +107 -60
- package/dist/migrator/operator.js.map +1 -1
- package/dist/migrator/serialize.d.ts +4 -0
- package/dist/migrator/serialize.d.ts.map +1 -0
- package/dist/migrator/serialize.js +21 -0
- package/dist/migrator/serialize.js.map +1 -0
- package/dist/migrator/special.d.ts.map +1 -1
- package/dist/migrator/special.js +31 -0
- package/dist/migrator/special.js.map +1 -1
- package/dist/migrator/state.d.ts +4 -4
- package/dist/migrator/state.d.ts.map +1 -1
- package/dist/migrator/state.js +29 -31
- package/dist/migrator/state.js.map +1 -1
- package/dist/migrator/symbol.d.ts.map +1 -1
- package/dist/migrator/symbol.js +34 -12
- package/dist/migrator/symbol.js.map +1 -1
- package/dist/migrator/to-type.d.ts.map +1 -1
- package/dist/migrator/to-type.js +21 -4
- package/dist/migrator/to-type.js.map +1 -1
- package/dist/migrator/utils.d.ts +6 -0
- package/dist/migrator/utils.d.ts.map +1 -1
- package/dist/migrator/utils.js +48 -1
- 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 +27 -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 +16 -17
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +127 -66
- 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 +21 -24
- package/dist/type.js.map +1 -1
- package/package.json +8 -5
- package/src/analyze.ts +20 -39
- package/src/definitions/argument.ts +3 -13
- package/src/definitions/constraint.ts +3 -3
- package/src/definitions/parameter-group.ts +4 -4
- package/src/definitions/parameter.ts +47 -24
- package/src/definitions/utils.ts +288 -0
- package/src/definitions/variable.ts +1 -1
- package/src/definitions.ts +1 -28
- package/src/eval.ts +16 -25
- package/src/expression.ts +16 -8
- package/src/index.ts +3 -1
- package/src/interface.ts +35 -0
- package/src/main.ts +232 -200
- package/src/migrate.ts +27 -4
- package/src/migrator/access.ts +67 -37
- package/src/migrator/call.ts +287 -190
- package/src/migrator/concat.ts +15 -2
- package/src/migrator/function.ts +27 -0
- package/src/migrator/interface.ts +3 -1
- package/src/migrator/node.ts +56 -6
- package/src/migrator/operator.ts +110 -64
- package/src/migrator/serialize.ts +21 -0
- package/src/migrator/special.ts +31 -0
- package/src/migrator/state.ts +31 -34
- package/src/migrator/symbol.ts +33 -12
- package/src/migrator/to-type.ts +23 -4
- package/src/migrator/utils.ts +48 -1
- package/src/parser.ts +33 -8
- package/src/re-exports.ts +47 -0
- package/src/scope.ts +146 -77
- package/src/type.ts +40 -25
- package/tests/analyze.ts +45 -6
- package/tests/compile.ts +65 -0
- package/tests/condition.ts +33 -17
- package/tests/definition.ts +237 -18
- package/tests/eval-complex.ts +79 -13
- package/tests/eval.ts +59 -12
- package/tests/import.ts +21 -7
- package/tests/main.ts +58 -0
- package/tests/migrate.ts +317 -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/src/context.ts +0 -54
package/src/scope.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createVmContext,
|
|
3
|
+
type VmAny,
|
|
4
|
+
type VmArray,
|
|
5
|
+
type VmContext,
|
|
6
|
+
type VmRecord,
|
|
7
|
+
type VmValue,
|
|
8
|
+
isVmWrapper,
|
|
9
|
+
isVmFunction,
|
|
10
|
+
} from '@mirascript/mirascript';
|
|
3
11
|
import {
|
|
4
12
|
type CompiledExpression,
|
|
5
13
|
type Expression,
|
|
@@ -7,41 +15,57 @@ 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';
|
|
13
|
-
|
|
21
|
+
import type { Evaluator } from './main.js';
|
|
22
|
+
const { hasOwn, keys } = Object;
|
|
23
|
+
const { isArray } = Array;
|
|
14
24
|
|
|
15
25
|
const RAW = Symbol.for('@private/expression:raw');
|
|
16
26
|
|
|
17
27
|
/** 是否需要创建代理对象 */
|
|
18
|
-
function
|
|
19
|
-
return
|
|
28
|
+
function isSpecialHostObject(value: object): boolean {
|
|
29
|
+
return (
|
|
30
|
+
value instanceof Date ||
|
|
31
|
+
value instanceof Promise ||
|
|
32
|
+
value instanceof RegExp ||
|
|
33
|
+
ArrayBuffer.isView(value) ||
|
|
34
|
+
value instanceof ArrayBuffer ||
|
|
35
|
+
(typeof SharedArrayBuffer != 'undefined' && value instanceof SharedArrayBuffer)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 是否需要创建代理对象 */
|
|
40
|
+
function needsProxy(value: ExpressionOrValue | undefined): value is Expression | VmArray | VmRecord {
|
|
41
|
+
if (value == null || (typeof value != 'object' && typeof value != 'function')) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (isExpression(value)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (isVmWrapper(value) || isVmFunction(value) || isSpecialHostObject(value)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
20
51
|
}
|
|
21
52
|
|
|
22
53
|
/** 创建代理对象 */
|
|
23
|
-
function createProxy(value: Expression |
|
|
24
|
-
// 先检查 isExpression,因为 'function' 也有可能是 Expression
|
|
54
|
+
function createProxy(value: Expression | VmArray | VmRecord, scope: Scope, path: readonly PropertyKey[]): unknown {
|
|
25
55
|
if (isExpression(value)) {
|
|
26
56
|
return scope.eval(value, path);
|
|
27
57
|
}
|
|
28
58
|
if (typeof value != 'object' || value == null) {
|
|
29
59
|
return value;
|
|
30
60
|
}
|
|
31
|
-
if (
|
|
32
|
-
value
|
|
33
|
-
|
|
34
|
-
value
|
|
35
|
-
ArrayBuffer.isView(value) ||
|
|
36
|
-
value instanceof ArrayBuffer ||
|
|
37
|
-
(typeof SharedArrayBuffer != 'undefined' && value instanceof SharedArrayBuffer)
|
|
38
|
-
) {
|
|
39
|
-
return value;
|
|
61
|
+
if (isArray(value)) {
|
|
62
|
+
return new Proxy(value, new ArrayScopeProxyHandler(scope, path));
|
|
63
|
+
} else {
|
|
64
|
+
return new Proxy(value, new RecordScopeProxyHandler(scope, path));
|
|
40
65
|
}
|
|
41
|
-
return new Proxy(value, new ScopeProxyHandler(scope, path));
|
|
42
66
|
}
|
|
43
67
|
/** 函数的执行环境 */
|
|
44
|
-
class
|
|
68
|
+
class ArrayScopeProxyHandler implements ProxyHandler<object> {
|
|
45
69
|
constructor(
|
|
46
70
|
readonly scope: Scope,
|
|
47
71
|
readonly path: readonly PropertyKey[],
|
|
@@ -53,75 +77,119 @@ class ScopeProxyHandler implements ProxyHandler<object> {
|
|
|
53
77
|
if (p === RAW) {
|
|
54
78
|
return target;
|
|
55
79
|
}
|
|
56
|
-
const value = Reflect.get(target, p) as
|
|
57
|
-
if (!needsProxy(value)) {
|
|
80
|
+
const value = Reflect.get(target, p) as ExpressionOrValue;
|
|
81
|
+
if (typeof p == 'symbol' || String(Number(p)) !== p || !needsProxy(value)) {
|
|
58
82
|
return value;
|
|
59
83
|
}
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
return createProxy(value, this.scope, [...this.path, p]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** 函数的执行环境 */
|
|
88
|
+
class RecordScopeProxyHandler implements ProxyHandler<object> {
|
|
89
|
+
constructor(
|
|
90
|
+
readonly scope: Scope,
|
|
91
|
+
readonly path: readonly PropertyKey[],
|
|
92
|
+
) {}
|
|
93
|
+
/**
|
|
94
|
+
* @inheritdoc
|
|
95
|
+
*/
|
|
96
|
+
get(target: object, p: string | symbol): unknown {
|
|
97
|
+
if (p === RAW) {
|
|
98
|
+
return target;
|
|
99
|
+
}
|
|
100
|
+
const value = Reflect.get(target, p) as ExpressionOrValue;
|
|
101
|
+
if (typeof p == 'symbol' || !hasOwn(target, p) || !needsProxy(value)) {
|
|
102
|
+
return value;
|
|
62
103
|
}
|
|
63
104
|
return createProxy(value, this.scope, [...this.path, p]);
|
|
64
105
|
}
|
|
65
106
|
}
|
|
66
107
|
|
|
108
|
+
/** 最大求值计数 */
|
|
109
|
+
const MAX_RECURSION = 500;
|
|
67
110
|
/** 函数的执行环境 */
|
|
68
111
|
export class Scope {
|
|
69
|
-
/** 最大求值计数 */
|
|
70
|
-
static readonly MAX_RECURSION = 500;
|
|
71
112
|
constructor(
|
|
72
113
|
/** 生成环境的工厂函数 */
|
|
73
|
-
readonly scope
|
|
114
|
+
readonly scope:
|
|
74
115
|
| ((key: string) => ExpressionOrValue | undefined)
|
|
75
|
-
| Record<string, ExpressionOrValue | undefined
|
|
116
|
+
| Record<string, ExpressionOrValue | undefined>
|
|
117
|
+
| null = null,
|
|
76
118
|
/** 求值失败是否抛异常 */
|
|
77
119
|
readonly throws = true,
|
|
78
120
|
/** 执行环境名称 */
|
|
79
121
|
readonly name = '<anonymous>',
|
|
80
|
-
) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
}
|
|
122
|
+
) {}
|
|
123
|
+
/** 枚举环境中的值 */
|
|
124
|
+
enumerator: (() => Iterable<string>) | null | undefined = null;
|
|
125
|
+
/** 描述环境中的值 */
|
|
126
|
+
describer: ((key: string) => string | undefined) | null | undefined = null;
|
|
99
127
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
protected context: Context | null = null;
|
|
128
|
+
/** 执行器上下文 */
|
|
129
|
+
protected evaluator: Evaluator | null = null;
|
|
103
130
|
/** 求值计数 */
|
|
104
131
|
protected evalCounter = 0;
|
|
105
|
-
/**
|
|
106
|
-
reset(
|
|
107
|
-
const
|
|
132
|
+
/** 重置执行器 */
|
|
133
|
+
reset(evaluator: Evaluator | null): () => void {
|
|
134
|
+
const oldEvaluator = this.evaluator;
|
|
108
135
|
const oldCounter = this.evalCounter;
|
|
109
|
-
this.
|
|
136
|
+
this.evaluator = evaluator;
|
|
110
137
|
this.evalCounter = 0;
|
|
111
138
|
return () => {
|
|
112
|
-
this.
|
|
139
|
+
this.evaluator = oldEvaluator;
|
|
113
140
|
this.evalCounter = oldCounter;
|
|
114
141
|
};
|
|
115
142
|
}
|
|
116
143
|
/** 函数的执行环境,惰性求值 */
|
|
117
|
-
protected _proxy
|
|
144
|
+
protected _proxy: VmContext | null = null;
|
|
118
145
|
/** 函数的执行环境,惰性求值 */
|
|
119
146
|
get proxy(): VmContext {
|
|
120
|
-
this._proxy
|
|
121
|
-
|
|
147
|
+
if (this._proxy != null) return this._proxy;
|
|
148
|
+
let getter: (key: string) => VmAny;
|
|
149
|
+
let enumerate: () => Iterable<string>;
|
|
150
|
+
let describe: ((key: string) => string | undefined) | undefined;
|
|
151
|
+
const { scope, enumerator, describer } = this;
|
|
152
|
+
if (scope == null) {
|
|
153
|
+
getter = (key) => this.evaluator?.imported.get(key);
|
|
154
|
+
enumerate = () => this.evaluator?.imported.keys() ?? [];
|
|
155
|
+
describe = () => undefined;
|
|
156
|
+
} else if (typeof scope == 'function') {
|
|
157
|
+
getter = (key) => {
|
|
158
|
+
const v = scope(key);
|
|
159
|
+
if (v === undefined) return this.evaluator?.imported.get(key);
|
|
160
|
+
if (!needsProxy(v)) return v;
|
|
161
|
+
return createProxy(v, this, [key]) as VmValue;
|
|
162
|
+
};
|
|
163
|
+
} else {
|
|
164
|
+
getter = (key) => {
|
|
165
|
+
if (!hasOwn(scope, key)) return this.evaluator?.imported.get(key);
|
|
166
|
+
const v = scope[key];
|
|
167
|
+
if (!needsProxy(v)) return v;
|
|
168
|
+
return createProxy(v, this, [key]) as VmValue;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (typeof enumerator == 'function') {
|
|
172
|
+
enumerate = () => {
|
|
173
|
+
const keys = enumerator();
|
|
174
|
+
const imported = this.evaluator?.imported.keys() ?? [];
|
|
175
|
+
return [...keys, ...imported];
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
enumerate = () => this.evaluator?.imported.keys() ?? [];
|
|
179
|
+
}
|
|
180
|
+
if (typeof describer == 'function') {
|
|
181
|
+
describe = (key) => {
|
|
182
|
+
const d = describer(key);
|
|
183
|
+
if (d) return d;
|
|
184
|
+
return undefined;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const proxy = createVmContext(getter, enumerate, describe);
|
|
188
|
+
this._proxy = proxy;
|
|
189
|
+
return proxy;
|
|
122
190
|
}
|
|
123
191
|
|
|
124
|
-
/**
|
|
192
|
+
/** 检查环境中是否有值,不包含 evaluator 和 default context 中的值 */
|
|
125
193
|
has(key: string): boolean {
|
|
126
194
|
const { scope } = this;
|
|
127
195
|
if (scope == null) return false;
|
|
@@ -134,7 +202,7 @@ export class Scope {
|
|
|
134
202
|
return v !== undefined;
|
|
135
203
|
}
|
|
136
204
|
|
|
137
|
-
/**
|
|
205
|
+
/** 获取环境中的值,不包含 evaluator 和 default context 中的值 */
|
|
138
206
|
get<V extends VmValue>(key: string): V | undefined {
|
|
139
207
|
const { scope } = this;
|
|
140
208
|
if (scope == null) return undefined;
|
|
@@ -148,37 +216,30 @@ export class Scope {
|
|
|
148
216
|
return this.eval(v, [key]) satisfies VmValue as V;
|
|
149
217
|
}
|
|
150
218
|
|
|
151
|
-
/**
|
|
152
|
-
* 表达式递归求值
|
|
153
|
-
*/
|
|
219
|
+
/** 表达式递归求值 */
|
|
154
220
|
eval(expression: Expression | CompiledExpression, path: readonly PropertyKey[]): VmValue {
|
|
155
|
-
if (this.evalCounter >=
|
|
221
|
+
if (this.evalCounter >= MAX_RECURSION) {
|
|
222
|
+
this.evalCounter = 0;
|
|
156
223
|
throw new Error(`Execution recursion exceeds limit`);
|
|
157
224
|
}
|
|
158
225
|
|
|
159
226
|
this.evalCounter++;
|
|
160
227
|
try {
|
|
161
|
-
let result;
|
|
228
|
+
let result: VmValue | null = null;
|
|
162
229
|
if (typeof expression == 'function') {
|
|
163
|
-
result = expression(this)
|
|
230
|
+
result = expression(this, this.evaluator!);
|
|
164
231
|
} else {
|
|
165
|
-
const {
|
|
166
|
-
|
|
167
|
-
throw new Error(`Undefined context`);
|
|
168
|
-
}
|
|
169
|
-
const exp = parse(context, expression.source, this.throws);
|
|
232
|
+
const { evaluator } = this;
|
|
233
|
+
const exp = parse(evaluator, expression.source, this.throws, false);
|
|
170
234
|
if (exp.func != null) {
|
|
171
|
-
result = evaluateEval(
|
|
235
|
+
result = evaluateEval(evaluator, exp, this);
|
|
172
236
|
} else if (exp.error != null) {
|
|
173
237
|
throw exp.error;
|
|
174
|
-
} else {
|
|
175
|
-
result = DEFAULTS;
|
|
176
238
|
}
|
|
177
239
|
}
|
|
178
|
-
if (result === DEFAULTS) return null;
|
|
179
240
|
const tag = expression[ExpressionTag];
|
|
180
241
|
if (!tag) return result;
|
|
181
|
-
return TypeInfo.to(result,
|
|
242
|
+
return TypeInfo.to(result, tag);
|
|
182
243
|
} catch (ex) {
|
|
183
244
|
throw new Error(`${(ex as Error).message || String(ex)}\nIn ${this.name}: @ ${path.join('/')}`);
|
|
184
245
|
}
|
|
@@ -190,15 +251,23 @@ function unwrapCore<T>(value: T): T | undefined {
|
|
|
190
251
|
if (value == null || (typeof value != 'object' && typeof value != 'function')) {
|
|
191
252
|
return undefined;
|
|
192
253
|
}
|
|
254
|
+
if (isVmWrapper(value) || isVmFunction(value) || isSpecialHostObject(value)) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
// 是否有 proxy
|
|
193
258
|
const raw = (value as T & { [RAW]?: T })[RAW];
|
|
259
|
+
|
|
260
|
+
// 不论是否被代理,均须递归处理子项
|
|
194
261
|
const obj = raw ?? value;
|
|
195
|
-
|
|
196
|
-
|
|
262
|
+
// 只处理 own enumerable 属性
|
|
263
|
+
for (const key of keys(obj) as Array<Extract<keyof T, string>>) {
|
|
197
264
|
const unwrapped = unwrapCore(obj[key]);
|
|
198
265
|
if (unwrapped !== undefined) {
|
|
199
266
|
obj[key] = unwrapped;
|
|
200
267
|
}
|
|
201
268
|
}
|
|
269
|
+
|
|
270
|
+
// 返回被代理的对象,如没有 proxy 则返回 undefined
|
|
202
271
|
return raw;
|
|
203
272
|
}
|
|
204
273
|
|
package/src/type.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TypeName, VmValue, VmValueMap } from '@mirascript/mirascript';
|
|
2
|
-
import {
|
|
2
|
+
import { convert } from '@mirascript/mirascript/subtle';
|
|
3
3
|
|
|
4
4
|
/** 类型信息 */
|
|
5
5
|
export type TypeInfo = 'string' | 'number' | 'boolean';
|
|
@@ -12,10 +12,11 @@ type TypeMap = VmValueMap & {
|
|
|
12
12
|
s: string;
|
|
13
13
|
f: number;
|
|
14
14
|
b: boolean;
|
|
15
|
+
'': VmValue;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
/** Ts 类型 */
|
|
18
|
-
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];
|
|
19
20
|
|
|
20
21
|
/** 兼容的旧类型 */
|
|
21
22
|
export type LegacyType = 's' | 'f' | 'b';
|
|
@@ -48,38 +49,52 @@ export namespace TypeInfo {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/** 转换类型 */
|
|
51
|
-
export function to<T extends TypeInfo | LegacyType
|
|
52
|
-
|
|
53
|
-
type
|
|
54
|
-
|
|
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, F = undefined>(
|
|
63
|
+
value: T | undefined,
|
|
64
|
+
_fallback?: F,
|
|
65
|
+
): T /* | Exclude<F, undefined> */ {
|
|
66
|
+
if (value == null) return null as T;
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 获取转换器 */
|
|
71
|
+
type Converters = {
|
|
72
|
+
string: typeof convert.toString;
|
|
73
|
+
s: typeof convert.toString;
|
|
74
|
+
number: typeof convert.toNumber;
|
|
75
|
+
f: typeof convert.toNumber;
|
|
76
|
+
boolean: typeof convert.toBoolean;
|
|
77
|
+
b: typeof convert.toBoolean;
|
|
78
|
+
'': typeof identity;
|
|
79
|
+
};
|
|
80
|
+
/** 获取转换器 */
|
|
81
|
+
export function converter<T extends TypeInfo | LegacyType | ''>(type: T): Converters[T] {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
55
83
|
switch (type) {
|
|
56
84
|
case 'b':
|
|
57
85
|
case 'boolean':
|
|
58
|
-
return
|
|
86
|
+
return toBoolean as Converters[T];
|
|
59
87
|
case 'f':
|
|
60
88
|
case 'number':
|
|
61
|
-
return
|
|
89
|
+
return toNumber as Converters[T];
|
|
62
90
|
case 's':
|
|
63
91
|
case 'string':
|
|
64
|
-
return
|
|
92
|
+
return toString as Converters[T];
|
|
65
93
|
default:
|
|
66
|
-
(type) satisfies
|
|
67
|
-
return
|
|
94
|
+
(type) satisfies '';
|
|
95
|
+
return identity as Converters[T];
|
|
68
96
|
}
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
export function toString(value: VmValue | undefined): string {
|
|
73
|
-
return lib.to_string(value);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** 转换为数字 */
|
|
77
|
-
export function toNumber(value: VmValue | undefined): number {
|
|
78
|
-
return lib.to_number(value);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 转换为布尔值 */
|
|
82
|
-
export function toBoolean(value: VmValue | undefined): boolean {
|
|
83
|
-
return lib.to_boolean(value);
|
|
84
|
-
}
|
|
99
|
+
export const { toBoolean, toFormat, toNumber, toString } = convert;
|
|
85
100
|
}
|
package/tests/analyze.ts
CHANGED
|
@@ -1,15 +1,54 @@
|
|
|
1
1
|
import { Evaluator, Expression, Scope } from '../dist/index.js';
|
|
2
2
|
|
|
3
3
|
const e = new Evaluator();
|
|
4
|
-
|
|
4
|
+
e.import({ w: 123 });
|
|
5
|
+
const s = new Scope({ sin: 12 }, false);
|
|
5
6
|
|
|
6
|
-
describe
|
|
7
|
+
describe('Analyze', () => {
|
|
7
8
|
it('simple', () => {
|
|
8
|
-
expect(e.analyze(Expression('
|
|
9
|
+
expect(e.analyze(Expression(' '), s)).toEqual([]);
|
|
10
|
+
expect(e.analyze(Expression('a + b + @c'), s)).toEqual([['a'], ['b'], ['@c']]);
|
|
11
|
+
});
|
|
12
|
+
it('bad', () => {
|
|
13
|
+
expect(e.analyze(Expression('a + '), s)).toEqual([]);
|
|
14
|
+
expect(e.analyze(Expression('a.b.'), s)).toEqual([]);
|
|
15
|
+
expect(() => e.analyze(Expression('a.b.'))).toThrow();
|
|
16
|
+
});
|
|
17
|
+
it('local', () => {
|
|
18
|
+
expect(e.analyze(Expression('let a = 12; a + nil'), s)).toEqual([]);
|
|
19
|
+
expect(e.analyze(Expression('let a = 12; a + nil'), s)).toEqual([]);
|
|
20
|
+
expect(e.analyze(Expression('let a = 12; a + b'), s)).toEqual([['b']]);
|
|
21
|
+
});
|
|
22
|
+
it('repeat', () => {
|
|
23
|
+
expect(e.analyze(Expression('a + b + a + a.b + b.c + a * a !. b'), s)).toEqual([
|
|
24
|
+
['a'],
|
|
25
|
+
['b'],
|
|
26
|
+
['a', 'b'],
|
|
27
|
+
['b', 'c'],
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
it('prefixed', () => {
|
|
31
|
+
expect(e.analyze(Expression('@@a + $b + c. $$ + $'), s)).toEqual([['@@a'], ['$b'], ['c', '$$'], ['$']]);
|
|
32
|
+
});
|
|
33
|
+
it('unicode', () => {
|
|
34
|
+
expect(e.analyze(Expression('变量.属性 + 变量2[索引] + 函数()'), s)).toEqual([
|
|
35
|
+
['变量', '属性'],
|
|
36
|
+
['变量2'],
|
|
37
|
+
['索引'],
|
|
38
|
+
['函数'],
|
|
39
|
+
]);
|
|
9
40
|
});
|
|
10
41
|
it('access', () => {
|
|
11
|
-
expect(e.analyze(Expression('a.b[1]+b'), s)).toEqual([['a', 'b'], ['b']]);
|
|
12
|
-
expect(e.analyze(Expression('a.b["x"]+b'), s)).toEqual([['a', 'b'
|
|
13
|
-
expect(e.analyze(Expression('a.b[x]+b'), s)).toEqual([['a', 'b'], ['x'], ['b']]);
|
|
42
|
+
expect(e.analyze(Expression('a .b [1]+b'), s)).toEqual([['a', 'b'], ['b']]);
|
|
43
|
+
expect(e.analyze(Expression('a. b["x"]+b'), s)).toEqual([['a', 'b'], ['b']]);
|
|
44
|
+
expect(e.analyze(Expression('a.b[x] + b + c.12.3'), s)).toEqual([['a', 'b'], ['x'], ['b'], ['c', 12, 3]]);
|
|
45
|
+
});
|
|
46
|
+
it('with lib', () => {
|
|
47
|
+
const e0 = new Evaluator();
|
|
48
|
+
const exp = Expression('sin(x) + y::z.t()::cos() + w.123');
|
|
49
|
+
expect(e.analyze(exp, s)).toEqual([['sin'], ['x'], ['z', 't'], ['y']]);
|
|
50
|
+
expect(e0.analyze(exp, s)).toEqual([['sin'], ['x'], ['z', 't'], ['y'], ['w', 123]]);
|
|
51
|
+
expect(e.analyze(exp)).toEqual([['x'], ['z', 't'], ['y']]);
|
|
52
|
+
expect(e0.analyze(exp)).toEqual([['x'], ['z', 't'], ['y'], ['w', 123]]);
|
|
14
53
|
});
|
|
15
54
|
});
|
package/tests/compile.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Evaluator, Expression, Scope } from '../dist/index.js';
|
|
2
|
+
|
|
3
|
+
const e = new Evaluator({
|
|
4
|
+
logger: {
|
|
5
|
+
warn: () => void 0,
|
|
6
|
+
error: () => void 0,
|
|
7
|
+
info: () => void 0,
|
|
8
|
+
debug: () => void 0,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
const s = new Scope({}, false);
|
|
12
|
+
|
|
13
|
+
describe('Evaluator.compile should work correctly', () => {
|
|
14
|
+
it('should eval const', () => {
|
|
15
|
+
const result = e.compile(Expression('12'), false);
|
|
16
|
+
expect(result(s, e)).toBe(12);
|
|
17
|
+
expect(e.evaluate(result, s)).toBe(12);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should eval exp', () => {
|
|
21
|
+
const result = e.compile(Expression('exp(2)'), false);
|
|
22
|
+
expect(result(s, e)).toBe(Math.exp(2));
|
|
23
|
+
expect(e.evaluate(result, s)).toBe(Math.exp(2));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should eval error', () => {
|
|
27
|
+
const result = e.compile(Expression('exp+++'), false);
|
|
28
|
+
expect(result(s, e)).toBe(null);
|
|
29
|
+
expect(e.evaluate(result, s)).toBe(null);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('Evaluator.compile should convert result', () => {
|
|
34
|
+
it('should eval const', () => {
|
|
35
|
+
const result = e.compile(Expression('12', 'string'), false);
|
|
36
|
+
expect(result(s, e)).toBe('12');
|
|
37
|
+
expect(e.evaluate(result, s)).toBe('12');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should eval exp', () => {
|
|
41
|
+
const result = e.compile(Expression('exp(2)', 's'), false);
|
|
42
|
+
expect(result(s, e)).toBe(Math.exp(2).toString());
|
|
43
|
+
expect(e.evaluate(result, s)).toBe(Math.exp(2).toString());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should not convert defaults', () => {
|
|
47
|
+
{
|
|
48
|
+
const result = e.compile(Expression('nil', 's'), false);
|
|
49
|
+
expect(result(s, e)).toBe(null);
|
|
50
|
+
expect(e.evaluate(result, s, 1)).toBe(1);
|
|
51
|
+
}
|
|
52
|
+
{
|
|
53
|
+
// syntax error
|
|
54
|
+
const result = e.compile(Expression('exp+++', 's'), false);
|
|
55
|
+
expect(result(s, e)).toBe(null);
|
|
56
|
+
expect(e.evaluate(result, s, 1)).toBe(1);
|
|
57
|
+
}
|
|
58
|
+
{
|
|
59
|
+
// runtime error
|
|
60
|
+
const result = e.compile(Expression('x() + 1', 's'), false);
|
|
61
|
+
expect(result(s, e)).toBe(null);
|
|
62
|
+
expect(e.evaluate(result, s, 1)).toBe(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
package/tests/condition.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { Evaluator, type ExpressionSource, Scope } from '../dist/index.js';
|
|
2
2
|
|
|
3
|
-
const e = new Evaluator(
|
|
3
|
+
const e = new Evaluator({
|
|
4
|
+
logger: undefined,
|
|
5
|
+
});
|
|
6
|
+
const en = new Evaluator({
|
|
7
|
+
logger: {
|
|
8
|
+
warn: () => void 0,
|
|
9
|
+
error: () => void 0,
|
|
10
|
+
info: () => void 0,
|
|
11
|
+
debug: () => void 0,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
4
14
|
const s = new Scope(undefined, false);
|
|
5
15
|
|
|
6
16
|
describe('Evaluate conditions', () => {
|
|
@@ -9,7 +19,6 @@ describe('Evaluate conditions', () => {
|
|
|
9
19
|
expect(e.evaluateCondition(false, s)).toBe(false);
|
|
10
20
|
});
|
|
11
21
|
it('can evaluate empty literals', () => {
|
|
12
|
-
// @ts-expect-error null
|
|
13
22
|
expect(e.evaluateCondition(null, s)).toBe(true);
|
|
14
23
|
expect(e.evaluateCondition(undefined, s)).toBe(true);
|
|
15
24
|
});
|
|
@@ -18,20 +27,28 @@ describe('Evaluate conditions', () => {
|
|
|
18
27
|
expect(e.evaluateCondition('true' as ExpressionSource<boolean>, s)).toBe(true);
|
|
19
28
|
expect(e.evaluateCondition('false' as ExpressionSource<boolean>, s)).toBe(false);
|
|
20
29
|
});
|
|
21
|
-
it('can
|
|
22
|
-
expect(e.evaluateCondition('""' as ExpressionSource<boolean>, s)).toBe(true);
|
|
23
|
-
expect(e.evaluateCondition('nil' as ExpressionSource<boolean>, s)).toBe(true);
|
|
24
|
-
expect(e.evaluateCondition('undefined' as ExpressionSource<boolean>, s)).toBe(true);
|
|
25
|
-
expect(e.evaluateCondition('0' as ExpressionSource<boolean>, s)).toBe(true);
|
|
26
|
-
expect(e.evaluateCondition('nan' as ExpressionSource<boolean>, s)).toBe(true);
|
|
30
|
+
it('can evaluate to boolean', () => {
|
|
27
31
|
expect(e.evaluateCondition('(false)' as ExpressionSource<boolean>, s)).toBe(false);
|
|
28
|
-
|
|
29
|
-
expect(e.evaluateCondition('""' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
30
|
-
expect(e.evaluateCondition('nil' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
31
|
-
expect(e.evaluateCondition('undefined' as ExpressionSource<boolean>, s, false)).toBe(false);
|
|
32
|
-
expect(e.evaluateCondition('0' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
33
|
-
expect(e.evaluateCondition('nan' as ExpressionSource<boolean>, s, false)).toBe(true);
|
|
32
|
+
expect(e.evaluateCondition('(true)' as ExpressionSource<boolean>, s)).toBe(true);
|
|
34
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);
|
|
35
52
|
});
|
|
36
53
|
it('can evaluate complex', () => {
|
|
37
54
|
expect(e.evaluateCondition('12>3' as ExpressionSource<boolean>, s)).toBe(true);
|
|
@@ -42,8 +59,7 @@ describe('Evaluate conditions', () => {
|
|
|
42
59
|
expect(e.evaluateCondition('12<=3' as ExpressionSource<boolean>, s)).toBe(false);
|
|
43
60
|
});
|
|
44
61
|
it('can return default on error', () => {
|
|
45
|
-
expect(
|
|
46
|
-
|
|
47
|
-
expect(e.evaluateCondition('12>>3' as ExpressionSource<boolean>, null, false)).toBe(false);
|
|
62
|
+
expect(en.evaluateCondition('12>>3' as ExpressionSource<boolean>, undefined, true)).toBe(true);
|
|
63
|
+
expect(en.evaluateCondition('12>>3' as ExpressionSource<boolean>, null, false)).toBe(false);
|
|
48
64
|
});
|
|
49
65
|
});
|