Anonymous View
Skip to content

Commit 9c1a6ca

Browse files
committed
feat(core): ternary operator support
1 parent 485956b commit 9c1a6ca

7 files changed

Lines changed: 306 additions & 2 deletions

File tree

packages/core/src/parser/astTypes.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,28 @@ export interface TemplateLiteral extends AstNodeBase {
292292
parts: (Expression | string)[];
293293
}
294294

295+
/**
296+
* Ternary expression.
297+
*/
298+
export interface TernaryExpression extends AstNodeBase {
299+
/**
300+
* Type of the expression.
301+
*/
302+
type: 'ternary';
303+
/**
304+
* Condition of the ternary expression.
305+
*/
306+
if: Expression;
307+
/**
308+
* Then part of the ternary expression.
309+
*/
310+
then: Expression;
311+
/**
312+
* Else part of the ternary expression.
313+
*/
314+
else: Expression;
315+
}
316+
295317
/**
296318
* Expression.
297319
*/
@@ -306,7 +328,8 @@ export type Expression =
306328
| NewExpression
307329
| OperatorExpression
308330
| ArrowFunctionExpression
309-
| TemplateLiteral;
331+
| TemplateLiteral
332+
| TernaryExpression;
310333

311334
/**
312335
* Script statement.

packages/core/src/parser/parseScript.conditionals.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,26 @@ test('if-elseif-else statement', () => {
132132

133133
expect(script).toMatchObject(expected);
134134
});
135+
136+
test('ternary operator', () => {
137+
const code = joinLines([
138+
//
139+
'a ? b : c',
140+
]);
141+
142+
const script = parseScript(code);
143+
144+
const expected: Script = {
145+
code,
146+
ast: [
147+
{
148+
type: 'ternary',
149+
if: { type: 'ident', name: 'a' },
150+
then: { type: 'ident', name: 'b' },
151+
else: { type: 'ident', name: 'c' },
152+
},
153+
],
154+
};
155+
156+
expect(script).toEqual(expected);
157+
});

packages/core/src/parser/parseScript.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ function parseExpression(expression: babel.Expression): Expression {
202202
? parseStatement(expression.body)
203203
: parseExpression(expression.body),
204204
};
205+
206+
case 'ConditionalExpression':
207+
return {
208+
type: 'ternary',
209+
if: parseExpression(expression.test),
210+
then: parseExpression(expression.consequent),
211+
else: parseExpression(expression.alternate),
212+
};
205213
}
206214

207215
throw new ParseError(`Unknown expression type: ${expression.type}`, {

packages/core/src/runtime/executeAgent.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
OperatorExpression,
3030
ReturnStatement,
3131
TemplateLiteral,
32+
TernaryExpression,
3233
} from '../parser/astTypes.js';
3334
import { isTool } from '../tools/defineTool.js';
3435
import type { ToolDefinition } from '../tools/defineTool.js';
@@ -307,6 +308,9 @@ async function runExpression(
307308
expression,
308309
);
309310

311+
case 'ternary':
312+
return await runTernaryExpression(agent, controller, closure, block, frame, expression);
313+
310314
case 'object':
311315
return await runObjectExpression(agent, controller, closure, block, frame, expression);
312316

@@ -785,6 +789,46 @@ async function runOperatorExpression(
785789
return updateFrame(frame, 'finished');
786790
}
787791

792+
async function runTernaryExpression(
793+
agent: Agent,
794+
controller: RuntimeController,
795+
closure: StackFrame,
796+
block: StackFrame,
797+
frame: StackFrame,
798+
expression: TernaryExpression,
799+
) {
800+
const conditionFrame = getFrame(frame, 0);
801+
802+
const conditionStatus = await runExpression(
803+
agent,
804+
controller,
805+
closure,
806+
block,
807+
conditionFrame,
808+
expression.if,
809+
);
810+
if (conditionStatus !== 'finished') {
811+
return updateFrame(frame, conditionStatus);
812+
}
813+
814+
const thenFrame = getFrame(frame, 1);
815+
const thenExpression = conditionFrame.value ? expression.then : expression.else;
816+
const thenStatus = await runExpression(
817+
agent,
818+
controller,
819+
closure,
820+
block,
821+
thenFrame,
822+
thenExpression,
823+
);
824+
if (thenStatus !== 'finished') {
825+
return updateFrame(frame, thenStatus);
826+
}
827+
828+
frame.value = thenFrame.value;
829+
return updateFrame(frame, 'finished');
830+
}
831+
788832
async function runNewExpression(
789833
agent: Agent,
790834
controller: RuntimeController,
File renamed without changes.
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { expect, test } from 'vitest';
2+
3+
import { joinLines } from '@agentscript-ai/utils';
4+
5+
import { createAgent } from '../../agent/createAgent.js';
6+
import { parseScript } from '../../parser/parseScript.js';
7+
import { executeAgent } from '../executeAgent.js';
8+
import { completedFrame, rootFrame } from './testUtils.js';
9+
10+
test('ternary operator with literals, true', async () => {
11+
const code = joinLines([
12+
//
13+
'true ? 1 : 2',
14+
]);
15+
16+
const script = parseScript(code);
17+
18+
const agent = createAgent({
19+
tools: {},
20+
script,
21+
});
22+
23+
await executeAgent({ agent });
24+
25+
const expectedStack = rootFrame({
26+
status: 'finished',
27+
children: [
28+
// ternary operator
29+
completedFrame({
30+
trace: '0:0',
31+
value: 1,
32+
children: [
33+
// condition
34+
completedFrame({
35+
trace: '0:0:0',
36+
value: true,
37+
}),
38+
// then
39+
completedFrame({
40+
trace: '0:0:1',
41+
value: 1,
42+
}),
43+
],
44+
}),
45+
],
46+
});
47+
48+
expect(agent.root).toEqual(expectedStack);
49+
expect(agent.status).toBe('finished');
50+
});
51+
52+
test('ternary operator with literals, false', async () => {
53+
const code = joinLines([
54+
//
55+
'false ? 1 : 2',
56+
]);
57+
58+
const script = parseScript(code);
59+
60+
const agent = createAgent({
61+
tools: {},
62+
script,
63+
});
64+
65+
await executeAgent({ agent });
66+
67+
const expectedStack = rootFrame({
68+
status: 'finished',
69+
children: [
70+
// ternary operator
71+
completedFrame({
72+
trace: '0:0',
73+
value: 2,
74+
children: [
75+
// condition
76+
completedFrame({
77+
trace: '0:0:0',
78+
value: false,
79+
}),
80+
// then
81+
completedFrame({
82+
trace: '0:0:1',
83+
value: 2,
84+
}),
85+
],
86+
}),
87+
],
88+
});
89+
90+
expect(agent.root).toEqual(expectedStack);
91+
expect(agent.status).toBe('finished');
92+
});
93+
94+
test('ternary operator with literals, undefined', async () => {
95+
const code = joinLines([
96+
//
97+
'undefined ? 1 : 2',
98+
]);
99+
100+
const script = parseScript(code);
101+
102+
const agent = createAgent({
103+
tools: {},
104+
script,
105+
});
106+
107+
await executeAgent({ agent });
108+
109+
const expectedStack = rootFrame({
110+
status: 'finished',
111+
children: [
112+
// ternary operator
113+
completedFrame({
114+
trace: '0:0',
115+
value: 2,
116+
children: [
117+
// condition
118+
completedFrame({
119+
trace: '0:0:0',
120+
value: undefined,
121+
}),
122+
// then
123+
completedFrame({
124+
trace: '0:0:1',
125+
value: 2,
126+
}),
127+
],
128+
}),
129+
],
130+
});
131+
132+
expect(agent.root).toEqual(expectedStack);
133+
expect(agent.status).toBe('finished');
134+
});
135+
136+
test('ternary operator with expressions', async () => {
137+
const code = joinLines([
138+
//
139+
'let a = 3',
140+
'a < 1 ? 1 : 2',
141+
]);
142+
143+
const script = parseScript(code);
144+
145+
const agent = createAgent({
146+
tools: {},
147+
script,
148+
});
149+
150+
await executeAgent({ agent });
151+
152+
const expectedStack = rootFrame({
153+
status: 'finished',
154+
variables: {
155+
a: 3,
156+
},
157+
children: [
158+
// var a declaration
159+
completedFrame({
160+
trace: '0:0',
161+
children: [
162+
// literal
163+
completedFrame({
164+
trace: '0:0:0',
165+
value: 3,
166+
}),
167+
],
168+
}),
169+
// ternary operator
170+
completedFrame({
171+
trace: '0:1',
172+
value: 2,
173+
children: [
174+
// condition
175+
completedFrame({
176+
trace: '0:1:0',
177+
value: false,
178+
children: [
179+
// left operand
180+
completedFrame({
181+
trace: '0:1:0:0',
182+
value: 3,
183+
}),
184+
// right operand
185+
completedFrame({
186+
trace: '0:1:0:1',
187+
value: 1,
188+
}),
189+
],
190+
}),
191+
// else
192+
completedFrame({
193+
trace: '0:1:1',
194+
value: 2,
195+
}),
196+
],
197+
}),
198+
],
199+
});
200+
201+
expect(agent.root).toEqual(expectedStack);
202+
expect(agent.status).toBe('finished');
203+
});

packages/core/src/runtime/utils/resolveExpression.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import type { Agent } from '../../agent/agentTypes.js';
12
import type { Expression, FunctionCall, Identifier, Literal } from '../../parser/astTypes.js';
23
import { RuntimeError } from '../RuntimeError.js';
3-
import type { Agent } from '../../agent/agentTypes.js';
44
import type { StackFrame } from '../runtimeTypes.js';
55

66
const allowedNativeIdentifiers = new Set([
@@ -70,6 +70,9 @@ export function resolveName(agent: Agent, frame: StackFrame, expression: Express
7070
*/
7171
export function resolveIdentifier(agent: Agent, frame: StackFrame, expression: Identifier) {
7272
const name = expression.name;
73+
if (name === 'undefined') {
74+
return undefined;
75+
}
7376

7477
while (frame) {
7578
const variables = frame.variables;

0 commit comments

Comments
 (0)