Skip to main content

somni_expr/
lib.rs

1//! # Somni expression evaluation Library
2//!
3//! This crate implements the expression evaluation subset of the Somnni language and VM. The crate
4//! can be used by itself, to evaluate simple expressions or even to run complete Somni programs, although
5//! slower than the Somni VM would.
6//!
7//! ## Overview
8//!
9//! Expressions are a subset of the Somni language:
10//!
11//! The expression language includes:
12//!
13//! - Literals: integers, floats, booleans, strings.
14//! - Variables
15//! - A basic set of operators
16//! - Function calls
17//!
18//! The expression language does not include:
19//!
20//! - Declaring new variables. You can assign to existing variables.
21//! - Control flow (if, loops, etc.)
22//! - Complex data structures (arrays, objects, etc.)
23//! - Defining functions and variables (these are provided by the context)
24//!
25//! ## Operators
26//!
27//! The following binary operators are supported, in order of precedence:
28//!
29//! - `=`: assign a value to an existing variable
30//! - `||`: logical OR, short-circuiting
31//! - `&&`: logical AND, short-circuiting
32//! - `<`, `<=`, `>`, `>=`, `==`, `!=`: comparison operators
33//! - `|`: bitwise OR
34//! - `^`: bitwise XOR
35//! - `&`: bitwise AND
36//! - `<<`, `>>`: bitwise shift
37//! - `+`, `-`: addition and subtraction
38//! - `*`, `/`: multiplication and division
39//!
40//! Unary operators include:
41//! - `&`: taking the address of a variable
42//! - `*`: dereferencing an address to a variable
43//! - `!`: logical NOT
44//! - `-`: negation
45//!
46//! For the full specification of the grammar, see the [`parser`] module's documentation.
47//!
48//! ## Numeric types
49//!
50//! The Somni language supports three numeric types:
51//!
52//! - Integers
53//! - Signed integers
54//! - Floats
55//!
56//! By default, the library uses the [`DefaultTypeSet`], which uses `u64`, `i64`, and `f64` for
57//! these types. You can use other type sets like [`TypeSet32`] or [`TypeSet128`] to use
58//! 32-bit or 128-bit integers and floats. You need to specify the type set when creating
59//! the context.
60//!
61//! Numeric integer literals can be either signed or unsigned integers. Their type is inferred from the usage.
62//!
63//! ## Usage
64//!
65//! To evaluate an expression, you need to create a [`Context`] first. You can assign
66//! variables and define functions in this context, and then you can use this context
67//! to evaluate expressions.
68//!
69//! ```rust
70//! use somni_expr::Context;
71//!
72//! let mut context = Context::new();
73//!
74//! // Define a variable
75//! context.add_variable::<u64>("x", 42);
76//! context.add_function("add_one", |x: u64| { x + 1 });
77//! context.add_function("floor", |x: f64| { x.floor() as u64 });
78//!
79//! // Evaluate an expression - we expect it to evaluate
80//! // to a number, which is u64 in the default type set.
81//! let result = context.evaluate::<u64>("add_one(x + floor(1.2))");
82//!
83//! assert_eq!(result, Ok(44));
84//! ```
85//!
86//! The context may also include a complete Somni program. The program may use the entirety
87//! of the Somni language, not just the expression language.
88//!
89//! ```rust
90//! use somni_expr::Context;
91//!
92//! let mut context = Context::parse("fn double(x: int) -> int { return x * 2; }").unwrap();
93//!
94//! // Evaluate an expression by calling the function defined by the program:
95//! let result = context.evaluate::<u64>("double(4)");
96//!
97//! assert_eq!(result, Ok(8));
98//! ```
99#![warn(missing_docs)]
100
101macro_rules! for_each {
102    // Any parenthesized set of choices, allows multiple matchers in the pattern
103    ($(($pattern:tt) in [$( ($($choice:tt)*) ),*] => $code:tt;)*) => {
104        $(
105            macro_rules! inner { $pattern => $code; }
106
107            $(
108                inner!( $($choice)* );
109            )*
110        )*
111    };
112    // Single type, single matcher
113    ($($pattern:tt in [$($choice:ty),*] => $code:tt;)*) => {
114        $(
115            macro_rules! inner { $pattern => $code; }
116
117            $(
118                inner!($choice);
119            )*
120        )*
121    };
122}
123
124pub mod error;
125pub mod function;
126pub mod iter;
127pub mod value;
128mod visitor;
129
130pub use function::{DynFunction, FunctionCallError};
131pub use iter::{SomniIterator, WithIterator};
132pub use value::{Place, Reference, SomniStruct, TypedValue};
133pub use visitor::ExpressionVisitor;
134
135/// Re-exported for use by the [`somni_struct!`] macro. Not a stable API.
136#[doc(hidden)]
137pub use indexmap;
138
139/// Constructs a [`SomniStruct`] value from Rust.
140///
141/// Field values are converted eagerly through the given type context (`&mut T`,
142/// obtainable via [`Context::type_context`]), so string fields are interned and
143/// nested structs may be built with nested invocations.
144///
145/// ```
146/// use somni_expr::{somni_struct, Context, ExprContext, TypedValue};
147///
148/// let mut ctx = Context::new();
149/// let tc = ctx.type_context();
150/// let point = somni_struct!(tc, Point { x: 1u64, y: 2u64 });
151/// assert_eq!(point.name(), "Point");
152/// assert_eq!(point.fields()["x"], TypedValue::Int(1));
153/// ```
154#[macro_export]
155macro_rules! somni_struct {
156    ($ctx:ident, $name:ident { $($field:ident : $value:expr),* $(,)? }) => {{
157        let $ctx: &mut _ = $ctx;
158        let mut fields = $crate::indexmap::IndexMap::new();
159        $(
160            let value = $crate::value::LoadStore::store(&$value, $ctx);
161            fields.insert(::std::boxed::Box::<str>::from(stringify!($field)), value);
162        )*
163        $crate::SomniStruct::new(
164            ::std::boxed::Box::<str>::from(stringify!($name)),
165            fields,
166        )
167    }};
168}
169
170use std::{
171    cell::RefCell,
172    collections::HashMap,
173    fmt::{Debug, Display},
174    rc::Rc,
175};
176
177use somni_parser::{
178    Location,
179    ast::{self, Expression, Function, Item, Program},
180    parser::{self, TypeSet as ParserTypeSet, parse},
181};
182
183use crate::{
184    error::MarkInSource,
185    function::ExprFn,
186    value::{LoadOwned, LoadStore, ValueType},
187};
188
189pub use somni_parser::parser::{DefaultTypeSet, TypeSet32, TypeSet128};
190
191/// Defines the backing types for Somni types.
192///
193/// The [`LoadStore`] and [`LoadOwned`] traits can be used to convert between Rust and Somni types.
194pub trait TypeSet: Sized + Default + Debug + 'static {
195    /// The typeset that will be used to parse source code.
196    type Parser: ParserTypeSet<Integer = Self::Integer, Float = Self::Float>;
197
198    /// The type of unsigned integers in this type set.
199    type Integer: Copy + ValueType<NegateOutput: LoadStore<Self>> + LoadStore<Self>;
200
201    /// The type of signed integers in this type set.
202    type SignedInteger: Copy + ValueType<NegateOutput: LoadStore<Self>> + LoadStore<Self>;
203
204    /// The type of floating point numbers in this type set.
205    type Float: Copy + ValueType<NegateOutput: LoadStore<Self>> + LoadStore<Self>;
206
207    /// The type of a string in this type set.
208    type String: ValueType<NegateOutput: LoadStore<Self>> + LoadStore<Self>;
209
210    /// The type of an iterator value in this type set.
211    ///
212    /// This is the payload carried directly by [`TypedValue::Iter`]. Type sets that
213    /// do not support iteration use the uninhabited [`NoIterator`], making it
214    /// impossible to construct an iterator value.
215    type Iterator: Clone + PartialEq + Debug;
216
217    /// Converts an unsigned integer into a signed integer.
218    fn to_signed(v: Self::Integer) -> Result<Self::SignedInteger, OperatorError>;
219
220    /// Converts an unsigned integer into a Rust usize.
221    fn to_usize(v: Self::Integer) -> Result<usize, OperatorError>;
222
223    /// Converts the given Rust usize to an integer.
224    fn int_from_usize(v: usize) -> Self::Integer;
225
226    /// Loads a string.
227    fn load_string<'s>(&'s self, str: &'s Self::String) -> &'s str;
228
229    /// Stores a string.
230    fn store_string(&mut self, str: &str) -> Self::String;
231
232    /// Returns whether the given iterator can yield another value.
233    fn iter_has_next(&self, iter: &Self::Iterator) -> bool;
234
235    /// Advances the given iterator, returning its next value, or `None` if the
236    /// iterator is exhausted.
237    fn iter_next(&self, iter: &Self::Iterator) -> Option<TypedValue<Self>>;
238}
239
240/// The iterator type used by type sets that do not support iteration.
241///
242/// This type is uninhabited, so such type sets can never construct a
243/// [`TypedValue::Iter`] value.
244#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
245pub enum NoIterator {}
246
247for_each! {
248    (($name:ident, $signed:ty)) in [(DefaultTypeSet, i64), (TypeSet32, i32), (TypeSet128, i128)] => {
249        impl TypeSet for $name {
250            type Parser = Self;
251
252            type Integer = <Self::Parser as ParserTypeSet>::Integer;
253            type SignedInteger = $signed;
254            type Float = <Self::Parser as ParserTypeSet>::Float;
255            type String = Box<str>;
256            type Iterator = NoIterator;
257
258            fn to_signed(v: Self::Integer) -> Result<Self::SignedInteger, OperatorError> {
259                <$signed>::try_from(v).map_err(|_| OperatorError::RuntimeError)
260            }
261
262            fn to_usize(v: Self::Integer) -> Result<usize, OperatorError> {
263                usize::try_from(v).map_err(|_| OperatorError::RuntimeError)
264            }
265
266            fn int_from_usize(v: usize) -> Self::Integer {
267                Self::Integer::try_from(v).unwrap()
268            }
269
270            fn load_string<'s>(&'s self, str: &'s Self::String) -> &'s str {
271                str
272            }
273
274            fn store_string(&mut self, str: &str) -> Self::String {
275                str.to_string().into_boxed_str()
276            }
277
278            fn iter_has_next(&self, iter: &Self::Iterator) -> bool {
279                match *iter {}
280            }
281
282            fn iter_next(&self, iter: &Self::Iterator) -> Option<TypedValue<Self>> {
283                match *iter {}
284            }
285        }
286    };
287}
288
289/// Represents an error that can occur during operator evaluation.
290#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
291pub enum OperatorError {
292    /// A type error occurred.
293    TypeError,
294    /// A runtime error occurred.
295    RuntimeError,
296}
297
298impl Display for OperatorError {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        let message = match self {
301            OperatorError::TypeError => "Type error",
302            OperatorError::RuntimeError => "Runtime error",
303        };
304
305        f.write_str(message)
306    }
307}
308
309macro_rules! dispatch_binary {
310    ($method:ident) => {
311        pub(crate) fn $method(ctx: &mut T, lhs: Self, rhs: Self) -> Result<Self, OperatorError> {
312            let result = match (lhs, rhs) {
313                (Self::Bool(value), Self::Bool(other)) => {
314                    ValueType::$method(value, other)?.store(ctx)
315                }
316                (Self::Int(value), Self::Int(other)) => {
317                    ValueType::$method(value, other)?.store(ctx)
318                }
319                (Self::SignedInt(value), Self::SignedInt(other)) => {
320                    ValueType::$method(value, other)?.store(ctx)
321                }
322                (Self::MaybeSignedInt(value), Self::MaybeSignedInt(other)) => {
323                    match ValueType::$method(value, other)?.store(ctx) {
324                        Self::Int(v) => Self::MaybeSignedInt(v),
325                        other => other,
326                    }
327                }
328                (Self::Float(value), Self::Float(other)) => {
329                    ValueType::$method(value, other)?.store(ctx)
330                }
331                (Self::String(value), Self::String(other)) => {
332                    ValueType::$method(value, other)?.store(ctx)
333                }
334                (Self::Int(value), Self::MaybeSignedInt(other)) => {
335                    ValueType::$method(value, other)?.store(ctx)
336                }
337                (Self::MaybeSignedInt(value), Self::Int(other)) => {
338                    ValueType::$method(value, other)?.store(ctx)
339                }
340                (Self::SignedInt(value), Self::MaybeSignedInt(other)) => {
341                    ValueType::$method(value, T::to_signed(other)?)?.store(ctx)
342                }
343                (Self::MaybeSignedInt(value), Self::SignedInt(other)) => {
344                    ValueType::$method(T::to_signed(value)?, other)?.store(ctx)
345                }
346                _ => return Err(OperatorError::TypeError),
347            };
348
349            Ok(result)
350        }
351    };
352}
353
354macro_rules! dispatch_unary {
355    ($method:ident) => {
356        pub(crate) fn $method(ctx: &mut T, operand: Self) -> Result<Self, OperatorError> {
357            match operand {
358                Self::Bool(value) => Ok(ValueType::$method(value)?.store(ctx)),
359                Self::Int(value) | Self::MaybeSignedInt(value) => {
360                    Ok(ValueType::$method(value)?.store(ctx))
361                }
362                Self::SignedInt(value) => Ok(ValueType::$method(value)?.store(ctx)),
363                Self::Float(value) => Ok(ValueType::$method(value)?.store(ctx)),
364                Self::String(value) => Ok(ValueType::$method(value)?.store(ctx)),
365                _ => return Err(OperatorError::TypeError),
366            }
367        }
368    };
369}
370
371impl<T> TypedValue<T>
372where
373    T: TypeSet,
374{
375    dispatch_binary!(equals);
376    dispatch_binary!(less_than);
377    dispatch_binary!(less_than_or_equal);
378    dispatch_binary!(not_equals);
379    dispatch_binary!(bitwise_or);
380    dispatch_binary!(bitwise_xor);
381    dispatch_binary!(bitwise_and);
382    dispatch_binary!(shift_left);
383    dispatch_binary!(shift_right);
384    dispatch_binary!(add);
385    dispatch_binary!(subtract);
386    dispatch_binary!(multiply);
387    dispatch_binary!(divide);
388    dispatch_binary!(modulo);
389    dispatch_unary!(not);
390    dispatch_unary!(negate);
391}
392
393/// An expression context that provides the necessary environment for evaluating expressions.
394pub trait ExprContext<T = DefaultTypeSet>
395where
396    T: TypeSet,
397{
398    /// Returns a reference to the `TypeSet`.
399    fn type_context(&mut self) -> &mut T;
400
401    /// Attempts to load a variable from the context.
402    fn try_load_variable(&mut self, variable: &str) -> Option<TypedValue<T>>;
403
404    /// Declares a variable in the context.
405    fn declare(&mut self, variable: &str, value: TypedValue<T>);
406
407    /// Assigns a new value to a variable in the context.
408    fn assign_variable(&mut self, variable: &str, value: &TypedValue<T>) -> Result<(), Box<str>>;
409
410    /// Returns the [`Place`] naming a variable (the root of a reference).
411    fn place_of_variable(&mut self, variable: &str) -> Result<Place, Box<str>>;
412
413    /// Loads (a clone of) the value at the given place, descending its field path.
414    fn load_place(&mut self, place: &Place) -> Result<TypedValue<T>, Box<str>>;
415
416    /// Stores a value into the given place, descending its field path.
417    fn store_place(&mut self, place: &Place, value: &TypedValue<T>) -> Result<(), Box<str>>;
418
419    /// Returns the `(field name, field type name)` pairs of a struct definition,
420    /// in declaration order, or `None` if no such struct is defined.
421    ///
422    /// Used to validate and coerce struct literals and to type-check struct-typed
423    /// annotations.
424    fn struct_fields(&self, struct_name: &str) -> Option<Vec<(Box<str>, Box<str>)>>;
425
426    /// Opens a new scope in the current stack frame.
427    fn open_scope(&mut self);
428
429    /// Closes the last scope in the current stack frame.
430    fn close_scope(&mut self);
431
432    /// Calls a function in the context.
433    fn call_function(
434        &mut self,
435        function_name: &str,
436        args: &[TypedValue<T>],
437    ) -> Result<TypedValue<T>, FunctionCallError>;
438}
439
440/// An error that occurs during evaluation of an expression.
441#[derive(Clone, Debug, PartialEq)]
442pub struct EvalError {
443    /// The error message.
444    pub message: Box<str>,
445    /// The location in the source code where the error occurred.
446    pub location: Location,
447}
448
449impl Display for EvalError {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        write!(f, "Evaluation error: {}", self.message)
452    }
453}
454
455/// An error that occurs during evaluation.
456///
457/// Printing this error will show the error message and the location in the source code.
458///
459/// ```rust
460/// use somni_expr::{Context, TypeSet32};
461/// let mut ctx = Context::<TypeSet32>::new_with_types();
462///
463/// let error = ctx.evaluate::<u32>("true + 1").unwrap_err();
464///
465/// println!("{error:?}");
466///
467/// // Output:
468/// //
469/// // Evaluation error
470/// // ---> at line 1 column 1
471/// //   |
472/// // 1 | true + 1
473/// //   | ^^^^^^^^ Failed to evaluate expression: Type error
474/// ```
475#[derive(Clone, PartialEq)]
476pub struct ExpressionError<'s> {
477    error: EvalError,
478    source: &'s str,
479}
480
481impl ExpressionError<'_> {
482    /// Returns the inner [`EvalError`].
483    pub fn into_inner(self) -> EvalError {
484        self.error
485    }
486}
487
488impl Debug for ExpressionError<'_> {
489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490        let marked = MarkInSource(
491            self.source,
492            self.error.location,
493            "Evaluation error",
494            &self.error.message,
495        );
496        marked.fmt(f)
497    }
498}
499
500/// A type in the Somni language.
501#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
502pub enum Type {
503    /// Represents no value, used for e.g. functions that do not return a value.
504    Void,
505    /// Represents integer that may be signed or unsigned.
506    MaybeSignedInt,
507    /// Represents an unsigned integer.
508    Int,
509    /// Represents a signed integer.
510    SignedInt,
511    /// Represents a floating point number.
512    Float,
513    /// Represents a boolean value.
514    Bool,
515    /// Represents a string value.
516    String,
517    /// Represents an iterator handle. The element type is not part of the type;
518    /// it is checked at runtime when a value is produced.
519    Iter,
520    /// Represents a struct value. The struct's identity (name and fields) is not
521    /// part of the type; it is carried by the value and checked at runtime.
522    Struct,
523    /// Represents a reference. The pointee *kind* is static; a struct pointee's
524    /// identity is checked at runtime.
525    Ref(RefPointee),
526}
527
528/// The kind of value a reference points to.
529///
530/// References are single-level (there is no `&&T`), so a pointee is always a
531/// non-reference type. A `Struct` pointee carries no identity — which struct it
532/// is gets checked at runtime.
533#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
534pub enum RefPointee {
535    /// A reference to nothing (uncommon, but representable).
536    Void,
537    /// A reference to an unsigned integer.
538    Int,
539    /// A reference to a signed integer.
540    SignedInt,
541    /// A reference to a float.
542    Float,
543    /// A reference to a boolean.
544    Bool,
545    /// A reference to a string.
546    String,
547    /// A reference to an iterator handle.
548    Iter,
549    /// A reference to a struct (identity checked at runtime).
550    Struct,
551}
552
553impl RefPointee {
554    /// Derives the pointee kind for a reference to a value of the given type.
555    ///
556    /// Returns `None` for `Type::Ref` (references to references are unsupported).
557    /// `MaybeSignedInt` is treated as `Int`.
558    pub fn from_type(ty: Type) -> Option<Self> {
559        Some(match ty {
560            Type::Void => RefPointee::Void,
561            Type::Int | Type::MaybeSignedInt => RefPointee::Int,
562            Type::SignedInt => RefPointee::SignedInt,
563            Type::Float => RefPointee::Float,
564            Type::Bool => RefPointee::Bool,
565            Type::String => RefPointee::String,
566            Type::Iter => RefPointee::Iter,
567            Type::Struct => RefPointee::Struct,
568            Type::Ref(_) => return None,
569        })
570    }
571}
572
573impl Display for RefPointee {
574    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
575        match self {
576            RefPointee::Void => write!(f, "void"),
577            RefPointee::Int => write!(f, "int"),
578            RefPointee::SignedInt => write!(f, "signed"),
579            RefPointee::Float => write!(f, "float"),
580            RefPointee::Bool => write!(f, "bool"),
581            RefPointee::String => write!(f, "string"),
582            RefPointee::Iter => write!(f, "iter"),
583            RefPointee::Struct => write!(f, "struct"),
584        }
585    }
586}
587
588impl Type {
589    fn from_name(source: &str) -> Result<Self, Box<str>> {
590        match source {
591            "int" => Ok(Type::Int),
592            "signed" => Ok(Type::SignedInt),
593            "float" => Ok(Type::Float),
594            "bool" => Ok(Type::Bool),
595            "string" => Ok(Type::String),
596            "iter" => Ok(Type::Iter),
597            other => Err(format!("Unknown type `{other}`").into_boxed_str()),
598        }
599    }
600}
601
602impl Display for Type {
603    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
604        match self {
605            Type::Void => write!(f, "void"),
606            Type::MaybeSignedInt => write!(f, "{{int/signed}}"),
607            Type::Int => write!(f, "int"),
608            Type::SignedInt => write!(f, "signed"),
609            Type::Bool => write!(f, "bool"),
610            Type::String => write!(f, "string"),
611            Type::Float => write!(f, "float"),
612            Type::Iter => write!(f, "iter"),
613            Type::Struct => write!(f, "struct"),
614            Type::Ref(pointee) => write!(f, "&{pointee}"),
615        }
616    }
617}
618
619/// State of an unevaluated global.
620enum InitializerState {
621    /// Untouched. Contains the item index of the global
622    Unevaluated(usize),
623    /// The global is being evaluated. This state is used to detect cycles.
624    Evaluating,
625}
626
627struct StackFrame<T: TypeSet> {
628    start_addr: usize,
629    variables: Vec<TypedValue<T>>,
630    scopes: Vec<HashMap<String, usize>>,
631}
632
633impl<T: TypeSet> StackFrame<T> {
634    fn new() -> StackFrame<T> {
635        StackFrame {
636            start_addr: 0,
637            variables: vec![],
638            scopes: vec![HashMap::new()],
639        }
640    }
641
642    fn next_call_frame(&self) -> StackFrame<T> {
643        StackFrame {
644            start_addr: self.start_addr + self.variables.len(),
645            variables: vec![],
646            scopes: vec![HashMap::new()],
647        }
648    }
649
650    fn declare(&mut self, variable: &str, value: TypedValue<T>) -> usize {
651        let index = self.variables.len();
652        self.variables.push(value);
653        self.scopes
654            .last_mut()
655            .unwrap()
656            .insert(variable.to_string(), index);
657        index + self.start_addr
658    }
659
660    fn lookup_index(&self, name: &str) -> Option<usize> {
661        for scope in self.scopes.iter().rev() {
662            if let Some(idx) = scope.get(name) {
663                return Some(*idx);
664            }
665        }
666        None
667    }
668
669    fn store(&mut self, variable: &str, value: &TypedValue<T>) -> bool {
670        if let Some(idx) = self.lookup_index(variable) {
671            self.variables.get_mut(idx).unwrap().clone_from(value);
672            true
673        } else {
674            false
675        }
676    }
677
678    fn lookup_by_address(&mut self, address: usize) -> Result<&mut TypedValue<T>, Box<str>> {
679        self.variables
680            .get_mut(address - self.start_addr)
681            .ok_or_else(|| format!("Invalid address {address}").into_boxed_str())
682    }
683
684    fn lookup_by_name<'s>(&'s mut self, variable: &str) -> Option<(usize, &'s mut TypedValue<T>)> {
685        let index = self.lookup_index(variable)?;
686        let address = index + self.start_addr;
687
688        Some((address, self.variables.get_mut(index).unwrap()))
689    }
690
691    fn open_scope(&mut self) {
692        self.scopes.push(HashMap::new());
693    }
694
695    fn close_scope(&mut self) {
696        self.scopes.pop().unwrap();
697    }
698}
699
700struct ProgramData<'ctx, T: TypeSet> {
701    source: &'ctx str,
702    program: Program<T::Parser>,
703    program_functions: HashMap<&'ctx str, usize>,
704    // Struct definitions, indexed by name (item index into `program.items`).
705    program_structs: HashMap<&'ctx str, usize>,
706    // User-registered functions
707    functions: RefCell<HashMap<&'ctx str, ExprFn<'ctx, T>>>,
708}
709
710impl<'ctx> Default for Context<'ctx, DefaultTypeSet> {
711    fn default() -> Self {
712        Self::new()
713    }
714}
715
716/// The expression context, which holds variables, functions, and other state needed for evaluation.
717pub struct Context<'ctx, T = DefaultTypeSet>
718where
719    T: TypeSet,
720{
721    program: Rc<ProgramData<'ctx, T>>,
722    // Program state
723    // ----
724    /// Variable stack. Element 0 is the global scope.
725    stack: Vec<StackFrame<T>>,
726    // unevaluated globals
727    initializers: HashMap<&'ctx str, InitializerState>,
728    type_context: T,
729}
730
731impl<'ctx> Context<'ctx, DefaultTypeSet> {
732    /// Creates a new context with [default types][DefaultTypeSet].
733    pub fn new() -> Self {
734        Self::new_with_types()
735    }
736
737    /// Loads the given program into a new context with [default types][DefaultTypeSet].
738    pub fn parse(source: &'ctx str) -> Result<Self, ExpressionError<'ctx>> {
739        Self::parse_with_types(source)
740    }
741}
742
743const GLOBAL_VARIABLE: usize = usize::MAX - usize::MAX / 2;
744
745impl<'ctx, T> Context<'ctx, T>
746where
747    T: TypeSet,
748{
749    /// Creates a new context. The type set must be specified when using this function.
750    ///
751    /// ```rust
752    /// use somni_expr::{Context, TypeSet32};
753    /// let mut ctx = Context::<TypeSet32>::new_with_types();
754    /// ```
755    pub fn new_with_types() -> Self {
756        Self::new_from_program("", Program { items: vec![] })
757    }
758
759    /// Parses the given program into a new context. The type set must be specified when using this function.
760    ///
761    /// ```rust
762    /// use somni_expr::{Context, TypeSet32};
763    /// let mut ctx = Context::<TypeSet32>::parse_with_types("// program source comes here").unwrap();
764    /// ```
765    pub fn parse_with_types(source: &'ctx str) -> Result<Self, ExpressionError<'ctx>> {
766        let program = parse::<T::Parser>(source).map_err(|e| ExpressionError {
767            error: EvalError {
768                message: format!("Failed to parse program: {e}").into_boxed_str(),
769                location: e.location,
770            },
771            source,
772        })?;
773
774        Ok(Self::new_from_program(source, program))
775    }
776
777    /// Loads the given program into a new context.
778    pub fn new_from_program(source: &'ctx str, program: Program<T::Parser>) -> Self {
779        let mut program_functions = HashMap::new();
780        let mut program_structs = HashMap::new();
781        let mut initializers = HashMap::new();
782        // Extract data for O(1) function/initializer/struct lookup
783        for (idx, item) in program.items.iter().enumerate() {
784            match item {
785                ast::Item::Function(function) => {
786                    program_functions.insert(function.name.source(source), idx);
787                }
788                ast::Item::GlobalVariable(global_variable) => {
789                    initializers.insert(
790                        global_variable.identifier.source(source),
791                        InitializerState::Unevaluated(idx),
792                    );
793                }
794                ast::Item::Struct(struct_def) => {
795                    program_structs.insert(struct_def.name.source(source), idx);
796                }
797                ast::Item::ExternFunction(_) => {}
798            }
799        }
800        Self {
801            program: Rc::new(ProgramData {
802                source,
803                program,
804                program_functions,
805                program_structs,
806                functions: RefCell::new(HashMap::new()),
807            }),
808            stack: vec![StackFrame::new()],
809            type_context: T::default(),
810            initializers,
811        }
812    }
813
814    fn evaluate_any_function_impl(
815        &mut self,
816        function_name: &Function<T::Parser>,
817        args: &[TypedValue<T>],
818    ) -> Result<TypedValue<T>, EvalError> {
819        let source = self.program.clone().source;
820
821        let stack_frame = self
822            .stack
823            .last()
824            .expect("The global scope must always be present")
825            .next_call_frame();
826        self.stack.push(stack_frame);
827
828        let mut visitor = ExpressionVisitor::<Self, T> {
829            context: self,
830            source,
831            _marker: std::marker::PhantomData,
832        };
833
834        let result = visitor.visit_function(function_name, args);
835
836        self.stack.pop();
837
838        result
839    }
840
841    /// Parses and evaluates an expression and returns the result as a specific value type.
842    ///
843    /// This function will attempt to convert the result of the expression to the specified type `V`.
844    /// If the conversion fails, it will return an `ExpressionError`.
845    ///
846    /// ```rust
847    /// use somni_expr::{Context, TypedValue};
848    ///
849    /// let mut context = Context::new();
850    ///
851    /// assert_eq!(context.evaluate::<u64>("1 + 2"), Ok(3));
852    /// assert_eq!(context.evaluate::<TypedValue>("1 + 2"), Ok(TypedValue::Int(3)));
853    /// ```
854    pub fn evaluate<'s, V>(&'s mut self, source: &'s str) -> Result<V::Output, ExpressionError<'s>>
855    where
856        V: LoadOwned<T>,
857    {
858        let expression =
859            parser::parse_expression::<T::Parser>(source).map_err(|e| ExpressionError {
860                error: EvalError {
861                    message: format!("Parser error: {e}").into_boxed_str(),
862                    location: e.location,
863                },
864                source,
865            })?;
866
867        self.evaluate_parsed::<V>(source, &expression)
868    }
869
870    /// Evaluates a pre-parsed expression and returns the result as a specific value type.
871    ///
872    /// This function will attempt to convert the result of the expression to the specified type `V`.
873    /// If the conversion fails, it will return an `ExpressionError`.
874    ///
875    /// ```rust
876    /// use somni_expr::{Context, TypedValue};
877    ///
878    /// let mut context = Context::new();
879    ///
880    /// let source = "1 + 2";
881    /// let expr = somni_parser::parser::parse_expression(source).unwrap();
882    ///
883    /// assert_eq!(context.evaluate_parsed::<u64>(source, &expr), Ok(3));
884    /// assert_eq!(context.evaluate_parsed::<TypedValue>(source, &expr), Ok(TypedValue::Int(3)));
885    /// ```
886    pub fn evaluate_parsed<'s, V>(
887        &'s mut self,
888        source: &'s str,
889        expression: &Expression<T::Parser>,
890    ) -> Result<V::Output, ExpressionError<'s>>
891    where
892        V: LoadOwned<T>,
893    {
894        self.evaluate_impl::<V>(source, expression)
895            .map_err(|error| ExpressionError { error, source })
896    }
897
898    fn evaluate_impl<V>(
899        &mut self,
900        source: &str,
901        expression: &Expression<T::Parser>,
902    ) -> Result<V::Output, EvalError>
903    where
904        V: LoadOwned<T>,
905    {
906        let mut visitor = ExpressionVisitor::<Self, T> {
907            context: self,
908            source,
909            _marker: std::marker::PhantomData,
910        };
911        let result = visitor.visit_expression(expression)?;
912        let result_ty = result.type_of();
913        V::load_owned(self.type_context(), &result).ok_or_else(|| EvalError {
914            message: format!(
915                "Expression evaluates to {result_ty}, which cannot be converted to {}",
916                std::any::type_name::<V>()
917            )
918            .into_boxed_str(),
919            location: expression.location(),
920        })
921    }
922
923    /// Defines a new variable in the context.
924    ///
925    /// The variable can be any type from the current [`TypeSet`], even [`TypedValue`].
926    ///
927    /// The variable will act as a global variable in the context of the program. Its
928    /// value can be changed by expressions.
929    ///
930    /// ```rust
931    /// use somni_expr::{Context, TypedValue};
932    ///
933    /// let mut context = Context::new();
934    ///
935    /// // Variable does not exist, it can't be assigned:
936    /// assert!(context.evaluate::<()>("counter = 0").is_err());
937    ///
938    /// context.add_variable::<u64>("counter", 0);
939    ///
940    /// // Variable exists now, so we can use it:
941    /// assert_eq!(context.evaluate::<()>("counter = counter + 1"), Ok(()));
942    /// assert_eq!(context.evaluate::<u64>("counter"), Ok(1));
943    /// ```
944    pub fn add_variable<V>(&mut self, name: &'ctx str, value: V)
945    where
946        V: LoadStore<T>,
947    {
948        let stored = value.store(self.type_context());
949        self.stack[0].declare(name, stored);
950    }
951
952    /// Adds a new function to the context.
953    ///
954    /// ```rust
955    /// use somni_expr::{Context, TypedValue};
956    ///
957    /// let mut context = Context::new();
958    ///
959    /// context.add_function("plus_one", |x: u64| x + 1);
960    ///
961    /// assert_eq!(context.evaluate::<u64>("plus_one(2)"), Ok(3));
962    /// ```
963    pub fn add_function<F, A>(&mut self, name: &'ctx str, func: F)
964    where
965        F: DynFunction<A, T> + 'ctx,
966    {
967        self.program
968            .functions
969            .borrow_mut()
970            .insert(name, ExprFn::new(func));
971    }
972
973    fn lookup(&mut self, variable: &str) -> Option<(usize, TypedValue<T>)> {
974        if self.stack.len() > 1 {
975            let frame = self.stack.last_mut().unwrap();
976            if let Some((index, var)) = frame.lookup_by_name(variable) {
977                // Already evaluated / user provided
978                return Some((index, var.clone()));
979            }
980        }
981
982        {
983            let global_frame = &mut self.stack[0];
984            if let Some((index, var)) = global_frame.lookup_by_name(variable) {
985                // Already evaluated / user provided
986                return Some((index | GLOBAL_VARIABLE, var.clone()));
987            }
988        }
989
990        // Mark as "initializing" to detect potential cycles
991        let state = self.initializers.get_mut(variable)?;
992        let InitializerState::Unevaluated(idx) =
993            std::mem::replace(state, InitializerState::Evaluating)
994        else {
995            return None;
996        };
997
998        // Get a reference to the initializer
999        let program = self.program.clone();
1000        let Some(Item::GlobalVariable(global)) = program.program.items.get(idx) else {
1001            return None;
1002        };
1003
1004        let value = self
1005            .evaluate_parsed::<TypedValue<T>>(self.program.source, &global.initializer)
1006            .ok()?;
1007
1008        let global_frame = &mut self.stack[0];
1009        let index = global_frame.declare(variable, value.clone());
1010
1011        Some((index | GLOBAL_VARIABLE, value))
1012    }
1013
1014    /// Resolves a raw root address to the variable slot it names.
1015    fn lookup_address_raw(&mut self, address: usize) -> Result<&mut TypedValue<T>, Box<str>> {
1016        if address & GLOBAL_VARIABLE != 0 {
1017            return self.stack[0].lookup_by_address(address & !GLOBAL_VARIABLE);
1018        }
1019
1020        for frame in self.stack.iter_mut().rev() {
1021            if frame.start_addr <= address {
1022                return frame.lookup_by_address(address);
1023            }
1024        }
1025
1026        Err(format!("Not a valid memory address: {address}").into_boxed_str())
1027    }
1028
1029    /// Resolves a place to the mutable slot it names, descending its field path.
1030    fn resolve_place_mut(&mut self, place: &Place) -> Result<&mut TypedValue<T>, Box<str>> {
1031        let mut current = self.lookup_address_raw(place.root)?;
1032        for field in place.path.iter() {
1033            let TypedValue::Struct(structure) = current else {
1034                return Err(
1035                    format!("Cannot access field `{field}` of a non-struct value").into_boxed_str(),
1036                );
1037            };
1038            let struct_name = structure.name().to_string();
1039            current = structure.fields_mut().get_mut(&**field).ok_or_else(|| {
1040                format!("Struct `{struct_name}` has no field `{field}`").into_boxed_str()
1041            })?;
1042        }
1043        Ok(current)
1044    }
1045}
1046
1047impl<T> ExprContext<T> for Context<'_, T>
1048where
1049    T: TypeSet,
1050{
1051    fn type_context(&mut self) -> &mut T {
1052        &mut self.type_context
1053    }
1054
1055    // TODO: return Result
1056    fn try_load_variable(&mut self, variable: &str) -> Option<TypedValue<T>> {
1057        self.lookup(variable).map(|(_idx, var)| var)
1058    }
1059
1060    fn place_of_variable(&mut self, variable: &str) -> Result<Place, Box<str>> {
1061        let root = self
1062            .lookup(variable)
1063            .map(|(address, _var)| address)
1064            .ok_or_else(|| format!("Variable not found: {variable}").into_boxed_str())?;
1065        Ok(Place {
1066            root,
1067            path: Box::new([]),
1068        })
1069    }
1070
1071    fn load_place(&mut self, place: &Place) -> Result<TypedValue<T>, Box<str>> {
1072        self.resolve_place_mut(place).map(|v| v.clone())
1073    }
1074
1075    fn store_place(&mut self, place: &Place, value: &TypedValue<T>) -> Result<(), Box<str>> {
1076        let slot = self.resolve_place_mut(place)?;
1077        slot.clone_from(value);
1078        Ok(())
1079    }
1080
1081    fn struct_fields(&self, struct_name: &str) -> Option<Vec<(Box<str>, Box<str>)>> {
1082        let idx = *self.program.program_structs.get(struct_name)?;
1083        let Some(Item::Struct(struct_def)) = self.program.program.items.get(idx) else {
1084            return None;
1085        };
1086        let source = self.program.source;
1087        Some(
1088            struct_def
1089                .fields
1090                .iter()
1091                .map(|field| {
1092                    (
1093                        Box::from(field.name.source(source)),
1094                        Box::from(field.field_type.type_name.source(source)),
1095                    )
1096                })
1097                .collect(),
1098        )
1099    }
1100
1101    /// Declares a variable in the context.
1102    fn declare(&mut self, variable: &str, value: TypedValue<T>) {
1103        self.stack.last_mut().unwrap().declare(variable, value);
1104    }
1105
1106    /// Assigns a new value to a variable in the context.
1107    fn assign_variable(&mut self, variable: &str, value: &TypedValue<T>) -> Result<(), Box<str>> {
1108        if self.stack.last_mut().unwrap().store(variable, value) {
1109            return Ok(());
1110        }
1111        if self.stack[0].store(variable, value) {
1112            return Ok(());
1113        }
1114
1115        Err(format!("Variable not found: {variable}").into_boxed_str())
1116    }
1117
1118    fn call_function(
1119        &mut self,
1120        function_name: &str,
1121        args: &[TypedValue<T>],
1122    ) -> Result<TypedValue<T>, FunctionCallError> {
1123        let program = self.program.clone();
1124        let Some(fn_item) = self.program.program_functions.get(function_name) else {
1125            // Call out to a Rust function
1126            return match program.functions.borrow().get(function_name) {
1127                Some(func) => func.call(self.type_context(), args),
1128                None => Err(FunctionCallError::FunctionNotFound),
1129            };
1130        };
1131
1132        // Call a Somni function
1133        let Some(ast::Item::Function(function)) = program.program.items.get(*fn_item) else {
1134            return Err(FunctionCallError::FunctionNotFound);
1135        };
1136        self.evaluate_any_function_impl(function, args)
1137            .map_err(|err| {
1138                FunctionCallError::Other(
1139                    format!(
1140                        "{:?}",
1141                        ExpressionError {
1142                            source: self.program.source,
1143                            error: err,
1144                        }
1145                    )
1146                    .into_boxed_str(),
1147                )
1148            })
1149    }
1150
1151    /// Opens a new scope in the current stack frame.
1152    fn open_scope(&mut self) {
1153        // TODO: error handling
1154        self.stack.last_mut().unwrap().open_scope();
1155    }
1156
1157    /// Closes the last scope in the current stack frame.
1158    fn close_scope(&mut self) {
1159        // TODO: error handling
1160        self.stack.last_mut().unwrap().close_scope();
1161    }
1162}
1163
1164#[macro_export]
1165#[doc(hidden)]
1166macro_rules! for_all_tuples {
1167    ($pat:tt => $code:tt;) => {
1168        macro_rules! inner { $pat => $code; }
1169
1170        inner!();
1171        inner!(V1);
1172        inner!(V1, V2);
1173        inner!(V1, V2, V3);
1174        inner!(V1, V2, V3, V4);
1175        inner!(V1, V2, V3, V4, V5);
1176        inner!(V1, V2, V3, V4, V5, V6);
1177        inner!(V1, V2, V3, V4, V5, V6, V7);
1178        inner!(V1, V2, V3, V4, V5, V6, V7, V8);
1179        inner!(V1, V2, V3, V4, V5, V6, V7, V8, V9);
1180        inner!(V1, V2, V3, V4, V5, V6, V7, V8, V9, V10);
1181    };
1182}
1183
1184#[cfg(test)]
1185mod test {
1186    use std::path::Path;
1187
1188    use super::*;
1189
1190    fn strip_ansi(s: impl AsRef<str>) -> String {
1191        use ansi_parser::AnsiParser;
1192        fn text_block(output: ansi_parser::Output<'_>) -> Option<&str> {
1193            match output {
1194                ansi_parser::Output::TextBlock(text) => Some(text),
1195                _ => None,
1196            }
1197        }
1198
1199        s.as_ref()
1200            .ansi_parse()
1201            .filter_map(text_block)
1202            .collect::<String>()
1203    }
1204
1205    #[test]
1206    fn test_evaluating_exprs() {
1207        let mut ctx = Context::new();
1208
1209        ctx.add_variable::<i64>("signed", 30);
1210        ctx.add_variable::<u64>("value", 30);
1211        ctx.add_function("func", |v: u64| 2 * v);
1212        ctx.add_function("func2", |v1: u64, v2: u64| v1 + v2);
1213        ctx.add_function("five", || "five");
1214        ctx.add_function("is_five", |num: &str| num == "five");
1215        ctx.add_function("concatenate", |a: &str, b: &str| format!("{a}{b}"));
1216
1217        assert_eq!(ctx.evaluate::<bool>("value / 5 == 6"), Ok(true));
1218        assert_eq!(ctx.evaluate::<bool>("five() == \"five\""), Ok(true));
1219        assert_eq!(
1220            ctx.evaluate::<bool>("is_five(five()) != is_five(\"six\")"),
1221            Ok(true)
1222        );
1223        assert_eq!(ctx.evaluate::<u64>("func(20) / 5"), Ok(8));
1224        assert_eq!(
1225            ctx.evaluate::<TypedValue>("func(20) / 5"),
1226            Ok(TypedValue::Int(8))
1227        );
1228        assert_eq!(ctx.evaluate::<u64>("func2(20, 20) / 5"), Ok(8));
1229        assert_eq!(ctx.evaluate::<bool>("true & false"), Ok(false));
1230        assert_eq!(ctx.evaluate::<bool>("!true"), Ok(false));
1231        assert_eq!(ctx.evaluate::<bool>("false | false"), Ok(false));
1232        assert_eq!(ctx.evaluate::<bool>("true ^ true"), Ok(false));
1233        assert_eq!(ctx.evaluate::<u64>("!0x1111"), Ok(0xFFFF_FFFF_FFFF_EEEE));
1234        assert_eq!(
1235            ctx.evaluate::<String>("concatenate(five(), \"six\")"),
1236            Ok(String::from("fivesix"))
1237        );
1238        assert_eq!(ctx.evaluate::<bool>("signed * 2 == 60"), Ok(true));
1239        assert_eq!(ctx.evaluate::<i64>("*&signed"), Ok(30));
1240    }
1241
1242    #[test]
1243    fn test_context_is_mutable() {
1244        let mut ctx = Context::new();
1245
1246        ctx.add_variable::<u64>("value", 30);
1247
1248        ctx.evaluate::<()>("value = 5").unwrap();
1249        assert_eq!(ctx.evaluate::<bool>("value == 5"), Ok(true));
1250    }
1251
1252    #[test]
1253    fn test_evaluating_exprs_with_u32() {
1254        let mut ctx = Context::<TypeSet32>::new_with_types();
1255
1256        ctx.add_variable::<u32>("value", 30);
1257        ctx.add_function("func", |v: u32| 2 * v);
1258        ctx.add_function("func2", |v1: u32, v2: u32| v1 + v2);
1259
1260        assert_eq!(ctx.evaluate::<bool>("value / 5 == 6"), Ok(true));
1261        assert_eq!(ctx.evaluate::<u32>("func(20) / 5"), Ok(8));
1262        assert_eq!(ctx.evaluate::<u32>("func2(20, 20) / 5"), Ok(8));
1263    }
1264
1265    #[test]
1266    fn test_evaluating_exprs_with_u128() {
1267        let mut ctx = Context::<TypeSet128>::new_with_types();
1268
1269        ctx.add_variable::<u128>("value", 30);
1270        ctx.add_function("func", |v: u128| 2 * v);
1271        ctx.add_function("func2", |v1: u128, v2: u128| v1 + v2);
1272
1273        assert_eq!(ctx.evaluate::<bool>("value / 5 == 6"), Ok(true));
1274        assert_eq!(ctx.evaluate::<u128>("func(20) / 5"), Ok(8));
1275        assert_eq!(ctx.evaluate::<u128>("func2(20, 20) / 5"), Ok(8));
1276    }
1277
1278    #[test]
1279    fn test_evaluate_function() {
1280        let mut ctx =
1281            Context::parse("fn multiply_with_global(a: int) -> int { return a * global; }")
1282                .unwrap();
1283
1284        ctx.add_variable::<u64>("global", 3);
1285
1286        assert_eq!(
1287            ctx.evaluate::<bool>("multiply_with_global(2) == 6"),
1288            Ok(true)
1289        );
1290        assert!(
1291            ctx.evaluate::<bool>("multiply_with_global(\"2\") == 6")
1292                .is_err()
1293        );
1294    }
1295
1296    #[test]
1297    fn run_eval_tests() {
1298        fn filter(path: &Path) -> bool {
1299            let Ok(env) = std::env::var("TEST_FILTER") else {
1300                // No filter set, walk folders and somni source files.
1301                return path.is_dir() || path.extension().map_or(false, |ext| ext == "sm");
1302            };
1303
1304            Path::new(&env) == path
1305        }
1306
1307        fn walk(dir: &Path, on_file: &impl Fn(&Path)) {
1308            for entry in std::fs::read_dir(dir)
1309                .unwrap_or_else(|_| panic!("Folder not found: {}", dir.display()))
1310                .flatten()
1311            {
1312                let path = entry.path();
1313
1314                if !filter(&path) {
1315                    continue;
1316                }
1317
1318                if path.is_file() {
1319                    on_file(&path);
1320                } else {
1321                    walk(&path, on_file);
1322                }
1323            }
1324        }
1325
1326        fn run_eval_test(path: &Path) {
1327            type Types = WithIterator<DefaultTypeSet>;
1328
1329            fn parse(source: &str) -> Context<'_, Types> {
1330                let mut context = Context::<Types>::parse_with_types(source).unwrap();
1331
1332                context.add_function("add_from_rust", |a: u64, b: u64| -> i64 { (a + b) as i64 });
1333                context.add_function("assert", |a: bool| a); // No-op to test calling Rust functions from expressions
1334                context.add_function("reverse", |s: &str| s.chars().rev().collect::<String>());
1335                context.add_function("range", |a: u64, b: u64| {
1336                    SomniIterator::new((a..b).map(TypedValue::<DefaultTypeSet>::Int))
1337                });
1338
1339                context
1340            }
1341
1342            let test_name = path.file_stem().unwrap();
1343            let parent = path.parent().unwrap().canonicalize().unwrap();
1344            let vm_error = parent.join(test_name).join("stderr");
1345            let expr_error = parent.join(test_name).join("stderr_expr");
1346            let source = std::fs::read_to_string(path).unwrap();
1347
1348            let expressions = source
1349                .lines()
1350                .filter_map(|line| line.trim().strip_prefix("//@"))
1351                .collect::<Vec<_>>();
1352
1353            let mut context = parse(&source);
1354            let fail_expected = std::fs::exists(&expr_error).unwrap_or(false)
1355                || std::fs::exists(&vm_error).unwrap_or(false);
1356
1357            let blessed = std::env::var("BLESS").as_deref() == Ok("1");
1358
1359            for expression in &expressions {
1360                let expression = if let Some(e) = expression.strip_prefix('+') {
1361                    // `//@+` preserves VM state (like changes to globals)
1362                    e.trim()
1363                } else {
1364                    // `//@` resets VM state (like changes to globals)
1365                    context = parse(&source);
1366                    expression
1367                };
1368                println!("Running `{expression}`");
1369                match context.evaluate::<TypedValue<Types>>(expression) {
1370                    Ok(_) if fail_expected => {
1371                        panic!(
1372                            "Expected {} to fail evaluating, but it succeeded",
1373                            path.display()
1374                        )
1375                    }
1376                    Ok(value) => assert_eq!(
1377                        value,
1378                        TypedValue::Bool(true),
1379                        "{}: Expression `{expression}` evaluated to {value:?}",
1380                        path.display()
1381                    ),
1382                    Err(e) if fail_expected => {
1383                        let error = strip_ansi(format!("{e:?}"));
1384                        if blessed {
1385                            std::fs::write(&expr_error, error).unwrap();
1386                        } else {
1387                            let expected_error = std::fs::read_to_string(&expr_error).unwrap();
1388                            pretty_assertions::assert_eq!(strip_ansi(expected_error), error);
1389                        }
1390                    }
1391                    Err(e) => panic!("{}: {e:?}", path.display()),
1392                };
1393            }
1394        }
1395
1396        walk("../tests/eval".as_ref(), &|path| {
1397            run_eval_test(path);
1398        });
1399    }
1400
1401    #[test]
1402    fn test_struct_literals_and_field_access() {
1403        let program = r#"
1404struct Point { x: int, y: int }
1405
1406fn make() -> Point {
1407    return Point { x: 3, y: 4 };
1408}
1409
1410fn sum_sq() -> int {
1411    var p = make();
1412    return p.x * p.x + p.y * p.y;
1413}
1414"#;
1415        let mut ctx = Context::parse(program).unwrap();
1416        assert_eq!(ctx.evaluate::<bool>("sum_sq() == 25"), Ok(true));
1417    }
1418
1419    #[test]
1420    fn test_struct_field_write() {
1421        let program = r#"
1422struct Point { x: int, y: int }
1423
1424fn moved() -> int {
1425    var p = Point { x: 1, y: 2 };
1426    p.x = 10;
1427    p.y = p.y + 5;
1428    return p.x + p.y;
1429}
1430"#;
1431        let mut ctx = Context::parse(program).unwrap();
1432        assert_eq!(ctx.evaluate::<bool>("moved() == 17"), Ok(true));
1433    }
1434
1435    #[test]
1436    fn test_nested_struct() {
1437        let program = r#"
1438struct Point { x: int, y: int }
1439struct Line { start: Point, end: Point }
1440
1441fn build() -> int {
1442    var l = Line { start: Point { x: 1, y: 2 }, end: Point { x: 3, y: 4 } };
1443    l.end.x = 30;
1444    return l.start.x + l.end.x;
1445}
1446"#;
1447        let mut ctx = Context::parse(program).unwrap();
1448        assert_eq!(ctx.evaluate::<bool>("build() == 31"), Ok(true));
1449    }
1450
1451    #[test]
1452    fn test_struct_pass_by_reference_and_autoderef() {
1453        let program = r#"
1454struct Point { x: int, y: int }
1455
1456fn scale(p: &Point, factor: int) {
1457    p.x = p.x * factor;
1458    p.y = p.y * factor;
1459}
1460
1461fn run() -> int {
1462    var p = Point { x: 2, y: 3 };
1463    scale(&p, 4);
1464    return p.x + p.y;
1465}
1466"#;
1467        let mut ctx = Context::parse(program).unwrap();
1468        assert_eq!(ctx.evaluate::<bool>("run() == 20"), Ok(true));
1469    }
1470
1471    #[test]
1472    fn test_reference_to_field() {
1473        let program = r#"
1474struct Point { x: int, y: int }
1475
1476fn double(v: &int) {
1477    *v = *v * 2;
1478}
1479
1480fn run() -> int {
1481    var p = Point { x: 5, y: 6 };
1482    double(&p.x);
1483    return p.x;
1484}
1485"#;
1486        let mut ctx = Context::parse(program).unwrap();
1487        assert_eq!(ctx.evaluate::<bool>("run() == 10"), Ok(true));
1488    }
1489
1490    #[test]
1491    fn test_struct_equality() {
1492        let program = r#"
1493struct Point { x: int, y: int }
1494
1495fn a() -> Point { return Point { x: 1, y: 2 }; }
1496fn b() -> Point { return Point { x: 1, y: 2 }; }
1497fn c() -> Point { return Point { x: 1, y: 9 }; }
1498"#;
1499        let mut ctx = Context::parse(program).unwrap();
1500        assert_eq!(ctx.evaluate::<bool>("a() == b()"), Ok(true));
1501        assert_eq!(ctx.evaluate::<bool>("a() != c()"), Ok(true));
1502        assert_eq!(ctx.evaluate::<bool>("a() == c()"), Ok(false));
1503    }
1504
1505    #[test]
1506    fn test_struct_boundary_and_macro() {
1507        let program = r#"
1508struct Point { x: int, y: int }
1509"#;
1510        let mut ctx = Context::parse(program).unwrap();
1511        ctx.add_function("origin_distance_sq", |p: SomniStruct| -> i64 {
1512            let TypedValue::Int(x) = p.fields()["x"] else {
1513                panic!("x not an int")
1514            };
1515            let TypedValue::Int(y) = p.fields()["y"] else {
1516                panic!("y not an int")
1517            };
1518            (x * x + y * y) as i64
1519        });
1520
1521        // Build a struct from Rust and hand it to the program.
1522        let tc = ctx.type_context();
1523        let point = somni_struct!(tc, Point { x: 3u64, y: 4u64 });
1524        assert_eq!(point.name(), "Point");
1525
1526        assert_eq!(
1527            ctx.evaluate::<bool>("origin_distance_sq(Point { x: 3, y: 4 }) == 25"),
1528            Ok(true)
1529        );
1530    }
1531
1532    #[test]
1533    fn test_unknown_struct_is_error() {
1534        let mut ctx = Context::new();
1535        assert!(ctx.evaluate::<TypedValue>("Nope { x: 1 }").is_err());
1536    }
1537
1538    #[test]
1539    fn test_eval_error() {
1540        let mut ctx = Context::new();
1541
1542        ctx.add_function("func", |v1: u64, v2: u64| v1 + v2);
1543
1544        let err = ctx
1545            .evaluate::<u64>("func(20, true)")
1546            .expect_err("Expected expression to return an error");
1547
1548        pretty_assertions::assert_eq!(
1549            strip_ansi(format!("\n{err:?}")),
1550            r#"
1551Evaluation error
1552 ---> at line 1 column 10
1553  |
15541 | func(20, true)
1555  |          ^^^^ func expects argument 1 to be u64, got bool"#,
1556        );
1557    }
1558}