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