@cloudpss/expression 0.6.0-alpha.1

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 (165) hide show
  1. package/README.md +3 -0
  2. package/dist/analyze.d.ts +7 -0
  3. package/dist/analyze.d.ts.map +1 -0
  4. package/dist/analyze.js +38 -0
  5. package/dist/analyze.js.map +1 -0
  6. package/dist/context.d.ts +41 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +18 -0
  9. package/dist/context.js.map +1 -0
  10. package/dist/definitions/argument.d.ts +33 -0
  11. package/dist/definitions/argument.d.ts.map +1 -0
  12. package/dist/definitions/argument.js +2 -0
  13. package/dist/definitions/argument.js.map +1 -0
  14. package/dist/definitions/constraint.d.ts +32 -0
  15. package/dist/definitions/constraint.d.ts.map +1 -0
  16. package/dist/definitions/constraint.js +20 -0
  17. package/dist/definitions/constraint.js.map +1 -0
  18. package/dist/definitions/parameter-decoration.d.ts +27 -0
  19. package/dist/definitions/parameter-decoration.d.ts.map +1 -0
  20. package/dist/definitions/parameter-decoration.js +17 -0
  21. package/dist/definitions/parameter-decoration.js.map +1 -0
  22. package/dist/definitions/parameter-group.d.ts +38 -0
  23. package/dist/definitions/parameter-group.d.ts.map +1 -0
  24. package/dist/definitions/parameter-group.js +31 -0
  25. package/dist/definitions/parameter-group.js.map +1 -0
  26. package/dist/definitions/parameter.d.ts +214 -0
  27. package/dist/definitions/parameter.d.ts.map +1 -0
  28. package/dist/definitions/parameter.js +44 -0
  29. package/dist/definitions/parameter.js.map +1 -0
  30. package/dist/definitions/variable.d.ts +14 -0
  31. package/dist/definitions/variable.d.ts.map +1 -0
  32. package/dist/definitions/variable.js +14 -0
  33. package/dist/definitions/variable.js.map +1 -0
  34. package/dist/definitions.d.ts +7 -0
  35. package/dist/definitions.d.ts.map +1 -0
  36. package/dist/definitions.js +7 -0
  37. package/dist/definitions.js.map +1 -0
  38. package/dist/eval.d.ts +15 -0
  39. package/dist/eval.d.ts.map +1 -0
  40. package/dist/eval.js +54 -0
  41. package/dist/eval.js.map +1 -0
  42. package/dist/expression.d.ts +37 -0
  43. package/dist/expression.d.ts.map +1 -0
  44. package/dist/expression.js +18 -0
  45. package/dist/expression.js.map +1 -0
  46. package/dist/index.d.ts +6 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +5 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/main.d.ts +49 -0
  51. package/dist/main.d.ts.map +1 -0
  52. package/dist/main.js +238 -0
  53. package/dist/main.js.map +1 -0
  54. package/dist/migrate.d.ts +5 -0
  55. package/dist/migrate.d.ts.map +1 -0
  56. package/dist/migrate.js +23 -0
  57. package/dist/migrate.js.map +1 -0
  58. package/dist/migrator/access.d.ts +6 -0
  59. package/dist/migrator/access.d.ts.map +1 -0
  60. package/dist/migrator/access.js +176 -0
  61. package/dist/migrator/access.js.map +1 -0
  62. package/dist/migrator/call.d.ts +6 -0
  63. package/dist/migrator/call.d.ts.map +1 -0
  64. package/dist/migrator/call.js +405 -0
  65. package/dist/migrator/call.js.map +1 -0
  66. package/dist/migrator/concat.d.ts +6 -0
  67. package/dist/migrator/concat.d.ts.map +1 -0
  68. package/dist/migrator/concat.js +32 -0
  69. package/dist/migrator/concat.js.map +1 -0
  70. package/dist/migrator/condition.d.ts +6 -0
  71. package/dist/migrator/condition.d.ts.map +1 -0
  72. package/dist/migrator/condition.js +110 -0
  73. package/dist/migrator/condition.js.map +1 -0
  74. package/dist/migrator/interface.d.ts +24 -0
  75. package/dist/migrator/interface.d.ts.map +1 -0
  76. package/dist/migrator/interface.js +8 -0
  77. package/dist/migrator/interface.js.map +1 -0
  78. package/dist/migrator/node.d.ts +12 -0
  79. package/dist/migrator/node.d.ts.map +1 -0
  80. package/dist/migrator/node.js +119 -0
  81. package/dist/migrator/node.js.map +1 -0
  82. package/dist/migrator/operator.d.ts +6 -0
  83. package/dist/migrator/operator.d.ts.map +1 -0
  84. package/dist/migrator/operator.js +299 -0
  85. package/dist/migrator/operator.js.map +1 -0
  86. package/dist/migrator/parser.d.ts +3 -0
  87. package/dist/migrator/parser.d.ts.map +1 -0
  88. package/dist/migrator/parser.js +76 -0
  89. package/dist/migrator/parser.js.map +1 -0
  90. package/dist/migrator/special.d.ts +4 -0
  91. package/dist/migrator/special.d.ts.map +1 -0
  92. package/dist/migrator/special.js +26 -0
  93. package/dist/migrator/special.js.map +1 -0
  94. package/dist/migrator/state.d.ts +64 -0
  95. package/dist/migrator/state.d.ts.map +1 -0
  96. package/dist/migrator/state.js +251 -0
  97. package/dist/migrator/state.js.map +1 -0
  98. package/dist/migrator/symbol.d.ts +6 -0
  99. package/dist/migrator/symbol.d.ts.map +1 -0
  100. package/dist/migrator/symbol.js +50 -0
  101. package/dist/migrator/symbol.js.map +1 -0
  102. package/dist/migrator/to-type.d.ts +10 -0
  103. package/dist/migrator/to-type.d.ts.map +1 -0
  104. package/dist/migrator/to-type.js +89 -0
  105. package/dist/migrator/to-type.js.map +1 -0
  106. package/dist/migrator/utils.d.ts +14 -0
  107. package/dist/migrator/utils.d.ts.map +1 -0
  108. package/dist/migrator/utils.js +64 -0
  109. package/dist/migrator/utils.js.map +1 -0
  110. package/dist/parser.d.ts +41 -0
  111. package/dist/parser.d.ts.map +1 -0
  112. package/dist/parser.js +36 -0
  113. package/dist/parser.js.map +1 -0
  114. package/dist/scope.d.ts +43 -0
  115. package/dist/scope.d.ts.map +1 -0
  116. package/dist/scope.js +204 -0
  117. package/dist/scope.js.map +1 -0
  118. package/dist/type.d.ts +21 -0
  119. package/dist/type.d.ts.map +1 -0
  120. package/dist/type.js +50 -0
  121. package/dist/type.js.map +1 -0
  122. package/jest.config.js +3 -0
  123. package/package.json +26 -0
  124. package/src/analyze.ts +44 -0
  125. package/src/context.ts +54 -0
  126. package/src/definitions/argument.ts +40 -0
  127. package/src/definitions/constraint.ts +49 -0
  128. package/src/definitions/parameter-decoration.ts +42 -0
  129. package/src/definitions/parameter-group.ts +62 -0
  130. package/src/definitions/parameter.ts +276 -0
  131. package/src/definitions/variable.ts +23 -0
  132. package/src/definitions.ts +6 -0
  133. package/src/eval.ts +64 -0
  134. package/src/expression.ts +62 -0
  135. package/src/index.ts +5 -0
  136. package/src/main.ts +295 -0
  137. package/src/migrate.ts +25 -0
  138. package/src/migrator/access.ts +201 -0
  139. package/src/migrator/call.ts +397 -0
  140. package/src/migrator/concat.ts +28 -0
  141. package/src/migrator/condition.ts +120 -0
  142. package/src/migrator/interface.ts +30 -0
  143. package/src/migrator/node.ts +131 -0
  144. package/src/migrator/operator.ts +339 -0
  145. package/src/migrator/parser.ts +106 -0
  146. package/src/migrator/special.ts +30 -0
  147. package/src/migrator/state.ts +262 -0
  148. package/src/migrator/symbol.ts +53 -0
  149. package/src/migrator/to-type.ts +93 -0
  150. package/src/migrator/utils.ts +67 -0
  151. package/src/parser.ts +69 -0
  152. package/src/scope.ts +208 -0
  153. package/src/type.ts +66 -0
  154. package/tests/analyze.ts +15 -0
  155. package/tests/condition.ts +49 -0
  156. package/tests/definition.ts +42 -0
  157. package/tests/eval-complex.ts +150 -0
  158. package/tests/eval.ts +42 -0
  159. package/tests/functions.ts +22 -0
  160. package/tests/import.ts +31 -0
  161. package/tests/scope.ts +33 -0
  162. package/tests/setup.ts +41 -0
  163. package/tests/tsconfig.json +3 -0
  164. package/tests/unwrap.ts +46 -0
  165. package/tsconfig.json +3 -0
@@ -0,0 +1,262 @@
1
+ import { createVmContext, type VmContext } from '@mirascript/mirascript';
2
+ import { parse } from './parser.js';
3
+ import {
4
+ type AssignmentNode,
5
+ type FunctionAssignmentNode,
6
+ isAssignmentNode,
7
+ isBlockNode,
8
+ isFunctionAssignmentNode,
9
+ isSymbolNode,
10
+ type MathNode,
11
+ } from 'mathjs';
12
+ import type { Result } from './interface.js';
13
+ import { migrateAtomic } from './node.js';
14
+ import { toBoolean } from './to-type.js';
15
+ import { constantValue, symbolName, unsupportedNode } from './utils.js';
16
+ import { migrateAccess } from './access.js';
17
+ import { specialMigrate } from './special.js';
18
+
19
+ /** 转换 AST */
20
+ function migrateLast(state: State, node: MathNode): Result {
21
+ if (isAssignmentNode(node)) {
22
+ if (node.index == null && isSymbolNode(node.object)) {
23
+ state.loose();
24
+ node = node.value;
25
+ } else {
26
+ return migrateAssignment(state, node);
27
+ }
28
+ }
29
+ if (state.condition) {
30
+ if (constantValue(node) === null) return migrateAtomic(state, node);
31
+ return toBoolean(state, node);
32
+ } else {
33
+ return migrateAtomic(state, node);
34
+ }
35
+ }
36
+ /** 转换赋值语句 */
37
+ function migrateAssignment(state: State, node: AssignmentNode): Result {
38
+ const { object, index, value } = node;
39
+ const name = symbolName(object);
40
+ if (name && index == null) {
41
+ // name = xxx;
42
+ if (state.locals.has(name)) {
43
+ state.err(`不支持局部变量重新赋值: ${name}'`);
44
+ return {
45
+ code: `${name} = ${migrateAtomic(state, value).code}`,
46
+ };
47
+ }
48
+ const expr = migrateAtomic(state, value);
49
+ state.locals.set(name, expr);
50
+ return {
51
+ code: `let ${name} = ${expr.code};`,
52
+ };
53
+ }
54
+ if (index != null) {
55
+ state.loose();
56
+ const access = migrateAccess(state, node);
57
+ return {
58
+ code: `${access.code} = ${migrateAtomic(state, value).code};`,
59
+ };
60
+ }
61
+ return unsupportedNode(state, node);
62
+ }
63
+ /** 转换函数定义语句 */
64
+ function migrateFunctionAssignment(state: State, node: FunctionAssignmentNode): Result {
65
+ state.loose();
66
+ const { name, params, expr } = node;
67
+ if (state.locals.has(name)) {
68
+ state.err(`重复定义函数: ${name}`);
69
+ }
70
+ const fState = new FunctionState(state, params);
71
+ let body = migrateAtomic(fState, expr);
72
+ if (!(body.code.startsWith('{') && body.code.endsWith('}'))) {
73
+ body = { ...body, code: `{ ${body.code} }` };
74
+ }
75
+ state.locals.set(name, {
76
+ type: 'function',
77
+ code: body.code,
78
+ });
79
+ return {
80
+ code: `fn ${name}(${params.join(', ')}) ${body.code}`,
81
+ };
82
+ }
83
+
84
+ /** 转换状态 */
85
+ export class BaseState {
86
+ constructor(
87
+ private readonly expr: string,
88
+ readonly condition: boolean,
89
+ /** 可识别的全局环境 */
90
+ readonly globals: VmContext = createVmContext(),
91
+ ) {}
92
+ /** 帮助函数 */
93
+ private readonly helpers = new Set<string>();
94
+ /** 添加帮助函数 */
95
+ helper(code: string): void {
96
+ this.helpers.add(code);
97
+ }
98
+ /** 产生的警告 */
99
+ private readonly warnings = new Set<string>();
100
+ /** 产生的错误 */
101
+ private readonly errors = new Set<string>();
102
+ /** 产生的失败 */
103
+ private readonly critical = new Set<string>();
104
+ /** 声明的本地变量 / 函数 */
105
+ readonly locals = new Map<string, Result>();
106
+ /** 标记为非精确转换 */
107
+ loose(): void {
108
+ this.warnings.add(`使用了非精确转换的语法或函数,结果可能不准确`);
109
+ }
110
+ /** 标记为非精确转换 */
111
+ warn(message: string): void {
112
+ this.warnings.add(message);
113
+ }
114
+ /** 标记为非精确转换 */
115
+ err(message: string): void {
116
+ this.errors.add(message);
117
+ }
118
+ /** 标记为转换失败 */
119
+ crit(message: string): void {
120
+ this.critical.add(message);
121
+ }
122
+ private readonly ret: string[] = [];
123
+ /** 转换一个语句 */
124
+ private migrateStmt(migrator: typeof migrateLast, stmt: boolean, node: MathNode): void {
125
+ const isFunc = isFunctionAssignmentNode(node);
126
+ const r = isFunc ? migrateFunctionAssignment(this, node) : migrator(this, node);
127
+ let { code } = r;
128
+ if (stmt && !isFunc && !code.endsWith(';')) code += ';';
129
+ if (node.comment) code += ' //' + node.comment;
130
+ this.ret.push(code);
131
+ }
132
+ /** 转换 */
133
+ migrate(): void {
134
+ let ast;
135
+ try {
136
+ const e = this.expr.startsWith('=') ? this.expr.slice(1) : this.expr;
137
+ ast = parse(e);
138
+ } catch (ex) {
139
+ this.crit(`解析 math.js 失败: ${(ex as Error).message || String(ex)}`);
140
+ return;
141
+ }
142
+ try {
143
+ const sp = specialMigrate(this.expr, ast);
144
+ if (sp != null) {
145
+ this.ret.push(sp);
146
+ this.loose();
147
+ return;
148
+ }
149
+ if (isBlockNode(ast)) {
150
+ const { blocks } = ast;
151
+ for (const [index, block] of blocks.entries()) {
152
+ if (index !== blocks.length - 1 || !block.visible) {
153
+ this.migrateStmt(
154
+ isAssignmentNode(block.node) ? (migrateAssignment as typeof migrateAtomic) : migrateAtomic,
155
+ true,
156
+ block.node,
157
+ );
158
+ } else {
159
+ this.migrateStmt(migrateLast, false, block.node);
160
+ }
161
+ }
162
+ return;
163
+ } else {
164
+ this.migrateStmt(migrateLast, false, ast);
165
+ return;
166
+ }
167
+ } catch (ex) {
168
+ this.crit(`转换失败: ${String(ex)}`.replaceAll('\n', ' '));
169
+ return;
170
+ }
171
+ } /** 获取结果 */
172
+ result(): string {
173
+ // 精确转换:产生了结果,没有帮助方法,且未产生错误和警告
174
+ if (this.ret.length && !this.helpers.size && !this.errors.size && !this.warnings.size && !this.critical.size) {
175
+ return this.ret.join('\n');
176
+ }
177
+
178
+ // 写入原始 math.js 作为注释
179
+ const lines = this.expr.split('\n').map((line) => `// ${line}`);
180
+ lines.unshift(`// # 原始 math.js 表达式`);
181
+ if (this.errors.size || this.warnings.size || this.critical.size) {
182
+ const warnings = [...this.warnings];
183
+ for (let i = warnings.length - 1; i >= 0; i--) {
184
+ const warn = warnings[i]!;
185
+ lines.unshift(`// - W: ${warn}`);
186
+ }
187
+ const errors = [...this.errors];
188
+ for (let i = errors.length - 1; i >= 0; i--) {
189
+ const err = errors[i]!;
190
+ lines.unshift(`// - E: ${err}`);
191
+ }
192
+ const critical = [...this.critical];
193
+ for (let i = critical.length - 1; i >= 0; i--) {
194
+ const crit = critical[i]!;
195
+ lines.unshift(`// - C: ${crit}`);
196
+ }
197
+ lines.unshift(`// # 转换日志`);
198
+ }
199
+ // 写入转换结果
200
+ if (this.ret.length) {
201
+ for (const value of this.helpers) {
202
+ lines.unshift(value);
203
+ }
204
+ if (this.helpers.size) {
205
+ lines.unshift(`// # 帮助函数`);
206
+ }
207
+ for (let i = this.ret.length - 1; i >= 0; i--) {
208
+ const value = this.ret[i]!;
209
+ const explicitRet = this.helpers.size && i === this.ret.length - 1 && !value.endsWith(';');
210
+ const retExpr = explicitRet ? `return ${value};` : value;
211
+ lines.unshift(retExpr);
212
+ }
213
+ }
214
+ // 添加异常
215
+ if (!this.ret.length || this.critical.size) {
216
+ lines.unshift(`panic("无法从 math.js 转换为 MiraScript")`);
217
+ }
218
+ return lines.join('\n');
219
+ }
220
+ }
221
+ /** 转换状态 */
222
+ export type State = Omit<{ [K in keyof BaseState]: BaseState[K] }, 'migrate' | 'result'>;
223
+ /** 函数状态 */
224
+ export class FunctionState implements State {
225
+ constructor(
226
+ readonly baseState: State,
227
+ params: readonly string[],
228
+ ) {
229
+ this.locals = new Map(baseState.locals);
230
+ for (const p of params) {
231
+ this.locals.set(p, { code: p });
232
+ }
233
+ }
234
+ /** @inheritdoc */
235
+ readonly condition = false;
236
+ /** @inheritdoc */
237
+ get globals(): VmContext {
238
+ return this.baseState.globals;
239
+ }
240
+ /** @inheritdoc */
241
+ helper(code: string): void {
242
+ this.baseState.helper(code);
243
+ }
244
+ /** @inheritdoc */
245
+ readonly locals: Map<string, Result>;
246
+ /** @inheritdoc */
247
+ loose(): void {
248
+ this.baseState.loose();
249
+ }
250
+ /** @inheritdoc */
251
+ warn(message: string): void {
252
+ this.baseState.warn(message);
253
+ }
254
+ /** @inheritdoc */
255
+ err(message: string): void {
256
+ this.baseState.err(message);
257
+ }
258
+ /** @inheritdoc */
259
+ crit(message: string): void {
260
+ this.baseState.crit(message);
261
+ }
262
+ }
@@ -0,0 +1,53 @@
1
+ import type { SymbolNode } from 'mathjs';
2
+ import type { Result } from './interface.js';
3
+ import type { State } from './state.js';
4
+ import { operations } from '@mirascript/mirascript/subtle';
5
+
6
+ /** 处理符号名称 */
7
+ export function migrateSymbol(state: State, node: SymbolNode, migrateConst: boolean): Result {
8
+ const { name } = node;
9
+ const local = state.locals.get(name);
10
+ if (local) {
11
+ return {
12
+ ...local,
13
+ code: name,
14
+ };
15
+ }
16
+ const global = state.globals.get(name);
17
+ if (global != null) {
18
+ return {
19
+ type: operations.$Type(global),
20
+ code: name,
21
+ };
22
+ }
23
+ if (name.startsWith('$') || name.startsWith('@')) {
24
+ return {
25
+ code: name,
26
+ };
27
+ }
28
+ if (migrateConst) {
29
+ if (name === 'i') {
30
+ state.err(`不支持复数`);
31
+ return {
32
+ type: 'number',
33
+ code: `i`,
34
+ };
35
+ }
36
+ if (name === 'pi' || name === 'PI') {
37
+ return {
38
+ type: 'number',
39
+ code: `@pi`,
40
+ };
41
+ }
42
+ if (name === 'e' || name === 'E') {
43
+ return {
44
+ type: 'number',
45
+ code: `@e`,
46
+ };
47
+ }
48
+ }
49
+ state.warn(`符号 '${name}' 未定义`);
50
+ return {
51
+ code: name,
52
+ };
53
+ }
@@ -0,0 +1,93 @@
1
+ import { isConstantNode, isNode, type MathNode } from 'mathjs';
2
+ import type { Result } from './interface.js';
3
+ import { serialize } from '@mirascript/mirascript/subtle';
4
+ import type { State } from './state.js';
5
+ import { migrateNode } from './node.js';
6
+
7
+ /** 转换 AST */
8
+ export function toBoolean(state: State, node: MathNode | Result): Result {
9
+ const helper = (): void => {
10
+ state.helper("fn @@to_boolean { it != '' && it != 0 && it is not nan && !!it }");
11
+ };
12
+ if (!isNode(node)) {
13
+ if (node.type === 'boolean') return node;
14
+ if (node.as_boolean) {
15
+ return {
16
+ type: 'boolean',
17
+ code: node.as_boolean,
18
+ };
19
+ }
20
+ helper();
21
+ return {
22
+ type: 'boolean',
23
+ code: `@@to_boolean(${node.code})`,
24
+ };
25
+ }
26
+ if (isConstantNode(node)) {
27
+ const lit = Boolean(node.value);
28
+ return {
29
+ type: 'boolean',
30
+ literal: lit,
31
+ code: serialize(lit),
32
+ };
33
+ }
34
+
35
+ const migrated = migrateNode(state, node, { format: 'no-paren' });
36
+ return toBoolean(state, migrated);
37
+ }
38
+
39
+ /** 转换 AST */
40
+ export function toString(state: State, node: MathNode | Result): Result {
41
+ if (!isNode(node)) {
42
+ if (node.type === 'string') return node;
43
+ if (node.as_string) {
44
+ return {
45
+ type: 'string',
46
+ code: node.as_string,
47
+ };
48
+ }
49
+ return {
50
+ type: 'string',
51
+ code: `to_string(${node.code})`,
52
+ };
53
+ }
54
+ if (isConstantNode(node)) {
55
+ const lit = String(node.value);
56
+ return {
57
+ type: 'string',
58
+ literal: lit,
59
+ code: serialize(lit),
60
+ };
61
+ }
62
+
63
+ const migrated = migrateNode(state, node, { format: 'no-paren' });
64
+ return toString(state, migrated);
65
+ }
66
+
67
+ /** 转换 AST */
68
+ export function toNumber(state: State, node: MathNode | Result): Result {
69
+ if (!isNode(node)) {
70
+ if (node.type === 'number') return node;
71
+ if (node.as_number) {
72
+ return {
73
+ type: 'number',
74
+ code: node.as_number,
75
+ };
76
+ }
77
+ return {
78
+ type: 'number',
79
+ code: `to_number(${node.code})`,
80
+ };
81
+ }
82
+ if (isConstantNode(node)) {
83
+ const lit = Number(node.value);
84
+ return {
85
+ type: 'number',
86
+ literal: lit,
87
+ code: serialize(lit),
88
+ };
89
+ }
90
+
91
+ const migrated = migrateNode(state, node, { format: 'no-paren' });
92
+ return toNumber(state, migrated);
93
+ }
@@ -0,0 +1,67 @@
1
+ import { type MathNode, isSymbolNode, isConstantNode } from 'mathjs';
2
+ import type { Result } from './interface.js';
3
+ import type { State } from './state.js';
4
+ import { toString } from './to-type.js';
5
+ import { migrateExpr } from './node.js';
6
+
7
+ /** 获取 symbol name */
8
+ export function symbolName(node: MathNode): string | undefined {
9
+ if (!isSymbolNode(node)) return undefined;
10
+ return node.name;
11
+ }
12
+
13
+ /** 获取 constant value */
14
+ export function constantValue(node: MathNode): number | string | boolean | null | undefined {
15
+ if (!isConstantNode(node)) return undefined;
16
+ return node.value ?? null;
17
+ }
18
+
19
+ /** 添加转换错误 */
20
+ export function unsupportedNode(state: State, node: MathNode): Result {
21
+ const str = node.toString();
22
+ state.crit(`不支持的节点类型 ${node.type}: ${str}`);
23
+ return {
24
+ code: `/* ${str} */`,
25
+ };
26
+ }
27
+
28
+ /** 可能是矩阵时报错 */
29
+ export function scalar(op: string, state: State, re: Result): Result {
30
+ if (re.type === 'array') {
31
+ state.err(`'${op.trim()}' 不支持矩阵`);
32
+ } else if (!re.type) {
33
+ state.warn(`'${op.trim()}' 不支持矩阵,计算结果可能不一致`);
34
+ }
35
+ return re;
36
+ }
37
+
38
+ /** 是否可能通过其他类型 toString 得到 */
39
+ function maybeToString(str: string): boolean {
40
+ // 布尔值和 null/undefined
41
+ if (str === 'true' || str === 'false' || str === 'null' || str === 'undefined') return true;
42
+ // 数字
43
+ if (!Number.isNaN(Number(str))) return true;
44
+ // Object.toString()
45
+ if (str.startsWith('[') && str.endsWith(']')) return true;
46
+ return false;
47
+ }
48
+
49
+ /** 比较字符串 */
50
+ export function equalText(state: State, op: '==' | '!=', l: MathNode, r: MathNode): Result {
51
+ const lr = scalar(`${op}/equalText`, state, migrateExpr(state, l));
52
+ const rr = scalar(`${op}/equalText`, state, migrateExpr(state, r));
53
+ if (
54
+ (lr.type === 'string' && rr.type === 'string') ||
55
+ (typeof lr.literal == 'string' && rr.type !== 'array' && !maybeToString(lr.literal)) ||
56
+ (typeof rr.literal == 'string' && lr.type !== 'array' && !maybeToString(rr.literal))
57
+ ) {
58
+ return {
59
+ type: 'boolean',
60
+ code: `${lr.code} ${op} ${rr.code}`,
61
+ };
62
+ }
63
+ return {
64
+ type: 'boolean',
65
+ code: `${toString(state, lr).code} ${op} ${toString(state, rr).code}`,
66
+ };
67
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { compileSync, type VmScript } from '@mirascript/mirascript';
2
+ import type { Context } from './context.js';
3
+ /** 一般表达式 */
4
+ export interface EvalExpressionCache {
5
+ /**
6
+ * 源码
7
+ */
8
+ source: string;
9
+ /**
10
+ * 求值函数
11
+ */
12
+ func: VmScript;
13
+ /**
14
+ * 编译错误
15
+ */
16
+ error: undefined;
17
+ }
18
+ /** 编译错误表达式 */
19
+ export interface ErrorExpressionCache {
20
+ /**
21
+ * 源码
22
+ */
23
+ source: string;
24
+ /**
25
+ * 求值函数
26
+ */
27
+ func: undefined;
28
+ /**
29
+ * 编译错误
30
+ */
31
+ error: Error;
32
+ }
33
+
34
+ /**
35
+ * 表达式缓存
36
+ */
37
+ export type ExpressionCache = ErrorExpressionCache | EvalExpressionCache;
38
+ /**
39
+ * 编译表达式
40
+ */
41
+ function compile(expression: string): ExpressionCache {
42
+ const compiled = {
43
+ source: expression,
44
+ } as ExpressionCache;
45
+ try {
46
+ const script = compileSync(expression);
47
+ compiled.func = script;
48
+ } catch (ex) {
49
+ compiled.error = ex as Error;
50
+ }
51
+ return compiled;
52
+ }
53
+
54
+ /**
55
+ * 解析表达式
56
+ */
57
+ export function parse(context: Context, expression: string, throws: boolean): ExpressionCache {
58
+ if (typeof expression != 'string') {
59
+ throw new TypeError(`${String(expression)} is not a valid expression`);
60
+ }
61
+ const v = context.expressionCache.get(expression);
62
+ if (v) return v;
63
+ const complied = compile(expression);
64
+ if (complied.error) {
65
+ if (throws) throw complied.error;
66
+ }
67
+ context.expressionCache.set(expression, complied);
68
+ return complied;
69
+ }