Skip to main content

somni_expr/
visitor.rs

1use indexmap::IndexMap;
2use somni_parser::{
3    Location,
4    ast::{
5        Body, Expression, For, Function, If, LeftHandExpression, LiteralValue, Loop,
6        RightHandExpression, Statement, TypeHint, VariableDefinition,
7    },
8    lexer,
9    parser::DefaultTypeSet,
10};
11
12use crate::{
13    EvalError, ExprContext, FunctionCallError, RefPointee, Type, TypeSet, TypedValue,
14    value::{LoadStore, Place, Reference, SomniStruct},
15};
16
17/// A visitor that can process an abstract syntax tree.
18pub struct ExpressionVisitor<'a, C, T = DefaultTypeSet> {
19    /// The context in which the expression is evaluated.
20    pub context: &'a mut C,
21    /// The source code from which the expression was parsed.
22    pub source: &'a str,
23    /// The types of the variables in the context.
24    pub _marker: std::marker::PhantomData<T>,
25}
26
27impl<'a, C, T> ExpressionVisitor<'a, C, T>
28where
29    C: ExprContext<T>,
30    T: TypeSet,
31{
32    fn visit_variable(&mut self, variable: &lexer::Token) -> Result<TypedValue<T>, EvalError> {
33        let name = variable.source(self.source);
34        self.context.try_load_variable(name).ok_or(EvalError {
35            message: format!("Variable {name} was not found").into_boxed_str(),
36            location: variable.location,
37        })
38    }
39
40    /// Visits an expression and evaluates it, returning the result as a `TypedValue`.
41    pub fn visit_expression(
42        &mut self,
43        expression: &Expression<T::Parser>,
44    ) -> Result<TypedValue<T>, EvalError> {
45        let result = match expression {
46            Expression::Expression { expression } => {
47                self.visit_right_hand_expression(expression)?
48            }
49            Expression::Assignment {
50                left_expr,
51                operator: _,
52                right_expr,
53            } => {
54                let rhs = self.visit_right_hand_expression(right_expr)?;
55                let assign_result = match left_expr {
56                    LeftHandExpression::Name { variable } => {
57                        let name = variable.source(self.source);
58                        self.context.assign_variable(name, &rhs)
59                    }
60                    LeftHandExpression::Deref { .. } | LeftHandExpression::Field { .. } => {
61                        let place = self.resolve_place_lhs(left_expr)?;
62                        self.context.store_place(&place, &rhs)
63                    }
64                };
65
66                if let Err(error) = assign_result {
67                    return Err(EvalError {
68                        message: error,
69                        location: expression.location(),
70                    });
71                }
72
73                TypedValue::Void
74            }
75        };
76
77        Ok(result)
78    }
79
80    /// Visits an expression and evaluates it, returning the result as a `TypedValue`.
81    pub fn visit_right_hand_expression(
82        &mut self,
83        expression: &RightHandExpression<T::Parser>,
84    ) -> Result<TypedValue<T>, EvalError> {
85        let result = match expression {
86            RightHandExpression::Variable { variable } => self.visit_variable(variable)?,
87            RightHandExpression::Literal { value } => match &value.value {
88                LiteralValue::Integer(value) => TypedValue::<T>::MaybeSignedInt(*value),
89                LiteralValue::Float(value) => TypedValue::<T>::Float(*value),
90                LiteralValue::String(value) => value.store(self.context.type_context()),
91                LiteralValue::Boolean(value) => TypedValue::<T>::Bool(*value),
92            },
93            RightHandExpression::UnaryOperator { name, operand } => {
94                match name.source(self.source) {
95                    "!" => {
96                        let operand = self.visit_right_hand_expression(operand)?;
97
98                        match TypedValue::<T>::not(self.context.type_context(), operand) {
99                            Ok(r) => r,
100                            Err(error) => {
101                                return Err(EvalError {
102                                    message: format!("Failed to evaluate expression: {error}")
103                                        .into_boxed_str(),
104                                    location: expression.location(),
105                                });
106                            }
107                        }
108                    }
109
110                    "-" => {
111                        let value = self.visit_right_hand_expression(operand)?;
112                        let ty = value.type_of();
113                        TypedValue::<T>::negate(self.context.type_context(), value).map_err(
114                            |e| EvalError {
115                                message: format!("Cannot negate {ty}: {e}").into_boxed_str(),
116                                location: operand.location(),
117                            },
118                        )?
119                    }
120
121                    "&" => {
122                        let place = self.resolve_place_rhs(operand)?;
123                        let value = self.context.load_place(&place).map_err(|e| EvalError {
124                            message: e,
125                            location: operand.location(),
126                        })?;
127                        let pointee =
128                            RefPointee::from_type(value.type_of()).ok_or_else(|| EvalError {
129                                message: String::from("Cannot take a reference to a reference")
130                                    .into_boxed_str(),
131                                location: operand.location(),
132                            })?;
133                        TypedValue::Ref(Reference::new(pointee, place))
134                    }
135                    "*" => {
136                        let value = self.visit_right_hand_expression(operand)?;
137                        let TypedValue::Ref(reference) = value else {
138                            return Err(EvalError {
139                                message: format!("Cannot dereference {}", value.type_of())
140                                    .into_boxed_str(),
141                                location: operand.location(),
142                            });
143                        };
144                        self.context
145                            .load_place(reference.place())
146                            .map_err(|e| EvalError {
147                                message: format!("Failed to load variable from address: {e}")
148                                    .into_boxed_str(),
149                                location: operand.location(),
150                            })?
151                    }
152                    _ => {
153                        return Err(EvalError {
154                            message: format!(
155                                "Unknown unary operator: {}",
156                                name.source(self.source)
157                            )
158                            .into_boxed_str(),
159                            location: expression.location(),
160                        });
161                    }
162                }
163            }
164            RightHandExpression::BinaryOperator { name, operands } => {
165                let lhs = self.visit_right_hand_expression(&operands[0])?;
166
167                let short_circuiting = ["&&", "||"];
168                let operator = name.source(self.source);
169
170                // Special cases
171                if short_circuiting.contains(&operator) {
172                    return match operator {
173                        "&&" if lhs == TypedValue::<T>::Bool(false) => Ok(TypedValue::Bool(false)),
174                        "||" if lhs == TypedValue::<T>::Bool(true) => Ok(TypedValue::Bool(true)),
175                        _ => self.visit_right_hand_expression(&operands[1]),
176                    };
177                }
178
179                // "Normal" binary operators
180                let rhs = self.visit_right_hand_expression(&operands[1])?;
181
182                // Structs and references support only structural equality. Handle
183                // it here since the scalar operator dispatch cannot.
184                if matches!(operator, "==" | "!=") {
185                    let is_aggregate =
186                        |v: &TypedValue<T>| matches!(v, TypedValue::Struct(_) | TypedValue::Ref(_));
187                    if is_aggregate(&lhs) || is_aggregate(&rhs) {
188                        let equal = lhs == rhs;
189                        return Ok(TypedValue::Bool(if operator == "==" {
190                            equal
191                        } else {
192                            !equal
193                        }));
194                    }
195                }
196
197                let type_context = self.context.type_context();
198                let result = match operator {
199                    "+" => TypedValue::<T>::add(type_context, lhs, rhs),
200                    "-" => TypedValue::<T>::subtract(type_context, lhs, rhs),
201                    "*" => TypedValue::<T>::multiply(type_context, lhs, rhs),
202                    "/" => TypedValue::<T>::divide(type_context, lhs, rhs),
203                    "%" => TypedValue::<T>::modulo(type_context, lhs, rhs),
204                    "<" => TypedValue::<T>::less_than(type_context, lhs, rhs),
205                    ">" => TypedValue::<T>::less_than(type_context, rhs, lhs),
206                    "<=" => TypedValue::<T>::less_than_or_equal(type_context, lhs, rhs),
207                    ">=" => TypedValue::<T>::less_than_or_equal(type_context, rhs, lhs),
208                    "==" => TypedValue::<T>::equals(type_context, lhs, rhs),
209                    "!=" => TypedValue::<T>::not_equals(type_context, lhs, rhs),
210                    "|" => TypedValue::<T>::bitwise_or(type_context, lhs, rhs),
211                    "^" => TypedValue::<T>::bitwise_xor(type_context, lhs, rhs),
212                    "&" => TypedValue::<T>::bitwise_and(type_context, lhs, rhs),
213                    "<<" => TypedValue::<T>::shift_left(type_context, lhs, rhs),
214                    ">>" => TypedValue::<T>::shift_right(type_context, lhs, rhs),
215
216                    other => {
217                        return Err(EvalError {
218                            message: format!("Unknown binary operator: {other}").into_boxed_str(),
219                            location: expression.location(),
220                        });
221                    }
222                };
223
224                match result {
225                    Ok(r) => r,
226                    Err(error) => {
227                        return Err(EvalError {
228                            message: format!("Failed to evaluate expression: {error}")
229                                .into_boxed_str(),
230                            location: expression.location(),
231                        });
232                    }
233                }
234            }
235            RightHandExpression::FunctionCall { name, arguments } => {
236                let function_name = name.source(self.source);
237                let mut args = Vec::with_capacity(arguments.len());
238                for arg in arguments {
239                    args.push(self.visit_right_hand_expression(arg)?);
240                }
241
242                match self.context.call_function(function_name, &args) {
243                    Ok(result) => result,
244                    Err(FunctionCallError::IncorrectArgumentCount { expected }) => {
245                        return Err(EvalError {
246                            message: format!(
247                                "{function_name} takes {expected} arguments, {} given",
248                                args.len()
249                            )
250                            .into_boxed_str(),
251                            location: expression.location(),
252                        });
253                    }
254                    Err(FunctionCallError::IncorrectArgumentType { idx, expected }) => {
255                        return Err(EvalError {
256                            message: format!(
257                                "{function_name} expects argument {idx} to be {expected}, got {}",
258                                args[idx].type_of()
259                            )
260                            .into_boxed_str(),
261                            location: arguments[idx].location(),
262                        });
263                    }
264                    Err(FunctionCallError::FunctionNotFound) => {
265                        return Err(EvalError {
266                            message: format!("Function {function_name} is not found")
267                                .into_boxed_str(),
268                            location: expression.location(),
269                        });
270                    }
271                    Err(FunctionCallError::Other(error)) => {
272                        return Err(EvalError {
273                            message: format!("Failed to call {function_name}: {error}")
274                                .into_boxed_str(),
275                            location: expression.location(),
276                        });
277                    }
278                }
279            }
280            RightHandExpression::FieldAccess { base, field, .. } => {
281                // Reading a field: evaluate the base, auto-dereferencing through a
282                // reference, then clone the named field out of the struct.
283                let base_value = self.visit_right_hand_expression(base)?;
284                let base_value = match base_value {
285                    TypedValue::Ref(reference) => self
286                        .context
287                        .load_place(reference.place())
288                        .map_err(|e| EvalError {
289                            message: e,
290                            location: base.location(),
291                        })?,
292                    other => other,
293                };
294
295                let field_name = field.source(self.source);
296                let TypedValue::Struct(structure) = base_value else {
297                    return Err(EvalError {
298                        message: format!(
299                            "Cannot access field `{field_name}` of {}",
300                            base_value.type_of()
301                        )
302                        .into_boxed_str(),
303                        location: base.location(),
304                    });
305                };
306
307                match structure.fields().get(field_name) {
308                    Some(value) => value.clone(),
309                    None => {
310                        return Err(EvalError {
311                            message: format!(
312                                "Struct `{}` has no field `{field_name}`",
313                                structure.name()
314                            )
315                            .into_boxed_str(),
316                            location: field.location,
317                        });
318                    }
319                }
320            }
321            RightHandExpression::StructLiteral { name, fields, .. } => {
322                self.visit_struct_literal(name, fields, expression.location())?
323            }
324        };
325
326        Ok(result)
327    }
328
329    /// Constructs a struct value from a struct literal, validating field names
330    /// against the definition and coercing each field to its declared type.
331    fn visit_struct_literal(
332        &mut self,
333        name: &lexer::Token,
334        fields: &[somni_parser::ast::StructLiteralField<T::Parser>],
335        location: Location,
336    ) -> Result<TypedValue<T>, EvalError> {
337        let struct_name = name.source(self.source);
338        let schema = self
339            .context
340            .struct_fields(struct_name)
341            .ok_or_else(|| EvalError {
342                message: format!("Unknown struct `{struct_name}`").into_boxed_str(),
343                location: name.location,
344            })?;
345
346        // Evaluate each provided field, rejecting unknown or duplicate fields.
347        let mut provided: IndexMap<Box<str>, (TypedValue<T>, Location)> = IndexMap::new();
348        for field in fields {
349            let field_name = field.name.source(self.source);
350            if !schema.iter().any(|(n, _)| &**n == field_name) {
351                return Err(EvalError {
352                    message: format!("Struct `{struct_name}` has no field `{field_name}`")
353                        .into_boxed_str(),
354                    location: field.name.location,
355                });
356            }
357            let value = self.visit_right_hand_expression(&field.value)?;
358            if provided
359                .insert(Box::from(field_name), (value, field.name.location))
360                .is_some()
361            {
362                return Err(EvalError {
363                    message: format!("Duplicate field `{field_name}` in struct `{struct_name}`")
364                        .into_boxed_str(),
365                    location: field.name.location,
366                });
367            }
368        }
369
370        // Build the fields in declaration order, coercing to each declared type.
371        let mut out = IndexMap::with_capacity(schema.len());
372        for (field_name, field_type) in &schema {
373            let (value, field_location) =
374                provided.swap_remove(field_name).ok_or_else(|| EvalError {
375                    message: format!("Missing field `{field_name}` in struct `{struct_name}`")
376                        .into_boxed_str(),
377                    location,
378                })?;
379            let coerced = self.typecheck_named(value, field_type, false, field_location)?;
380            out.insert(field_name.clone(), coerced);
381        }
382
383        Ok(TypedValue::Struct(SomniStruct::new(
384            Box::from(struct_name),
385            out,
386        )))
387    }
388
389    /// Resolves the [`Place`] denoted by a right-hand expression used as the
390    /// operand of `&`. Supports variables, field accesses (auto-dereferencing
391    /// through references), and explicit dereferences.
392    fn resolve_place_rhs(
393        &mut self,
394        expr: &RightHandExpression<T::Parser>,
395    ) -> Result<Place, EvalError> {
396        match expr {
397            RightHandExpression::Variable { variable } => {
398                let name = variable.source(self.source);
399                self.context
400                    .place_of_variable(name)
401                    .map_err(|message| EvalError {
402                        message,
403                        location: variable.location,
404                    })
405            }
406            RightHandExpression::UnaryOperator { name, operand }
407                if name.source(self.source) == "*" =>
408            {
409                let value = self.visit_right_hand_expression(operand)?;
410                match value {
411                    TypedValue::Ref(reference) => Ok(reference.into_place()),
412                    other => Err(EvalError {
413                        message: format!("Cannot dereference {}", other.type_of()).into_boxed_str(),
414                        location: operand.location(),
415                    }),
416                }
417            }
418            RightHandExpression::FieldAccess { base, field, .. } => {
419                let base_place = self.resolve_place_rhs(base)?;
420                let container = self.container_place(base_place, base.location())?;
421                self.append_field(container, field)
422            }
423            other => Err(EvalError {
424                message: String::from("Cannot take the address of this expression")
425                    .into_boxed_str(),
426                location: other.location(),
427            }),
428        }
429    }
430
431    /// Resolves the [`Place`] denoted by a left-hand (assignment) expression.
432    fn resolve_place_lhs(&mut self, lhs: &LeftHandExpression) -> Result<Place, EvalError> {
433        match lhs {
434            LeftHandExpression::Name { variable } => {
435                let name = variable.source(self.source);
436                self.context
437                    .place_of_variable(name)
438                    .map_err(|message| EvalError {
439                        message,
440                        location: variable.location,
441                    })
442            }
443            LeftHandExpression::Deref { name, .. } => {
444                let value = self.visit_variable(name)?;
445                match value {
446                    TypedValue::Ref(reference) => Ok(reference.into_place()),
447                    other => Err(EvalError {
448                        message: format!("Cannot dereference {}", other.type_of()).into_boxed_str(),
449                        location: name.location,
450                    }),
451                }
452            }
453            LeftHandExpression::Field { base, field, .. } => {
454                let base_place = self.resolve_place_lhs(base)?;
455                let container = self.container_place(base_place, base.location())?;
456                self.append_field(container, field)
457            }
458        }
459    }
460
461    /// Given the place of a field-access base, returns the place of the aggregate
462    /// to index into: if the base holds a reference, its target (auto-deref);
463    /// otherwise the base place itself.
464    fn container_place(
465        &mut self,
466        base_place: Place,
467        base_location: Location,
468    ) -> Result<Place, EvalError> {
469        let base_value = self
470            .context
471            .load_place(&base_place)
472            .map_err(|message| EvalError {
473                message,
474                location: base_location,
475            })?;
476        Ok(match base_value {
477            TypedValue::Ref(reference) => reference.into_place(),
478            _ => base_place,
479        })
480    }
481
482    /// Appends a field to a container place, validating that the container is a
483    /// struct that has the named field.
484    fn append_field(&mut self, container: Place, field: &lexer::Token) -> Result<Place, EvalError> {
485        let container_value = self
486            .context
487            .load_place(&container)
488            .map_err(|message| EvalError {
489                message,
490                location: field.location,
491            })?;
492        let field_name = field.source(self.source);
493        let TypedValue::Struct(structure) = &container_value else {
494            return Err(EvalError {
495                message: format!(
496                    "Cannot access field `{field_name}` of {}",
497                    container_value.type_of()
498                )
499                .into_boxed_str(),
500                location: field.location,
501            });
502        };
503        if !structure.fields().contains_key(field_name) {
504            return Err(EvalError {
505                message: format!("Struct `{}` has no field `{field_name}`", structure.name())
506                    .into_boxed_str(),
507                location: field.location,
508            });
509        }
510
511        let mut path = container.path.into_vec();
512        path.push(Box::from(field_name));
513        Ok(Place {
514            root: container.root,
515            path: path.into_boxed_slice(),
516        })
517    }
518
519    fn typecheck_with_hint(
520        &self,
521        value: TypedValue<T>,
522        hint: Option<TypeHint>,
523    ) -> Result<TypedValue<T>, EvalError> {
524        let Some(hint) = hint else {
525            // No hint
526            return Ok(value);
527        };
528
529        self.typecheck_named(
530            value,
531            hint.type_name.source(self.source),
532            false,
533            hint.type_name.location,
534        )
535    }
536
537    /// Type-checks a value against a named type, which may be a scalar, a struct
538    /// name, or (when `is_ref`) a reference to one of those.
539    fn typecheck_named(
540        &self,
541        value: TypedValue<T>,
542        type_name: &str,
543        is_ref: bool,
544        location: Location,
545    ) -> Result<TypedValue<T>, EvalError> {
546        if is_ref {
547            // Reference-typed position: accept any reference. The pointee kind is
548            // checked loosely and struct identity is checked at runtime on use.
549            return match &value {
550                TypedValue::Ref(_) => Ok(value),
551                other => Err(EvalError {
552                    message: format!("Expected &{type_name}, got {}", other.type_of())
553                        .into_boxed_str(),
554                    location,
555                }),
556            };
557        }
558
559        match Type::from_name(type_name) {
560            Ok(ty) => self.typecheck(value, ty, location),
561            Err(_) => match &value {
562                // A struct-typed position: the value must be a struct with a
563                // matching name (loose, runtime-checked identity).
564                TypedValue::Struct(structure) if structure.name() == type_name => Ok(value),
565                TypedValue::Struct(structure) => Err(EvalError {
566                    message: format!(
567                        "Expected struct `{type_name}`, got struct `{}`",
568                        structure.name()
569                    )
570                    .into_boxed_str(),
571                    location,
572                }),
573                other => {
574                    if self.context.struct_fields(type_name).is_some() {
575                        Err(EvalError {
576                            message: format!(
577                                "Expected struct `{type_name}`, got {}",
578                                other.type_of()
579                            )
580                            .into_boxed_str(),
581                            location,
582                        })
583                    } else {
584                        Err(EvalError {
585                            message: format!("Unknown type `{type_name}`").into_boxed_str(),
586                            location,
587                        })
588                    }
589                }
590            },
591        }
592    }
593
594    fn typecheck(
595        &self,
596        value: TypedValue<T>,
597        hint: Type,
598        location: Location,
599    ) -> Result<TypedValue<T>, EvalError> {
600        match (value, hint) {
601            (value, hint) if value.type_of() == hint => Ok(value),
602            (TypedValue::MaybeSignedInt(val), Type::Int) => Ok(TypedValue::Int(val)),
603            (TypedValue::MaybeSignedInt(val), Type::SignedInt) => Ok(TypedValue::<T>::SignedInt(
604                T::to_signed(val).map_err(|_| EvalError {
605                    message: format!("Failed to cast {val:?} to signed int").into_boxed_str(),
606                    location,
607                })?,
608            )),
609            (value, hint) => Err(EvalError {
610                message: format!("Expected {hint}, got {}", value.type_of()).into_boxed_str(),
611                location,
612            }),
613        }
614    }
615
616    /// Evaluates a function with the given arguments.
617    pub fn visit_function(
618        &mut self,
619        function: &Function<T::Parser>,
620        args: &[TypedValue<T>],
621    ) -> Result<TypedValue<T>, EvalError> {
622        for (arg, arg_value) in function.arguments.iter().zip(args.iter()) {
623            let arg_name = arg.name.source(self.source);
624
625            let arg_value = self.typecheck_named(
626                arg_value.clone(),
627                arg.arg_type.type_name.source(self.source),
628                arg.reference_token.is_some(),
629                arg.arg_type.type_name.location,
630            )?;
631
632            self.context.declare(arg_name, arg_value);
633        }
634
635        let retval = match self.visit_body(&function.body)? {
636            StatementResult::Return(typed_value) | StatementResult::ImplicitReturn(typed_value) => {
637                typed_value
638            }
639            StatementResult::EndOfBody => TypedValue::Void,
640            StatementResult::LoopBreak | StatementResult::LoopContinue => todo!(),
641        };
642
643        let retval =
644            self.typecheck_with_hint(retval, function.return_decl.as_ref().map(|d| d.return_type))?;
645
646        Ok(retval)
647    }
648
649    fn visit_body(&mut self, body: &Body<T::Parser>) -> Result<StatementResult<T>, EvalError> {
650        self.context.open_scope();
651
652        let mut body_result = StatementResult::EndOfBody;
653        for statement in body.statements.iter() {
654            if let Some(retval) = self.visit_statement(statement)? {
655                body_result = retval;
656                match body_result {
657                    StatementResult::ImplicitReturn(_) => {}
658                    _ => break,
659                }
660            } else {
661                // Reset result if we have statements after implicit returns.
662                body_result = StatementResult::EndOfBody;
663            }
664        }
665
666        self.context.close_scope();
667        Ok(body_result)
668    }
669
670    fn visit_statement(
671        &mut self,
672        statement: &Statement<T::Parser>,
673    ) -> Result<Option<StatementResult<T>>, EvalError> {
674        match statement {
675            Statement::Return(return_with_value) => {
676                return self
677                    .visit_right_hand_expression(&return_with_value.expression)
678                    .map(|rv| Some(StatementResult::Return(rv)));
679            }
680            Statement::ImplicitReturn(expression) => {
681                return self
682                    .visit_right_hand_expression(expression)
683                    .map(|rv| Some(StatementResult::ImplicitReturn(rv)));
684            }
685            Statement::EmptyReturn(_) => {
686                return Ok(Some(StatementResult::Return(TypedValue::Void)));
687            }
688            Statement::If(if_statement) => return self.visit_if(if_statement),
689            Statement::Loop(loop_statement) => return self.visit_loop(loop_statement),
690            Statement::For(for_statement) => return self.visit_for(for_statement),
691            Statement::Break(_) => return Ok(Some(StatementResult::LoopBreak)),
692            Statement::Continue(_) => return Ok(Some(StatementResult::LoopContinue)),
693            Statement::Scope(body) => {
694                return self.visit_body(body).map(|r| match r {
695                    StatementResult::EndOfBody => None,
696                    r => Some(r),
697                });
698            }
699            Statement::VariableDefinition(variable_definition) => {
700                self.visit_declaration(variable_definition)?;
701            }
702            Statement::Expression { expression, .. } => {
703                self.visit_expression(expression)?;
704            }
705        }
706
707        Ok(None)
708    }
709
710    fn visit_declaration(&mut self, decl: &VariableDefinition<T::Parser>) -> Result<(), EvalError> {
711        let name = decl.identifier.source(self.source);
712        let value = self.visit_right_hand_expression(&decl.initializer)?;
713
714        let value = self.typecheck_with_hint(value, decl.type_token)?;
715
716        self.context.declare(name, value);
717
718        Ok(())
719    }
720
721    fn visit_if(
722        &mut self,
723        if_statement: &If<T::Parser>,
724    ) -> Result<Option<StatementResult<T>>, EvalError> {
725        let condition = self.visit_right_hand_expression(&if_statement.condition)?;
726
727        let condition = self.typecheck(condition, Type::Bool, if_statement.condition.location())?;
728
729        let body = if condition == TypedValue::Bool(true) {
730            &if_statement.body
731        } else if let Some(ref else_branch) = if_statement.else_branch {
732            &else_branch.else_body
733        } else {
734            // Condition is false, but there is no `else`
735            return Ok(None);
736        };
737
738        let retval = match self.visit_body(body)? {
739            StatementResult::EndOfBody => None,
740            other => Some(other),
741        };
742        Ok(retval)
743    }
744
745    fn visit_loop(
746        &mut self,
747        loop_statement: &Loop<T::Parser>,
748    ) -> Result<Option<StatementResult<T>>, EvalError> {
749        loop {
750            match self.visit_body(&loop_statement.body)? {
751                ret @ StatementResult::Return(_) => return Ok(Some(ret)),
752                StatementResult::LoopBreak => return Ok(None),
753                StatementResult::LoopContinue
754                | StatementResult::EndOfBody
755                | StatementResult::ImplicitReturn(_) => {}
756            }
757        }
758    }
759
760    fn visit_for(
761        &mut self,
762        for_statement: &For<T::Parser>,
763    ) -> Result<Option<StatementResult<T>>, EvalError> {
764        // Evaluate the iterable. It must produce an iterator.
765        let iterable = self.visit_right_hand_expression(&for_statement.iterable)?;
766        let TypedValue::Iter(iter) = iterable else {
767            return Err(EvalError {
768                message: format!("Expected an iterator, got {}", iterable.type_of())
769                    .into_boxed_str(),
770                location: for_statement.iterable.location(),
771            });
772        };
773
774        // The loop variable's declared type, if annotated. Values produced by the
775        // iterator are checked against it, matching the VM's runtime behavior. When
776        // the annotation is omitted, values are bound as-is.
777        let elem_ty = match for_statement.var_type.as_ref() {
778            Some(var_type) => Some(
779                Type::from_name(var_type.type_name.source(self.source)).map_err(|message| {
780                    EvalError {
781                        message,
782                        location: var_type.type_name.location,
783                    }
784                })?,
785            ),
786            None => None,
787        };
788        let var_name = for_statement.variable.source(self.source);
789
790        loop {
791            let Some(value) = self.context.type_context().iter_next(&iter) else {
792                return Ok(None);
793            };
794            // A mismatch is about the declared element type, so point the error at
795            // the type annotation rather than the loop variable name. Without an
796            // annotation the value is bound as-is.
797            let value = match elem_ty {
798                Some(elem_ty) => self.typecheck(value, elem_ty, for_statement.variable.location)?,
799                None => value,
800            };
801
802            // Bind the loop variable in a fresh scope for this iteration.
803            self.context.open_scope();
804            self.context.declare(var_name, value);
805
806            let body_result = self.visit_body(&for_statement.body);
807            self.context.close_scope();
808
809            match body_result? {
810                ret @ StatementResult::Return(_) => return Ok(Some(ret)),
811                StatementResult::LoopBreak => return Ok(None),
812                StatementResult::LoopContinue
813                | StatementResult::EndOfBody
814                | StatementResult::ImplicitReturn(_) => {}
815            }
816        }
817    }
818}
819
820enum StatementResult<T: TypeSet> {
821    Return(TypedValue<T>),
822    ImplicitReturn(TypedValue<T>),
823    LoopBreak,
824    LoopContinue,
825    EndOfBody,
826}