Skip to content

Commit 18e2920

Browse files
committed
feat(constant): implement constant function
1 parent 29ef49b commit 18e2920

4 files changed

Lines changed: 143 additions & 2 deletions

File tree

src/constant.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { expect, test } from "bun:test";
2+
import { z } from "zod";
3+
import { compile, constant, evaluate, expr, variable } from "./index";
4+
5+
test("constant: 数字常量", () => {
6+
const PI = constant(3.14159);
7+
const compiled = compile(PI, {});
8+
const result = evaluate<number>(compiled, {});
9+
expect(result).toBe(3.14159);
10+
});
11+
12+
test("constant: 字符串常量", () => {
13+
const greeting = constant("Hello, World!");
14+
const compiled = compile(greeting, {});
15+
const result = evaluate<string>(compiled, {});
16+
expect(result).toBe("Hello, World!");
17+
});
18+
19+
test("constant: 布尔常量", () => {
20+
const flag = constant(true);
21+
const compiled = compile(flag, {});
22+
const result = evaluate<boolean>(compiled, {});
23+
expect(result).toBe(true);
24+
});
25+
26+
test("constant: null 常量", () => {
27+
const nullVal = constant(null);
28+
const compiled = compile(nullVal, {});
29+
const result = evaluate<null>(compiled, {});
30+
expect(result).toBe(null);
31+
});
32+
33+
test("constant: 数组常量", () => {
34+
const arr = constant([1, 2, 3]);
35+
const compiled = compile(arr, {});
36+
const result = evaluate<number[]>(compiled, {});
37+
expect(result).toEqual([1, 2, 3]);
38+
});
39+
40+
test("constant: 对象常量", () => {
41+
const config = constant({ maxRetries: 3, timeout: 5000 });
42+
const compiled = compile(config, {});
43+
const result = evaluate<{ maxRetries: number; timeout: number }>(compiled, {});
44+
expect(result).toEqual({ maxRetries: 3, timeout: 5000 });
45+
});
46+
47+
test("constant: 在表达式中使用常量", () => {
48+
const PI = constant(3.14159);
49+
const radius = variable(z.number());
50+
const area = expr({ PI, radius })("PI * radius * radius");
51+
52+
const compiled = compile(area, { radius });
53+
const result = evaluate<number>(compiled, { radius: 2 });
54+
expect(result).toBeCloseTo(12.56636, 4);
55+
});
56+
57+
test("constant: 多个常量组合", () => {
58+
const a = constant(10);
59+
const b = constant(20);
60+
const sum = expr({ a, b })("a + b");
61+
62+
const compiled = compile(sum, {});
63+
const result = evaluate<number>(compiled, {});
64+
expect(result).toBe(30);
65+
});
66+
67+
test("constant: 常量与变量混合", () => {
68+
const multiplier = constant(2);
69+
const x = variable(z.number());
70+
const doubled = expr({ multiplier, x })("multiplier * x");
71+
72+
const compiled = compile(doubled, { x });
73+
const result = evaluate<number>(compiled, { x: 5 });
74+
expect(result).toBe(10);
75+
});
76+
77+
test("constant: 嵌套对象常量", () => {
78+
const nested = constant({
79+
level1: {
80+
level2: {
81+
value: 42,
82+
},
83+
},
84+
});
85+
const compiled = compile(nested, {});
86+
const result = evaluate<{ level1: { level2: { value: number } } }>(compiled, {});
87+
expect(result.level1.level2.value).toBe(42);
88+
});
89+
90+
test("constant: source 应为 JSON 字符串", () => {
91+
const num = constant(42);
92+
expect(num.source).toBe("42");
93+
94+
const str = constant("test");
95+
expect(str.source).toBe('"test"');
96+
97+
const obj = constant({ a: 1 });
98+
expect(num._tag).toBe("expression");
99+
expect(JSON.parse(obj.source)).toEqual({ a: 1 });
100+
});

src/constant.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { Expression } from "./types";
2+
3+
/**
4+
* JSON 可序列化的值类型
5+
*/
6+
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
7+
8+
/**
9+
* 创建一个编译期常量表达式
10+
*
11+
* 这是 `expr({})(JSON.stringify(value))` 的快速路径,
12+
* 用于在表达式中嵌入静态值,避免在运行时传入或在多处重复编写。
13+
*
14+
* @template T - 常量值类型(必须是 JSON 可序列化的)
15+
* @param value - 要嵌入的常量值
16+
* @returns 返回一个 Expression 对象,其结果类型为 T
17+
*
18+
* @example
19+
* ```ts
20+
* // 创建一个数字常量
21+
* const PI = constant(3.14159)
22+
*
23+
* // 创建一个字符串常量
24+
* const greeting = constant("Hello, World!")
25+
*
26+
* // 创建一个对象常量
27+
* const config = constant({ maxRetries: 3, timeout: 5000 })
28+
*
29+
* // 在表达式中使用常量
30+
* const radius = variable(z.number())
31+
* const area = expr({ PI, radius })("PI * radius * radius")
32+
* ```
33+
*/
34+
export function constant<const T extends JsonValue>(value: T): Expression<{}, T> {
35+
return {
36+
_tag: "expression",
37+
context: {},
38+
source: JSON.stringify(value),
39+
_type: undefined as unknown as T,
40+
};
41+
}

src/evaluate.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ function buildEvaluatorFunctionBody(expressions: string[], variableCount: number
9292

9393
// 为了使 $0, $1 等能在函数体中访问,我们需要创建局部变量
9494
// 或者使用代理访问值数组
95-
lines.push("const $0 = $values[0];");
96-
for (let i = 1; i < variableCount; i++) {
95+
for (let i = 0; i < variableCount; i++) {
9796
lines.push(`const $${i} = $values[${i}];`);
9897
}
9998

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { compile, type CompileOptions } from "./compile";
2+
export { constant } from "./constant";
23
export { evaluate } from "./evaluate";
34
export { expr } from "./expr";
45
export { variable } from "./variable";

0 commit comments

Comments
 (0)