miden_assembly_syntax/ast/
constants.rs

1use alloc::{boxed::Box, string::String, sync::Arc};
2use core::fmt;
3
4use miden_core::FieldElement;
5use miden_debug_types::{SourceSpan, Span, Spanned};
6
7use super::DocString;
8use crate::{
9    Felt,
10    ast::Ident,
11    parser::{IntValue, ParsingError, WordValue},
12};
13
14// CONSTANT
15// ================================================================================================
16
17/// Represents a constant definition in Miden Assembly syntax, i.e. `const.FOO = 1 + 1`.
18#[derive(Clone)]
19pub struct Constant {
20    /// The source span of the definition.
21    pub span: SourceSpan,
22    /// The documentation string attached to this definition.
23    pub docs: Option<DocString>,
24    /// The name of the constant.
25    pub name: Ident,
26    /// The expression associated with the constant.
27    pub value: ConstantExpr,
28}
29
30impl Constant {
31    /// Creates a new [Constant] from the given source span, name, and value.
32    pub fn new(span: SourceSpan, name: Ident, value: ConstantExpr) -> Self {
33        Self { span, docs: None, name, value }
34    }
35
36    /// Adds documentation to this constant declaration.
37    pub fn with_docs(mut self, docs: Option<Span<String>>) -> Self {
38        self.docs = docs.map(DocString::new);
39        self
40    }
41}
42
43impl fmt::Debug for Constant {
44    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45        f.debug_struct("Constant")
46            .field("docs", &self.docs)
47            .field("name", &self.name)
48            .field("value", &self.value)
49            .finish()
50    }
51}
52
53impl crate::prettier::PrettyPrint for Constant {
54    fn render(&self) -> crate::prettier::Document {
55        use crate::prettier::*;
56
57        let mut doc = self
58            .docs
59            .as_ref()
60            .map(|docstring| docstring.render())
61            .unwrap_or(Document::Empty);
62
63        doc += flatten(const_text("const") + const_text(" ") + display(&self.name));
64        doc += const_text(" = ");
65
66        doc + self.value.render()
67    }
68}
69
70impl Eq for Constant {}
71
72impl PartialEq for Constant {
73    fn eq(&self, other: &Self) -> bool {
74        self.name == other.name && self.value == other.value
75    }
76}
77
78impl Spanned for Constant {
79    fn span(&self) -> SourceSpan {
80        self.span
81    }
82}
83
84// CONSTANT EXPRESSION
85// ================================================================================================
86
87/// Represents a constant expression or value in Miden Assembly syntax.
88#[derive(Clone)]
89pub enum ConstantExpr {
90    /// A literal [`Felt`] value.
91    Int(Span<IntValue>),
92    /// A reference to another constant.
93    Var(Ident),
94    /// An binary arithmetic operator.
95    BinaryOp {
96        span: SourceSpan,
97        op: ConstantOp,
98        lhs: Box<ConstantExpr>,
99        rhs: Box<ConstantExpr>,
100    },
101    /// A plain spanned string.
102    String(Ident),
103    /// A literal ['WordValue'].
104    Word(Span<WordValue>),
105    /// A spanned string with a [`HashKind`] showing to which type of value the given string should
106    /// be hashed.
107    Hash(HashKind, Ident),
108}
109
110impl ConstantExpr {
111    /// Unwrap an [`IntValue`] from this expression or panic.
112    ///
113    /// This is used in places where we expect the expression to have been folded to an integer,
114    /// otherwise a bug occurred.
115    #[track_caller]
116    pub fn expect_int(&self) -> IntValue {
117        match self {
118            Self::Int(spanned) => spanned.into_inner(),
119            other => panic!("expected constant expression to be a literal, got {other:#?}"),
120        }
121    }
122
123    /// Unwrap a [`Felt`] value from this expression or panic.
124    ///
125    /// This is used in places where we expect the expression to have been folded to a felt value,
126    /// otherwise a bug occurred.
127    #[track_caller]
128    pub fn expect_felt(&self) -> Felt {
129        match self {
130            Self::Int(spanned) => Felt::new(spanned.inner().as_int()),
131            other => panic!("expected constant expression to be a literal, got {other:#?}"),
132        }
133    }
134
135    pub fn expect_string(&self) -> Arc<str> {
136        match self {
137            Self::String(spanned) => spanned.clone().into_inner(),
138            other => panic!("expected constant expression to be a string, got {other:#?}"),
139        }
140    }
141
142    /// Attempt to fold to a single value.
143    ///
144    /// This will only succeed if the expression has no references to other constants.
145    ///
146    /// # Errors
147    /// Returns an error if an invalid expression is found while folding, such as division by zero.
148    pub fn try_fold(self) -> Result<Self, ParsingError> {
149        match self {
150            Self::String(_) | Self::Word(_) | Self::Int(_) | Self::Var(_) | Self::Hash(..) => {
151                Ok(self)
152            },
153            Self::BinaryOp { span, op, lhs, rhs } => {
154                if rhs.is_literal() {
155                    let rhs = Self::into_inner(rhs).try_fold()?;
156                    match rhs {
157                        Self::String(ident) => {
158                            Err(ParsingError::StringInArithmeticExpression { span: ident.span() })
159                        },
160                        Self::Int(rhs) => {
161                            let lhs = Self::into_inner(lhs).try_fold()?;
162                            match lhs {
163                                Self::String(ident) => {
164                                    Err(ParsingError::StringInArithmeticExpression {
165                                        span: ident.span(),
166                                    })
167                                },
168                                Self::Int(lhs) => {
169                                    let lhs = lhs.into_inner();
170                                    let rhs = rhs.into_inner();
171                                    let is_division =
172                                        matches!(op, ConstantOp::Div | ConstantOp::IntDiv);
173                                    let is_division_by_zero = is_division && rhs == Felt::ZERO;
174                                    if is_division_by_zero {
175                                        return Err(ParsingError::DivisionByZero { span });
176                                    }
177                                    match op {
178                                        ConstantOp::Add => {
179                                            Ok(Self::Int(Span::new(span, lhs + rhs)))
180                                        },
181                                        ConstantOp::Sub => {
182                                            Ok(Self::Int(Span::new(span, lhs - rhs)))
183                                        },
184                                        ConstantOp::Mul => {
185                                            Ok(Self::Int(Span::new(span, lhs * rhs)))
186                                        },
187                                        ConstantOp::Div => {
188                                            Ok(Self::Int(Span::new(span, lhs / rhs)))
189                                        },
190                                        ConstantOp::IntDiv => {
191                                            Ok(Self::Int(Span::new(span, lhs / rhs)))
192                                        },
193                                    }
194                                },
195                                lhs => Ok(Self::BinaryOp {
196                                    span,
197                                    op,
198                                    lhs: Box::new(lhs),
199                                    rhs: Box::new(Self::Int(rhs)),
200                                }),
201                            }
202                        },
203                        rhs => {
204                            let lhs = Self::into_inner(lhs).try_fold()?;
205                            Ok(Self::BinaryOp {
206                                span,
207                                op,
208                                lhs: Box::new(lhs),
209                                rhs: Box::new(rhs),
210                            })
211                        },
212                    }
213                } else {
214                    let lhs = Self::into_inner(lhs).try_fold()?;
215                    Ok(Self::BinaryOp { span, op, lhs: Box::new(lhs), rhs })
216                }
217            },
218        }
219    }
220
221    fn is_literal(&self) -> bool {
222        match self {
223            Self::Int(_) | Self::String(_) | Self::Word(_) | Self::Hash(..) => true,
224            Self::Var(_) => false,
225            Self::BinaryOp { lhs, rhs, .. } => lhs.is_literal() && rhs.is_literal(),
226        }
227    }
228
229    #[inline(always)]
230    #[allow(clippy::boxed_local)]
231    fn into_inner(self: Box<Self>) -> Self {
232        *self
233    }
234}
235
236impl Eq for ConstantExpr {}
237
238impl PartialEq for ConstantExpr {
239    fn eq(&self, other: &Self) -> bool {
240        match (self, other) {
241            (Self::Int(l), Self::Int(y)) => l == y,
242            (Self::Word(l), Self::Word(y)) => l == y,
243            (Self::Var(l), Self::Var(y)) => l == y,
244            (Self::Hash(l_hk, l_i), Self::Hash(r_hk, r_i)) => l_i == r_i && l_hk == r_hk,
245            (
246                Self::BinaryOp { op: lop, lhs: llhs, rhs: lrhs, .. },
247                Self::BinaryOp { op: rop, lhs: rlhs, rhs: rrhs, .. },
248            ) => lop == rop && llhs == rlhs && lrhs == rrhs,
249            _ => false,
250        }
251    }
252}
253
254impl core::hash::Hash for ConstantExpr {
255    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
256        core::mem::discriminant(self).hash(state);
257        match self {
258            Self::Int(value) => value.hash(state),
259            Self::Word(value) => value.hash(state),
260            Self::String(value) => value.hash(state),
261            Self::Var(value) => value.hash(state),
262            Self::Hash(hash_kind, string) => {
263                hash_kind.hash(state);
264                string.hash(state);
265            },
266            Self::BinaryOp { op, lhs, rhs, .. } => {
267                op.hash(state);
268                lhs.hash(state);
269                rhs.hash(state);
270            },
271        }
272    }
273}
274
275impl fmt::Debug for ConstantExpr {
276    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277        match self {
278            Self::Int(lit) => fmt::Debug::fmt(&**lit, f),
279            Self::Word(lit) => fmt::Debug::fmt(&**lit, f),
280            Self::Var(name) | Self::String(name) => fmt::Debug::fmt(&**name, f),
281            Self::Hash(hash_kind, str) => fmt::Debug::fmt(&(str, hash_kind), f),
282            Self::BinaryOp { op, lhs, rhs, .. } => {
283                f.debug_tuple(op.name()).field(lhs).field(rhs).finish()
284            },
285        }
286    }
287}
288
289impl crate::prettier::PrettyPrint for ConstantExpr {
290    fn render(&self) -> crate::prettier::Document {
291        use crate::prettier::*;
292
293        match self {
294            Self::Int(literal) => display(literal),
295            Self::Word(literal) => display(literal),
296            Self::Var(ident) | Self::String(ident) => display(ident),
297            Self::Hash(hash_kind, str) => {
298                flatten(display(hash_kind) + const_text("(") + display(str) + const_text(")"))
299            },
300            Self::BinaryOp { op, lhs, rhs, .. } => {
301                let single_line = lhs.render() + display(op) + rhs.render();
302                let multi_line = lhs.render() + nl() + (display(op)) + rhs.render();
303                single_line | multi_line
304            },
305        }
306    }
307}
308
309impl Spanned for ConstantExpr {
310    fn span(&self) -> SourceSpan {
311        match self {
312            Self::Int(spanned) => spanned.span(),
313            Self::Word(spanned) => spanned.span(),
314            Self::Hash(_, spanned) => spanned.span(),
315            Self::Var(spanned) | Self::String(spanned) => spanned.span(),
316            Self::BinaryOp { span, .. } => *span,
317        }
318    }
319}
320
321// CONSTANT OPERATION
322// ================================================================================================
323
324/// Represents the set of binary arithmetic operators supported in Miden Assembly syntax.
325#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
326pub enum ConstantOp {
327    Add,
328    Sub,
329    Mul,
330    Div,
331    IntDiv,
332}
333
334impl ConstantOp {
335    const fn name(&self) -> &'static str {
336        match self {
337            Self::Add => "Add",
338            Self::Sub => "Sub",
339            Self::Mul => "Mul",
340            Self::Div => "Div",
341            Self::IntDiv => "IntDiv",
342        }
343    }
344}
345
346impl fmt::Display for ConstantOp {
347    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
348        match self {
349            Self::Add => f.write_str("+"),
350            Self::Sub => f.write_str("-"),
351            Self::Mul => f.write_str("*"),
352            Self::Div => f.write_str("/"),
353            Self::IntDiv => f.write_str("//"),
354        }
355    }
356}
357
358// HASH KIND
359// ================================================================================================
360
361/// Represents the type of the final value to which some string value should be converted.
362#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
363pub enum HashKind {
364    /// Reduce a string to a word using Blake3 hash function
365    Word,
366    /// Reduce a string to a felt using Blake3 hash function (via 64-bit reduction)
367    Event,
368}
369
370impl fmt::Display for HashKind {
371    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
372        match self {
373            Self::Word => f.write_str("word"),
374            Self::Event => f.write_str("event"),
375        }
376    }
377}