@codehz/json-expr 0.4.0 → 0.5.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.
- package/README.md +603 -13
- package/dist/index.d.mts +173 -5
- package/dist/index.mjs +635 -131
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
- 🧩 **可组合** - 表达式可以相互组合,形成复杂的计算树
|
|
14
14
|
- 🔄 **短路求值** - 支持 `&&`、`||`、`??` 和三元表达式的控制流优化
|
|
15
15
|
- 📝 **内联优化** - 自动内联只被引用一次的子表达式
|
|
16
|
+
- 🔗 **Proxy 变量系统** - 支持链式属性访问和方法调用,如 `config.timeout`、`user.profile.name`
|
|
17
|
+
- 🎛️ **Lambda 表达式** - 类型安全的数组方法支持(map、filter、reduce 等)
|
|
18
|
+
- 🛡️ **错误检测** - 编译时检测未定义变量和类型错误
|
|
16
19
|
|
|
17
20
|
## 快速开始
|
|
18
21
|
|
|
@@ -25,7 +28,7 @@ bun install @codehz/json-expr
|
|
|
25
28
|
### 基本用法
|
|
26
29
|
|
|
27
30
|
```typescript
|
|
28
|
-
import { variable, expr, compile, evaluate } from "@codehz/json-expr";
|
|
31
|
+
import { variable, expr, compile, evaluate, t, lambda, wrap } from "@codehz/json-expr";
|
|
29
32
|
|
|
30
33
|
// 定义类型化变量(使用 TypeScript 泛型)
|
|
31
34
|
const x = variable<number>();
|
|
@@ -43,6 +46,19 @@ const compiled = compile(result, { x, y });
|
|
|
43
46
|
// 执行编译后的表达式
|
|
44
47
|
const value = evaluate(compiled, { x: 2, y: 3 });
|
|
45
48
|
// => 11 (2+3 + 2*3 = 5 + 6 = 11)
|
|
49
|
+
|
|
50
|
+
// 使用模板字符串
|
|
51
|
+
const name = variable<string>();
|
|
52
|
+
const greeting = t`Hello, ${name}!`;
|
|
53
|
+
|
|
54
|
+
// 使用 lambda 表达式
|
|
55
|
+
const numbers = variable<number[]>();
|
|
56
|
+
const doubled = numbers.map(lambda<[number], number>((n) => expr({ n })("n * 2")));
|
|
57
|
+
|
|
58
|
+
// 使用 wrap 包装静态值
|
|
59
|
+
const pattern = wrap(/^[a-z]+$/i);
|
|
60
|
+
const input = variable<string>();
|
|
61
|
+
const isValid = pattern.test(input);
|
|
46
62
|
```
|
|
47
63
|
|
|
48
64
|
## 核心概念
|
|
@@ -215,8 +231,270 @@ const result = evaluate(compiled, { name: "Alice" });
|
|
|
215
231
|
// => "Hello, Alice!"
|
|
216
232
|
```
|
|
217
233
|
|
|
234
|
+
### `lambda<Args, R>(builder: LambdaBuilder<Args, R>): Lambda<Args, R>`
|
|
235
|
+
|
|
236
|
+
创建类型安全的 lambda 表达式,用于数组方法(map、filter、reduce 等)。
|
|
237
|
+
|
|
238
|
+
**参数:**
|
|
239
|
+
|
|
240
|
+
- `builder` - Lambda 构建函数,接收参数代理,返回函数体表达式
|
|
241
|
+
|
|
242
|
+
**返回值:** Lambda 表达式,可在数组方法中使用
|
|
243
|
+
|
|
244
|
+
**示例:**
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { lambda } from "@codehz/json-expr";
|
|
248
|
+
|
|
249
|
+
// 单参数 lambda
|
|
250
|
+
const numbers = variable<number[]>();
|
|
251
|
+
const doubled = numbers.map(lambda<[number], number>((n) => expr({ n })("n * 2")));
|
|
252
|
+
|
|
253
|
+
const compiled = compile(doubled, { numbers });
|
|
254
|
+
const result = evaluate(compiled, { numbers: [1, 2, 3] });
|
|
255
|
+
// => [2, 4, 6]
|
|
256
|
+
|
|
257
|
+
// 多参数 lambda(reduce)
|
|
258
|
+
const sum = numbers.reduce(
|
|
259
|
+
lambda<[number, number], number>((acc, val) => expr({ acc, val })("acc + val")),
|
|
260
|
+
0
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// 捕获外部变量
|
|
264
|
+
const multiplier = variable<number>();
|
|
265
|
+
const scaled = numbers.map(lambda<[number], number>((n) => expr({ n, multiplier })("n * multiplier")));
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `wrap<T>(value: T): Proxify<T>`
|
|
269
|
+
|
|
270
|
+
将静态值包装为 Proxy Expression,使其可以像 Variable 一样调用方法和访问属性。
|
|
271
|
+
|
|
272
|
+
**参数:**
|
|
273
|
+
|
|
274
|
+
- `value` - 要包装的静态值(支持原始值、对象、数组、Date、RegExp、BigInt、URL、Map、Set、TypedArray 等)
|
|
275
|
+
|
|
276
|
+
**返回值:** Proxy Expression,可以继续链式调用
|
|
277
|
+
|
|
278
|
+
**示例:**
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
// 包装 RegExp
|
|
282
|
+
const pattern = wrap(/^[a-z]+$/i);
|
|
283
|
+
const input = variable<string>();
|
|
284
|
+
const isValid = pattern.test(input);
|
|
285
|
+
|
|
286
|
+
const compiled = compile(isValid, { input });
|
|
287
|
+
evaluate(compiled, { input: "hello" }); // => true
|
|
288
|
+
evaluate(compiled, { input: "hello123" }); // => false
|
|
289
|
+
|
|
290
|
+
// 包装 Date
|
|
291
|
+
const now = wrap(new Date("2024-01-01"));
|
|
292
|
+
const year = now.getFullYear();
|
|
293
|
+
|
|
294
|
+
// 包装数组
|
|
295
|
+
const staticNumbers = wrap([1, 2, 3, 4, 5]);
|
|
296
|
+
const x = variable<number>();
|
|
297
|
+
const doubled = staticNumbers.map(lambda((n: number) => expr({ n, x })("n * x")));
|
|
298
|
+
|
|
299
|
+
// 包装对象
|
|
300
|
+
const config = wrap({ port: 8080, host: "localhost" });
|
|
301
|
+
const port = config.port; // 直接访问属性
|
|
302
|
+
|
|
303
|
+
// 包装 Map
|
|
304
|
+
const map = wrap(
|
|
305
|
+
new Map([
|
|
306
|
+
["a", 1],
|
|
307
|
+
["b", 2],
|
|
308
|
+
])
|
|
309
|
+
);
|
|
310
|
+
const key = variable<string>();
|
|
311
|
+
const value = map.get(key);
|
|
312
|
+
|
|
313
|
+
// 链式调用
|
|
314
|
+
const text = wrap(" hello world ");
|
|
315
|
+
const result = text.trim().toUpperCase().replace("HELLO", "HI");
|
|
316
|
+
// => "HI WORLD"
|
|
317
|
+
```
|
|
318
|
+
|
|
218
319
|
## 高级用法
|
|
219
320
|
|
|
321
|
+
### 包装静态值(wrap)
|
|
322
|
+
|
|
323
|
+
`wrap()` 函数可以将任意静态值转换为 Proxy Expression,使其可以像 Variable 一样调用方法和访问属性。这在需要对常量值执行操作时非常有用。
|
|
324
|
+
|
|
325
|
+
**基本用法:**
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// 不使用 wrap(传统方式)
|
|
329
|
+
interface Validator {
|
|
330
|
+
match(text: string, pattern: RegExp): boolean;
|
|
331
|
+
}
|
|
332
|
+
const validator = variable<Validator>();
|
|
333
|
+
const result = validator.match("hello", /^[a-z]+$/i);
|
|
334
|
+
|
|
335
|
+
// 使用 wrap(推荐方式)
|
|
336
|
+
const pattern = wrap(/^[a-z]+$/i);
|
|
337
|
+
const input = variable<string>();
|
|
338
|
+
const result = pattern.test(input);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**支持的类型:**
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// 原始值
|
|
345
|
+
const num = wrap(42);
|
|
346
|
+
const str = wrap("hello");
|
|
347
|
+
const bool = wrap(true);
|
|
348
|
+
|
|
349
|
+
// Date 和 RegExp
|
|
350
|
+
const date = wrap(new Date("2024-01-01"));
|
|
351
|
+
const year = date.getFullYear();
|
|
352
|
+
|
|
353
|
+
const regex = wrap(/\d+/g);
|
|
354
|
+
const text = variable<string>();
|
|
355
|
+
const matches = text.match(regex);
|
|
356
|
+
|
|
357
|
+
// BigInt
|
|
358
|
+
const bigNum = wrap(123456789n);
|
|
359
|
+
const x = variable<bigint>();
|
|
360
|
+
const sum = expr({ bigNum, x })("bigNum + x");
|
|
361
|
+
|
|
362
|
+
// URL
|
|
363
|
+
const url = wrap(new URL("https://example.com/path"));
|
|
364
|
+
const host = url.hostname;
|
|
365
|
+
const port = url.port;
|
|
366
|
+
|
|
367
|
+
// Map 和 Set
|
|
368
|
+
const map = wrap(
|
|
369
|
+
new Map([
|
|
370
|
+
["key1", 100],
|
|
371
|
+
["key2", 200],
|
|
372
|
+
])
|
|
373
|
+
);
|
|
374
|
+
const key = variable<string>();
|
|
375
|
+
const value = map.get(key);
|
|
376
|
+
|
|
377
|
+
const set = wrap(new Set([1, 2, 3]));
|
|
378
|
+
const num = variable<number>();
|
|
379
|
+
const has = set.has(num);
|
|
380
|
+
|
|
381
|
+
// TypedArray
|
|
382
|
+
const arr = wrap(new Uint8Array([10, 20, 30]));
|
|
383
|
+
const index = variable<number>();
|
|
384
|
+
const value = expr({ arr, index })("arr[index]");
|
|
385
|
+
|
|
386
|
+
// 数组和对象
|
|
387
|
+
const numbers = wrap([1, 2, 3, 4, 5]);
|
|
388
|
+
const multiplier = variable<number>();
|
|
389
|
+
const scaled = numbers.map(lambda((n: number) => expr({ n, multiplier })("n * multiplier")));
|
|
390
|
+
|
|
391
|
+
const config = wrap({ port: 8080, host: "localhost" });
|
|
392
|
+
const port = config.port;
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**链式调用:**
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
const text = wrap(" Hello, World! ");
|
|
399
|
+
const result = text.trim().toLowerCase().replace("world", "universe");
|
|
400
|
+
// => "hello, universe!"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**与 variable 结合:**
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
const staticData = wrap({ users: ["alice", "bob", "charlie"] });
|
|
407
|
+
const index = variable<number>();
|
|
408
|
+
const username = expr({ staticData, index })("staticData.users[index]");
|
|
409
|
+
|
|
410
|
+
const compiled = compile(username, { index });
|
|
411
|
+
evaluate(compiled, { index: 1 }); // => "bob"
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Proxy 变量系统
|
|
415
|
+
|
|
416
|
+
`variable()` 创建的变量是 Proxy 对象,支持链式属性访问和方法调用,所有操作都会自动转换为表达式。
|
|
417
|
+
|
|
418
|
+
**属性访问:**
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const config = variable<{
|
|
422
|
+
timeout: number;
|
|
423
|
+
retries: number;
|
|
424
|
+
database: {
|
|
425
|
+
host: string;
|
|
426
|
+
port: number;
|
|
427
|
+
};
|
|
428
|
+
}>();
|
|
429
|
+
|
|
430
|
+
// 链式属性访问
|
|
431
|
+
const timeout = config.timeout; // 自动转换为表达式
|
|
432
|
+
const dbHost = config.database.host; // 支持嵌套访问
|
|
433
|
+
|
|
434
|
+
const compiled = compile(timeout, { config });
|
|
435
|
+
const result = evaluate(compiled, {
|
|
436
|
+
config: { timeout: 5000, retries: 3, database: { host: "localhost", port: 5432 } },
|
|
437
|
+
});
|
|
438
|
+
// => 5000
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**方法调用:**
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const calculator = variable<{
|
|
445
|
+
add(a: number, b: number): number;
|
|
446
|
+
multiply(x: number, y: number): number;
|
|
447
|
+
}>();
|
|
448
|
+
|
|
449
|
+
// 方法调用
|
|
450
|
+
const sum = calculator.add(1, 2);
|
|
451
|
+
const product = calculator.multiply(5, 3);
|
|
452
|
+
|
|
453
|
+
// 链式方法调用
|
|
454
|
+
const builder = variable<{
|
|
455
|
+
setName(name: string): typeof builder;
|
|
456
|
+
build(): { name: string };
|
|
457
|
+
}>();
|
|
458
|
+
const result = builder.setName("test").build();
|
|
459
|
+
|
|
460
|
+
// 编译并执行
|
|
461
|
+
const compiled = compile(sum, { calculator });
|
|
462
|
+
const value = evaluate(compiled, {
|
|
463
|
+
calculator: {
|
|
464
|
+
add: (a, b) => a + b,
|
|
465
|
+
multiply: (x, y) => x * y,
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
// => 3
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**数组方法:**
|
|
472
|
+
|
|
473
|
+
数组变量支持所有标准数组方法,并自动处理类型推导:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
const numbers = variable<number[]>();
|
|
477
|
+
const users = variable<{ id: number; name: string }[]>();
|
|
478
|
+
|
|
479
|
+
// map
|
|
480
|
+
const doubled = numbers.map((n) => expr({ n })("n * 2"));
|
|
481
|
+
|
|
482
|
+
// filter
|
|
483
|
+
const activeUsers = users.filter((u) => expr({ u })("u.active"));
|
|
484
|
+
|
|
485
|
+
// reduce
|
|
486
|
+
const sum = numbers.reduce(
|
|
487
|
+
lambda<[number, number], number>((acc, val) => expr({ acc, val })("acc + val")),
|
|
488
|
+
0
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
// find, some, every, sort 等
|
|
492
|
+
const firstMatch = users.find((u) => expr({ u })("u.id === 1"));
|
|
493
|
+
const hasAdmins = users.some((u) => expr({ u })("u.role === 'admin'"));
|
|
494
|
+
const allActive = users.every((u) => expr({ u })("u.active"));
|
|
495
|
+
const sorted = numbers.toSorted(lambda<[number, number], number>((a, b) => expr({ a, b })("a - b")));
|
|
496
|
+
```
|
|
497
|
+
|
|
220
498
|
### 内置全局对象
|
|
221
499
|
|
|
222
500
|
表达式中可以直接使用以下内置对象(无需在上下文中定义):
|
|
@@ -235,6 +513,44 @@ const result = evaluate(compiled, { x: 16 });
|
|
|
235
513
|
// => 4
|
|
236
514
|
```
|
|
237
515
|
|
|
516
|
+
### 支持的运算符和语法
|
|
517
|
+
|
|
518
|
+
**算术运算符:**
|
|
519
|
+
|
|
520
|
+
- `+`, `-`, `*`, `/`, `%`, `**` (幂运算)
|
|
521
|
+
|
|
522
|
+
**比较运算符:**
|
|
523
|
+
|
|
524
|
+
- `==`, `===`, `!=`, `!==`, `<`, `>`, `<=`, `>=`
|
|
525
|
+
|
|
526
|
+
**逻辑运算符:**
|
|
527
|
+
|
|
528
|
+
- `&&`, `||`, `!`, `??` (空值合并)
|
|
529
|
+
|
|
530
|
+
**位运算符:**
|
|
531
|
+
|
|
532
|
+
- `&`, `|`, `^`, `~`, `<<`, `>>`, `>>>`
|
|
533
|
+
|
|
534
|
+
**其他运算符:**
|
|
535
|
+
|
|
536
|
+
- `? :` (三元表达式)
|
|
537
|
+
- `in` (属性存在检查)
|
|
538
|
+
- `instanceof` (类型检查)
|
|
539
|
+
- `typeof` (类型检测)
|
|
540
|
+
- `?.` (可选链)
|
|
541
|
+
- `?.()` (可选调用)
|
|
542
|
+
- `?.[]` (可选元素访问)
|
|
543
|
+
|
|
544
|
+
**语法特性:**
|
|
545
|
+
|
|
546
|
+
- 对象字面量:`{ key: value, ... }`
|
|
547
|
+
- 数组字面量:`[element1, element2, ...]`
|
|
548
|
+
- 箭头函数:`(param) => expression`
|
|
549
|
+
- 函数调用:`func(arg1, arg2, ...)`
|
|
550
|
+
- 成员访问:`obj.prop`, `obj["prop"]`, `arr[0]`
|
|
551
|
+
- 模板字面量(通过 `t` 标签函数)
|
|
552
|
+
- 分组括号:`(expression)`
|
|
553
|
+
|
|
238
554
|
### 条件表达式
|
|
239
555
|
|
|
240
556
|
```typescript
|
|
@@ -275,6 +591,75 @@ const result = evaluate(compiled, { a: 2, b: 3 });
|
|
|
275
591
|
// => (2+3) * (2*3) - (2-3) = 5 * 6 - (-1) = 30 + 1 = 31
|
|
276
592
|
```
|
|
277
593
|
|
|
594
|
+
### 短路求值(控制流优化)
|
|
595
|
+
|
|
596
|
+
编译器支持为 `&&`、`||`、`??` 和三元表达式生成短路求值代码,避免不必要的计算:
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
const a = variable<boolean>();
|
|
600
|
+
const b = variable<boolean>();
|
|
601
|
+
|
|
602
|
+
// 逻辑或短路
|
|
603
|
+
const orExpr = expr({ a, b })("a || b");
|
|
604
|
+
const compiled = compile(orExpr, { a, b }, { shortCircuit: true });
|
|
605
|
+
|
|
606
|
+
// 当 a 为 true 时,b 不会被求值
|
|
607
|
+
// 编译数据包含控制流节点:
|
|
608
|
+
// [["a", "b"], ["br", "$0", 1], "$1", ["phi"]]
|
|
609
|
+
|
|
610
|
+
// 空值合并
|
|
611
|
+
const x = variable<number | null>();
|
|
612
|
+
const y = variable<number>();
|
|
613
|
+
const coalesce = expr({ x, y })("x ?? y");
|
|
614
|
+
|
|
615
|
+
// 三元表达式
|
|
616
|
+
const condition = variable<boolean>();
|
|
617
|
+
const result = variable<number>();
|
|
618
|
+
const alternative = variable<number>();
|
|
619
|
+
const ternary = expr({ condition, result, alternative })("condition ? result : alternative");
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### 自动内联优化
|
|
623
|
+
|
|
624
|
+
编译器自动将只被引用一次的子表达式内联到使用位置,减少中间计算:
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
const x = variable<number>();
|
|
628
|
+
const y = variable<number>();
|
|
629
|
+
|
|
630
|
+
const sum = expr({ x, y })("x + y");
|
|
631
|
+
const product = expr({ x, y })("x * y");
|
|
632
|
+
const result = expr({ sum, product })("sum + product");
|
|
633
|
+
|
|
634
|
+
// 自动内联后,编译结果为:
|
|
635
|
+
// [["x", "y"], "($0+$1)+($0*$1)"]
|
|
636
|
+
// 而不是 [["x", "y"], "$0+$1", "$0*$1", "$2+$3"]
|
|
637
|
+
|
|
638
|
+
const compiled = compile(result, { x, y });
|
|
639
|
+
const value = evaluate(compiled, { x: 2, y: 3 });
|
|
640
|
+
// => 11
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### 直接编译对象和数组
|
|
644
|
+
|
|
645
|
+
`compile` 函数支持直接编译包含 Proxy 的对象和数组:
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
const x = variable<number>();
|
|
649
|
+
const y = variable<number>();
|
|
650
|
+
const sum = expr({ x, y })("x + y");
|
|
651
|
+
|
|
652
|
+
// 编译对象
|
|
653
|
+
const objCompiled = compile({ result: sum, original: { x, y } }, { x, y });
|
|
654
|
+
const objResult = evaluate(objCompiled, { x: 10, y: 20 });
|
|
655
|
+
// => { result: 30, original: { x: 10, y: 20 } }
|
|
656
|
+
|
|
657
|
+
// 编译数组
|
|
658
|
+
const arrCompiled = compile([x, sum, 100], { x, y });
|
|
659
|
+
const arrResult = evaluate(arrCompiled, { x: 5, y: 3 });
|
|
660
|
+
// => [5, 8, 100]
|
|
661
|
+
```
|
|
662
|
+
|
|
278
663
|
## 序列化和传输
|
|
279
664
|
|
|
280
665
|
编译后的数据可以轻松进行 JSON 序列化,适合网络传输或持久化存储:
|
|
@@ -296,6 +681,89 @@ const deserialized = JSON.parse(json);
|
|
|
296
681
|
const value = evaluate(deserialized, { x: 5, y: 3 });
|
|
297
682
|
```
|
|
298
683
|
|
|
684
|
+
## 编译数据格式
|
|
685
|
+
|
|
686
|
+
### V1 格式(基础表达式)
|
|
687
|
+
|
|
688
|
+
基础格式为 JSON 数组:`[variableNames, ...expressions]`
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
// 输入
|
|
692
|
+
const sum = expr({ x, y })("x + y");
|
|
693
|
+
const compiled = compile(sum, { x, y });
|
|
694
|
+
|
|
695
|
+
// 输出
|
|
696
|
+
// [["x", "y"], "$0+$1"]
|
|
697
|
+
// $0 引用 x,$1 引用 y
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### V2 格式(控制流节点)
|
|
701
|
+
|
|
702
|
+
启用短路求值时,生成包含控制流节点的格式:
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
// 输入
|
|
706
|
+
const result = expr({ a, b })("a || b");
|
|
707
|
+
const compiled = compile(result, { a, b }, { shortCircuit: true });
|
|
708
|
+
|
|
709
|
+
// 输出
|
|
710
|
+
// [
|
|
711
|
+
// ["a", "b"],
|
|
712
|
+
// ["br", "$0", 1], // 如果 $0 为 truthy,跳过 1 条指令
|
|
713
|
+
// "$1", // 否则求值 $1
|
|
714
|
+
// ["phi"] // 取最近求值结果
|
|
715
|
+
// ]
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**控制流节点类型:**
|
|
719
|
+
|
|
720
|
+
- `["br", condition, offset]` - 条件跳转,条件为真时跳过 offset 条指令
|
|
721
|
+
- `["jmp", offset]` - 无条件跳转,跳过 offset 条指令
|
|
722
|
+
- `["phi"]` - 取最近求值结果(用于合并分支)
|
|
723
|
+
|
|
724
|
+
## 错误处理
|
|
725
|
+
|
|
726
|
+
### 编译时错误
|
|
727
|
+
|
|
728
|
+
编译器会检测并报告以下错误:
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
const x = variable<number>();
|
|
732
|
+
const y = variable<number>();
|
|
733
|
+
|
|
734
|
+
// 错误:引用未定义的变量
|
|
735
|
+
const invalid = expr({ x, y })("x + y + z");
|
|
736
|
+
compile(invalid, { x, y });
|
|
737
|
+
// => Error: Undefined variable(s): z
|
|
738
|
+
|
|
739
|
+
// 错误:变量名冲突
|
|
740
|
+
const xy = variable<number>();
|
|
741
|
+
const conflict = expr({ xy, x })("xy + x");
|
|
742
|
+
// 正确处理:编译器能区分 xy 和 x
|
|
743
|
+
const compiled = compile(conflict, { xy, x });
|
|
744
|
+
// => [["xy", "x"], "$0+$1"]
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### 运行时错误
|
|
748
|
+
|
|
749
|
+
求值器会验证输入并报告运行时错误:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
const x = variable<number>();
|
|
753
|
+
const y = variable<number>();
|
|
754
|
+
|
|
755
|
+
const sum = expr({ x, y })("x + y");
|
|
756
|
+
const compiled = compile(sum, { x, y });
|
|
757
|
+
|
|
758
|
+
// 错误:缺少必需变量
|
|
759
|
+
evaluate(compiled, { x: 2 });
|
|
760
|
+
// => Error: Missing required variable: y
|
|
761
|
+
|
|
762
|
+
// 错误:无效的编译数据
|
|
763
|
+
evaluate([], { x: 1 });
|
|
764
|
+
// => Error: Invalid compiled data: must have at least variable names
|
|
765
|
+
```
|
|
766
|
+
|
|
299
767
|
## 类型安全
|
|
300
768
|
|
|
301
769
|
项目充分利用 TypeScript 的类型系统进行编译时检查和类型推导:
|
|
@@ -309,28 +777,150 @@ const y = variable<string>();
|
|
|
309
777
|
const valid = expr({ x })("-x"); // 编译器推导为 number
|
|
310
778
|
```
|
|
311
779
|
|
|
780
|
+
## 实际应用示例
|
|
781
|
+
|
|
782
|
+
### 动态表单验证规则
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
const formData = variable<{
|
|
786
|
+
username: string;
|
|
787
|
+
password: string;
|
|
788
|
+
confirmPassword: string;
|
|
789
|
+
age: number;
|
|
790
|
+
}>();
|
|
791
|
+
|
|
792
|
+
// 创建验证规则表达式
|
|
793
|
+
const isUsernameValid = expr({ formData })("formData.username.length >= 3 && formData.username.length <= 20");
|
|
794
|
+
|
|
795
|
+
const isPasswordValid = expr({ formData })("formData.password.length >= 8 && /[A-Z]/.test(formData.password)");
|
|
796
|
+
|
|
797
|
+
const doPasswordsMatch = expr({ formData })("formData.password === formData.confirmPassword");
|
|
798
|
+
|
|
799
|
+
const isAgeValid = expr({ formData })("formData.age >= 18 && formData.age <= 120");
|
|
800
|
+
|
|
801
|
+
const isFormValid = expr({
|
|
802
|
+
isUsernameValid,
|
|
803
|
+
isPasswordValid,
|
|
804
|
+
doPasswordsMatch,
|
|
805
|
+
isAgeValid,
|
|
806
|
+
})("isUsernameValid && isPasswordValid && doPasswordsMatch && isAgeValid");
|
|
807
|
+
|
|
808
|
+
// 编译一次,多次执行
|
|
809
|
+
const compiled = compile(isFormValid, { formData });
|
|
810
|
+
|
|
811
|
+
// 在表单输入时实时验证
|
|
812
|
+
evaluate(compiled, {
|
|
813
|
+
formData: {
|
|
814
|
+
username: "john_doe",
|
|
815
|
+
password: "Secure123",
|
|
816
|
+
confirmPassword: "Secure123",
|
|
817
|
+
age: 25,
|
|
818
|
+
},
|
|
819
|
+
}); // => true
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### 数据转换管道
|
|
823
|
+
|
|
824
|
+
```typescript
|
|
825
|
+
const rawData = variable<any[]>();
|
|
826
|
+
const config = variable<{
|
|
827
|
+
minValue: number;
|
|
828
|
+
maxValue: number;
|
|
829
|
+
transform: (x: number) => number;
|
|
830
|
+
}>();
|
|
831
|
+
|
|
832
|
+
// 构建数据处理管道
|
|
833
|
+
const filtered = rawData.filter(
|
|
834
|
+
lambda<[any], boolean>((item) =>
|
|
835
|
+
expr({ item, config })("item.value >= config.minValue && item.value <= config.maxValue")
|
|
836
|
+
)
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
const transformed = filtered.map(
|
|
840
|
+
lambda<[any], number>((item) => expr({ item, config })("config.transform(item.value)"))
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
const sorted = transformed.toSorted(lambda<[number, number], number>((a, b) => expr({ a, b })("a - b")));
|
|
844
|
+
|
|
845
|
+
const pipeline = compile(sorted, { rawData, config });
|
|
846
|
+
|
|
847
|
+
// 执行数据处理
|
|
848
|
+
const result = evaluate(pipeline, {
|
|
849
|
+
rawData: [{ value: 10 }, { value: 5 }, { value: 20 }, { value: 15 }],
|
|
850
|
+
config: { minValue: 8, maxValue: 18, transform: (x: number) => x * 2 },
|
|
851
|
+
});
|
|
852
|
+
// => [10, 20, 30] (5 被过滤,10*2=20, 15*2=30, 20 被过滤)
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### 规则引擎
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
// 定义规则条件
|
|
859
|
+
const user = variable<{
|
|
860
|
+
age: number;
|
|
861
|
+
role: string;
|
|
862
|
+
balance: number;
|
|
863
|
+
}>();
|
|
864
|
+
|
|
865
|
+
const isEligible = expr({ user })(
|
|
866
|
+
"(user.age >= 18 && user.age <= 65) && (user.role === 'premium' || user.balance > 10000)"
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
const discountRate = expr({ user, isEligible })("isEligible ? (user.role === 'premium' ? 0.2 : 0.1) : 0");
|
|
870
|
+
|
|
871
|
+
const rule = compile(discountRate, { user });
|
|
872
|
+
|
|
873
|
+
// 应用规则
|
|
874
|
+
const discount = evaluate(rule, {
|
|
875
|
+
user: { age: 30, role: "premium", balance: 5000 },
|
|
876
|
+
});
|
|
877
|
+
// => 0.2 (20% 折扣)
|
|
878
|
+
```
|
|
879
|
+
|
|
312
880
|
## 性能考虑
|
|
313
881
|
|
|
314
882
|
- **编译时间**:编译过程涉及依赖分析和拓扑排序,通常快速完成
|
|
315
883
|
- **执行时间**:表达式通过 `new Function()` 编译为原生 JavaScript,执行性能接近原生代码
|
|
316
884
|
- **内存占用**:编译数据为纯 JSON,占用空间小,适合在网络上传输
|
|
885
|
+
- **缓存机制**:求值器缓存已编译的函数,重复执行时性能更优
|
|
886
|
+
|
|
887
|
+
### 最佳实践
|
|
888
|
+
|
|
889
|
+
1. **编译一次,多次执行**:对于重复使用的表达式,先编译后多次求值
|
|
890
|
+
|
|
891
|
+
```typescript
|
|
892
|
+
const compiled = compile(expression, variables);
|
|
893
|
+
// 缓存 compiled,多次调用 evaluate
|
|
894
|
+
evaluate(compiled, values1);
|
|
895
|
+
evaluate(compiled, values2);
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
2. **合理使用短路求值**:对于条件表达式,启用短路求值可以避免不必要的计算
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
compile(expression, variables, { shortCircuit: true });
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
3. **利用自动内联**:编译器会自动内联只引用一次的子表达式,无需手动优化
|
|
905
|
+
|
|
906
|
+
4. **优先使用 Proxy 链式调用**:对于对象属性访问,使用 `config.timeout` 比 `expr({ config })("config.timeout")` 更简洁且类型更安全
|
|
317
907
|
|
|
318
908
|
## 项目结构
|
|
319
909
|
|
|
320
910
|
```
|
|
321
911
|
src/
|
|
322
|
-
├── index.ts
|
|
323
|
-
├── variable.ts
|
|
324
|
-
├── expr.ts
|
|
325
|
-
├── template.ts
|
|
326
|
-
├──
|
|
327
|
-
├──
|
|
328
|
-
├──
|
|
329
|
-
├──
|
|
330
|
-
├──
|
|
331
|
-
├── proxy-
|
|
332
|
-
├──
|
|
333
|
-
└──
|
|
912
|
+
├── index.ts # 导出入口
|
|
913
|
+
├── variable.ts # variable<T>() 函数
|
|
914
|
+
├── expr.ts # expr() 函数
|
|
915
|
+
├── template.ts # t() 标签模板函数
|
|
916
|
+
├── lambda.ts # lambda() 函数(数组方法支持)
|
|
917
|
+
├── compile.ts # 编译器(内联优化、短路求值)
|
|
918
|
+
├── evaluate.ts # 运行时求值
|
|
919
|
+
├── parser.ts # 表达式 AST 解析器
|
|
920
|
+
├── type-parser.ts # TypeScript 类型级表达式解析
|
|
921
|
+
├── proxy-variable.ts # Proxy 变量实现
|
|
922
|
+
├── proxy-metadata.ts # Proxy 元数据管理
|
|
923
|
+
└── types.ts # 类型定义(Variable、Expression、Lambda 等)
|
|
334
924
|
```
|
|
335
925
|
|
|
336
926
|
## 开发
|