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<Scope>,
631}
632
633struct Scope {
634    variable_start: usize,
635    variables: HashMap<String, usize>,
636}
637
638impl<T: TypeSet> StackFrame<T> {
639    fn new() -> StackFrame<T> {
640        StackFrame {
641            start_addr: 0,
642            variables: vec![],
643            scopes: vec![Scope {
644                variable_start: 0,
645                variables: HashMap::new(),
646            }],
647        }
648    }
649
650    fn next_address(&self) -> usize {
651        self.start_addr + self.variables.len()
652    }
653
654    fn reset(&mut self, start_addr: usize) {
655        self.start_addr = start_addr;
656        self.variables.clear();
657        self.scopes.truncate(1);
658        self.scopes[0].variable_start = 0;
659        self.scopes[0].variables.clear();
660    }
661
662    fn declare(&mut self, variable: &str, value: TypedValue<T>) -> usize {
663        let index = self.variables.len();
664        self.variables.push(value);
665        self.scopes
666            .last_mut()
667            .unwrap()
668            .variables
669            .insert(variable.to_string(), index);
670        index + self.start_addr
671    }
672
673    fn lookup_index(&self, name: &str) -> Option<usize> {
674        for scope in self.scopes.iter().rev() {
675            if let Some(idx) = scope.variables.get(name) {
676                return Some(*idx);
677            }
678        }
679        None
680    }
681
682    fn store(&mut self, variable: &str, value: &TypedValue<T>) -> bool {
683        if let Some(idx) = self.lookup_index(variable) {
684            self.variables.get_mut(idx).unwrap().clone_from(value);
685            true
686        } else {
687            false
688        }
689    }
690
691    fn lookup_by_address(&mut self, address: usize) -> Result<&mut TypedValue<T>, Box<str>> {
692        self.variables
693            .get_mut(address - self.start_addr)
694            .ok_or_else(|| format!("Invalid address {address}").into_boxed_str())
695    }
696
697    fn lookup_by_name<'s>(&'s mut self, variable: &str) -> Option<(usize, &'s mut TypedValue<T>)> {
698        let index = self.lookup_index(variable)?;
699        let address = index + self.start_addr;
700
701        Some((address, self.variables.get_mut(index).unwrap()))
702    }
703
704    fn open_scope(&mut self) {
705        self.scopes.push(Scope {
706            variable_start: self.variables.len(),
707            variables: HashMap::new(),
708        });
709    }
710
711    fn close_scope(&mut self) {
712        let scope = self.scopes.pop().unwrap();
713        self.variables.truncate(scope.variable_start);
714    }
715}
716
717struct ProgramData<'ctx, T: TypeSet> {
718    source: &'ctx str,
719    program: Program<T::Parser>,
720    program_functions: HashMap<&'ctx str, usize>,
721    // Struct definitions, indexed by name (item index into `program.items`).
722    program_structs: HashMap<&'ctx str, usize>,
723    // User-registered functions
724    functions: RefCell<HashMap<&'ctx str, ExprFn<'ctx, T>>>,
725}
726
727impl<'ctx> Default for Context<'ctx, DefaultTypeSet> {
728    fn default() -> Self {
729        Self::new()
730    }
731}
732
733/// The expression context, which holds variables, functions, and other state needed for evaluation.
734pub struct Context<'ctx, T = DefaultTypeSet>
735where
736    T: TypeSet,
737{
738    program: Rc<ProgramData<'ctx, T>>,
739    // Program state
740    // ----
741    /// Variable stack. Element 0 is the global scope.
742    stack: Vec<StackFrame<T>>,
743    /// Call frames retained for reuse after functions return.
744    frame_pool: Vec<StackFrame<T>>,
745    // unevaluated globals
746    initializers: HashMap<&'ctx str, InitializerState>,
747    type_context: T,
748}
749
750impl<'ctx> Context<'ctx, DefaultTypeSet> {
751    /// Creates a new context with [default types][DefaultTypeSet].
752    pub fn new() -> Self {
753        Self::new_with_types()
754    }
755
756    /// Loads the given program into a new context with [default types][DefaultTypeSet].
757    pub fn parse(source: &'ctx str) -> Result<Self, ExpressionError<'ctx>> {
758        Self::parse_with_types(source)
759    }
760}
761
762const GLOBAL_VARIABLE: usize = usize::MAX - usize::MAX / 2;
763
764impl<'ctx, T> Context<'ctx, T>
765where
766    T: TypeSet,
767{
768    /// Creates a new context. The type set must be specified when using this function.
769    ///
770    /// ```rust
771    /// use somni_expr::{Context, TypeSet32};
772    /// let mut ctx = Context::<TypeSet32>::new_with_types();
773    /// ```
774    pub fn new_with_types() -> Self {
775        Self::new_from_program("", Program { items: vec![] })
776    }
777
778    /// Parses the given program into a new context. The type set must be specified when using this function.
779    ///
780    /// ```rust
781    /// use somni_expr::{Context, TypeSet32};
782    /// let mut ctx = Context::<TypeSet32>::parse_with_types("// program source comes here").unwrap();
783    /// ```
784    pub fn parse_with_types(source: &'ctx str) -> Result<Self, ExpressionError<'ctx>> {
785        let program = parse::<T::Parser>(source).map_err(|e| ExpressionError {
786            error: EvalError {
787                message: format!("Failed to parse program: {e}").into_boxed_str(),
788                location: e.location,
789            },
790            source,
791        })?;
792
793        Ok(Self::new_from_program(source, program))
794    }
795
796    /// Loads the given program into a new context.
797    pub fn new_from_program(source: &'ctx str, program: Program<T::Parser>) -> Self {
798        let mut program_functions = HashMap::new();
799        let mut program_structs = HashMap::new();
800        let mut initializers = HashMap::new();
801        // Extract data for O(1) function/initializer/struct lookup
802        for (idx, item) in program.items.iter().enumerate() {
803            match item {
804                ast::Item::Function(function) => {
805                    program_functions.insert(function.name.source(source), idx);
806                }
807                ast::Item::GlobalVariable(global_variable) => {
808                    initializers.insert(
809                        global_variable.identifier.source(source),
810                        InitializerState::Unevaluated(idx),
811                    );
812                }
813                ast::Item::Struct(struct_def) => {
814                    program_structs.insert(struct_def.name.source(source), idx);
815                }
816                ast::Item::ExternFunction(_) => {}
817            }
818        }
819        Self {
820            program: Rc::new(ProgramData {
821                source,
822                program,
823                program_functions,
824                program_structs,
825                functions: RefCell::new(HashMap::new()),
826            }),
827            stack: vec![StackFrame::new()],
828            frame_pool: Vec::new(),
829            type_context: T::default(),
830            initializers,
831        }
832    }
833
834    fn evaluate_any_function_impl(
835        &mut self,
836        function_name: &Function<T::Parser>,
837        args: &[TypedValue<T>],
838    ) -> Result<TypedValue<T>, EvalError> {
839        let source = self.program.clone().source;
840
841        let start_addr = self
842            .stack
843            .last()
844            .expect("The global scope must always be present")
845            .next_address();
846        let mut stack_frame = self.frame_pool.pop().unwrap_or_else(StackFrame::new);
847        stack_frame.reset(start_addr);
848        self.stack.push(stack_frame);
849
850        let mut visitor = ExpressionVisitor::<Self, T> {
851            context: self,
852            source,
853            _marker: std::marker::PhantomData,
854        };
855
856        let result = visitor.visit_function(function_name, args);
857
858        let stack_frame = self.stack.pop().unwrap();
859        self.frame_pool.push(stack_frame);
860
861        result
862    }
863
864    /// Parses and evaluates an expression and returns the result as a specific value type.
865    ///
866    /// This function will attempt to convert the result of the expression to the specified type `V`.
867    /// If the conversion fails, it will return an `ExpressionError`.
868    ///
869    /// ```rust
870    /// use somni_expr::{Context, TypedValue};
871    ///
872    /// let mut context = Context::new();
873    ///
874    /// assert_eq!(context.evaluate::<u64>("1 + 2"), Ok(3));
875    /// assert_eq!(context.evaluate::<TypedValue>("1 + 2"), Ok(TypedValue::Int(3)));
876    /// ```
877    pub fn evaluate<'s, V>(&'s mut self, source: &'s str) -> Result<V::Output, ExpressionError<'s>>
878    where
879        V: LoadOwned<T>,
880    {
881        let expression =
882            parser::parse_expression::<T::Parser>(source).map_err(|e| ExpressionError {
883                error: EvalError {
884                    message: format!("Parser error: {e}").into_boxed_str(),
885                    location: e.location,
886                },
887                source,
888            })?;
889
890        self.evaluate_parsed::<V>(source, &expression)
891    }
892
893    /// Evaluates a pre-parsed expression and returns the result as a specific value type.
894    ///
895    /// This function will attempt to convert the result of the expression to the specified type `V`.
896    /// If the conversion fails, it will return an `ExpressionError`.
897    ///
898    /// ```rust
899    /// use somni_expr::{Context, TypedValue};
900    ///
901    /// let mut context = Context::new();
902    ///
903    /// let source = "1 + 2";
904    /// let expr = somni_parser::parser::parse_expression(source).unwrap();
905    ///
906    /// assert_eq!(context.evaluate_parsed::<u64>(source, &expr), Ok(3));
907    /// assert_eq!(context.evaluate_parsed::<TypedValue>(source, &expr), Ok(TypedValue::Int(3)));
908    /// ```
909    pub fn evaluate_parsed<'s, V>(
910        &'s mut self,
911        source: &'s str,
912        expression: &Expression<T::Parser>,
913    ) -> Result<V::Output, ExpressionError<'s>>
914    where
915        V: LoadOwned<T>,
916    {
917        self.evaluate_impl::<V>(source, expression)
918            .map_err(|error| ExpressionError { error, source })
919    }
920
921    fn evaluate_impl<V>(
922        &mut self,
923        source: &str,
924        expression: &Expression<T::Parser>,
925    ) -> Result<V::Output, EvalError>
926    where
927        V: LoadOwned<T>,
928    {
929        let mut visitor = ExpressionVisitor::<Self, T> {
930            context: self,
931            source,
932            _marker: std::marker::PhantomData,
933        };
934        let result = visitor.visit_expression(expression)?;
935        let result_ty = result.type_of();
936        V::load_owned(self.type_context(), &result).ok_or_else(|| EvalError {
937            message: format!(
938                "Expression evaluates to {result_ty}, which cannot be converted to {}",
939                std::any::type_name::<V>()
940            )
941            .into_boxed_str(),
942            location: expression.location(),
943        })
944    }
945
946    /// Defines a new variable in the context.
947    ///
948    /// The variable can be any type from the current [`TypeSet`], even [`TypedValue`].
949    ///
950    /// The variable will act as a global variable in the context of the program. Its
951    /// value can be changed by expressions.
952    ///
953    /// ```rust
954    /// use somni_expr::{Context, TypedValue};
955    ///
956    /// let mut context = Context::new();
957    ///
958    /// // Variable does not exist, it can't be assigned:
959    /// assert!(context.evaluate::<()>("counter = 0").is_err());
960    ///
961    /// context.add_variable::<u64>("counter", 0);
962    ///
963    /// // Variable exists now, so we can use it:
964    /// assert_eq!(context.evaluate::<()>("counter = counter + 1"), Ok(()));
965    /// assert_eq!(context.evaluate::<u64>("counter"), Ok(1));
966    /// ```
967    pub fn add_variable<V>(&mut self, name: &'ctx str, value: V)
968    where
969        V: LoadStore<T>,
970    {
971        let stored = value.store(self.type_context());
972        self.stack[0].declare(name, stored);
973    }
974
975    /// Adds a new function to the context.
976    ///
977    /// ```rust
978    /// use somni_expr::{Context, TypedValue};
979    ///
980    /// let mut context = Context::new();
981    ///
982    /// context.add_function("plus_one", |x: u64| x + 1);
983    ///
984    /// assert_eq!(context.evaluate::<u64>("plus_one(2)"), Ok(3));
985    /// ```
986    pub fn add_function<F, A>(&mut self, name: &'ctx str, func: F)
987    where
988        F: DynFunction<A, T> + 'ctx,
989    {
990        self.program
991            .functions
992            .borrow_mut()
993            .insert(name, ExprFn::new(func));
994    }
995
996    fn lookup(&mut self, variable: &str) -> Option<(usize, TypedValue<T>)> {
997        if self.stack.len() > 1 {
998            let frame = self.stack.last_mut().unwrap();
999            if let Some((index, var)) = frame.lookup_by_name(variable) {
1000                // Already evaluated / user provided
1001                return Some((index, var.clone()));
1002            }
1003        }
1004
1005        {
1006            let global_frame = &mut self.stack[0];
1007            if let Some((index, var)) = global_frame.lookup_by_name(variable) {
1008                // Already evaluated / user provided
1009                return Some((index | GLOBAL_VARIABLE, var.clone()));
1010            }
1011        }
1012
1013        // Mark as "initializing" to detect potential cycles
1014        let state = self.initializers.get_mut(variable)?;
1015        let InitializerState::Unevaluated(idx) =
1016            std::mem::replace(state, InitializerState::Evaluating)
1017        else {
1018            return None;
1019        };
1020
1021        // Get a reference to the initializer
1022        let program = self.program.clone();
1023        let Some(Item::GlobalVariable(global)) = program.program.items.get(idx) else {
1024            return None;
1025        };
1026
1027        let value = self
1028            .evaluate_parsed::<TypedValue<T>>(self.program.source, &global.initializer)
1029            .ok()?;
1030
1031        let global_frame = &mut self.stack[0];
1032        let index = global_frame.declare(variable, value.clone());
1033
1034        Some((index | GLOBAL_VARIABLE, value))
1035    }
1036
1037    /// Resolves a raw root address to the variable slot it names.
1038    fn lookup_address_raw(&mut self, address: usize) -> Result<&mut TypedValue<T>, Box<str>> {
1039        if address & GLOBAL_VARIABLE != 0 {
1040            return self.stack[0].lookup_by_address(address & !GLOBAL_VARIABLE);
1041        }
1042
1043        for frame in self.stack.iter_mut().rev() {
1044            if frame.start_addr <= address {
1045                return frame.lookup_by_address(address);
1046            }
1047        }
1048
1049        Err(format!("Not a valid memory address: {address}").into_boxed_str())
1050    }
1051
1052    /// Resolves a place to the mutable slot it names, descending its field path.
1053    fn resolve_place_mut(&mut self, place: &Place) -> Result<&mut TypedValue<T>, Box<str>> {
1054        let mut current = self.lookup_address_raw(place.root)?;
1055        for field in place.path.iter() {
1056            let TypedValue::Struct(structure) = current else {
1057                return Err(
1058                    format!("Cannot access field `{field}` of a non-struct value").into_boxed_str(),
1059                );
1060            };
1061            let struct_name = structure.name().to_string();
1062            current = structure.fields_mut().get_mut(&**field).ok_or_else(|| {
1063                format!("Struct `{struct_name}` has no field `{field}`").into_boxed_str()
1064            })?;
1065        }
1066        Ok(current)
1067    }
1068}
1069
1070impl<T> ExprContext<T> for Context<'_, T>
1071where
1072    T: TypeSet,
1073{
1074    fn type_context(&mut self) -> &mut T {
1075        &mut self.type_context
1076    }
1077
1078    // TODO: return Result
1079    fn try_load_variable(&mut self, variable: &str) -> Option<TypedValue<T>> {
1080        self.lookup(variable).map(|(_idx, var)| var)
1081    }
1082
1083    fn place_of_variable(&mut self, variable: &str) -> Result<Place, Box<str>> {
1084        let root = self
1085            .lookup(variable)
1086            .map(|(address, _var)| address)
1087            .ok_or_else(|| format!("Variable not found: {variable}").into_boxed_str())?;
1088        Ok(Place {
1089            root,
1090            path: Box::new([]),
1091        })
1092    }
1093
1094    fn load_place(&mut self, place: &Place) -> Result<TypedValue<T>, Box<str>> {
1095        self.resolve_place_mut(place).map(|v| v.clone())
1096    }
1097
1098    fn store_place(&mut self, place: &Place, value: &TypedValue<T>) -> Result<(), Box<str>> {
1099        let slot = self.resolve_place_mut(place)?;
1100        slot.clone_from(value);
1101        Ok(())
1102    }
1103
1104    fn struct_fields(&self, struct_name: &str) -> Option<Vec<(Box<str>, Box<str>)>> {
1105        let idx = *self.program.program_structs.get(struct_name)?;
1106        let Some(Item::Struct(struct_def)) = self.program.program.items.get(idx) else {
1107            return None;
1108        };
1109        let source = self.program.source;
1110        Some(
1111            struct_def
1112                .fields
1113                .iter()
1114                .map(|field| {
1115                    (
1116                        Box::from(field.name.source(source)),
1117                        Box::from(field.field_type.type_name.source(source)),
1118                    )
1119                })
1120                .collect(),
1121        )
1122    }
1123
1124    /// Declares a variable in the context.
1125    fn declare(&mut self, variable: &str, value: TypedValue<T>) {
1126        self.stack.last_mut().unwrap().declare(variable, value);
1127    }
1128
1129    /// Assigns a new value to a variable in the context.
1130    fn assign_variable(&mut self, variable: &str, value: &TypedValue<T>) -> Result<(), Box<str>> {
1131        if self.stack.last_mut().unwrap().store(variable, value) {
1132            return Ok(());
1133        }
1134        if self.stack[0].store(variable, value) {
1135            return Ok(());
1136        }
1137
1138        Err(format!("Variable not found: {variable}").into_boxed_str())
1139    }
1140
1141    fn call_function(
1142        &mut self,
1143        function_name: &str,
1144        args: &[TypedValue<T>],
1145    ) -> Result<TypedValue<T>, FunctionCallError> {
1146        let program = self.program.clone();
1147        let Some(fn_item) = self.program.program_functions.get(function_name) else {
1148            // Call out to a Rust function
1149            return match program.functions.borrow().get(function_name) {
1150                Some(func) => func.call(self.type_context(), args),
1151                None => Err(FunctionCallError::FunctionNotFound),
1152            };
1153        };
1154
1155        // Call a Somni function
1156        let Some(ast::Item::Function(function)) = program.program.items.get(*fn_item) else {
1157            return Err(FunctionCallError::FunctionNotFound);
1158        };
1159        self.evaluate_any_function_impl(function, args)
1160            .map_err(|err| {
1161                FunctionCallError::Other(
1162                    format!(
1163                        "{:?}",
1164                        ExpressionError {
1165                            source: self.program.source,
1166                            error: err,
1167                        }
1168                    )
1169                    .into_boxed_str(),
1170                )
1171            })
1172    }
1173
1174    /// Opens a new scope in the current stack frame.
1175    fn open_scope(&mut self) {
1176        // TODO: error handling
1177        self.stack.last_mut().unwrap().open_scope();
1178    }
1179
1180    /// Closes the last scope in the current stack frame.
1181    fn close_scope(&mut self) {
1182        // TODO: error handling
1183        self.stack.last_mut().unwrap().close_scope();
1184    }
1185}
1186
1187#[macro_export]
1188#[doc(hidden)]
1189macro_rules! for_all_tuples {
1190    ($pat:tt => $code:tt;) => {
1191        macro_rules! inner { $pat => $code; }
1192
1193        inner!();
1194        inner!(V1);
1195        inner!(V1, V2);
1196        inner!(V1, V2, V3);
1197        inner!(V1, V2, V3, V4);
1198        inner!(V1, V2, V3, V4, V5);
1199        inner!(V1, V2, V3, V4, V5, V6);
1200        inner!(V1, V2, V3, V4, V5, V6, V7);
1201        inner!(V1, V2, V3, V4, V5, V6, V7, V8);
1202        inner!(V1, V2, V3, V4, V5, V6, V7, V8, V9);
1203        inner!(V1, V2, V3, V4, V5, V6, V7, V8, V9, V10);
1204    };
1205}
1206
1207#[cfg(test)]
1208mod test {
1209    use std::path::Path;
1210
1211    use super::*;
1212
1213    fn strip_ansi(s: impl AsRef<str>) -> String {
1214        use ansi_parser::AnsiParser;
1215        fn text_block(output: ansi_parser::Output<'_>) -> Option<&str> {
1216            match output {
1217                ansi_parser::Output::TextBlock(text) => Some(text),
1218                _ => None,
1219            }
1220        }
1221
1222        s.as_ref()
1223            .ansi_parse()
1224            .filter_map(text_block)
1225            .collect::<String>()
1226    }
1227
1228    #[test]
1229    fn test_evaluating_exprs() {
1230        let mut ctx = Context::new();
1231
1232        ctx.add_variable::<i64>("signed", 30);
1233        ctx.add_variable::<u64>("value", 30);
1234        ctx.add_function("func", |v: u64| 2 * v);
1235        ctx.add_function("func2", |v1: u64, v2: u64| v1 + v2);
1236        ctx.add_function("five", || "five");
1237        ctx.add_function("is_five", |num: &str| num == "five");
1238        ctx.add_function("concatenate", |a: &str, b: &str| format!("{a}{b}"));
1239
1240        assert_eq!(ctx.evaluate::<bool>("value / 5 == 6"), Ok(true));
1241        assert_eq!(ctx.evaluate::<bool>("five() == \"five\""), Ok(true));
1242        assert_eq!(
1243            ctx.evaluate::<bool>("is_five(five()) != is_five(\"six\")"),
1244            Ok(true)
1245        );
1246        assert_eq!(ctx.evaluate::<u64>("func(20) / 5"), Ok(8));
1247        assert_eq!(
1248            ctx.evaluate::<TypedValue>("func(20) / 5"),
1249            Ok(TypedValue::Int(8))
1250        );
1251        assert_eq!(ctx.evaluate::<u64>("func2(20, 20) / 5"), Ok(8));
1252        assert_eq!(ctx.evaluate::<bool>("true & false"), Ok(false));
1253        assert_eq!(ctx.evaluate::<bool>("!true"), Ok(false));
1254        assert_eq!(ctx.evaluate::<bool>("false | false"), Ok(false));
1255        assert_eq!(ctx.evaluate::<bool>("true ^ true"), Ok(false));
1256        assert_eq!(ctx.evaluate::<u64>("!0x1111"), Ok(0xFFFF_FFFF_FFFF_EEEE));
1257        assert_eq!(
1258            ctx.evaluate::<String>("concatenate(five(), \"six\")"),
1259            Ok(String::from("fivesix"))
1260        );
1261        assert_eq!(ctx.evaluate::<bool>("signed * 2 == 60"), Ok(true));
1262        assert_eq!(ctx.evaluate::<i64>("*&signed"), Ok(30));
1263    }
1264
1265    #[test]
1266    fn test_context_is_mutable() {
1267        let mut ctx = Context::new();
1268
1269        ctx.add_variable::<u64>("value", 30);
1270
1271        ctx.evaluate::<()>("value = 5").unwrap();
1272        assert_eq!(ctx.evaluate::<bool>("value == 5"), Ok(true));
1273    }
1274
1275    #[test]
1276    fn closing_scope_releases_its_variables() {
1277        let mut frame = StackFrame::<DefaultTypeSet>::new();
1278        frame.declare("outer", TypedValue::Int(1));
1279        frame.open_scope();
1280        frame.declare("inner", TypedValue::Int(2));
1281
1282        frame.close_scope();
1283
1284        assert_eq!(frame.variables.len(), 1);
1285        assert_eq!(frame.lookup_index("outer"), Some(0));
1286        assert_eq!(frame.lookup_index("inner"), None);
1287    }
1288
1289    #[test]
1290    fn test_evaluating_exprs_with_u32() {
1291        let mut ctx = Context::<TypeSet32>::new_with_types();
1292
1293        ctx.add_variable::<u32>("value", 30);
1294        ctx.add_function("func", |v: u32| 2 * v);
1295        ctx.add_function("func2", |v1: u32, v2: u32| v1 + v2);
1296
1297        assert_eq!(ctx.evaluate::<bool>("value / 5 == 6"), Ok(true));
1298        assert_eq!(ctx.evaluate::<u32>("func(20) / 5"), Ok(8));
1299        assert_eq!(ctx.evaluate::<u32>("func2(20, 20) / 5"), Ok(8));
1300    }
1301
1302    #[test]
1303    fn test_evaluating_exprs_with_u128() {
1304        let mut ctx = Context::<TypeSet128>::new_with_types();
1305
1306        ctx.add_variable::<u128>("value", 30);
1307        ctx.add_function("func", |v: u128| 2 * v);
1308        ctx.add_function("func2", |v1: u128, v2: u128| v1 + v2);
1309
1310        assert_eq!(ctx.evaluate::<bool>("value / 5 == 6"), Ok(true));
1311        assert_eq!(ctx.evaluate::<u128>("func(20) / 5"), Ok(8));
1312        assert_eq!(ctx.evaluate::<u128>("func2(20, 20) / 5"), Ok(8));
1313    }
1314
1315    #[test]
1316    fn test_evaluate_function() {
1317        let mut ctx =
1318            Context::parse("fn multiply_with_global(a: int) -> int { return a * global; }")
1319                .unwrap();
1320
1321        ctx.add_variable::<u64>("global", 3);
1322
1323        assert_eq!(
1324            ctx.evaluate::<bool>("multiply_with_global(2) == 6"),
1325            Ok(true)
1326        );
1327        assert!(
1328            ctx.evaluate::<bool>("multiply_with_global(\"2\") == 6")
1329                .is_err()
1330        );
1331    }
1332
1333    #[test]
1334    fn run_eval_tests() {
1335        fn filter(path: &Path) -> bool {
1336            let Ok(env) = std::env::var("TEST_FILTER") else {
1337                // No filter set, walk folders and somni source files.
1338                return path.is_dir() || path.extension().map_or(false, |ext| ext == "sm");
1339            };
1340
1341            Path::new(&env) == path
1342        }
1343
1344        fn walk(dir: &Path, on_file: &impl Fn(&Path)) {
1345            for entry in std::fs::read_dir(dir)
1346                .unwrap_or_else(|_| panic!("Folder not found: {}", dir.display()))
1347                .flatten()
1348            {
1349                let path = entry.path();
1350
1351                if !filter(&path) {
1352                    continue;
1353                }
1354
1355                if path.is_file() {
1356                    on_file(&path);
1357                } else {
1358                    walk(&path, on_file);
1359                }
1360            }
1361        }
1362
1363        fn run_eval_test(path: &Path) {
1364            type Types = WithIterator<DefaultTypeSet>;
1365
1366            fn parse(source: &str) -> Context<'_, Types> {
1367                let mut context = Context::<Types>::parse_with_types(source).unwrap();
1368
1369                context.add_function("add_from_rust", |a: u64, b: u64| -> i64 { (a + b) as i64 });
1370                context.add_function("assert", |a: bool| a); // No-op to test calling Rust functions from expressions
1371                context.add_function("reverse", |s: &str| s.chars().rev().collect::<String>());
1372                context.add_function("range", |a: u64, b: u64| {
1373                    SomniIterator::new((a..b).map(TypedValue::<DefaultTypeSet>::Int))
1374                });
1375
1376                context
1377            }
1378
1379            let test_name = path.file_stem().unwrap();
1380            let parent = path.parent().unwrap().canonicalize().unwrap();
1381            let vm_error = parent.join(test_name).join("stderr");
1382            let expr_error = parent.join(test_name).join("stderr_expr");
1383            let source = std::fs::read_to_string(path).unwrap();
1384
1385            let expressions = source
1386                .lines()
1387                .filter_map(|line| line.trim().strip_prefix("//@"))
1388                .collect::<Vec<_>>();
1389
1390            let mut context = parse(&source);
1391            let fail_expected = std::fs::exists(&expr_error).unwrap_or(false)
1392                || std::fs::exists(&vm_error).unwrap_or(false);
1393
1394            let blessed = std::env::var("BLESS").as_deref() == Ok("1");
1395
1396            for expression in &expressions {
1397                let expression = if let Some(e) = expression.strip_prefix('+') {
1398                    // `//@+` preserves VM state (like changes to globals)
1399                    e.trim()
1400                } else {
1401                    // `//@` resets VM state (like changes to globals)
1402                    context = parse(&source);
1403                    expression
1404                };
1405                println!("Running `{expression}`");
1406                match context.evaluate::<TypedValue<Types>>(expression) {
1407                    Ok(_) if fail_expected => {
1408                        panic!(
1409                            "Expected {} to fail evaluating, but it succeeded",
1410                            path.display()
1411                        )
1412                    }
1413                    Ok(value) => assert_eq!(
1414                        value,
1415                        TypedValue::Bool(true),
1416                        "{}: Expression `{expression}` evaluated to {value:?}",
1417                        path.display()
1418                    ),
1419                    Err(e) if fail_expected => {
1420                        let error = strip_ansi(format!("{e:?}"));
1421                        if blessed {
1422                            std::fs::write(&expr_error, error).unwrap();
1423                        } else {
1424                            let expected_error = std::fs::read_to_string(&expr_error).unwrap();
1425                            pretty_assertions::assert_eq!(strip_ansi(expected_error), error);
1426                        }
1427                    }
1428                    Err(e) => panic!("{}: {e:?}", path.display()),
1429                };
1430            }
1431        }
1432
1433        walk("../tests/eval".as_ref(), &|path| {
1434            run_eval_test(path);
1435        });
1436    }
1437
1438    #[test]
1439    fn test_struct_literals_and_field_access() {
1440        let program = r#"
1441struct Point { x: int, y: int }
1442
1443fn make() -> Point {
1444    return Point { x: 3, y: 4 };
1445}
1446
1447fn sum_sq() -> int {
1448    var p = make();
1449    return p.x * p.x + p.y * p.y;
1450}
1451"#;
1452        let mut ctx = Context::parse(program).unwrap();
1453        assert_eq!(ctx.evaluate::<bool>("sum_sq() == 25"), Ok(true));
1454    }
1455
1456    #[test]
1457    fn test_struct_field_write() {
1458        let program = r#"
1459struct Point { x: int, y: int }
1460
1461fn moved() -> int {
1462    var p = Point { x: 1, y: 2 };
1463    p.x = 10;
1464    p.y = p.y + 5;
1465    return p.x + p.y;
1466}
1467"#;
1468        let mut ctx = Context::parse(program).unwrap();
1469        assert_eq!(ctx.evaluate::<bool>("moved() == 17"), Ok(true));
1470    }
1471
1472    #[test]
1473    fn test_nested_struct() {
1474        let program = r#"
1475struct Point { x: int, y: int }
1476struct Line { start: Point, end: Point }
1477
1478fn build() -> int {
1479    var l = Line { start: Point { x: 1, y: 2 }, end: Point { x: 3, y: 4 } };
1480    l.end.x = 30;
1481    return l.start.x + l.end.x;
1482}
1483"#;
1484        let mut ctx = Context::parse(program).unwrap();
1485        assert_eq!(ctx.evaluate::<bool>("build() == 31"), Ok(true));
1486    }
1487
1488    #[test]
1489    fn test_struct_pass_by_reference_and_autoderef() {
1490        let program = r#"
1491struct Point { x: int, y: int }
1492
1493fn scale(p: &Point, factor: int) {
1494    p.x = p.x * factor;
1495    p.y = p.y * factor;
1496}
1497
1498fn run() -> int {
1499    var p = Point { x: 2, y: 3 };
1500    scale(&p, 4);
1501    return p.x + p.y;
1502}
1503"#;
1504        let mut ctx = Context::parse(program).unwrap();
1505        assert_eq!(ctx.evaluate::<bool>("run() == 20"), Ok(true));
1506    }
1507
1508    #[test]
1509    fn test_reference_to_field() {
1510        let program = r#"
1511struct Point { x: int, y: int }
1512
1513fn double(v: &int) {
1514    *v = *v * 2;
1515}
1516
1517fn run() -> int {
1518    var p = Point { x: 5, y: 6 };
1519    double(&p.x);
1520    return p.x;
1521}
1522"#;
1523        let mut ctx = Context::parse(program).unwrap();
1524        assert_eq!(ctx.evaluate::<bool>("run() == 10"), Ok(true));
1525    }
1526
1527    #[test]
1528    fn test_struct_equality() {
1529        let program = r#"
1530struct Point { x: int, y: int }
1531
1532fn a() -> Point { return Point { x: 1, y: 2 }; }
1533fn b() -> Point { return Point { x: 1, y: 2 }; }
1534fn c() -> Point { return Point { x: 1, y: 9 }; }
1535"#;
1536        let mut ctx = Context::parse(program).unwrap();
1537        assert_eq!(ctx.evaluate::<bool>("a() == b()"), Ok(true));
1538        assert_eq!(ctx.evaluate::<bool>("a() != c()"), Ok(true));
1539        assert_eq!(ctx.evaluate::<bool>("a() == c()"), Ok(false));
1540    }
1541
1542    #[test]
1543    fn test_struct_boundary_and_macro() {
1544        let program = r#"
1545struct Point { x: int, y: int }
1546"#;
1547        let mut ctx = Context::parse(program).unwrap();
1548        ctx.add_function("origin_distance_sq", |p: SomniStruct| -> i64 {
1549            let TypedValue::Int(x) = p.fields()["x"] else {
1550                panic!("x not an int")
1551            };
1552            let TypedValue::Int(y) = p.fields()["y"] else {
1553                panic!("y not an int")
1554            };
1555            (x * x + y * y) as i64
1556        });
1557
1558        // Build a struct from Rust and hand it to the program.
1559        let tc = ctx.type_context();
1560        let point = somni_struct!(tc, Point { x: 3u64, y: 4u64 });
1561        assert_eq!(point.name(), "Point");
1562
1563        assert_eq!(
1564            ctx.evaluate::<bool>("origin_distance_sq(Point { x: 3, y: 4 }) == 25"),
1565            Ok(true)
1566        );
1567    }
1568
1569    #[test]
1570    fn test_unknown_struct_is_error() {
1571        let mut ctx = Context::new();
1572        assert!(ctx.evaluate::<TypedValue>("Nope { x: 1 }").is_err());
1573    }
1574
1575    #[test]
1576    fn test_eval_error() {
1577        let mut ctx = Context::new();
1578
1579        ctx.add_function("func", |v1: u64, v2: u64| v1 + v2);
1580
1581        let err = ctx
1582            .evaluate::<u64>("func(20, true)")
1583            .expect_err("Expected expression to return an error");
1584
1585        pretty_assertions::assert_eq!(
1586            strip_ansi(format!("\n{err:?}")),
1587            r#"
1588Evaluation error
1589 ---> at line 1 column 10
1590  |
15911 | func(20, true)
1592  |          ^^^^ func expects argument 1 to be u64, got bool"#,
1593        );
1594    }
1595}