gent/interpreter/block_eval.rs
1//! Block evaluation module
2//!
3//! This module provides async block evaluation for executing tool bodies
4//! with let bindings, return statements, if/else, and expression statements.
5
6use crate::errors::{GentError, GentResult};
7use crate::interpreter::builtins::{call_builtin, is_builtin};
8use crate::interpreter::expr_eval::evaluate_expr;
9use crate::interpreter::array_methods::{call_array_method, call_array_method_with_callback, is_callback_method};
10use crate::interpreter::string_methods::call_string_method;
11use crate::interpreter::types::EnumValue;
12use crate::interpreter::{Environment, Value};
13use crate::logging::{Logger, NullLogger};
14use crate::parser::ast::{Block, BlockStmt, Expression, MatchBody, MatchPattern};
15use crate::runtime::tools::ToolRegistry;
16use crate::runtime::{run_agent_with_tools, LLMClient};
17use std::collections::HashMap;
18
19/// Context for block evaluation that includes optional LLM client for agent execution
20pub struct BlockEvalContext<'a> {
21 pub llm: Option<&'a dyn LLMClient>,
22 pub logger: &'a dyn Logger,
23}
24
25impl<'a> BlockEvalContext<'a> {
26 /// Create a new context with LLM client
27 pub fn with_llm(llm: &'a dyn LLMClient, logger: &'a dyn Logger) -> Self {
28 Self { llm: Some(llm), logger }
29 }
30
31 /// Create an empty context (no agent execution support)
32 pub fn empty() -> Self {
33 // Use a static NullLogger for the empty context
34 static NULL_LOGGER: NullLogger = NullLogger;
35 Self { llm: None, logger: &NULL_LOGGER }
36 }
37}
38
39/// Control flow signal for break/continue/return propagation
40#[derive(Debug, Clone, PartialEq)]
41enum ControlFlow {
42 /// Normal execution, continue to next statement
43 Continue,
44 /// Break out of the current loop
45 Break,
46 /// Skip to next iteration of the current loop
47 LoopContinue,
48 /// Return from the function with the given value (boxed to reduce enum size)
49 Return(Box<Value>),
50}
51
52/// Type alias for async block evaluation result with control flow
53type BlockInternalFuture<'a> =
54 std::pin::Pin<Box<dyn std::future::Future<Output = GentResult<(ControlFlow, Value)>> + 'a>>;
55
56/// Evaluate a block of statements in the given environment
57///
58/// Returns the value of the first return statement encountered,
59/// or Value::Null if the block completes without returning.
60///
61/// Note: This version does not support agent execution. Use `evaluate_block_with_llm`
62/// if you need to call agent methods like `.run()`.
63pub fn evaluate_block<'a>(
64 block: &'a Block,
65 env: &'a mut Environment,
66 tools: &'a ToolRegistry,
67) -> std::pin::Pin<Box<dyn std::future::Future<Output = GentResult<Value>> + 'a>> {
68 Box::pin(async move {
69 // Create a new scope for this block
70 env.push_scope();
71
72 let ctx = BlockEvalContext::empty();
73 let (flow, result) = evaluate_block_internal(block, env, tools, &ctx).await?;
74
75 // Pop the scope
76 env.pop_scope();
77
78 // Handle control flow that escaped the block
79 match flow {
80 ControlFlow::Return(val) => Ok(*val),
81 ControlFlow::Continue => Ok(result),
82 // Break/LoopContinue outside of a loop is an error, but we treat it as normal
83 // completion for now (the loop handler consumes these signals)
84 ControlFlow::Break | ControlFlow::LoopContinue => Ok(result),
85 }
86 })
87}
88
89/// Evaluate a block with LLM support for agent execution
90///
91/// This version supports calling agent methods like `.run()` within the block.
92pub fn evaluate_block_with_llm<'a>(
93 block: &'a Block,
94 env: &'a mut Environment,
95 tools: &'a ToolRegistry,
96 llm: &'a dyn LLMClient,
97 logger: &'a dyn Logger,
98) -> std::pin::Pin<Box<dyn std::future::Future<Output = GentResult<Value>> + 'a>> {
99 Box::pin(async move {
100 // Create a new scope for this block
101 env.push_scope();
102
103 let ctx = BlockEvalContext::with_llm(llm, logger);
104 let (flow, result) = evaluate_block_internal(block, env, tools, &ctx).await?;
105
106 // Pop the scope
107 env.pop_scope();
108
109 // Handle control flow that escaped the block
110 match flow {
111 ControlFlow::Return(val) => Ok(*val),
112 ControlFlow::Continue => Ok(result),
113 ControlFlow::Break | ControlFlow::LoopContinue => Ok(result),
114 }
115 })
116}
117
118/// Evaluate a block with an existing context
119///
120/// This is used internally when calling functions to preserve the LLM context.
121fn evaluate_block_with_ctx<'a>(
122 block: &'a Block,
123 env: &'a mut Environment,
124 tools: &'a ToolRegistry,
125 ctx: &'a BlockEvalContext<'a>,
126) -> std::pin::Pin<Box<dyn std::future::Future<Output = GentResult<Value>> + 'a>> {
127 Box::pin(async move {
128 // Create a new scope for this block
129 env.push_scope();
130
131 let (flow, result) = evaluate_block_internal(block, env, tools, ctx).await?;
132
133 // Pop the scope
134 env.pop_scope();
135
136 // Handle control flow that escaped the block
137 match flow {
138 ControlFlow::Return(val) => Ok(*val),
139 ControlFlow::Continue => Ok(result),
140 ControlFlow::Break | ControlFlow::LoopContinue => Ok(result),
141 }
142 })
143}
144
145/// Internal block evaluation that returns control flow signals
146fn evaluate_block_internal<'a>(
147 block: &'a Block,
148 env: &'a mut Environment,
149 tools: &'a ToolRegistry,
150 ctx: &'a BlockEvalContext<'a>,
151) -> BlockInternalFuture<'a> {
152 Box::pin(async move {
153 let mut result = Value::Null;
154
155 for stmt in &block.statements {
156 match stmt {
157 BlockStmt::Let(let_stmt) => {
158 // Check if the value is a mutating array method call (push/pop)
159 let value = if let Some((arr_var, method_name, args)) = extract_array_method_call(&let_stmt.value) {
160 if method_name == "push" || method_name == "pop" {
161 if let Some(Value::Array(arr)) = env.get(&arr_var).cloned() {
162 let mut arr_mut = arr;
163
164 // Evaluate arguments
165 let mut arg_values = Vec::new();
166 for arg in args {
167 let val = evaluate_expr_async(arg, env, tools, ctx).await?;
168 arg_values.push(val);
169 }
170
171 // Call the method and get result
172 let result = call_array_method(&mut arr_mut, &method_name, &arg_values)?;
173
174 // Update the array variable with the mutated array
175 env.set(&arr_var, Value::Array(arr_mut));
176
177 result
178 } else {
179 evaluate_expr_async(&let_stmt.value, env, tools, ctx).await?
180 }
181 } else {
182 evaluate_expr_async(&let_stmt.value, env, tools, ctx).await?
183 }
184 } else {
185 evaluate_expr_async(&let_stmt.value, env, tools, ctx).await?
186 };
187
188 // Define the variable in the current scope
189 env.define(&let_stmt.name, value);
190 }
191
192 BlockStmt::Assignment(assign_stmt) => {
193 // Evaluate the right-hand side expression
194 let value = evaluate_expr_async(&assign_stmt.value, env, tools, ctx).await?;
195
196 // Update the variable in the environment
197 if !env.set(&assign_stmt.name, value) {
198 return Err(GentError::SyntaxError {
199 message: format!("Undefined variable: '{}'", assign_stmt.name),
200 span: assign_stmt.span.clone(),
201 });
202 }
203 }
204
205 BlockStmt::Return(return_stmt) => {
206 // Evaluate the return value (if any)
207 result = if let Some(ref expr) = return_stmt.value {
208 evaluate_expr_async(expr, env, tools, ctx).await?
209 } else {
210 Value::Null
211 };
212 return Ok((ControlFlow::Return(Box::new(result)), Value::Null));
213 }
214
215 BlockStmt::If(if_stmt) => {
216 // Evaluate the condition
217 let condition = evaluate_expr_async(&if_stmt.condition, env, tools, ctx).await?;
218
219 // Execute the appropriate block
220 if condition.is_truthy() {
221 // Execute then block (create a new scope)
222 env.push_scope();
223 let (flow, _) = evaluate_block_internal(&if_stmt.then_block, env, tools, ctx).await?;
224 env.pop_scope();
225
226 // Propagate control flow signals
227 match flow {
228 ControlFlow::Continue => {}
229 other => return Ok((other, Value::Null)),
230 }
231 } else if let Some(ref else_block) = if_stmt.else_block {
232 // Execute else block (create a new scope)
233 env.push_scope();
234 let (flow, _) = evaluate_block_internal(else_block, env, tools, ctx).await?;
235 env.pop_scope();
236
237 // Propagate control flow signals
238 match flow {
239 ControlFlow::Continue => {}
240 other => return Ok((other, Value::Null)),
241 }
242 }
243 }
244
245 BlockStmt::For(for_stmt) => {
246 // Evaluate the iterable expression
247 let iterable = evaluate_expr(&for_stmt.iterable, env)?;
248
249 // Convert iterable to a list of items
250 let items: Vec<Value> = match iterable {
251 Value::Array(arr) => arr,
252 Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(),
253 other => {
254 return Err(GentError::TypeError {
255 expected: "Array or String".to_string(),
256 got: other.type_name().to_string(),
257 span: for_stmt.span.clone(),
258 });
259 }
260 };
261
262 // Iterate over items
263 'outer: for item in items {
264 env.push_scope();
265 env.define(&for_stmt.variable, item);
266
267 // Execute the loop body using internal evaluation
268 let (flow, _) = evaluate_block_internal(&for_stmt.body, env, tools, ctx).await?;
269
270 env.pop_scope();
271
272 // Handle control flow from the loop body
273 match flow {
274 ControlFlow::Continue => {
275 // Normal completion, continue to next iteration
276 }
277 ControlFlow::LoopContinue => {
278 // Skip to next iteration (already handled by continuing the loop)
279 continue 'outer;
280 }
281 ControlFlow::Break => {
282 // Exit the loop
283 break 'outer;
284 }
285 ControlFlow::Return(val) => {
286 // Propagate return up
287 return Ok((ControlFlow::Return(val), Value::Null));
288 }
289 }
290 }
291 }
292
293 BlockStmt::Expr(expr) => {
294 // Check for mutating array method calls (push/pop) and handle specially
295 if let Some((var_name, method_name, args)) = extract_array_method_call(expr) {
296 if method_name == "push" || method_name == "pop" {
297 // Get the current array value
298 if let Some(Value::Array(arr)) = env.get(&var_name).cloned() {
299 let mut arr_mut = arr;
300
301 // Evaluate arguments
302 let mut arg_values = Vec::new();
303 for arg in args {
304 let val = evaluate_expr_async(arg, env, tools, ctx).await?;
305 arg_values.push(val);
306 }
307
308 // Call the method
309 call_array_method(&mut arr_mut, &method_name, &arg_values)?;
310
311 // Update the variable with the mutated array
312 env.set(&var_name, Value::Array(arr_mut));
313 }
314 continue;
315 }
316 }
317
318 // Evaluate the expression for side effects, discarding the result
319 evaluate_expr_async(expr, env, tools, ctx).await?;
320 }
321
322 BlockStmt::While(while_stmt) => {
323 const MAX_ITERATIONS: usize = 10000; // Prevent infinite loops
324 let mut iterations = 0;
325
326 'while_loop: loop {
327 iterations += 1;
328 if iterations > MAX_ITERATIONS {
329 return Err(GentError::SyntaxError {
330 message: format!(
331 "While loop exceeded maximum iterations ({})",
332 MAX_ITERATIONS
333 ),
334 span: while_stmt.span.clone(),
335 });
336 }
337
338 // Evaluate condition
339 let condition =
340 evaluate_expr_async(&while_stmt.condition, env, tools, ctx).await?;
341 if !condition.is_truthy() {
342 break;
343 }
344
345 // Execute body statements with a new scope
346 env.push_scope();
347 let (flow, _) =
348 evaluate_block_internal(&while_stmt.body, env, tools, ctx).await?;
349 env.pop_scope();
350
351 // Handle control flow from the loop body
352 match flow {
353 ControlFlow::Continue => {
354 // Normal completion, continue to next iteration
355 }
356 ControlFlow::LoopContinue => {
357 // Skip to next iteration
358 continue 'while_loop;
359 }
360 ControlFlow::Break => {
361 // Exit the loop
362 break 'while_loop;
363 }
364 ControlFlow::Return(val) => {
365 // Propagate return up
366 return Ok((ControlFlow::Return(val), Value::Null));
367 }
368 }
369 }
370 }
371
372 BlockStmt::Break(_) => {
373 // Signal break to the enclosing loop
374 return Ok((ControlFlow::Break, Value::Null));
375 }
376
377 BlockStmt::Continue(_) => {
378 // Signal continue to the enclosing loop
379 return Ok((ControlFlow::LoopContinue, Value::Null));
380 }
381
382 BlockStmt::Try(try_stmt) => {
383 // Execute try block and capture result
384 env.push_scope();
385 let try_result = evaluate_block_internal(&try_stmt.try_block, env, tools, ctx).await;
386 env.pop_scope();
387
388 match try_result {
389 Ok((flow, _value)) => {
390 // Try block succeeded
391 match flow {
392 ControlFlow::Return(val) => {
393 return Ok((ControlFlow::Return(val), Value::Null));
394 }
395 ControlFlow::Break => {
396 return Ok((ControlFlow::Break, Value::Null));
397 }
398 ControlFlow::LoopContinue => {
399 return Ok((ControlFlow::LoopContinue, Value::Null));
400 }
401 ControlFlow::Continue => {
402 // Normal completion, continue with next statement after try/catch
403 }
404 }
405 }
406 Err(e) => {
407 // Error occurred, execute catch block with error bound
408 env.push_scope();
409 env.define(&try_stmt.error_var, Value::String(e.to_string()));
410
411 let catch_result =
412 evaluate_block_internal(&try_stmt.catch_block, env, tools, ctx).await?;
413 env.pop_scope();
414
415 match catch_result.0 {
416 ControlFlow::Return(val) => {
417 return Ok((ControlFlow::Return(val), Value::Null));
418 }
419 ControlFlow::Break => {
420 return Ok((ControlFlow::Break, Value::Null));
421 }
422 ControlFlow::LoopContinue => {
423 return Ok((ControlFlow::LoopContinue, Value::Null));
424 }
425 ControlFlow::Continue => {
426 // Normal completion, continue with next statement after try/catch
427 }
428 }
429 }
430 }
431 }
432 }
433 }
434
435 Ok((ControlFlow::Continue, result))
436 })
437}
438
439/// Evaluate an expression in an async context, handling function calls
440///
441/// This function is similar to evaluate_expr but supports async tool calls.
442/// The `ctx` parameter provides optional LLM client for agent execution.
443pub fn evaluate_expr_async<'a>(
444 expr: &'a Expression,
445 env: &'a Environment,
446 tools: &'a ToolRegistry,
447 ctx: &'a BlockEvalContext<'a>,
448) -> std::pin::Pin<Box<dyn std::future::Future<Output = GentResult<Value>> + 'a>> {
449 Box::pin(async move {
450 match expr {
451 // Function/tool calls require async context
452 Expression::Call(callee_expr, args, span) => {
453 // Check if this is a method call on a string, array, or enum constructor
454 if let Expression::Member(obj_expr, method_name, _) = callee_expr.as_ref() {
455 // First check if this could be an enum constructor call: EnumName.Variant(args)
456 if let Expression::Identifier(name, _) = obj_expr.as_ref() {
457 if let Some(enum_def) = env.get_enum(name) {
458 // Find the variant
459 if let Some(v) = enum_def.variants.iter().find(|v| v.name == *method_name) {
460 // Evaluate arguments
461 let mut arg_values = Vec::new();
462 for arg in args {
463 let val = evaluate_expr_async(arg, env, tools, ctx).await?;
464 arg_values.push(val);
465 }
466
467 if arg_values.len() != v.fields.len() {
468 return Err(GentError::TypeError {
469 expected: format!(
470 "Variant '{}' expects {} arguments",
471 method_name, v.fields.len()
472 ),
473 got: format!("{} arguments", arg_values.len()),
474 span: span.clone(),
475 });
476 }
477
478 return Ok(Value::Enum(EnumValue {
479 enum_name: name.clone(),
480 variant: method_name.clone(),
481 data: arg_values,
482 }));
483 } else {
484 return Err(GentError::TypeError {
485 expected: format!("valid variant of enum '{}'", name),
486 got: method_name.clone(),
487 span: span.clone(),
488 });
489 }
490 }
491 }
492
493 // Evaluate the object expression
494 let obj = evaluate_expr_async(obj_expr, env, tools, ctx).await?;
495
496 // If it's a string, dispatch to string methods
497 if let Value::String(s) = &obj {
498 // Evaluate method arguments
499 let mut arg_values = Vec::new();
500 for arg in args {
501 let val = evaluate_expr_async(arg, env, tools, ctx).await?;
502 arg_values.push(val);
503 }
504 return call_string_method(s, method_name, &arg_values);
505 }
506
507 // Check if this is an enum .is() or .data() call
508 if let Value::Enum(ref enum_val) = obj {
509 if method_name == "is" {
510 // Evaluate the argument (should be an EnumValue or EnumConstructor)
511 if args.len() != 1 {
512 return Err(GentError::TypeError {
513 expected: "1 argument for .is()".to_string(),
514 got: format!("{} arguments", args.len()),
515 span: span.clone(),
516 });
517 }
518
519 let arg = evaluate_expr_async(&args[0], env, tools, ctx).await?;
520 let matches = match arg {
521 Value::Enum(other) => {
522 enum_val.enum_name == other.enum_name && enum_val.variant == other.variant
523 }
524 Value::EnumConstructor(ctor) => {
525 enum_val.enum_name == ctor.enum_name && enum_val.variant == ctor.variant
526 }
527 _ => false,
528 };
529
530 return Ok(Value::Boolean(matches));
531 }
532
533 if method_name == "data" {
534 if args.len() != 1 {
535 return Err(GentError::TypeError {
536 expected: "1 argument for .data()".to_string(),
537 got: format!("{} arguments", args.len()),
538 span: span.clone(),
539 });
540 }
541
542 let arg = evaluate_expr_async(&args[0], env, tools, ctx).await?;
543
544 match arg {
545 Value::Number(n) => {
546 let idx = n as usize;
547 return Ok(enum_val.data.get(idx).cloned().unwrap_or(Value::Null));
548 }
549 Value::String(_) => {
550 // Named access not yet implemented
551 return Err(GentError::TypeError {
552 expected: "number index for .data()".to_string(),
553 got: "string (named access not yet implemented)".to_string(),
554 span: span.clone(),
555 });
556 }
557 _ => {
558 return Err(GentError::TypeError {
559 expected: "number index".to_string(),
560 got: arg.type_name(),
561 span: span.clone(),
562 });
563 }
564 }
565 }
566 }
567
568 // If it's an array, dispatch to array methods
569 if let Value::Array(ref arr) = obj {
570 // Evaluate method arguments
571 let mut arg_values = Vec::new();
572 for arg in args {
573 let val = evaluate_expr_async(arg, env, tools, ctx).await?;
574 arg_values.push(val);
575 }
576
577 // Check if this is a callback method (map, filter, reduce, find)
578 if is_callback_method(method_name) {
579 let callback = arg_values.first().ok_or_else(|| GentError::TypeError {
580 expected: "callback function for array method".to_string(),
581 got: "missing argument".to_string(),
582 span: span.clone(),
583 })?;
584 let extra_args = if arg_values.len() > 1 { &arg_values[1..] } else { &[] };
585 return call_array_method_with_callback(
586 arr,
587 method_name,
588 callback,
589 extra_args,
590 env,
591 tools,
592 ).await;
593 }
594
595 // Non-callback methods
596 let mut arr_clone = arr.clone();
597 let result = call_array_method(
598 &mut arr_clone,
599 method_name,
600 &arg_values,
601 )?;
602
603 return Ok(result);
604 }
605
606 // Handle Agent method calls (userPrompt, systemPrompt, run)
607 if let Value::Agent(mut agent) = obj {
608 match method_name.as_str() {
609 "userPrompt" => {
610 // Set user_prompt and return modified agent
611 if args.is_empty() {
612 return Err(GentError::SyntaxError {
613 message: "userPrompt() requires an argument".to_string(),
614 span: span.clone(),
615 });
616 }
617 let arg = evaluate_expr_async(&args[0], env, tools, ctx).await?;
618 let prompt = match arg {
619 Value::String(s) => s,
620 other => format!("{}", other),
621 };
622 agent.user_prompt = Some(prompt);
623 return Ok(Value::Agent(agent));
624 }
625 "systemPrompt" => {
626 // Set system_prompt and return modified agent
627 if args.is_empty() {
628 return Err(GentError::SyntaxError {
629 message: "systemPrompt() requires an argument".to_string(),
630 span: span.clone(),
631 });
632 }
633 let arg = evaluate_expr_async(&args[0], env, tools, ctx).await?;
634 let prompt = match arg {
635 Value::String(s) => s,
636 other => format!("{}", other),
637 };
638 agent.system_prompt = prompt;
639 return Ok(Value::Agent(agent));
640 }
641 "run" => {
642 // Execute the agent - requires LLM client
643 if let Some(llm) = ctx.llm {
644 let result = run_agent_with_tools(&agent, None, llm, tools, ctx.logger).await?;
645 // If agent has structured output, parse as JSON
646 if agent.output_schema.is_some() {
647 if let Ok(json_val) = serde_json::from_str::<serde_json::Value>(&result) {
648 return Ok(json_to_value(&json_val));
649 }
650 }
651 return Ok(Value::String(result));
652 } else {
653 return Err(GentError::SyntaxError {
654 message: "Cannot call .run() on agent in this context (no LLM client available)".to_string(),
655 span: span.clone(),
656 });
657 }
658 }
659 _ => {
660 return Err(GentError::SyntaxError {
661 message: format!("Unknown agent method: {}", method_name),
662 span: span.clone(),
663 });
664 }
665 }
666 }
667
668 // For other types, return an error for now
669 return Err(GentError::TypeError {
670 expected: "String, Array, or Agent".to_string(),
671 got: obj.type_name().to_string(),
672 span: span.clone(),
673 });
674 }
675
676 // Get the callable name
677 let callable_name = if let Expression::Identifier(name, _) = callee_expr.as_ref() {
678 name.clone()
679 } else {
680 let callee = evaluate_expr(callee_expr, env)?;
681 return Err(GentError::TypeError {
682 expected: "function or tool name".to_string(),
683 got: callee.type_name().to_string(),
684 span: span.clone(),
685 });
686 };
687
688 // Evaluate arguments first (needed for both functions and tools)
689 let mut arg_values = Vec::new();
690 for arg in args {
691 let val = evaluate_expr_async(arg, env, tools, ctx).await?;
692 arg_values.push(val);
693 }
694
695 // Check if it's a built-in function
696 if is_builtin(&callable_name) {
697 return call_builtin(&callable_name, &arg_values, span);
698 }
699
700 // Check if it's a function in the environment
701 if let Some(Value::Function(fn_val)) = env.get(&callable_name) {
702 // Clone the function value since we need to borrow env mutably later
703 let fn_val = fn_val.clone();
704
705 // Check argument count
706 if arg_values.len() != fn_val.params.len() {
707 return Err(GentError::SyntaxError {
708 message: format!(
709 "Function '{}' expects {} arguments, got {}",
710 fn_val.name,
711 fn_val.params.len(),
712 arg_values.len()
713 ),
714 span: span.clone(),
715 });
716 }
717
718 // Create a new environment with function scope
719 let mut fn_env = env.clone();
720 fn_env.push_scope();
721
722 // Bind parameters to arguments
723 for (param, arg_val) in fn_val.params.iter().zip(arg_values.iter()) {
724 fn_env.define(¶m.name, arg_val.clone());
725 }
726
727 // Evaluate the function body
728 let result = evaluate_block_with_ctx(&fn_val.body, &mut fn_env, tools, ctx).await?;
729 return Ok(result);
730 }
731
732 // Look up the tool in the registry
733 let tool = tools
734 .get(&callable_name)
735 .ok_or_else(|| GentError::UnknownTool {
736 name: callable_name.clone(),
737 span: span.clone(),
738 })?;
739
740 // Convert arguments to JSON for tool execution
741 let json_args = args_to_json(&arg_values);
742
743 // Execute the tool
744 let result = tool
745 .execute(json_args)
746 .await
747 .map_err(|e| GentError::ToolError {
748 tool: callable_name.clone(),
749 message: e,
750 })?;
751
752 // For now, return the result as a string
753 // TODO: Parse JSON results in the future
754 Ok(Value::String(result))
755 }
756
757 // Match expression
758 Expression::Match(match_expr) => {
759 let subject = evaluate_expr_async(&match_expr.subject, env, tools, ctx).await?;
760
761 for arm in &match_expr.arms {
762 if let Some(bindings) = match_pattern(&subject, &arm.pattern) {
763 // Create new scope with bindings
764 let mut match_env = env.clone();
765 match_env.push_scope();
766 for (name, value) in bindings {
767 match_env.define(&name, value);
768 }
769
770 // Evaluate arm body
771 let result = match &arm.body {
772 MatchBody::Expression(expr) => {
773 evaluate_expr_async(expr, &match_env, tools, ctx).await?
774 }
775 MatchBody::Block(block) => {
776 evaluate_block_with_ctx(block, &mut match_env, tools, ctx).await?
777 }
778 };
779
780 return Ok(result);
781 }
782 }
783
784 // No match found
785 Err(GentError::SyntaxError {
786 message: "Non-exhaustive match: no pattern matched".to_string(),
787 span: match_expr.span.clone(),
788 })
789 }
790
791 // All other expressions can be evaluated synchronously
792 _ => evaluate_expr(expr, env),
793 }
794 })
795}
796
797/// Convert a vector of Values to a JSON value for tool execution
798fn args_to_json(args: &[Value]) -> serde_json::Value {
799 use serde_json::{json, Map, Value as JsonValue};
800
801 fn value_to_json(val: &Value) -> JsonValue {
802 match val {
803 Value::String(s) => JsonValue::String(s.clone()),
804 Value::Number(n) => json!(n),
805 Value::Boolean(b) => JsonValue::Bool(*b),
806 Value::Null => JsonValue::Null,
807 Value::Array(items) => {
808 let json_items: Vec<JsonValue> = items.iter().map(value_to_json).collect();
809 JsonValue::Array(json_items)
810 }
811 Value::Object(map) => {
812 let mut json_map = Map::new();
813 for (k, v) in map {
814 json_map.insert(k.clone(), value_to_json(v));
815 }
816 JsonValue::Object(json_map)
817 }
818 Value::Agent(_) => JsonValue::String("<agent>".to_string()),
819 Value::Tool(_) => JsonValue::String("<tool>".to_string()),
820 Value::Function(_) => JsonValue::String("<function>".to_string()),
821 Value::Lambda(_) => JsonValue::String("<lambda>".to_string()),
822 Value::Enum(e) => {
823 if e.data.is_empty() {
824 JsonValue::String(format!("{}.{}", e.enum_name, e.variant))
825 } else {
826 let mut map = Map::new();
827 map.insert("enum".to_string(), JsonValue::String(e.enum_name.clone()));
828 map.insert(
829 "variant".to_string(),
830 JsonValue::String(e.variant.clone()),
831 );
832 let data: Vec<JsonValue> = e.data.iter().map(value_to_json).collect();
833 map.insert("data".to_string(), JsonValue::Array(data));
834 JsonValue::Object(map)
835 }
836 }
837 Value::EnumConstructor(c) => {
838 JsonValue::String(format!("<enum constructor {}.{}>", c.enum_name, c.variant))
839 }
840 Value::Parallel(p) => JsonValue::String(format!("<parallel {}>", p.name)),
841 }
842 }
843
844 // If there's a single object argument, use it directly
845 // Otherwise, wrap arguments in an array
846 if args.len() == 1 {
847 if let Value::Object(_) = &args[0] {
848 return value_to_json(&args[0]);
849 }
850 }
851
852 // For multiple args or non-object single arg, create an array
853 JsonValue::Array(args.iter().map(value_to_json).collect())
854}
855
856/// Extract variable name, method name, and arguments from a method call expression
857/// Returns Some((var_name, method_name, args)) if this is a method call on an identifier
858fn extract_array_method_call(expr: &Expression) -> Option<(String, String, &Vec<Expression>)> {
859 if let Expression::Call(callee_expr, args, _) = expr {
860 if let Expression::Member(obj_expr, method_name, _) = callee_expr.as_ref() {
861 if let Expression::Identifier(var_name, _) = obj_expr.as_ref() {
862 return Some((var_name.clone(), method_name.clone(), args));
863 }
864 }
865 }
866 None
867}
868
869/// Match a value against a pattern, returning bindings if successful
870fn match_pattern(value: &Value, pattern: &MatchPattern) -> Option<Vec<(String, Value)>> {
871 match pattern {
872 MatchPattern::Wildcard => Some(vec![]),
873 MatchPattern::EnumVariant { enum_name, variant_name, bindings } => {
874 if let Value::Enum(enum_val) = value {
875 if enum_val.enum_name == *enum_name && enum_val.variant == *variant_name {
876 // Bind data to pattern variables
877 let mut result = Vec::new();
878 for (i, binding) in bindings.iter().enumerate() {
879 if let Some(data) = enum_val.data.get(i) {
880 result.push((binding.clone(), data.clone()));
881 }
882 }
883 return Some(result);
884 }
885 }
886 None
887 }
888 }
889}
890
891/// Convert a JSON value to a GENT Value
892fn json_to_value(json: &serde_json::Value) -> Value {
893 match json {
894 serde_json::Value::Null => Value::Null,
895 serde_json::Value::Bool(b) => Value::Boolean(*b),
896 serde_json::Value::Number(n) => {
897 if let Some(f) = n.as_f64() {
898 Value::Number(f)
899 } else {
900 Value::Null
901 }
902 }
903 serde_json::Value::String(s) => Value::String(s.clone()),
904 serde_json::Value::Array(arr) => {
905 let items = arr.iter().map(json_to_value).collect();
906 Value::Array(items)
907 }
908 serde_json::Value::Object(obj) => {
909 let mut map = HashMap::new();
910 for (k, v) in obj {
911 map.insert(k.clone(), json_to_value(v));
912 }
913 Value::Object(map)
914 }
915 }
916}