1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::{
8 MastForestContributor, MastNodeContext, MastNodeExt, fingerprint_with_child_fingerprints,
9};
10use crate::{
11 Felt, Word,
12 chiplets::hasher,
13 mast::{MastForest, MastForestError, MastNodeId},
14 operations::opcodes,
15 prettier::PrettyPrint,
16 utils::LookupByIdx,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
27pub struct JoinNode {
28 children: [MastNodeId; 2],
29 digest: Word,
30}
31
32impl JoinNode {
34 pub const DOMAIN: Felt = Felt::new_unchecked(opcodes::JOIN as u64);
36}
37
38impl JoinNode {
40 pub fn first(&self) -> MastNodeId {
42 self.children[0]
43 }
44
45 pub fn second(&self) -> MastNodeId {
48 self.children[1]
49 }
50}
51
52impl JoinNode {
56 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
57 JoinNodePrettyPrint { join_node: self, mast_forest }
58 }
59
60 pub(super) fn to_pretty_print<'a>(
61 &'a self,
62 mast_forest: &'a MastForest,
63 ) -> impl PrettyPrint + 'a {
64 JoinNodePrettyPrint { join_node: self, mast_forest }
65 }
66}
67
68struct JoinNodePrettyPrint<'a> {
69 join_node: &'a JoinNode,
70 mast_forest: &'a MastForest,
71}
72
73impl PrettyPrint for JoinNodePrettyPrint<'_> {
74 #[rustfmt::skip]
75 fn render(&self) -> crate::prettier::Document {
76 use crate::prettier::*;
77
78 let first_child =
79 self.mast_forest[self.join_node.first()].to_pretty_print(self.mast_forest);
80 let second_child =
81 self.mast_forest[self.join_node.second()].to_pretty_print(self.mast_forest);
82
83 indent(
84 4,
85 const_text("join")
86 + nl()
87 + first_child.render()
88 + nl()
89 + second_child.render(),
90 ) + nl() + const_text("end")
91 }
92}
93
94impl fmt::Display for JoinNodePrettyPrint<'_> {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 use crate::prettier::PrettyPrint;
97 self.pretty_print(f)
98 }
99}
100
101impl MastNodeExt for JoinNode {
105 fn digest(&self) -> Word {
117 self.digest
118 }
119
120 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
121 Box::new(JoinNode::to_display(self, mast_forest))
122 }
123
124 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
125 Box::new(JoinNode::to_pretty_print(self, mast_forest))
126 }
127
128 fn has_children(&self) -> bool {
129 true
130 }
131
132 fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
133 target.push(self.first());
134 target.push(self.second());
135 }
136
137 fn for_each_child<F>(&self, mut f: F)
138 where
139 F: FnMut(MastNodeId),
140 {
141 f(self.first());
142 f(self.second());
143 }
144
145 fn domain(&self) -> Felt {
146 Self::DOMAIN
147 }
148
149 type Builder = JoinNodeBuilder;
150
151 fn to_builder(self, _forest: &MastForest) -> Self::Builder {
152 JoinNodeBuilder::new(self.children).with_digest(self.digest)
153 }
154}
155
156#[cfg(all(feature = "arbitrary", test))]
160impl proptest::prelude::Arbitrary for JoinNode {
161 type Parameters = ();
162
163 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
164 use proptest::prelude::*;
165
166 use crate::Felt;
167
168 (any::<MastNodeId>(), any::<MastNodeId>(), any::<[u64; 4]>())
170 .prop_map(|(first_child, second_child, digest_array)| {
171 let digest = Word::from(digest_array.map(Felt::new_unchecked));
173 JoinNode {
175 children: [first_child, second_child],
176 digest,
177 }
178 })
179 .no_shrink() .boxed()
181 }
182
183 type Strategy = proptest::prelude::BoxedStrategy<Self>;
184}
185
186#[derive(Debug)]
189pub struct JoinNodeBuilder {
190 children: [MastNodeId; 2],
191 digest: Option<Word>,
192}
193
194impl JoinNodeBuilder {
195 pub fn new(children: [MastNodeId; 2]) -> Self {
197 Self { children, digest: None }
198 }
199
200 pub fn build(self, context: &impl MastNodeContext) -> Result<JoinNode, MastForestError> {
202 let left_child = context.get_node_by_id(self.children[0]).ok_or_else(|| {
203 MastForestError::NodeIdOverflow(self.children[0], context.node_count())
204 })?;
205 let right_child = context.get_node_by_id(self.children[1]).ok_or_else(|| {
206 MastForestError::NodeIdOverflow(self.children[1], context.node_count())
207 })?;
208
209 let digest = if let Some(forced_digest) = self.digest {
211 forced_digest
212 } else {
213 let left_child_hash = left_child.digest();
214 let right_child_hash = right_child.digest();
215
216 hasher::merge_in_domain(&[left_child_hash, right_child_hash], JoinNode::DOMAIN)
217 };
218
219 Ok(JoinNode { children: self.children, digest })
220 }
221
222 pub(in crate::mast) fn build_linked(self) -> Result<JoinNode, MastForestError> {
223 Ok(JoinNode {
224 children: self.children,
225 digest: self.digest.ok_or(MastForestError::DigestRequiredForDeserialization)?,
226 })
227 }
228}
229
230#[cfg(any(test, feature = "arbitrary"))]
231impl JoinNodeBuilder {
232 pub fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
234 let node = self.build(forest)?;
235 forest.nodes.push(node.into()).map_err(|_| MastForestError::TooManyNodes)
236 }
237}
238
239impl MastForestContributor for JoinNodeBuilder {
240 fn fingerprint_for_node(
241 &self,
242 context: &impl MastNodeContext,
243 hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
244 ) -> Result<Word, MastForestError> {
245 let node_digest = if let Some(forced_digest) = self.digest {
246 forced_digest
247 } else {
248 let left_child_hash = context
249 .get_node_by_id(self.children[0])
250 .ok_or_else(|| {
251 MastForestError::NodeIdOverflow(self.children[0], context.node_count())
252 })?
253 .digest();
254 let right_child_hash = context
255 .get_node_by_id(self.children[1])
256 .ok_or_else(|| {
257 MastForestError::NodeIdOverflow(self.children[1], context.node_count())
258 })?
259 .digest();
260
261 hasher::merge_in_domain(&[left_child_hash, right_child_hash], JoinNode::DOMAIN)
262 };
263
264 fingerprint_with_child_fingerprints(node_digest, &self.children, context, hash_by_node_id)
265 }
266
267 fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
268 JoinNodeBuilder {
269 children: [
270 *remapping.get(self.children[0]).unwrap_or(&self.children[0]),
271 *remapping.get(self.children[1]).unwrap_or(&self.children[1]),
272 ],
273 digest: self.digest,
274 }
275 }
276
277 fn with_digest(mut self, digest: Word) -> Self {
278 self.digest = Some(digest);
279 self
280 }
281}
282
283#[cfg(any(test, feature = "arbitrary"))]
284impl proptest::prelude::Arbitrary for JoinNodeBuilder {
285 type Parameters = ();
286 type Strategy = proptest::strategy::BoxedStrategy<Self>;
287
288 fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {
289 use proptest::prelude::*;
290
291 any::<[MastNodeId; 2]>().prop_map(Self::new).boxed()
292 }
293}