miden_core/mast/node/
loop_node.rs1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use super::{
5 MastForestContributor, MastNodeContext, MastNodeExt, fingerprint_with_child_fingerprints,
6};
7use crate::{
8 Felt, Word,
9 chiplets::hasher,
10 mast::{MastForest, MastForestError, MastNodeId},
11 operations::opcodes,
12 prettier::PrettyPrint,
13 utils::LookupByIdx,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct LoopNode {
28 body: MastNodeId,
29 digest: Word,
30}
31
32impl LoopNode {
34 pub const DOMAIN: Felt = Felt::new_unchecked(opcodes::LOOP as u64);
36}
37
38impl LoopNode {
39 pub fn body(&self) -> MastNodeId {
41 self.body
42 }
43}
44
45impl LoopNode {
49 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
50 LoopNodePrettyPrint { loop_node: self, mast_forest }
51 }
52
53 pub(super) fn to_pretty_print<'a>(
54 &'a self,
55 mast_forest: &'a MastForest,
56 ) -> impl PrettyPrint + 'a {
57 LoopNodePrettyPrint { loop_node: self, mast_forest }
58 }
59}
60
61struct LoopNodePrettyPrint<'a> {
62 loop_node: &'a LoopNode,
63 mast_forest: &'a MastForest,
64}
65
66impl PrettyPrint for LoopNodePrettyPrint<'_> {
67 fn render(&self) -> crate::prettier::Document {
68 use crate::prettier::*;
69
70 let loop_body = self.mast_forest[self.loop_node.body].to_pretty_print(self.mast_forest);
71
72 indent(4, const_text("loop") + nl() + loop_body.render()) + nl() + const_text("end")
73 }
74}
75
76impl fmt::Display for LoopNodePrettyPrint<'_> {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 use crate::prettier::PrettyPrint;
79 self.pretty_print(f)
80 }
81}
82
83impl MastNodeExt for LoopNode {
87 fn digest(&self) -> Word {
98 self.digest
99 }
100
101 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
102 Box::new(LoopNode::to_display(self, mast_forest))
103 }
104
105 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
106 Box::new(LoopNode::to_pretty_print(self, mast_forest))
107 }
108
109 fn has_children(&self) -> bool {
110 true
111 }
112
113 fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
114 target.push(self.body());
115 }
116
117 fn for_each_child<F>(&self, mut f: F)
118 where
119 F: FnMut(MastNodeId),
120 {
121 f(self.body());
122 }
123
124 fn domain(&self) -> Felt {
125 Self::DOMAIN
126 }
127
128 type Builder = LoopNodeBuilder;
129
130 fn to_builder(self, _forest: &MastForest) -> Self::Builder {
131 LoopNodeBuilder::new(self.body).with_digest(self.digest)
132 }
133}
134
135#[derive(Debug)]
138pub struct LoopNodeBuilder {
139 body: MastNodeId,
140 digest: Option<Word>,
141}
142
143impl LoopNodeBuilder {
144 pub fn new(body: MastNodeId) -> Self {
146 Self { body, digest: None }
147 }
148
149 pub fn build(self, context: &impl MastNodeContext) -> Result<LoopNode, MastForestError> {
151 let body = context
152 .get_node_by_id(self.body)
153 .ok_or_else(|| MastForestError::NodeIdOverflow(self.body, context.node_count()))?;
154
155 let digest = if let Some(forced_digest) = self.digest {
157 forced_digest
158 } else {
159 let body_hash = body.digest();
160
161 hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
162 };
163
164 Ok(LoopNode { body: self.body, digest })
165 }
166
167 pub(in crate::mast) fn build_linked(self) -> Result<LoopNode, MastForestError> {
168 Ok(LoopNode {
169 body: self.body,
170 digest: self.digest.ok_or(MastForestError::DigestRequiredForDeserialization)?,
171 })
172 }
173}
174
175#[cfg(any(test, feature = "arbitrary"))]
176impl LoopNodeBuilder {
177 pub fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
179 let node = self.build(forest)?;
180 forest.nodes.push(node.into()).map_err(|_| MastForestError::TooManyNodes)
181 }
182}
183
184impl MastForestContributor for LoopNodeBuilder {
185 fn fingerprint_for_node(
186 &self,
187 context: &impl MastNodeContext,
188 hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
189 ) -> Result<Word, MastForestError> {
190 let node_digest = if let Some(forced_digest) = self.digest {
191 forced_digest
192 } else {
193 let body_hash = context
194 .get_node_by_id(self.body)
195 .ok_or_else(|| MastForestError::NodeIdOverflow(self.body, context.node_count()))?
196 .digest();
197
198 hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
199 };
200
201 fingerprint_with_child_fingerprints(node_digest, &[self.body], context, hash_by_node_id)
202 }
203
204 fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
205 LoopNodeBuilder {
206 body: *remapping.get(self.body).unwrap_or(&self.body),
207 digest: self.digest,
208 }
209 }
210
211 fn with_digest(mut self, digest: Word) -> Self {
212 self.digest = Some(digest);
213 self
214 }
215}
216
217#[cfg(any(test, feature = "arbitrary"))]
218impl proptest::prelude::Arbitrary for LoopNodeBuilder {
219 type Parameters = ();
220 type Strategy = proptest::strategy::BoxedStrategy<Self>;
221
222 fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {
223 use proptest::prelude::*;
224
225 any::<MastNodeId>().prop_map(Self::new).boxed()
226 }
227}