miden_assembly_syntax/ast/procedure/
procedure.rs

1use alloc::{collections::BTreeSet, string::String};
2use core::fmt;
3
4use miden_debug_types::{SourceSpan, Span, Spanned};
5
6use super::ProcedureName;
7use crate::ast::{Attribute, AttributeSet, Block, DocString, FunctionType, Invoke, Visibility};
8
9// PROCEDURE
10// ================================================================================================
11
12/// Represents a concrete procedure definition in Miden Assembly syntax
13#[derive(Clone)]
14pub struct Procedure {
15    /// The source span of the full procedure body
16    span: SourceSpan,
17    /// The documentation attached to this procedure
18    docs: Option<DocString>,
19    /// The attributes attached to this procedure
20    attrs: AttributeSet,
21    /// The local name of this procedure
22    name: ProcedureName,
23    /// The visibility of this procedure (i.e. whether it is exported or not)
24    visibility: Visibility,
25    /// A flag which indicates that this procedure is only syscall-able
26    syscall: bool,
27    /// The type signature of this procedure, if known
28    ty: Option<FunctionType>,
29    /// The number of locals to allocate for this procedure
30    num_locals: u16,
31    /// The body of the procedure
32    body: Block,
33    /// The set of callees for any call-like instruction in the procedure body.
34    pub(crate) invoked: BTreeSet<Invoke>,
35}
36
37/// Construction
38impl Procedure {
39    /// Creates a new [Procedure] from the given source span, visibility, name, number of locals,
40    /// and code block.
41    pub fn new(
42        span: SourceSpan,
43        visibility: Visibility,
44        name: ProcedureName,
45        num_locals: u16,
46        body: Block,
47    ) -> Self {
48        Self {
49            span,
50            docs: None,
51            attrs: Default::default(),
52            name,
53            visibility,
54            syscall: false,
55            ty: None,
56            num_locals,
57            invoked: Default::default(),
58            body,
59        }
60    }
61
62    /// Same as [Self::new], but marks the procedure as being only visible to `syscall`
63    pub fn new_syscall(
64        span: SourceSpan,
65        name: ProcedureName,
66        num_locals: u16,
67        body: Block,
68    ) -> Self {
69        Self {
70            span,
71            docs: None,
72            attrs: Default::default(),
73            name,
74            visibility: Visibility::Public,
75            syscall: true,
76            ty: None,
77            num_locals,
78            invoked: Default::default(),
79            body,
80        }
81    }
82
83    /// Specify the type signature of this procedure
84    pub fn with_signature(mut self, ty: FunctionType) -> Self {
85        self.ty = Some(ty);
86        self
87    }
88
89    /// Adds documentation to this procedure definition
90    pub fn with_docs(mut self, docs: Option<Span<String>>) -> Self {
91        self.docs = docs.map(DocString::new);
92        self
93    }
94
95    /// Adds attributes to this procedure definition
96    pub fn with_attributes<I>(mut self, attrs: I) -> Self
97    where
98        I: IntoIterator<Item = Attribute>,
99    {
100        self.attrs.extend(attrs);
101        self
102    }
103
104    /// Override the visibility of this procedure.
105    pub fn set_visibility(&mut self, visibility: Visibility) {
106        self.visibility = visibility;
107    }
108
109    /// Override the syscall-only flag of this procedure.
110    pub fn set_syscall(&mut self, yes: bool) {
111        self.syscall = yes;
112    }
113
114    /// Override the type signature of this procedure.
115    pub fn set_signature(&mut self, signature: FunctionType) {
116        self.ty = Some(signature);
117    }
118
119    /// Override the number of locals allocated by this procedure.
120    pub fn set_num_locals(&mut self, num_locals: u16) {
121        self.num_locals = num_locals;
122    }
123}
124
125/// Metadata
126impl Procedure {
127    /// Returns the name of this procedure within its containing module.
128    pub fn name(&self) -> &ProcedureName {
129        &self.name
130    }
131
132    /// Returns the visibility of this procedure
133    pub fn visibility(&self) -> Visibility {
134        self.visibility
135    }
136
137    /// Returns whether or not this procedure requires `syscall` to invoke
138    pub fn is_syscall(&self) -> bool {
139        self.syscall
140    }
141
142    /// Get the type signature of this procedure, if known
143    pub fn signature(&self) -> Option<&FunctionType> {
144        self.ty.as_ref()
145    }
146
147    /// Get the type signature of this procedure mutably, if known
148    pub fn signature_mut(&mut self) -> Option<&mut FunctionType> {
149        self.ty.as_mut()
150    }
151
152    /// Returns the number of locals allocated by this procedure.
153    pub fn num_locals(&self) -> u16 {
154        self.num_locals
155    }
156
157    /// Returns true if this procedure corresponds to the `begin`..`end` block of an executable
158    /// module.
159    pub fn is_entrypoint(&self) -> bool {
160        self.name.is_main()
161    }
162
163    /// Returns the documentation for this procedure, if present.
164    pub fn docs(&self) -> Option<Span<&str>> {
165        self.docs.as_ref().map(|docstring| docstring.as_spanned_str())
166    }
167
168    /// Get the attributes attached to this procedure
169    #[inline]
170    pub fn attributes(&self) -> &AttributeSet {
171        &self.attrs
172    }
173
174    /// Get the attributes attached to this procedure, mutably
175    #[inline]
176    pub fn attributes_mut(&mut self) -> &mut AttributeSet {
177        &mut self.attrs
178    }
179
180    /// Returns true if this procedure has an attribute named `name`
181    #[inline]
182    pub fn has_attribute(&self, name: impl AsRef<str>) -> bool {
183        self.attrs.has(name)
184    }
185
186    /// Returns the attribute named `name`, if present
187    #[inline]
188    pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<&Attribute> {
189        self.attrs.get(name)
190    }
191
192    /// Returns a reference to the [Block] containing the body of this procedure.
193    pub fn body(&self) -> &Block {
194        &self.body
195    }
196
197    /// Returns a mutable reference to the [Block] containing the body of this procedure.
198    pub fn body_mut(&mut self) -> &mut Block {
199        &mut self.body
200    }
201
202    /// Returns an iterator over the operations of the top-level [Block] of this procedure.
203    pub fn iter(&self) -> core::slice::Iter<'_, crate::ast::Op> {
204        self.body.iter()
205    }
206
207    /// Returns an iterator over the set of invocation targets of this procedure, i.e. the callees
208    /// of any call instructions in the body of this procedure.
209    pub fn invoked<'a, 'b: 'a>(&'b self) -> impl Iterator<Item = &'a Invoke> + 'a {
210        if self.invoked.is_empty() {
211            InvokedIter::Empty
212        } else {
213            InvokedIter::NonEmpty(self.invoked.iter())
214        }
215    }
216
217    /// Extends the set of procedures known to be invoked by this procedure.
218    ///
219    /// This is for internal use only, and is called during semantic analysis once we've identified
220    /// the set of invoked procedures for a given definition.
221    pub fn extend_invoked<I>(&mut self, iter: I)
222    where
223        I: IntoIterator<Item = Invoke>,
224    {
225        self.invoked.extend(iter);
226    }
227}
228
229#[doc(hidden)]
230pub(crate) enum InvokedIter<'a, I: Iterator<Item = &'a Invoke> + 'a> {
231    Empty,
232    NonEmpty(I),
233}
234
235impl<'a, I> Iterator for InvokedIter<'a, I>
236where
237    I: Iterator<Item = &'a Invoke> + 'a,
238{
239    type Item = <I as Iterator>::Item;
240
241    fn next(&mut self) -> Option<Self::Item> {
242        match self {
243            Self::Empty => None,
244            Self::NonEmpty(iter) => {
245                let result = iter.next();
246                if result.is_none() {
247                    *self = Self::Empty;
248                }
249                result
250            },
251        }
252    }
253}
254
255impl Spanned for Procedure {
256    fn span(&self) -> SourceSpan {
257        self.span
258    }
259}
260
261impl crate::prettier::PrettyPrint for Procedure {
262    fn render(&self) -> crate::prettier::Document {
263        use crate::prettier::*;
264
265        let mut doc = self
266            .docs
267            .as_ref()
268            .map(|docstring| docstring.render())
269            .unwrap_or(Document::Empty);
270
271        if !self.attrs.is_empty() {
272            doc += self
273                .attrs
274                .iter()
275                .map(|attr| attr.render())
276                .reduce(|acc, attr| acc + nl() + attr)
277                .unwrap_or(Document::Empty);
278        }
279
280        if self.is_entrypoint() {
281            doc += const_text("begin");
282        } else {
283            if self.num_locals > 0 {
284                doc += text(format!("@locals(\"{}\")", &self.num_locals)) + nl();
285            }
286            match self.signature() {
287                Some(sig) if sig.cc != crate::ast::types::CallConv::Fast => {
288                    doc += text(format!("@callconv(\"{}\")", &sig.cc)) + nl();
289                },
290                _ => (),
291            }
292            if self.visibility.is_public() {
293                doc += display(self.visibility) + const_text(" ");
294            }
295            doc += const_text("proc") + const_text(" ") + display(&self.name);
296            if let Some(sig) = self.signature() {
297                doc += sig.render();
298            }
299        }
300
301        doc + self.body.render() + const_text("end") + nl()
302    }
303}
304
305impl fmt::Debug for Procedure {
306    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307        f.debug_struct("Procedure")
308            .field("docs", &self.docs)
309            .field("attrs", &self.attrs)
310            .field("name", &self.name)
311            .field("visibility", &self.visibility)
312            .field("syscall", &self.syscall)
313            .field("num_locals", &self.num_locals)
314            .field("ty", &self.ty)
315            .field("body", &self.body)
316            .field("invoked", &self.invoked)
317            .finish()
318    }
319}
320
321impl Eq for Procedure {}
322
323impl PartialEq for Procedure {
324    fn eq(&self, other: &Self) -> bool {
325        self.name == other.name
326            && self.visibility == other.visibility
327            && self.syscall == other.syscall
328            && self.num_locals == other.num_locals
329            && self.ty == other.ty
330            && self.body == other.body
331            && self.attrs == other.attrs
332            && self.docs == other.docs
333    }
334}