Skip to content

Commit ce1a926

Browse files
authored
fix: implement short-circuit evaluation for logical operators in interpreter (#31)
- Added short-circuit evaluation for && and || operators in the evaluateBinaryExpression function. - Updated tests to verify the behavior of logical operators without async/await.
1 parent c627681 commit ce1a926

File tree

2 files changed

+77
-56
lines changed

2 files changed

+77
-56
lines changed

src/interpreter.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,24 @@ export const evaluateAst = (
136136
* @example x > y → context.x > context.y
137137
*/
138138
const evaluateBinaryExpression = (node: BinaryExpression): unknown => {
139+
// Implement short-circuit evaluation for logical operators
140+
if (node.operator === "&&") {
141+
const left = evaluateNode(node.left);
142+
// If left side is falsy, return it immediately without evaluating right side
143+
if (!left) return left;
144+
// Otherwise evaluate and return right side
145+
return evaluateNode(node.right);
146+
}
147+
148+
if (node.operator === "||") {
149+
const left = evaluateNode(node.left);
150+
// If left side is truthy, return it immediately without evaluating right side
151+
if (left) return left;
152+
// Otherwise evaluate and return right side
153+
return evaluateNode(node.right);
154+
}
155+
156+
// For other operators, evaluate both sides normally
139157
const left = evaluateNode(node.left);
140158
const right = evaluateNode(node.right);
141159

@@ -164,10 +182,6 @@ export const evaluateAst = (
164182
return (left as number) < (right as number);
165183
case "<=":
166184
return (left as number) <= (right as number);
167-
case "&&":
168-
return (left as boolean) && (right as boolean);
169-
case "||":
170-
return (left as boolean) || (right as boolean);
171185
default:
172186
throw new ExpressionError(`Unknown operator: ${node.operator}`);
173187
}

tests/interpreter.test.ts

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,29 @@ import { parse } from "../src/parser";
44
import { tokenize } from "../src/tokenizer";
55

66
describe("Interpreter", () => {
7-
async function evaluateExpression(
8-
input: string,
9-
context = {},
10-
functions = {},
11-
) {
7+
function evaluateExpression(input: string, context = {}, functions = {}) {
128
const tokens = tokenize(input);
139
const ast = parse(tokens);
1410
const interpreterState = createInterpreterState({}, functions);
1511
return evaluateAst(ast, interpreterState, context);
1612
}
1713

1814
describe("Literals", () => {
19-
it("should evaluate number literals", async () => {
20-
expect(await evaluateExpression("42")).toBe(42);
15+
it("should evaluate number literals", () => {
16+
expect(evaluateExpression("42")).toBe(42);
2117
});
2218

23-
it("should evaluate string literals", async () => {
24-
expect(await evaluateExpression('"hello"')).toBe("hello");
19+
it("should evaluate string literals", () => {
20+
expect(evaluateExpression('"hello"')).toBe("hello");
2521
});
2622

27-
it("should evaluate boolean literals", async () => {
28-
expect(await evaluateExpression("true")).toBe(true);
29-
expect(await evaluateExpression("false")).toBe(false);
23+
it("should evaluate boolean literals", () => {
24+
expect(evaluateExpression("true")).toBe(true);
25+
expect(evaluateExpression("false")).toBe(false);
3026
});
3127

32-
it("should evaluate null", async () => {
33-
expect(await evaluateExpression("null")).toBe(null);
28+
it("should evaluate null", () => {
29+
expect(evaluateExpression("null")).toBe(null);
3430
});
3531
});
3632

@@ -44,16 +40,16 @@ describe("Interpreter", () => {
4440
},
4541
};
4642

47-
it("should evaluate dot notation", async () => {
48-
expect(await evaluateExpression("data.value", context)).toBe(42);
43+
it("should evaluate dot notation", () => {
44+
expect(evaluateExpression("data.value", context)).toBe(42);
4945
});
5046

51-
it("should evaluate bracket notation", async () => {
52-
expect(await evaluateExpression('data["value"]', context)).toBe(42);
47+
it("should evaluate bracket notation", () => {
48+
expect(evaluateExpression('data["value"]', context)).toBe(42);
5349
});
5450

55-
it("should evaluate nested access", async () => {
56-
expect(await evaluateExpression("data.nested.array[1]", context)).toBe(2);
51+
it("should evaluate nested access", () => {
52+
expect(evaluateExpression("data.nested.array[1]", context)).toBe(2);
5753
});
5854
});
5955

@@ -63,48 +59,46 @@ describe("Interpreter", () => {
6359
max: Math.max,
6460
};
6561

66-
it("should evaluate function calls", async () => {
67-
expect(await evaluateExpression("@sum(1, 2, 3)", {}, functions)).toBe(6);
62+
it("should evaluate function calls", () => {
63+
expect(evaluateExpression("@sum(1, 2, 3)", {}, functions)).toBe(6);
6864
});
6965

70-
it("should evaluate nested expressions in arguments", async () => {
66+
it("should evaluate nested expressions in arguments", () => {
7167
const context = { x: 1, y: 2 };
72-
expect(
73-
await evaluateExpression("@max(x, y, 3)", context, functions),
74-
).toBe(3);
68+
expect(evaluateExpression("@max(x, y, 3)", context, functions)).toBe(3);
7569
});
7670
});
7771

7872
describe("Binary Expressions", () => {
7973
const context = { a: 5, b: 3 };
8074

81-
it("should evaluate arithmetic operators", async () => {
82-
expect(await evaluateExpression("a + b", context)).toBe(8);
83-
expect(await evaluateExpression("a - b", context)).toBe(2);
84-
expect(await evaluateExpression("a * b", context)).toBe(15);
85-
expect(await evaluateExpression("a / b", context)).toBe(5 / 3);
75+
it("should evaluate arithmetic operators", () => {
76+
expect(evaluateExpression("a + b", context)).toBe(8);
77+
expect(evaluateExpression("a - b", context)).toBe(2);
78+
expect(evaluateExpression("a * b", context)).toBe(15);
79+
expect(evaluateExpression("a / b", context)).toBe(5 / 3);
8680
});
8781

88-
it("should evaluate comparison operators", async () => {
89-
expect(await evaluateExpression("a > b", context)).toBe(true);
90-
expect(await evaluateExpression("a === b", context)).toBe(false);
82+
it("should evaluate comparison operators", () => {
83+
expect(evaluateExpression("a > b", context)).toBe(true);
84+
expect(evaluateExpression("a === b", context)).toBe(false);
9185
});
9286

93-
it("should evaluate logical operators", async () => {
94-
expect(await evaluateExpression("true && false")).toBe(false);
95-
expect(await evaluateExpression("true || false")).toBe(true);
87+
it("should evaluate logical operators", () => {
88+
expect(evaluateExpression("true && false")).toBe(false);
89+
expect(evaluateExpression("true || false")).toBe(true);
9690
});
9791
});
9892

9993
describe("Conditional Expressions", () => {
100-
it("should evaluate simple conditionals", async () => {
101-
expect(await evaluateExpression("true ? 1 : 2")).toBe(1);
102-
expect(await evaluateExpression("false ? 1 : 2")).toBe(2);
94+
it("should evaluate simple conditionals", () => {
95+
expect(evaluateExpression("true ? 1 : 2")).toBe(1);
96+
expect(evaluateExpression("false ? 1 : 2")).toBe(2);
10397
});
10498

105-
it("should evaluate nested conditionals", async () => {
99+
it("should evaluate nested conditionals", () => {
106100
const input = "true ? false ? 1 : 2 : 3";
107-
expect(await evaluateExpression(input)).toBe(2);
101+
expect(evaluateExpression(input)).toBe(2);
108102
});
109103
});
110104

@@ -120,32 +114,45 @@ describe("Interpreter", () => {
120114
sum: (arr: number[]) => arr.reduce((a, b) => a + b, 0),
121115
};
122116

123-
it("should evaluate complex expressions", async () => {
117+
it("should evaluate complex expressions", () => {
124118
const input = '@sum(data.values) > 5 ? data["status"] : "inactive"';
125-
expect(await evaluateExpression(input, context, functions)).toBe(
126-
"active",
127-
);
119+
expect(evaluateExpression(input, context, functions)).toBe("active");
128120
});
129121
});
130122

131123
describe("Error Handling", () => {
132-
it("should throw for undefined variables", async () => {
133-
await expect(evaluateExpression("unknownVar")).rejects.toThrow(
124+
it("should throw for undefined variables", () => {
125+
expect(() => evaluateExpression("unknownVar")).toThrow(
134126
"Undefined variable",
135127
);
136128
});
137129

138-
it("should throw for undefined functions", async () => {
139-
await expect(evaluateExpression("@unknown()")).rejects.toThrow(
130+
it("should throw for undefined functions", () => {
131+
expect(() => evaluateExpression("@unknown()")).toThrow(
140132
"Undefined function",
141133
);
142134
});
143135

144-
it("should throw for null property access", async () => {
136+
it("should throw for null property access", () => {
145137
const context = { data: null };
146-
await expect(evaluateExpression("data.value", context)).rejects.toThrow(
138+
expect(() => evaluateExpression("data.value", context)).toThrow(
147139
"Cannot access property of null",
148140
);
149141
});
150142
});
143+
144+
describe("Logical Operators", () => {
145+
it("should implement short-circuit evaluation for &&", () => {
146+
expect(evaluateExpression("false && true")).toBe(false);
147+
expect(evaluateExpression("true && true")).toBe(true);
148+
expect(evaluateExpression("data && data.value", { data: null })).toBe(
149+
null,
150+
);
151+
});
152+
153+
it("should implement short-circuit evaluation for ||", () => {
154+
expect(evaluateExpression("true || true")).toBe(true);
155+
expect(evaluateExpression("false || true")).toBe(true);
156+
});
157+
});
151158
});

0 commit comments

Comments
 (0)