Skip to content

Commit ace1c47

Browse files
committed
refactor(parser): simplify code generation and fix parentheses handling
1 parent 6a263bd commit ace1c47

File tree

4 files changed

+126
-163
lines changed

4 files changed

+126
-163
lines changed

src/compile.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,16 @@ export function compile<TResult>(
101101
// 转换 AST:将占位符替换为变量名,然后替换为 $N
102102
const undefinedVars: string[] = [];
103103
const transformed = transformIdentifiers(ast, (name) => {
104-
// 占位符替换为变量名
105104
const placeholderMatch = name.match(/^\$\$VAR_(.+)\$\$$/);
106-
if (placeholderMatch) {
107-
const varName = descToName.get(placeholderMatch[1]!);
108-
if (!varName) throw new Error(`Unknown variable placeholder: ${name}`);
109-
name = varName;
110-
}
105+
const resolvedName = placeholderMatch ? descToName.get(placeholderMatch[1]!) : name;
106+
107+
if (placeholderMatch && !resolvedName) throw new Error(`Unknown variable placeholder: ${name}`);
111108

112-
// 变量名替换为 $N
113-
const index = variableToIndex.get(name);
109+
const index = variableToIndex.get(resolvedName!);
114110
if (index !== undefined) return `$${index}`;
115111

116-
// 检查是否为允许的全局对象
117-
if (!ALLOWED_GLOBALS.has(name)) undefinedVars.push(name);
118-
return name;
112+
if (!ALLOWED_GLOBALS.has(resolvedName!)) undefinedVars.push(resolvedName!);
113+
return resolvedName!;
119114
});
120115

121116
if (undefinedVars.length > 0) {

src/evaluate.ts

Lines changed: 41 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -93,29 +93,16 @@ export function evaluate<TResult = unknown>(data: CompiledData, values: Record<s
9393
* ```
9494
*/
9595
function buildEvaluatorFunctionBody(expressions: string[], variableCount: number): string {
96-
if (expressions.length === 0) {
97-
throw new Error("No expressions to evaluate");
98-
}
99-
100-
const lines: string[] = [];
101-
102-
// 为了使 $0, $1 等能在函数体中访问,我们需要创建局部变量
103-
// 或者使用代理访问值数组
104-
for (let i = 0; i < variableCount; i++) {
105-
lines.push(`const $${i} = $values[${i}];`);
106-
}
107-
108-
// 依次对每个表达式求值,结果追加到值数组
109-
for (let i = 0; i < expressions.length; i++) {
110-
const exprSource = expressions[i];
111-
const resultIndex = variableCount + i;
96+
if (expressions.length === 0) throw new Error("No expressions to evaluate");
11297

113-
lines.push(`const $${resultIndex} = ${exprSource};`);
114-
lines.push(`$values[${resultIndex}] = $${resultIndex};`);
115-
}
116-
117-
// 返回最后一个表达式的结果(即最后一个元素)
118-
lines.push(`return $values[$values.length - 1];`);
98+
const lines = [
99+
...Array.from({ length: variableCount }, (_, i) => `const $${i} = $values[${i}];`),
100+
...expressions.map((expr, i) => {
101+
const idx = variableCount + i;
102+
return `const $${idx} = ${expr}; $values[${idx}] = $${idx};`;
103+
}),
104+
`return $values[$values.length - 1];`,
105+
];
119106

120107
return lines.join("\n");
121108
}
@@ -128,63 +115,45 @@ function buildEvaluatorFunctionBody(expressions: string[], variableCount: number
128115
* @returns 函数体字符串
129116
*/
130117
function buildEvaluatorFunctionBodyV2(expressions: CompiledExpression[], variableCount: number): string {
131-
if (expressions.length === 0) {
132-
throw new Error("No expressions to evaluate");
133-
}
134-
135-
const lines: string[] = [];
136-
137-
// 初始化变量
138-
for (let i = 0; i < variableCount; i++) {
139-
lines.push(`const $${i} = $values[${i}];`);
140-
}
141-
142-
// 程序计数器和最近值寄存器
143-
lines.push(`let $pc = 0;`);
144-
lines.push(`let $lastValue;`);
145-
146-
// 预先声明所有中间变量
147-
for (let i = 0; i < expressions.length; i++) {
148-
lines.push(`let $${variableCount + i};`);
149-
}
150-
151-
lines.push(`while ($pc < ${expressions.length}) {`);
152-
lines.push(` switch ($pc) {`);
153-
154-
for (let i = 0; i < expressions.length; i++) {
155-
const expr = expressions[i]!;
118+
if (expressions.length === 0) throw new Error("No expressions to evaluate");
119+
120+
const lines = [
121+
// 初始化变量
122+
...Array.from({ length: variableCount }, (_, i) => `const $${i} = $values[${i}];`),
123+
"let $pc = 0;",
124+
"let $lastValue;",
125+
// 预先声明所有中间变量
126+
...expressions.map((_, i) => `let $${variableCount + i};`),
127+
`while ($pc < ${expressions.length}) {`,
128+
" switch ($pc) {",
129+
];
130+
131+
expressions.forEach((expr, i) => {
156132
const idx = variableCount + i;
157-
158133
lines.push(` case ${i}: {`);
159134

160135
if (typeof expr === "string") {
161-
// 普通表达式:求值并存储
162-
lines.push(` $${idx} = ${expr};`);
136+
lines.push(` $${idx} = $lastValue = ${expr};`);
163137
lines.push(` $values[${idx}] = $${idx};`);
164-
lines.push(` $lastValue = $${idx};`);
165-
lines.push(` $pc++; break;`);
166-
} else if (expr[0] === "br") {
167-
// 条件跳转:条件为任意表达式
168-
const [, condExpr, offset] = expr;
169-
lines.push(` if (${condExpr}) { $pc += ${offset + 1}; } else { $pc++; }`);
170-
lines.push(` break;`);
171-
} else if (expr[0] === "jmp") {
172-
// 无条件跳转
173-
const [, offset] = expr;
174-
lines.push(` $pc += ${offset + 1}; break;`);
175-
} else if (expr[0] === "phi") {
176-
// phi 节点:取最近值
177-
lines.push(` $${idx} = $lastValue;`);
178-
lines.push(` $values[${idx}] = $lastValue;`);
179-
lines.push(` $pc++; break;`);
138+
lines.push(" $pc++; break;");
139+
} else {
140+
const [type] = expr;
141+
switch (type) {
142+
case "br":
143+
lines.push(` if (${expr[1]}) { $pc += ${expr[2] + 1}; } else { $pc++; } break;`);
144+
break;
145+
case "jmp":
146+
lines.push(` $pc += ${expr[1] + 1}; break;`);
147+
break;
148+
case "phi":
149+
lines.push(` $${idx} = $values[${idx}] = $lastValue; $pc++; break;`);
150+
break;
151+
}
180152
}
153+
lines.push(" }");
154+
});
181155

182-
lines.push(` }`);
183-
}
184-
185-
lines.push(` }`);
186-
lines.push(`}`);
187-
lines.push(`return $values[$values.length - 1];`);
156+
lines.push(" }", "}", "return $values[$values.length - 1];");
188157

189158
return lines.join("\n");
190159
}

src/parser.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,24 @@ describe("parser 单元测试", () => {
138138
test("用于明确语义的括号", () => {
139139
// 条件表达式在二元表达式中需要括号
140140
expect(generate(parse("(a ? b : c) + d"))).toBe("(a?b:c)+d");
141+
expect(generate(parse("a + (b ? c : d)"))).toBe("a+(b?c:d)");
142+
});
143+
144+
test("生成代码时的括号处理", () => {
145+
// MemberExpr 对象需要括号的情况
146+
expect(generate(parse("(a + b).c"))).toBe("(a+b).c");
147+
expect(generate(parse("(a ? b : c).d"))).toBe("(a?b:c).d");
148+
expect(generate(parse("(-a).b"))).toBe("(-a).b");
149+
expect(generate(parse("({a: 1}).a"))).toBe("({a:1}).a");
150+
expect(generate(parse("(42).toString()"))).toBe("(42).toString()");
151+
152+
// CallExpr callee 需要括号的情况
153+
expect(generate(parse("(a + b)()"))).toBe("(a+b)()");
154+
expect(generate(parse("(a ? b : c)()"))).toBe("(a?b:c)()");
155+
expect(generate(parse("(() => a)()"))).toBe("(()=>a)()");
156+
157+
// ConditionalExpr test 需要括号的情况
158+
expect(generate(parse("(a ? b : c) ? d : e"))).toBe("(a?b:c)?d:e");
141159
});
142160

143161
test("复杂表达式", () => {

src/parser.ts

Lines changed: 61 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,29 @@ const PRECEDENCE: Record<string, number> = {
136136
// 右结合运算符
137137
const RIGHT_ASSOCIATIVE = new Set(["**"]);
138138

139+
// 内置构造函数列表
140+
const BUILTIN_CONSTRUCTORS = new Set([
141+
"Date",
142+
"RegExp",
143+
"URL",
144+
"URLSearchParams",
145+
"Map",
146+
"Set",
147+
"Int8Array",
148+
"Uint8Array",
149+
"Uint8ClampedArray",
150+
"Int16Array",
151+
"Uint16Array",
152+
"Int32Array",
153+
"Uint32Array",
154+
"Float32Array",
155+
"Float64Array",
156+
"BigInt64Array",
157+
"BigUint64Array",
158+
"ArrayBuffer",
159+
"DataView",
160+
]);
161+
139162
class Parser {
140163
private pos = 0;
141164
private source: string;
@@ -824,53 +847,25 @@ export function generate(node: ASTNode): string {
824847
return generate(node.argument) + node.operator;
825848

826849
case "ConditionalExpr": {
827-
const test = generate(node.test);
828-
const consequent = generate(node.consequent);
829-
const alternate = generate(node.alternate);
850+
const test = wrapIfNeeded(node.test, node, "test");
851+
const consequent = wrapIfNeeded(node.consequent, node, "consequent");
852+
const alternate = wrapIfNeeded(node.alternate, node, "alternate");
830853
return `${test}?${consequent}:${alternate}`;
831854
}
832855

833856
case "MemberExpr": {
834857
const object = wrapIfNeeded(node.object, node, "object");
835-
if (node.computed) {
836-
const property = generate(node.property);
837-
return node.optional ? `${object}?.[${property}]` : `${object}[${property}]`;
838-
}
839858
const property = generate(node.property);
840-
return node.optional ? `${object}?.${property}` : `${object}.${property}`;
859+
return node.computed
860+
? `${object}${node.optional ? "?." : ""}[${property}]`
861+
: `${object}${node.optional ? "?." : "."}${property}`;
841862
}
842863

843864
case "CallExpr": {
844865
const callee = wrapIfNeeded(node.callee, node, "callee");
845866
const args = node.arguments.map(generate).join(",");
846-
// 检测是否需要使用 new 关键字(构造函数)
847-
const needsNew =
848-
node.callee.type === "Identifier" &&
849-
[
850-
"Date",
851-
"RegExp",
852-
"URL",
853-
"URLSearchParams",
854-
"Map",
855-
"Set",
856-
"Int8Array",
857-
"Uint8Array",
858-
"Uint8ClampedArray",
859-
"Int16Array",
860-
"Uint16Array",
861-
"Int32Array",
862-
"Uint32Array",
863-
"Float32Array",
864-
"Float64Array",
865-
"BigInt64Array",
866-
"BigUint64Array",
867-
"ArrayBuffer",
868-
"DataView",
869-
].includes(node.callee.name);
870-
if (needsNew) {
871-
return node.optional ? `new ${callee}?.(${args})` : `new ${callee}(${args})`;
872-
}
873-
return node.optional ? `${callee}?.(${args})` : `${callee}(${args})`;
867+
const isNew = node.callee.type === "Identifier" && BUILTIN_CONSTRUCTORS.has(node.callee.name);
868+
return `${isNew ? "new " : ""}${callee}${node.optional ? "?." : ""}(${args})`;
874869
}
875870

876871
case "ArrayExpr":
@@ -889,15 +884,9 @@ export function generate(node: ASTNode): string {
889884

890885
case "ArrowFunctionExpr": {
891886
const params = node.params.map((p) => p.name).join(",");
892-
let body = generate(node.body);
893-
// 如果 body 是对象字面量,需要用括号包裹,否则会被解释为代码块
894-
if (node.body.type === "ObjectExpr") {
895-
body = `(${body})`;
896-
}
897-
if (node.params.length === 1) {
898-
return `${params}=>${body}`;
899-
}
900-
return `(${params})=>${body}`;
887+
const body = node.body.type === "ObjectExpr" ? `(${generate(node.body)})` : generate(node.body);
888+
const paramsStr = node.params.length === 1 ? params : `(${params})`;
889+
return `${paramsStr}=>${body}`;
901890
}
902891

903892
default: {
@@ -914,7 +903,7 @@ export function generate(node: ASTNode): string {
914903
function wrapIfNeeded(
915904
child: ASTNode,
916905
parent: ASTNode,
917-
position: "left" | "right" | "argument" | "object" | "callee"
906+
position: "left" | "right" | "argument" | "object" | "callee" | "test" | "consequent" | "alternate"
918907
): string {
919908
const code = generate(child);
920909

@@ -928,48 +917,40 @@ function wrapIfNeeded(
928917
* 判断子节点是否需要括号
929918
*/
930919
function needsParens(child: ASTNode, parent: ASTNode, position: string): boolean {
931-
// 条件表达式在二元表达式中需要括号
932-
if (child.type === "ConditionalExpr" && parent.type === "BinaryExpr") {
933-
return true;
934-
}
935-
936-
// 二元表达式嵌套时根据优先级判断
937-
if (child.type === "BinaryExpr" && parent.type === "BinaryExpr") {
938-
const childPrec = PRECEDENCE[child.operator] || 0;
939-
const parentPrec = PRECEDENCE[parent.operator] || 0;
940-
941-
if (childPrec < parentPrec) {
942-
return true;
920+
switch (parent.type) {
921+
case "BinaryExpr": {
922+
if (child.type === "ConditionalExpr" || child.type === "UnaryExpr") return true;
923+
if (child.type === "BinaryExpr") {
924+
const childPrec = PRECEDENCE[child.operator] ?? 0;
925+
const parentPrec = PRECEDENCE[parent.operator] ?? 0;
926+
if (childPrec < parentPrec) return true;
927+
if (childPrec === parentPrec && position === "right" && !RIGHT_ASSOCIATIVE.has(parent.operator)) return true;
928+
}
929+
return false;
943930
}
944931

945-
// 相同优先级时,右侧需要括号(除了右结合运算符)
946-
if (childPrec === parentPrec && position === "right") {
947-
if (!RIGHT_ASSOCIATIVE.has(parent.operator)) {
932+
case "UnaryExpr":
933+
return position === "argument" && (child.type === "BinaryExpr" || child.type === "ConditionalExpr");
934+
935+
case "MemberExpr":
936+
case "CallExpr": {
937+
if (position !== "object" && position !== "callee") return false;
938+
if (["BinaryExpr", "ConditionalExpr", "UnaryExpr", "ArrowFunctionExpr", "ObjectExpr"].includes(child.type)) {
948939
return true;
949940
}
941+
// 处理 (42).toString() 这种整数紧跟点号的情况
942+
if (child.type === "NumberLiteral" && parent.type === "MemberExpr" && !parent.computed) {
943+
return !child.raw.includes(".") && !child.raw.includes("e") && !child.raw.includes("x");
944+
}
945+
return false;
950946
}
951-
}
952947

953-
// 二元表达式或条件表达式作为一元表达式的参数时需要括号
954-
if (
955-
(child.type === "BinaryExpr" || child.type === "ConditionalExpr") &&
956-
parent.type === "UnaryExpr" &&
957-
position === "argument"
958-
) {
959-
return true;
960-
}
948+
case "ConditionalExpr":
949+
return position === "test" && child.type === "ConditionalExpr";
961950

962-
// 一元表达式作为二元表达式的操作数时需要括号(为了保持原有的语义清晰)
963-
if (child.type === "UnaryExpr" && parent.type === "BinaryExpr") {
964-
// ** 运算符左侧不能有一元表达式
965-
if (parent.operator === "**" && position === "left") {
966-
return true;
967-
}
968-
// 逻辑运算符、位运算符等需要明确一元表达式的边界
969-
return true;
951+
default:
952+
return false;
970953
}
971-
972-
return false;
973954
}
974955

975956
/**

0 commit comments

Comments
 (0)