@cloudpss/expression 0.6.0-alpha.1 → 0.6.0-alpha.10
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 +12 -3
- package/dist/migrate.js.map +1 -1
- package/dist/migrator/access.d.ts.map +1 -1
- package/dist/migrator/access.js +22 -24
- package/dist/migrator/access.js.map +1 -1
- package/dist/migrator/call.d.ts.map +1 -1
- package/dist/migrator/call.js +235 -209
- package/dist/migrator/call.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 +19 -5
- 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 +16 -0
- package/dist/migrator/to-type.js.map +1 -1
- package/dist/migrator/utils.d.ts +2 -0
- package/dist/migrator/utils.d.ts.map +1 -1
- package/dist/migrator/utils.js +18 -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 +4 -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 +15 -6
- package/src/migrator/access.ts +21 -26
- package/src/migrator/call.ts +225 -194
- package/src/migrator/interface.ts +3 -1
- package/src/migrator/node.ts +1 -1
- package/src/migrator/operator.ts +19 -5
- package/src/migrator/state.ts +2 -2
- package/src/migrator/symbol.ts +13 -0
- package/src/migrator/to-type.ts +16 -0
- package/src/migrator/utils.ts +21 -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 +77 -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/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
|
}
|
package/src/type.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TypeName, VmValue, VmValueMap } from '@mirascript/mirascript';
|
|
2
2
|
import { lib } from '@mirascript/mirascript/subtle';
|
|
3
|
+
const { to_string, to_number, to_boolean } = lib;
|
|
3
4
|
|
|
4
5
|
/** 类型信息 */
|
|
5
6
|
export type TypeInfo = 'string' | 'number' | 'boolean';
|
|
@@ -21,14 +22,16 @@ export type TsTypeOf<T extends TypeInfo | LegacyType> = TypeMap[T];
|
|
|
21
22
|
export type LegacyType = 's' | 'f' | 'b';
|
|
22
23
|
|
|
23
24
|
/** 类型信息 */
|
|
24
|
-
export
|
|
25
|
-
|
|
25
|
+
export namespace TypeInfo {
|
|
26
|
+
/** 解析类型 */
|
|
27
|
+
export function parse(type: TypeInfo | LegacyType): TypeInfo {
|
|
26
28
|
if (type === 's' || type === 'string') return 'string';
|
|
27
29
|
if (type === 'f' || type === 'number') return 'number';
|
|
28
30
|
if (type === 'b' || type === 'boolean') return 'boolean';
|
|
29
31
|
throw new TypeError(`Invalid type '${String(type)}'`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
+
}
|
|
33
|
+
/** 判断类型 */
|
|
34
|
+
export function is<T extends TypeInfo | LegacyType>(value: VmValue | undefined, type: T): value is TsTypeOf<T> {
|
|
32
35
|
switch (type) {
|
|
33
36
|
case 'b':
|
|
34
37
|
case 'boolean':
|
|
@@ -43,24 +46,42 @@ export const TypeInfo = {
|
|
|
43
46
|
(type) satisfies never;
|
|
44
47
|
return false;
|
|
45
48
|
}
|
|
46
|
-
}
|
|
47
|
-
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 转换类型 */
|
|
52
|
+
export function to<T extends TypeInfo | LegacyType>(value: VmValue | undefined, type: T): TsTypeOf<T> {
|
|
48
53
|
/** 返回类型 */
|
|
49
54
|
type R = TsTypeOf<T>;
|
|
55
|
+
value ??= null;
|
|
50
56
|
if (!type) return value as R;
|
|
51
57
|
switch (type) {
|
|
52
58
|
case 'b':
|
|
53
59
|
case 'boolean':
|
|
54
|
-
return
|
|
60
|
+
return toBoolean(value) as R;
|
|
55
61
|
case 'f':
|
|
56
62
|
case 'number':
|
|
57
|
-
return
|
|
63
|
+
return toNumber(value) as R;
|
|
58
64
|
case 's':
|
|
59
65
|
case 'string':
|
|
60
|
-
return
|
|
66
|
+
return toString(value) as R;
|
|
61
67
|
default:
|
|
62
68
|
(type) satisfies never;
|
|
63
69
|
return value as R;
|
|
64
70
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
71
|
+
}
|
|
72
|
+
|
|
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
|
+
}
|
|
87
|
+
}
|
package/tests/analyze.ts
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
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([]);
|
|
9
35
|
});
|
|
10
36
|
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']]);
|
|
37
|
+
expect(e.analyze(Expression('a .b [1]+b'), s)).toEqual([['a', 'b'], ['b']]);
|
|
38
|
+
expect(e.analyze(Expression('a. b["x"]+b'), s)).toEqual([['a', 'b'], ['b']]);
|
|
39
|
+
expect(e.analyze(Expression('a.b[x] + b + c.12.3'), s)).toEqual([['a', 'b'], ['x'], ['b'], ['c', 12, 3]]);
|
|
40
|
+
});
|
|
41
|
+
it('with lib', () => {
|
|
42
|
+
const e0 = new Evaluator();
|
|
43
|
+
const exp = Expression('sin(x) + y::z.t()::cos() + w.123');
|
|
44
|
+
expect(e.analyze(exp, s)).toEqual([['sin'], ['x'], ['z', 't'], ['y']]);
|
|
45
|
+
expect(e0.analyze(exp, s)).toEqual([['sin'], ['x'], ['z', 't'], ['y'], ['w', 123]]);
|
|
46
|
+
expect(e.analyze(exp)).toEqual([['x'], ['z', 't'], ['y']]);
|
|
47
|
+
expect(e0.analyze(exp)).toEqual([['x'], ['z', 't'], ['y'], ['w', 123]]);
|
|
14
48
|
});
|
|
15
49
|
});
|
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
|
});
|
|
@@ -42,8 +51,7 @@ describe('Evaluate conditions', () => {
|
|
|
42
51
|
expect(e.evaluateCondition('12<=3' as ExpressionSource<boolean>, s)).toBe(false);
|
|
43
52
|
});
|
|
44
53
|
it('can return default on error', () => {
|
|
45
|
-
expect(
|
|
46
|
-
|
|
47
|
-
expect(e.evaluateCondition('12>>3' as ExpressionSource<boolean>, null, false)).toBe(false);
|
|
54
|
+
expect(en.evaluateCondition('12>>3' as ExpressionSource<boolean>, undefined, true)).toBe(true);
|
|
55
|
+
expect(en.evaluateCondition('12>>3' as ExpressionSource<boolean>, null, false)).toBe(false);
|
|
48
56
|
});
|
|
49
57
|
});
|