@cloudpss/expression 0.6.0-alpha.2 → 0.6.0-alpha.20

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.
Files changed (169) hide show
  1. package/dist/analyze.d.ts +6 -3
  2. package/dist/analyze.d.ts.map +1 -1
  3. package/dist/analyze.js +67 -33
  4. package/dist/analyze.js.map +1 -1
  5. package/dist/definitions/argument.d.ts +3 -11
  6. package/dist/definitions/argument.d.ts.map +1 -1
  7. package/dist/definitions/constraint.d.ts +2 -2
  8. package/dist/definitions/constraint.d.ts.map +1 -1
  9. package/dist/definitions/constraint.js +1 -1
  10. package/dist/definitions/constraint.js.map +1 -1
  11. package/dist/definitions/parameter-decoration.d.ts +3 -3
  12. package/dist/definitions/parameter-decoration.d.ts.map +1 -1
  13. package/dist/definitions/parameter-decoration.js +0 -7
  14. package/dist/definitions/parameter-decoration.js.map +1 -1
  15. package/dist/definitions/parameter-group.d.ts +0 -4
  16. package/dist/definitions/parameter-group.d.ts.map +1 -1
  17. package/dist/definitions/parameter-group.js +5 -17
  18. package/dist/definitions/parameter-group.js.map +1 -1
  19. package/dist/definitions/parameter.d.ts +47 -40
  20. package/dist/definitions/parameter.d.ts.map +1 -1
  21. package/dist/definitions/parameter.js +9 -21
  22. package/dist/definitions/parameter.js.map +1 -1
  23. package/dist/definitions/utils.d.ts +28 -0
  24. package/dist/definitions/utils.d.ts.map +1 -0
  25. package/dist/definitions/utils.js +209 -0
  26. package/dist/definitions/utils.js.map +1 -0
  27. package/dist/definitions/variable.d.ts +0 -2
  28. package/dist/definitions/variable.d.ts.map +1 -1
  29. package/dist/definitions/variable.js +1 -5
  30. package/dist/definitions/variable.js.map +1 -1
  31. package/dist/definitions.d.ts +1 -0
  32. package/dist/definitions.d.ts.map +1 -1
  33. package/dist/definitions.js +1 -0
  34. package/dist/definitions.js.map +1 -1
  35. package/dist/eval.d.ts +3 -5
  36. package/dist/eval.d.ts.map +1 -1
  37. package/dist/eval.js +12 -20
  38. package/dist/eval.js.map +1 -1
  39. package/dist/expression.d.ts +10 -4
  40. package/dist/expression.d.ts.map +1 -1
  41. package/dist/expression.js +6 -6
  42. package/dist/expression.js.map +1 -1
  43. package/dist/index.d.ts +3 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/interface.d.ts +30 -0
  48. package/dist/interface.d.ts.map +1 -0
  49. package/dist/interface.js +6 -0
  50. package/dist/interface.js.map +1 -0
  51. package/dist/main.d.ts +41 -28
  52. package/dist/main.d.ts.map +1 -1
  53. package/dist/main.js +149 -147
  54. package/dist/main.js.map +1 -1
  55. package/dist/migrate.d.ts +3 -2
  56. package/dist/migrate.d.ts.map +1 -1
  57. package/dist/migrate.js +25 -3
  58. package/dist/migrate.js.map +1 -1
  59. package/dist/migrator/access.d.ts.map +1 -1
  60. package/dist/migrator/access.js +45 -25
  61. package/dist/migrator/access.js.map +1 -1
  62. package/dist/migrator/call.d.ts.map +1 -1
  63. package/dist/migrator/call.js +298 -201
  64. package/dist/migrator/call.js.map +1 -1
  65. package/dist/migrator/concat.d.ts.map +1 -1
  66. package/dist/migrator/concat.js +15 -2
  67. package/dist/migrator/concat.js.map +1 -1
  68. package/dist/migrator/function.d.ts +6 -0
  69. package/dist/migrator/function.d.ts.map +1 -0
  70. package/dist/migrator/function.js +25 -0
  71. package/dist/migrator/function.js.map +1 -0
  72. package/dist/migrator/interface.d.ts +3 -1
  73. package/dist/migrator/interface.d.ts.map +1 -1
  74. package/dist/migrator/interface.js.map +1 -1
  75. package/dist/migrator/node.d.ts.map +1 -1
  76. package/dist/migrator/node.js +35 -6
  77. package/dist/migrator/node.js.map +1 -1
  78. package/dist/migrator/operator.d.ts.map +1 -1
  79. package/dist/migrator/operator.js +107 -60
  80. package/dist/migrator/operator.js.map +1 -1
  81. package/dist/migrator/serialize.d.ts +4 -0
  82. package/dist/migrator/serialize.d.ts.map +1 -0
  83. package/dist/migrator/serialize.js +21 -0
  84. package/dist/migrator/serialize.js.map +1 -0
  85. package/dist/migrator/special.d.ts.map +1 -1
  86. package/dist/migrator/special.js +31 -0
  87. package/dist/migrator/special.js.map +1 -1
  88. package/dist/migrator/state.d.ts +2 -2
  89. package/dist/migrator/state.d.ts.map +1 -1
  90. package/dist/migrator/state.js +30 -33
  91. package/dist/migrator/state.js.map +1 -1
  92. package/dist/migrator/symbol.d.ts.map +1 -1
  93. package/dist/migrator/symbol.js +23 -9
  94. package/dist/migrator/symbol.js.map +1 -1
  95. package/dist/migrator/to-type.d.ts.map +1 -1
  96. package/dist/migrator/to-type.js +21 -4
  97. package/dist/migrator/to-type.js.map +1 -1
  98. package/dist/migrator/utils.d.ts +6 -0
  99. package/dist/migrator/utils.d.ts.map +1 -1
  100. package/dist/migrator/utils.js +35 -0
  101. package/dist/migrator/utils.js.map +1 -1
  102. package/dist/parser.d.ts +2 -2
  103. package/dist/parser.d.ts.map +1 -1
  104. package/dist/parser.js +27 -8
  105. package/dist/parser.js.map +1 -1
  106. package/dist/re-exports.d.ts +4 -0
  107. package/dist/re-exports.d.ts.map +1 -0
  108. package/dist/re-exports.js +3 -0
  109. package/dist/re-exports.js.map +1 -0
  110. package/dist/scope.d.ts +17 -18
  111. package/dist/scope.d.ts.map +1 -1
  112. package/dist/scope.js +84 -53
  113. package/dist/scope.js.map +1 -1
  114. package/dist/type.d.ts +27 -6
  115. package/dist/type.d.ts.map +1 -1
  116. package/dist/type.js +34 -15
  117. package/dist/type.js.map +1 -1
  118. package/package.json +8 -5
  119. package/src/analyze.ts +77 -37
  120. package/src/definitions/argument.ts +3 -13
  121. package/src/definitions/constraint.ts +3 -3
  122. package/src/definitions/parameter-decoration.ts +3 -9
  123. package/src/definitions/parameter-group.ts +4 -19
  124. package/src/definitions/parameter.ts +70 -62
  125. package/src/definitions/utils.ts +224 -0
  126. package/src/definitions/variable.ts +1 -6
  127. package/src/definitions.ts +1 -0
  128. package/src/eval.ts +16 -25
  129. package/src/expression.ts +16 -8
  130. package/src/index.ts +3 -1
  131. package/src/interface.ts +35 -0
  132. package/src/main.ts +217 -199
  133. package/src/migrate.ts +30 -6
  134. package/src/migrator/access.ts +50 -33
  135. package/src/migrator/call.ts +287 -190
  136. package/src/migrator/concat.ts +15 -2
  137. package/src/migrator/function.ts +27 -0
  138. package/src/migrator/interface.ts +3 -1
  139. package/src/migrator/node.ts +36 -5
  140. package/src/migrator/operator.ts +110 -64
  141. package/src/migrator/serialize.ts +21 -0
  142. package/src/migrator/special.ts +31 -0
  143. package/src/migrator/state.ts +30 -33
  144. package/src/migrator/symbol.ts +23 -9
  145. package/src/migrator/to-type.ts +23 -4
  146. package/src/migrator/utils.ts +38 -0
  147. package/src/parser.ts +33 -8
  148. package/src/re-exports.ts +47 -0
  149. package/src/scope.ts +101 -65
  150. package/src/type.ts +52 -18
  151. package/tests/analyze.ts +40 -6
  152. package/tests/compile.ts +65 -0
  153. package/tests/condition.ts +33 -17
  154. package/tests/definition.ts +237 -18
  155. package/tests/eval-complex.ts +19 -11
  156. package/tests/eval.ts +59 -12
  157. package/tests/import.ts +21 -7
  158. package/tests/main.ts +58 -0
  159. package/tests/migrate.ts +308 -0
  160. package/tests/scope.ts +3 -3
  161. package/tests/template.ts +36 -0
  162. package/dist/context.d.ts +0 -41
  163. package/dist/context.d.ts.map +0 -1
  164. package/dist/context.js +0 -18
  165. package/dist/context.js.map +0 -1
  166. package/jest.config.js +0 -3
  167. package/src/context.ts +0 -54
  168. package/tests/tsconfig.json +0 -3
  169. package/tsconfig.json +0 -3
@@ -2,6 +2,7 @@ import type { SymbolNode } from 'mathjs';
2
2
  import type { Result } from './interface.js';
3
3
  import type { State } from './state.js';
4
4
  import { operations } from '@mirascript/mirascript/subtle';
5
+ import { migrateSymbolName } from './utils.js';
5
6
 
6
7
  /** 处理符号名称 */
7
8
  export function migrateSymbol(state: State, node: SymbolNode, migrateConst: boolean): Result {
@@ -10,14 +11,15 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
10
11
  if (local) {
11
12
  return {
12
13
  ...local,
13
- code: name,
14
+ code: migrateSymbolName(name, true),
14
15
  };
15
16
  }
16
- const global = state.globals.get(name);
17
- if (global != null) {
17
+ if (state.globals.has(name)) {
18
+ const global = state.globals.get(name);
18
19
  return {
19
20
  type: operations.$Type(global),
20
- code: name,
21
+ code: migrateSymbolName(name, false),
22
+ global,
21
23
  };
22
24
  }
23
25
  if (name.startsWith('$') || name.startsWith('@')) {
@@ -33,21 +35,33 @@ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: bool
33
35
  code: `i`,
34
36
  };
35
37
  }
36
- if (name === 'pi' || name === 'PI') {
38
+ if (name === 'pi') {
37
39
  return {
38
40
  type: 'number',
39
- code: `@pi`,
41
+ code: `PI`,
40
42
  };
41
43
  }
42
- if (name === 'e' || name === 'E') {
44
+ if (name === 'e') {
43
45
  return {
44
46
  type: 'number',
45
- code: `@e`,
47
+ code: `E`,
48
+ };
49
+ }
50
+ if (name === '__PREVIEW') {
51
+ return {
52
+ type: 'boolean',
53
+ code: '@PREVIEW',
54
+ };
55
+ }
56
+ if (name === '__ICON') {
57
+ return {
58
+ type: 'boolean',
59
+ code: '@ICON',
46
60
  };
47
61
  }
48
62
  }
49
63
  state.warn(`符号 '${name}' 未定义`);
50
64
  return {
51
- code: name,
65
+ code: migrateSymbolName(name, false),
52
66
  };
53
67
  }
@@ -1,13 +1,16 @@
1
1
  import { isConstantNode, isNode, type MathNode } from 'mathjs';
2
2
  import type { Result } from './interface.js';
3
- import { serialize } from '@mirascript/mirascript/subtle';
3
+ import { serialize } from './serialize.js';
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("fn @@to_boolean { it != '' && it != 0 && it is not nan && !!it }");
11
+ state.helper(
12
+ "fn @@to_boolean { if it is nil { nil } else { it != '' && it != '0' && it != 0 && it is not nan && it != false } }",
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: `to_string(${node.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.type === 'boolean') && 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: `to_number(${node.code})`,
98
+ code: `${globalFnName(state, 'to_number')}(${node.code})`,
80
99
  };
81
100
  }
82
101
  if (isConstantNode(node)) {
@@ -1,15 +1,32 @@
1
1
  import { type MathNode, isSymbolNode, isConstantNode } from 'mathjs';
2
+ import { keywords } from '@mirascript/mirascript/subtle';
2
3
  import type { Result } from './interface.js';
3
4
  import type { State } from './state.js';
4
5
  import { toString } from './to-type.js';
5
6
  import { migrateExpr } from './node.js';
6
7
 
8
+ /** 获取全局函数的名字 */
9
+ export function globalFnName(state: State, fnName: string): string {
10
+ if (state.locals.has(fnName)) {
11
+ return `global.${fnName}`;
12
+ }
13
+ return fnName;
14
+ }
15
+
7
16
  /** 获取 symbol name */
8
17
  export function symbolName(node: MathNode): string | undefined {
9
18
  if (!isSymbolNode(node)) return undefined;
10
19
  return node.name;
11
20
  }
12
21
 
22
+ const kw = new Set(keywords());
23
+ /** 转换为 mirascript 符号 */
24
+ export function migrateSymbolName(name: string, local: boolean): string {
25
+ if (!kw.has(name)) return name;
26
+ if (local) return `__${name}__`;
27
+ return `global.${name}`;
28
+ }
29
+
13
30
  /** 获取 constant value */
14
31
  export function constantValue(node: MathNode): number | string | boolean | null | undefined {
15
32
  if (!isConstantNode(node)) return undefined;
@@ -65,3 +82,24 @@ export function equalText(state: State, op: '==' | '!=', l: MathNode, r: MathNod
65
82
  code: `${toString(state, lr).code} ${op} ${toString(state, rr).code}`,
66
83
  };
67
84
  }
85
+
86
+ /** 计算长度 */
87
+ export function len(state: State, obj: Result): Result {
88
+ if (obj.type === 'string')
89
+ return {
90
+ type: 'number',
91
+ code: `len(chars(${obj.code}))`,
92
+ };
93
+ if (obj.type === 'array')
94
+ return {
95
+ type: 'number',
96
+ code: `len(${obj.code})`,
97
+ };
98
+ state.helper(
99
+ `fn @@length(x) { if type(x) == 'string' { len(chars(x)) } else if type(x) == 'array' { len(x) } else { x.length } }`,
100
+ );
101
+ return {
102
+ type: 'number',
103
+ code: `@@length(${obj.code})`,
104
+ };
105
+ }
package/src/parser.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { compileSync, type VmScript } from '@mirascript/mirascript';
2
- import type { Context } from './context.js';
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,42 @@ 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 | null, template: boolean): LRUCache<string, EvalExpressionCache> | null {
61
+ if (evaluator == null) return null;
62
+ const key = template ? kTemplateCache : kExpressionCache;
63
+ if (!(key in evaluator)) {
64
+ const size = template ? evaluator.options.templateCacheSize : evaluator.options.expressionCacheSize;
65
+ Object.defineProperty(evaluator, key, {
66
+ value: new LRUCache<string, EvalExpressionCache>({ max: size! || 50 }),
67
+ });
68
+ }
69
+ return (evaluator as Evaluator & Record<typeof key, LRUCache<string, EvalExpressionCache>>)[key];
70
+ }
71
+
54
72
  /**
55
73
  * 解析表达式
56
74
  */
57
- export function parse(context: Context, expression: string, throws: boolean): ExpressionCache {
75
+ export function parse(
76
+ evaluator: Evaluator | null,
77
+ expression: string,
78
+ throws: boolean,
79
+ template: boolean,
80
+ ): ExpressionCache {
58
81
  if (typeof expression != 'string') {
59
82
  throw new TypeError(`${String(expression)} is not a valid expression`);
60
83
  }
61
- const v = context.expressionCache.get(expression);
62
- if (v) return v;
63
- const complied = compile(expression);
84
+ const cache = getCache(evaluator, template);
85
+ const cached = cache?.get(expression);
86
+ if (cached != null) return cached;
87
+ const complied = compile(expression, template);
64
88
  if (complied.error) {
65
89
  if (throws) throw complied.error;
90
+ } else {
91
+ cache?.set(expression, complied);
66
92
  }
67
- context.expressionCache.set(expression, complied);
68
93
  return complied;
69
94
  }
@@ -0,0 +1,47 @@
1
+ export {
2
+ isVmAny,
3
+ isVmArray,
4
+ isVmConst,
5
+ isVmContext,
6
+ isVmExtern,
7
+ isVmFunction,
8
+ isVmImmutable,
9
+ isVmModule,
10
+ isVmPrimitive,
11
+ isVmRecord,
12
+ isVmArrayLikeRecord,
13
+ isVmArrayLikeRecordByEntires,
14
+ isVmArrayLikeRecordByKeys,
15
+ isVmCallable,
16
+ isVmScript,
17
+ isVmWrapper,
18
+ isVmValue,
19
+ VmExtern,
20
+ VmFunction,
21
+ VmModule,
22
+ VmError,
23
+ defineVmContextValue,
24
+ } from '@mirascript/mirascript';
25
+ export type {
26
+ VmScript,
27
+ VmValueMap,
28
+ VmAny,
29
+ VmArray,
30
+ VmConst,
31
+ VmContext,
32
+ VmImmutable,
33
+ VmPrimitive,
34
+ VmRecord,
35
+ VmUninitialized,
36
+ VmValue,
37
+ } from '@mirascript/mirascript';
38
+ export {
39
+ serialize,
40
+ serializeRecordKey,
41
+ serializeString,
42
+ type SerializeOptions,
43
+ constants,
44
+ DefaultVmContext,
45
+ lib,
46
+ operations,
47
+ } from '@mirascript/mirascript/subtle';
package/src/scope.ts CHANGED
@@ -1,5 +1,13 @@
1
- import { createVmContext, isVmExtern, type VmAny, type VmContext, type VmValue } from '@mirascript/mirascript';
2
- import type { Context } from './context.js';
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 { DEFAULTS, evaluateEval } from './eval.js';
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: unknown): value is Expression | object {
19
- return isExpression(value) || (value != null && typeof value == 'object');
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,75 +72,99 @@ class ScopeProxyHandler implements ProxyHandler<object> {
53
72
  if (p === RAW) {
54
73
  return target;
55
74
  }
56
- const value = Reflect.get(target, p) as unknown;
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
  }
66
82
 
83
+ /** 最大求值计数 */
84
+ const MAX_RECURSION = 500;
67
85
  /** 函数的执行环境 */
68
86
  export class Scope {
69
- /** 最大求值计数 */
70
- 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
+ ) {}
98
+ /** 枚举环境中的值 */
99
+ enumerator: (() => Iterable<string>) | null | undefined = null;
100
+ /** 描述环境中的值 */
101
+ describer: ((key: string) => string | undefined) | null | undefined = null;
99
102
 
100
- protected readonly getter: (key: string) => VmAny;
101
- /** 上下文 */
102
- protected context: Context | null = null;
103
+ /** 执行器上下文 */
104
+ protected evaluator: Evaluator | null = null;
103
105
  /** 求值计数 */
104
106
  protected evalCounter = 0;
105
- /** 重置执行环境 */
106
- reset(context: Context | null): () => void {
107
- const oldContext = this.context;
107
+ /** 重置执行器 */
108
+ reset(evaluator: Evaluator | null): () => void {
109
+ const oldEvaluator = this.evaluator;
108
110
  const oldCounter = this.evalCounter;
109
- this.context = context;
111
+ this.evaluator = evaluator;
110
112
  this.evalCounter = 0;
111
113
  return () => {
112
- this.context = oldContext;
114
+ this.evaluator = oldEvaluator;
113
115
  this.evalCounter = oldCounter;
114
116
  };
115
117
  }
116
118
  /** 函数的执行环境,惰性求值 */
117
- protected _proxy?: VmContext;
119
+ protected _proxy: VmContext | null = null;
118
120
  /** 函数的执行环境,惰性求值 */
119
121
  get proxy(): VmContext {
120
- this._proxy ??= createVmContext(this.getter);
121
- return this._proxy;
122
+ if (this._proxy != null) return this._proxy;
123
+ let getter: (key: string) => VmAny;
124
+ let enumerate: () => Iterable<string>;
125
+ let describe: ((key: string) => string | undefined) | undefined;
126
+ const { scope, enumerator, describer } = this;
127
+ if (scope == null) {
128
+ getter = (key) => this.evaluator?.imported.get(key);
129
+ enumerate = () => this.evaluator?.imported.keys() ?? [];
130
+ describe = () => undefined;
131
+ } else if (typeof scope == 'function') {
132
+ getter = (key) => {
133
+ const v = scope(key);
134
+ if (v === undefined) return this.evaluator?.imported.get(key);
135
+ if (!needsProxy(v)) return v;
136
+ return createProxy(v, this, [key]) as VmValue;
137
+ };
138
+ } else {
139
+ getter = (key) => {
140
+ if (!hasOwn(scope, key)) return this.evaluator?.imported.get(key);
141
+ const v = scope[key];
142
+ if (!needsProxy(v)) return v;
143
+ return createProxy(v, this, [key]) as VmValue;
144
+ };
145
+ }
146
+ if (typeof enumerator == 'function') {
147
+ enumerate = () => {
148
+ const keys = enumerator();
149
+ const imported = this.evaluator?.imported.keys() ?? [];
150
+ return [...keys, ...imported];
151
+ };
152
+ } else {
153
+ enumerate = () => this.evaluator?.imported.keys() ?? [];
154
+ }
155
+ if (typeof describer == 'function') {
156
+ describe = (key) => {
157
+ const d = describer(key);
158
+ if (d) return d;
159
+ return undefined;
160
+ };
161
+ }
162
+ const proxy = createVmContext(getter, enumerate, describe);
163
+ this._proxy = proxy;
164
+ return proxy;
122
165
  }
123
166
 
124
- /** 检查环境中是否有值 */
167
+ /** 检查环境中是否有值,不包含 evaluator 和 default context 中的值 */
125
168
  has(key: string): boolean {
126
169
  const { scope } = this;
127
170
  if (scope == null) return false;
@@ -134,8 +177,8 @@ export class Scope {
134
177
  return v !== undefined;
135
178
  }
136
179
 
137
- /** 获取环境中的值 */
138
- get(key: string): VmAny {
180
+ /** 获取环境中的值,不包含 evaluator 和 default context 中的值 */
181
+ get<V extends VmValue>(key: string): V | undefined {
139
182
  const { scope } = this;
140
183
  if (scope == null) return undefined;
141
184
  let v;
@@ -144,41 +187,34 @@ export class Scope {
144
187
  } else if (hasOwn(scope, key)) {
145
188
  v = scope[key];
146
189
  }
147
- if (!isExpression(v)) return v;
148
- return this.eval(v, [key]);
190
+ if (!isExpression(v)) return v satisfies VmValue | undefined as V | undefined;
191
+ return this.eval(v, [key]) satisfies VmValue as V;
149
192
  }
150
193
 
151
- /**
152
- * 表达式递归求值
153
- */
194
+ /** 表达式递归求值 */
154
195
  eval(expression: Expression | CompiledExpression, path: readonly PropertyKey[]): VmValue {
155
- if (this.evalCounter >= Scope.MAX_RECURSION) {
196
+ if (this.evalCounter >= MAX_RECURSION) {
197
+ this.evalCounter = 0;
156
198
  throw new Error(`Execution recursion exceeds limit`);
157
199
  }
158
200
 
159
201
  this.evalCounter++;
160
202
  try {
161
- let result;
203
+ let result: VmValue | null = null;
162
204
  if (typeof expression == 'function') {
163
- result = expression(this) as VmValue;
205
+ result = expression(this, this.evaluator!);
164
206
  } else {
165
- const { context } = this;
166
- if (!context) {
167
- throw new Error(`Undefined context`);
168
- }
169
- const exp = parse(context, expression.source, this.throws);
207
+ const { evaluator } = this;
208
+ const exp = parse(evaluator, expression.source, this.throws, false);
170
209
  if (exp.func != null) {
171
- result = evaluateEval(context, exp, this);
210
+ result = evaluateEval(evaluator, exp, this);
172
211
  } else if (exp.error != null) {
173
212
  throw exp.error;
174
- } else {
175
- result = DEFAULTS;
176
213
  }
177
214
  }
178
- if (result === DEFAULTS) return null;
179
215
  const tag = expression[ExpressionTag];
180
216
  if (!tag) return result;
181
- return TypeInfo.to(result, TypeInfo.parse(tag));
217
+ return TypeInfo.to(result, tag);
182
218
  } catch (ex) {
183
219
  throw new Error(`${(ex as Error).message || String(ex)}\nIn ${this.name}: @ ${path.join('/')}`);
184
220
  }
package/src/type.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { TypeName, VmValue, VmValueMap } from '@mirascript/mirascript';
2
- import { lib } from '@mirascript/mirascript/subtle';
2
+ import { convert } from '@mirascript/mirascript/subtle';
3
3
 
4
4
  /** 类型信息 */
5
5
  export type TypeInfo = 'string' | 'number' | 'boolean';
@@ -12,23 +12,26 @@ 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';
22
23
 
23
24
  /** 类型信息 */
24
- export const TypeInfo = {
25
- parse: (type: TypeInfo | LegacyType): TypeInfo => {
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
- is: <T extends TypeInfo | LegacyType>(value: VmValue | undefined, type: T): value is TsTypeOf<T> => {
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,55 @@ export const TypeInfo = {
43
46
  (type) satisfies never;
44
47
  return false;
45
48
  }
46
- },
47
- to: <T extends TypeInfo | LegacyType>(value: VmValue | undefined, type: T): TsTypeOf<T> => {
48
- /** 返回类型 */
49
- type R = TsTypeOf<T>;
50
- if (!type) return value as R;
49
+ }
50
+
51
+ /** 转换类型 */
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
51
83
  switch (type) {
52
84
  case 'b':
53
85
  case 'boolean':
54
- return lib.to_boolean(value) as R;
86
+ return toBoolean as Converters[T];
55
87
  case 'f':
56
88
  case 'number':
57
- return lib.to_number(value) as R;
89
+ return toNumber as Converters[T];
58
90
  case 's':
59
91
  case 'string':
60
- return lib.to_string(value) as R;
92
+ return toString as Converters[T];
61
93
  default:
62
- (type) satisfies never;
63
- return value as R;
94
+ (type) satisfies '';
95
+ return identity as Converters[T];
64
96
  }
65
- },
66
- };
97
+ }
98
+
99
+ export const { toBoolean, toFormat, toNumber, toString } = convert;
100
+ }