@variojs/core 0.1.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/__tests__/README.md +101 -0
- package/__tests__/errors.test.ts +139 -0
- package/__tests__/expression/cache.test.ts +118 -0
- package/__tests__/expression/compiler.test.ts +111 -0
- package/__tests__/expression/evaluate.test.ts +95 -0
- package/__tests__/expression/parser.test.ts +57 -0
- package/__tests__/expression/whitelist.test.ts +59 -0
- package/__tests__/performance.test.ts +379 -0
- package/__tests__/runtime/create-context.test.ts +78 -0
- package/__tests__/runtime/loop-context-pool.test.ts +74 -0
- package/__tests__/runtime/path.test.ts +128 -0
- package/__tests__/vm/executor-timeout.test.ts +117 -0
- package/__tests__/vm/executor.test.ts +173 -0
- package/__tests__/vm/handlers/array.test.ts +113 -0
- package/__tests__/vm/handlers/call.test.ts +93 -0
- package/dist/errors.d.ts +100 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +132 -0
- package/dist/errors.js.map +1 -0
- package/dist/expression/cache.d.ts +53 -0
- package/dist/expression/cache.d.ts.map +1 -0
- package/dist/expression/cache.js +158 -0
- package/dist/expression/cache.js.map +1 -0
- package/dist/expression/compiler.d.ts +34 -0
- package/dist/expression/compiler.d.ts.map +1 -0
- package/dist/expression/compiler.js +123 -0
- package/dist/expression/compiler.js.map +1 -0
- package/dist/expression/dependencies.d.ts +17 -0
- package/dist/expression/dependencies.d.ts.map +1 -0
- package/dist/expression/dependencies.js +106 -0
- package/dist/expression/dependencies.js.map +1 -0
- package/dist/expression/evaluate.d.ts +22 -0
- package/dist/expression/evaluate.d.ts.map +1 -0
- package/dist/expression/evaluate.js +75 -0
- package/dist/expression/evaluate.js.map +1 -0
- package/dist/expression/evaluator.d.ts +22 -0
- package/dist/expression/evaluator.d.ts.map +1 -0
- package/dist/expression/evaluator.js +506 -0
- package/dist/expression/evaluator.js.map +1 -0
- package/dist/expression/index.d.ts +12 -0
- package/dist/expression/index.d.ts.map +1 -0
- package/dist/expression/index.js +12 -0
- package/dist/expression/index.js.map +1 -0
- package/dist/expression/parser.d.ts +15 -0
- package/dist/expression/parser.d.ts.map +1 -0
- package/dist/expression/parser.js +42 -0
- package/dist/expression/parser.js.map +1 -0
- package/dist/expression/utils.d.ts +46 -0
- package/dist/expression/utils.d.ts.map +1 -0
- package/dist/expression/utils.js +78 -0
- package/dist/expression/utils.js.map +1 -0
- package/dist/expression/whitelist.d.ts +24 -0
- package/dist/expression/whitelist.d.ts.map +1 -0
- package/dist/expression/whitelist.js +198 -0
- package/dist/expression/whitelist.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/context.d.ts +8 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +7 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/create-context.d.ts +50 -0
- package/dist/runtime/create-context.d.ts.map +1 -0
- package/dist/runtime/create-context.js +73 -0
- package/dist/runtime/create-context.js.map +1 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/loop-context-pool.d.ts +58 -0
- package/dist/runtime/loop-context-pool.d.ts.map +1 -0
- package/dist/runtime/loop-context-pool.js +114 -0
- package/dist/runtime/loop-context-pool.js.map +1 -0
- package/dist/runtime/path.d.ts +114 -0
- package/dist/runtime/path.d.ts.map +1 -0
- package/dist/runtime/path.js +302 -0
- package/dist/runtime/path.js.map +1 -0
- package/dist/runtime/proxy.d.ts +18 -0
- package/dist/runtime/proxy.d.ts.map +1 -0
- package/dist/runtime/proxy.js +53 -0
- package/dist/runtime/proxy.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +20 -0
- package/dist/runtime/sandbox.d.ts.map +1 -0
- package/dist/runtime/sandbox.js +32 -0
- package/dist/runtime/sandbox.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/vm/action.d.ts +11 -0
- package/dist/vm/action.d.ts.map +1 -0
- package/dist/vm/action.js +10 -0
- package/dist/vm/action.js.map +1 -0
- package/dist/vm/errors.d.ts +5 -0
- package/dist/vm/errors.d.ts.map +1 -0
- package/dist/vm/errors.js +5 -0
- package/dist/vm/errors.js.map +1 -0
- package/dist/vm/executor.d.ts +35 -0
- package/dist/vm/executor.d.ts.map +1 -0
- package/dist/vm/executor.js +137 -0
- package/dist/vm/executor.js.map +1 -0
- package/dist/vm/handlers/array/pop.d.ts +12 -0
- package/dist/vm/handlers/array/pop.d.ts.map +1 -0
- package/dist/vm/handlers/array/pop.js +28 -0
- package/dist/vm/handlers/array/pop.js.map +1 -0
- package/dist/vm/handlers/array/push.d.ts +13 -0
- package/dist/vm/handlers/array/push.d.ts.map +1 -0
- package/dist/vm/handlers/array/push.js +42 -0
- package/dist/vm/handlers/array/push.js.map +1 -0
- package/dist/vm/handlers/array/shift.d.ts +12 -0
- package/dist/vm/handlers/array/shift.d.ts.map +1 -0
- package/dist/vm/handlers/array/shift.js +28 -0
- package/dist/vm/handlers/array/shift.js.map +1 -0
- package/dist/vm/handlers/array/splice.d.ts +12 -0
- package/dist/vm/handlers/array/splice.d.ts.map +1 -0
- package/dist/vm/handlers/array/splice.js +59 -0
- package/dist/vm/handlers/array/splice.js.map +1 -0
- package/dist/vm/handlers/array/unshift.d.ts +13 -0
- package/dist/vm/handlers/array/unshift.d.ts.map +1 -0
- package/dist/vm/handlers/array/unshift.js +42 -0
- package/dist/vm/handlers/array/unshift.js.map +1 -0
- package/dist/vm/handlers/array/utils.d.ts +10 -0
- package/dist/vm/handlers/array/utils.d.ts.map +1 -0
- package/dist/vm/handlers/array/utils.js +33 -0
- package/dist/vm/handlers/array/utils.js.map +1 -0
- package/dist/vm/handlers/batch.d.ts +12 -0
- package/dist/vm/handlers/batch.d.ts.map +1 -0
- package/dist/vm/handlers/batch.js +40 -0
- package/dist/vm/handlers/batch.js.map +1 -0
- package/dist/vm/handlers/call.d.ts +14 -0
- package/dist/vm/handlers/call.d.ts.map +1 -0
- package/dist/vm/handlers/call.js +65 -0
- package/dist/vm/handlers/call.js.map +1 -0
- package/dist/vm/handlers/emit.d.ts +12 -0
- package/dist/vm/handlers/emit.d.ts.map +1 -0
- package/dist/vm/handlers/emit.js +26 -0
- package/dist/vm/handlers/emit.js.map +1 -0
- package/dist/vm/handlers/if.d.ts +13 -0
- package/dist/vm/handlers/if.d.ts.map +1 -0
- package/dist/vm/handlers/if.js +35 -0
- package/dist/vm/handlers/if.js.map +1 -0
- package/dist/vm/handlers/index.d.ts +11 -0
- package/dist/vm/handlers/index.d.ts.map +1 -0
- package/dist/vm/handlers/index.js +46 -0
- package/dist/vm/handlers/index.js.map +1 -0
- package/dist/vm/handlers/log.d.ts +12 -0
- package/dist/vm/handlers/log.d.ts.map +1 -0
- package/dist/vm/handlers/log.js +41 -0
- package/dist/vm/handlers/log.js.map +1 -0
- package/dist/vm/handlers/loop.d.ts +12 -0
- package/dist/vm/handlers/loop.d.ts.map +1 -0
- package/dist/vm/handlers/loop.js +71 -0
- package/dist/vm/handlers/loop.js.map +1 -0
- package/dist/vm/handlers/navigate.d.ts +12 -0
- package/dist/vm/handlers/navigate.d.ts.map +1 -0
- package/dist/vm/handlers/navigate.js +43 -0
- package/dist/vm/handlers/navigate.js.map +1 -0
- package/dist/vm/handlers/set.d.ts +15 -0
- package/dist/vm/handlers/set.d.ts.map +1 -0
- package/dist/vm/handlers/set.js +30 -0
- package/dist/vm/handlers/set.js.map +1 -0
- package/dist/vm/index.d.ts +8 -0
- package/dist/vm/index.d.ts.map +1 -0
- package/dist/vm/index.js +7 -0
- package/dist/vm/index.js.map +1 -0
- package/package.json +34 -0
- package/src/errors.ts +194 -0
- package/src/expression/README.md +192 -0
- package/src/expression/cache.ts +199 -0
- package/src/expression/compiler.ts +144 -0
- package/src/expression/dependencies.ts +116 -0
- package/src/expression/evaluate.ts +95 -0
- package/src/expression/evaluator.ts +640 -0
- package/src/expression/index.ts +27 -0
- package/src/expression/parser.ts +54 -0
- package/src/expression/utils.ts +89 -0
- package/src/expression/whitelist.ts +224 -0
- package/src/globals.d.ts +10 -0
- package/src/index.ts +72 -0
- package/src/runtime/context.ts +8 -0
- package/src/runtime/create-context.ts +133 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/loop-context-pool.ts +134 -0
- package/src/runtime/path.ts +372 -0
- package/src/runtime/proxy.ts +66 -0
- package/src/runtime/sandbox.ts +43 -0
- package/src/types.ts +177 -0
- package/src/vm/errors.ts +10 -0
- package/src/vm/executor.ts +210 -0
- package/src/vm/handlers/array/pop.ts +47 -0
- package/src/vm/handlers/array/push.ts +68 -0
- package/src/vm/handlers/array/shift.ts +47 -0
- package/src/vm/handlers/array/splice.ts +78 -0
- package/src/vm/handlers/array/unshift.ts +68 -0
- package/src/vm/handlers/array/utils.ts +41 -0
- package/src/vm/handlers/batch.ts +57 -0
- package/src/vm/handlers/call.ts +92 -0
- package/src/vm/handlers/emit.ts +39 -0
- package/src/vm/handlers/if.ts +48 -0
- package/src/vm/handlers/index.ts +52 -0
- package/src/vm/handlers/log.ts +54 -0
- package/src/vm/handlers/loop.ts +102 -0
- package/src/vm/handlers/navigate.ts +64 -0
- package/src/vm/handlers/set.ts +43 -0
- package/src/vm/index.ts +8 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +30 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式编译器
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 将简单表达式编译为直接访问函数
|
|
6
|
+
* - 提升简单表达式的执行性能
|
|
7
|
+
* - 复杂表达式回退到解释执行
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type * as ESTree from '@babel/types'
|
|
11
|
+
import type { RuntimeContext } from '../types.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 编译后的表达式函数类型
|
|
15
|
+
*/
|
|
16
|
+
export type CompiledExpression = (ctx: RuntimeContext) => unknown
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 编译缓存(全局,表达式字符串 → 编译函数)
|
|
20
|
+
*/
|
|
21
|
+
const compiledCache = new Map<string, CompiledExpression | null>()
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 提取静态路径(从 MemberExpression)
|
|
25
|
+
*
|
|
26
|
+
* 例如:user.name → "user.name"
|
|
27
|
+
* 例如:items.0.text → "items.0.text"
|
|
28
|
+
*/
|
|
29
|
+
function extractStaticPath(node: ESTree.Node): string | null {
|
|
30
|
+
if (node.type === 'Identifier') {
|
|
31
|
+
return node.name
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (node.type === 'MemberExpression') {
|
|
35
|
+
const object = extractStaticPath(node.object)
|
|
36
|
+
if (!object) return null
|
|
37
|
+
|
|
38
|
+
if (node.computed) {
|
|
39
|
+
// 计算属性:obj[key],需要检查 key 是否为字面量
|
|
40
|
+
if (node.property.type === 'NumericLiteral') {
|
|
41
|
+
const prop = (node.property as ESTree.NumericLiteral).value
|
|
42
|
+
return `${object}.${String(prop)}`
|
|
43
|
+
}
|
|
44
|
+
if (node.property.type === 'StringLiteral') {
|
|
45
|
+
const prop = (node.property as ESTree.StringLiteral).value
|
|
46
|
+
return `${object}.${String(prop)}`
|
|
47
|
+
}
|
|
48
|
+
// 处理其他字面量类型(已由上面的具体类型处理,这里不需要)
|
|
49
|
+
return null
|
|
50
|
+
} else {
|
|
51
|
+
// 静态属性:obj.prop
|
|
52
|
+
if (node.property.type === 'Identifier') {
|
|
53
|
+
return `${object}.${node.property.name}`
|
|
54
|
+
}
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 编译简单表达式为直接访问函数
|
|
64
|
+
*
|
|
65
|
+
* @param ast AST 节点
|
|
66
|
+
* @returns 编译后的函数,如果无法编译则返回 null
|
|
67
|
+
*/
|
|
68
|
+
export function compileSimpleExpression(ast: ESTree.Node): CompiledExpression | null {
|
|
69
|
+
// 字面量:{{ 42 }} → () => 42
|
|
70
|
+
if (ast.type === 'NumericLiteral') {
|
|
71
|
+
const value = (ast as ESTree.NumericLiteral).value
|
|
72
|
+
return () => value
|
|
73
|
+
}
|
|
74
|
+
if (ast.type === 'StringLiteral') {
|
|
75
|
+
const value = (ast as ESTree.StringLiteral).value
|
|
76
|
+
return () => value
|
|
77
|
+
}
|
|
78
|
+
if (ast.type === 'BooleanLiteral') {
|
|
79
|
+
const value = (ast as ESTree.BooleanLiteral).value
|
|
80
|
+
return () => value
|
|
81
|
+
}
|
|
82
|
+
if (ast.type === 'NullLiteral') {
|
|
83
|
+
return () => null
|
|
84
|
+
}
|
|
85
|
+
// 注意:Literal 类型已被更具体的类型(NumericLiteral, StringLiteral等)替代
|
|
86
|
+
// 如果遇到其他字面量类型,回退到解释执行
|
|
87
|
+
|
|
88
|
+
// 标识符:{{ user }} → (ctx) => ctx._get('user')
|
|
89
|
+
// 注意:全局对象名称(window, document等)不应该被编译,应该回退到解释执行以进行安全检查
|
|
90
|
+
if (ast.type === 'Identifier') {
|
|
91
|
+
const name = ast.name
|
|
92
|
+
// 全局对象名称不应该被编译(需要安全检查)
|
|
93
|
+
const globalObjectNames = ['window', 'document', 'global', 'globalThis', 'self']
|
|
94
|
+
if (globalObjectNames.includes(name)) {
|
|
95
|
+
return null // 回退到解释执行
|
|
96
|
+
}
|
|
97
|
+
return (ctx: RuntimeContext) => ctx._get(name)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 静态成员访问:{{ user.name }} → (ctx) => ctx._get('user.name')
|
|
101
|
+
// 注意:如果路径以全局对象名称开头,不应该被编译
|
|
102
|
+
const path = extractStaticPath(ast)
|
|
103
|
+
if (path) {
|
|
104
|
+
// 检查路径是否以全局对象名称开头
|
|
105
|
+
const globalObjectNames = ['window', 'document', 'global', 'globalThis', 'self']
|
|
106
|
+
const firstSegment = path.split('.')[0]
|
|
107
|
+
if (globalObjectNames.includes(firstSegment)) {
|
|
108
|
+
return null // 回退到解释执行以进行安全检查
|
|
109
|
+
}
|
|
110
|
+
return (ctx: RuntimeContext) => ctx._get(path)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null // 复杂表达式,无法编译
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 获取或编译表达式
|
|
118
|
+
*
|
|
119
|
+
* @param expr 表达式字符串
|
|
120
|
+
* @param ast AST 节点(已解析)
|
|
121
|
+
* @returns 编译后的函数,如果无法编译则返回 null
|
|
122
|
+
*/
|
|
123
|
+
export function getCompiledExpression(
|
|
124
|
+
expr: string,
|
|
125
|
+
ast: ESTree.Node
|
|
126
|
+
): CompiledExpression | null {
|
|
127
|
+
// 检查缓存
|
|
128
|
+
if (compiledCache.has(expr)) {
|
|
129
|
+
return compiledCache.get(expr) || null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 尝试编译
|
|
133
|
+
const compiled = compileSimpleExpression(ast)
|
|
134
|
+
compiledCache.set(expr, compiled)
|
|
135
|
+
|
|
136
|
+
return compiled
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 清除编译缓存
|
|
141
|
+
*/
|
|
142
|
+
export function clearCompiledCache(): void {
|
|
143
|
+
compiledCache.clear()
|
|
144
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 依赖提取算法
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 从 AST 中提取状态依赖
|
|
6
|
+
* - 支持通配符依赖(items.*)
|
|
7
|
+
* - 保守策略:标记整个对象
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type * as ESTree from '@babel/types'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 从 AST 中提取依赖的状态路径
|
|
14
|
+
*
|
|
15
|
+
* @param ast AST 节点
|
|
16
|
+
* @returns 依赖路径数组(支持通配符)
|
|
17
|
+
*/
|
|
18
|
+
export function extractDependencies(ast: ESTree.Node): string[] {
|
|
19
|
+
const dependencies = new Set<string>()
|
|
20
|
+
|
|
21
|
+
function traverse(node: ESTree.Node, path: string = ''): void {
|
|
22
|
+
switch (node.type) {
|
|
23
|
+
case 'Identifier': {
|
|
24
|
+
const id = node as ESTree.Identifier
|
|
25
|
+
// 标识符可能是状态依赖(排除全局函数)
|
|
26
|
+
if (!isGlobalFunction(id.name)) {
|
|
27
|
+
dependencies.add(id.name)
|
|
28
|
+
}
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
case 'MemberExpression': {
|
|
33
|
+
const member = node as ESTree.MemberExpression
|
|
34
|
+
const object = member.object
|
|
35
|
+
|
|
36
|
+
if (object.type === 'Identifier') {
|
|
37
|
+
const basePath = object.name
|
|
38
|
+
|
|
39
|
+
if (member.computed) {
|
|
40
|
+
// 计算属性:items[0] -> 保守策略:标记 items.*
|
|
41
|
+
dependencies.add(`${basePath}.*`)
|
|
42
|
+
traverse(member.property, path)
|
|
43
|
+
} else {
|
|
44
|
+
// 静态属性:user.name
|
|
45
|
+
const prop = (member.property as ESTree.Identifier).name
|
|
46
|
+
dependencies.add(`${basePath}.${prop}`)
|
|
47
|
+
// 同时添加通配符依赖(保守策略)
|
|
48
|
+
dependencies.add(`${basePath}.*`)
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
// 嵌套成员访问:user.profile.name
|
|
52
|
+
traverse(object, path)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case 'OptionalMemberExpression': {
|
|
59
|
+
const member = node as ESTree.OptionalMemberExpression
|
|
60
|
+
const object = member.object
|
|
61
|
+
|
|
62
|
+
if (object.type === 'Identifier') {
|
|
63
|
+
const basePath = object.name
|
|
64
|
+
|
|
65
|
+
if (member.computed) {
|
|
66
|
+
dependencies.add(`${basePath}.*`)
|
|
67
|
+
traverse(member.property, path)
|
|
68
|
+
} else {
|
|
69
|
+
const prop = (member.property as ESTree.Identifier).name
|
|
70
|
+
dependencies.add(`${basePath}.${prop}`)
|
|
71
|
+
dependencies.add(`${basePath}.*`)
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
traverse(object, path)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
default: {
|
|
81
|
+
// 递归遍历所有子节点
|
|
82
|
+
for (const key in node) {
|
|
83
|
+
const value = (node as unknown as Record<string, unknown>)[key]
|
|
84
|
+
|
|
85
|
+
if (value == null) continue
|
|
86
|
+
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
value.forEach(child => {
|
|
89
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
90
|
+
traverse(child as ESTree.Node, path)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
} else if (value && typeof value === 'object' && 'type' in value) {
|
|
94
|
+
traverse(value as ESTree.Node, path)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
traverse(ast)
|
|
102
|
+
return Array.from(dependencies)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 检查是否为全局函数(不应作为依赖)
|
|
107
|
+
*/
|
|
108
|
+
function isGlobalFunction(name: string): boolean {
|
|
109
|
+
const globals = new Set([
|
|
110
|
+
'String', 'Number', 'Boolean', 'BigInt', 'Symbol',
|
|
111
|
+
'Array', 'Object', 'Math', 'Date',
|
|
112
|
+
'console', 'JSON', 'parseInt', 'parseFloat',
|
|
113
|
+
'isNaN', 'isFinite',
|
|
114
|
+
])
|
|
115
|
+
return globals.has(name)
|
|
116
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表达式求值入口函数
|
|
3
|
+
*
|
|
4
|
+
* 整合解析、验证、缓存、求值流程
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RuntimeContext, ExpressionOptions } from '../types.js'
|
|
8
|
+
import { ExpressionError, ErrorCodes } from '../errors.js'
|
|
9
|
+
import { parseExpression } from './parser.js'
|
|
10
|
+
import { validateAST } from './whitelist.js'
|
|
11
|
+
import { evaluateExpression } from './evaluator.js'
|
|
12
|
+
import { extractDependencies } from './dependencies.js'
|
|
13
|
+
import { getCompiledExpression } from './compiler.js'
|
|
14
|
+
import {
|
|
15
|
+
getCachedExpression,
|
|
16
|
+
setCachedExpression,
|
|
17
|
+
} from './cache.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 求值表达式(完整流程)
|
|
21
|
+
*
|
|
22
|
+
* @param expr 表达式字符串
|
|
23
|
+
* @param ctx 运行时上下文
|
|
24
|
+
* @returns 求值结果(类型无法静态推导,返回 unknown)
|
|
25
|
+
*
|
|
26
|
+
* 注意:表达式求值结果类型无法在编译时确定,因为:
|
|
27
|
+
* 1. 表达式是运行时字符串
|
|
28
|
+
* 2. 状态类型是动态的
|
|
29
|
+
* 3. 表达式可能返回任意类型
|
|
30
|
+
*
|
|
31
|
+
* 如果需要类型安全,应在使用结果时进行类型守卫或类型断言
|
|
32
|
+
*/
|
|
33
|
+
export function evaluate(
|
|
34
|
+
expr: string,
|
|
35
|
+
ctx: RuntimeContext,
|
|
36
|
+
options: ExpressionOptions = {}
|
|
37
|
+
): unknown {
|
|
38
|
+
try {
|
|
39
|
+
// 合并选项:ctx.$exprOptions 优先级低于直接传入的 options
|
|
40
|
+
const mergedOptions = {
|
|
41
|
+
...ctx.$exprOptions,
|
|
42
|
+
...options
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 1. 检查结果缓存
|
|
46
|
+
const cached = getCachedExpression(expr, ctx)
|
|
47
|
+
if (cached !== null) {
|
|
48
|
+
return cached
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 2. 解析为 AST
|
|
52
|
+
const ast = parseExpression(expr)
|
|
53
|
+
|
|
54
|
+
// 3. AST 白名单校验(传递 allowGlobals 和 maxNestingDepth 选项)
|
|
55
|
+
validateAST(ast, {
|
|
56
|
+
allowGlobals: mergedOptions.allowGlobals,
|
|
57
|
+
maxNestingDepth: mergedOptions.maxNestingDepth
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// 4. 尝试使用编译缓存(简单表达式)
|
|
61
|
+
const compiled = getCompiledExpression(expr, ast)
|
|
62
|
+
if (compiled) {
|
|
63
|
+
const result = compiled(ctx)
|
|
64
|
+
// 提取依赖并缓存结果
|
|
65
|
+
const dependencies = extractDependencies(ast)
|
|
66
|
+
setCachedExpression(expr, result, dependencies, ctx)
|
|
67
|
+
return result
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 5. 复杂表达式,使用解释执行
|
|
71
|
+
const result = evaluateExpression(ast, ctx, mergedOptions)
|
|
72
|
+
|
|
73
|
+
// 6. 提取依赖并缓存
|
|
74
|
+
const dependencies = extractDependencies(ast)
|
|
75
|
+
setCachedExpression(expr, result, dependencies, ctx)
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
} catch (error: unknown) {
|
|
79
|
+
if (error instanceof ExpressionError) {
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
83
|
+
throw new ExpressionError(
|
|
84
|
+
expr,
|
|
85
|
+
`Expression evaluation failed: ${errorMessage}`,
|
|
86
|
+
ErrorCodes.EXPRESSION_EVALUATION_ERROR,
|
|
87
|
+
{
|
|
88
|
+
metadata: {
|
|
89
|
+
originalError: error instanceof Error ? error.name : 'Unknown',
|
|
90
|
+
stack: error instanceof Error ? error.stack?.split('\n').slice(0, 5) : undefined
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|