Skip to main content

graphcal_compiler/registry/
runtime_value.rs

1//! Runtime value types used during evaluation.
2
3use indexmap::IndexMap;
4
5use crate::dag_id::DagId;
6use crate::registry::declared_type::{IndexTypeRef, StructTypeRef};
7use crate::syntax::names::{
8    FieldName, IndexName, IndexVariantName, ResolvedIndexVariant, StructTypeName,
9};
10
11/// The kind of a [`RuntimeValue`], used in type-mismatch error reporting.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum RuntimeValueKind {
14    Scalar,
15    Bool,
16    Int,
17    Label {
18        index_name: IndexTypeRef,
19        variant: IndexVariantName,
20    },
21    Struct {
22        type_name: StructTypeRef,
23    },
24    Indexed {
25        index_name: IndexTypeRef,
26    },
27    RangeLabel,
28    Datetime,
29}
30
31impl std::fmt::Display for RuntimeValueKind {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Self::Scalar => write!(f, "Scalar"),
35            Self::Bool => write!(f, "Bool"),
36            Self::Int => write!(f, "Int"),
37            Self::Label {
38                index_name,
39                variant,
40            } => {
41                let display_index = index_name.display_name();
42                write!(f, "label `{}`", variant.qualified_by(&display_index))
43            }
44            Self::Struct { type_name } => write!(f, "struct `{type_name}`"),
45            Self::Indexed { index_name } => write!(f, "indexed value `{index_name}[...]`"),
46            Self::RangeLabel => write!(f, "RangeLabel"),
47            Self::Datetime => write!(f, "Datetime"),
48        }
49    }
50}
51
52/// Error returned when a [`RuntimeValue`] accessor is called on an incompatible variant.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct RuntimeValueError {
55    /// What kind of value was expected (e.g. "scalar", "Bool").
56    pub expected: &'static str,
57    /// A description of what the value was being used for.
58    pub context: String,
59    /// The actual variant encountered.
60    pub actual: RuntimeValueKind,
61}
62
63impl std::fmt::Display for RuntimeValueError {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        write!(
66            f,
67            "expected {} for {}, got {}",
68            self.expected, self.context, self.actual
69        )
70    }
71}
72
73impl std::error::Error for RuntimeValueError {}
74
75/// A runtime value: either a scalar (f64 in SI units), a bool, a struct, or an indexed collection.
76#[derive(Debug, Clone)]
77pub enum RuntimeValue {
78    Scalar(f64),
79    Bool(bool),
80    Int(i64),
81    /// Internal carrier for a named-index loop case.
82    ///
83    /// The type checker prevents this from escaping as a user value; evaluation
84    /// uses it only for index access and `match` dispatch.
85    Label {
86        index_name: IndexTypeRef,
87        variant: IndexVariantName,
88    },
89    Struct {
90        /// Concrete constructor/type leaf for display plus optional canonical owning struct type.
91        ///
92        /// Tagged-union values keep the constructor leaf here (e.g. `LowThrust`) while
93        /// module-aware evaluation stores the owning union's canonical `StructType` identity
94        /// in the carrier's `resolved` field.
95        type_name: StructTypeRef,
96        fields: IndexMap<FieldName, Self>,
97    },
98    /// An indexed collection: maps variant names to values, preserving declaration order.
99    Indexed {
100        index_name: IndexTypeRef,
101        entries: IndexMap<IndexVariantName, Self>,
102    },
103    /// A range index label during `Unfold` iteration.
104    /// Carries the step index and SI value (for arithmetic like `t - prev_t`).
105    RangeLabel {
106        step_index: usize,
107        value: f64,
108    },
109    /// A datetime instant (internally stored as a `hifitime::Epoch`).
110    Datetime(hifitime::Epoch),
111}
112
113impl RuntimeValue {
114    /// Construct a label value after resolving the index leaf into an owner.
115    #[must_use]
116    pub fn label_with_owner(
117        owner: DagId,
118        index_name: IndexName,
119        variant: IndexVariantName,
120    ) -> Self {
121        Self::Label {
122            index_name: IndexTypeRef::with_owner(owner, index_name),
123            variant,
124        }
125    }
126
127    /// Construct a module-aware label value from a resolved index-variant reference.
128    #[must_use]
129    pub fn resolved_label(resolved: &ResolvedIndexVariant) -> Self {
130        Self::Label {
131            index_name: IndexTypeRef::from_resolved(resolved.index().clone()),
132            variant: resolved.variant().clone(),
133        }
134    }
135
136    /// Construct a struct value after resolving the struct leaf into an owner.
137    #[must_use]
138    pub fn struct_with_owner(
139        owner: DagId,
140        type_name: StructTypeName,
141        fields: IndexMap<FieldName, Self>,
142    ) -> Self {
143        Self::Struct {
144            type_name: StructTypeRef::with_owner(owner, type_name),
145            fields,
146        }
147    }
148
149    /// Construct an indexed value after resolving the index leaf into an owner.
150    #[must_use]
151    pub fn indexed_with_owner(
152        owner: DagId,
153        index_name: IndexName,
154        entries: IndexMap<IndexVariantName, Self>,
155    ) -> Self {
156        Self::Indexed {
157            index_name: IndexTypeRef::with_owner(owner, index_name),
158            entries,
159        }
160    }
161
162    /// Return the [`RuntimeValueKind`] of this value.
163    #[must_use]
164    pub fn kind(&self) -> RuntimeValueKind {
165        match self {
166            Self::Scalar(_) => RuntimeValueKind::Scalar,
167            Self::Bool(_) => RuntimeValueKind::Bool,
168            Self::Int(_) => RuntimeValueKind::Int,
169            Self::Label {
170                index_name,
171                variant,
172            } => RuntimeValueKind::Label {
173                index_name: index_name.clone(),
174                variant: variant.clone(),
175            },
176            Self::Struct { type_name, .. } => RuntimeValueKind::Struct {
177                type_name: type_name.clone(),
178            },
179            Self::Indexed { index_name, .. } => RuntimeValueKind::Indexed {
180                index_name: index_name.clone(),
181            },
182            Self::RangeLabel { .. } => RuntimeValueKind::RangeLabel,
183            Self::Datetime(_) => RuntimeValueKind::Datetime,
184        }
185    }
186
187    /// Extract scalar value, returning a structured error if this is not a scalar.
188    /// (Type mismatches should be caught by `dim_check`; this is defense-in-depth.)
189    pub fn expect_scalar(&self, context: &str) -> Result<f64, RuntimeValueError> {
190        match self {
191            Self::Scalar(v) | Self::RangeLabel { value: v, .. } => Ok(*v),
192            other => Err(RuntimeValueError {
193                expected: "scalar",
194                context: context.to_string(),
195                actual: other.kind(),
196            }),
197        }
198    }
199
200    /// Extract boolean value, returning a structured error if this is not a Bool.
201    /// (Type mismatches should be caught by `dim_check`; this is defense-in-depth.)
202    pub fn expect_bool(&self, context: &str) -> Result<bool, RuntimeValueError> {
203        match self {
204            Self::Bool(b) => Ok(*b),
205            other => Err(RuntimeValueError {
206                expected: "Bool",
207                context: context.to_string(),
208                actual: other.kind(),
209            }),
210        }
211    }
212}