miden_assembly_syntax/ast/procedure/
name.rs

1use alloc::{
2    string::{String, ToString},
3    sync::Arc,
4};
5use core::{
6    fmt,
7    hash::{Hash, Hasher},
8    ops::Deref,
9    str::FromStr,
10};
11
12use miden_debug_types::{SourceSpan, Span, Spanned};
13use miden_utils_diagnostics::{IntoDiagnostic, Report, miette};
14
15use crate::{
16    Path, PathBuf,
17    ast::{CaseKindError, Ident, IdentError},
18};
19
20// QUALIFIED PROCEDURE NAME
21// ================================================================================================
22
23/// Represents a qualified procedure name, e.g. `std::math::u64::add`, parsed into it's
24/// constituent [Path] and [ProcedureName] components.
25///
26/// A qualified procedure name can be context-sensitive, i.e. the module path might refer
27/// to an imported
28#[derive(Clone)]
29#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
30pub struct QualifiedProcedureName {
31    /// The source span associated with this identifier.
32    #[cfg_attr(feature = "arbitrary", proptest(value = "SourceSpan::default()"))]
33    span: SourceSpan,
34    #[cfg_attr(
35        feature = "arbitrary",
36        proptest(strategy = "crate::arbitrary::path::path_random_length(2)")
37    )]
38    path: Arc<Path>,
39}
40
41impl QualifiedProcedureName {
42    /// Create a new [QualifiedProcedureName] with the given fully-qualified module path
43    /// and procedure name.
44    pub fn new(module: impl AsRef<Path>, name: ProcedureName) -> Self {
45        let span = name.span();
46        let path = module.as_ref().join(name).into();
47        Self { span, path }
48    }
49
50    #[inline(always)]
51    pub fn with_span(mut self, span: SourceSpan) -> Self {
52        self.span = span;
53        self
54    }
55
56    /// Get the module/namespace of this procedure
57    pub fn namespace(&self) -> &Path {
58        self.path.parent().unwrap()
59    }
60
61    /// Get the name of this procedure as a `str`
62    pub fn name(&self) -> &str {
63        self.path.last().unwrap()
64    }
65
66    /// Get this [QualifiedProcedureName] as a [Path]
67    #[inline]
68    pub fn as_path(&self) -> &Path {
69        &self.path
70    }
71
72    /// Get this [QualifiedProcedureName] as a `Span<&Path>`
73    #[inline]
74    pub fn to_spanned_path(&self) -> Span<&Path> {
75        Span::new(self.span, self.as_path())
76    }
77
78    #[inline]
79    pub fn into_inner(self) -> Arc<Path> {
80        self.path
81    }
82}
83
84impl Deref for QualifiedProcedureName {
85    type Target = Path;
86
87    #[inline]
88    fn deref(&self) -> &Self::Target {
89        &self.path
90    }
91}
92
93impl From<Arc<Path>> for QualifiedProcedureName {
94    fn from(path: Arc<Path>) -> Self {
95        assert!(path.parent().is_some());
96        Self { span: SourceSpan::default(), path }
97    }
98}
99
100impl From<PathBuf> for QualifiedProcedureName {
101    fn from(path: PathBuf) -> Self {
102        assert!(path.parent().is_some());
103        Self {
104            span: SourceSpan::default(),
105            path: path.into(),
106        }
107    }
108}
109
110impl From<&Path> for QualifiedProcedureName {
111    fn from(path: &Path) -> Self {
112        assert!(path.parent().is_some());
113        Self {
114            span: SourceSpan::default(),
115            path: path.to_path_buf().into(),
116        }
117    }
118}
119
120impl From<QualifiedProcedureName> for Arc<Path> {
121    fn from(value: QualifiedProcedureName) -> Self {
122        value.path
123    }
124}
125
126impl FromStr for QualifiedProcedureName {
127    type Err = Report;
128
129    fn from_str(s: &str) -> Result<Self, Self::Err> {
130        let path = PathBuf::new(s).into_diagnostic()?;
131        if path.parent().is_none() {
132            return Err(Report::msg("invalid procedure path: must be qualified with a namespace"));
133        }
134        ProcedureName::validate(path.last().unwrap()).into_diagnostic()?;
135        Ok(Self {
136            span: SourceSpan::default(),
137            path: path.into(),
138        })
139    }
140}
141
142impl TryFrom<&str> for QualifiedProcedureName {
143    type Error = Report;
144
145    fn try_from(name: &str) -> Result<Self, Self::Error> {
146        Self::from_str(name)
147    }
148}
149
150impl TryFrom<String> for QualifiedProcedureName {
151    type Error = Report;
152
153    fn try_from(name: String) -> Result<Self, Self::Error> {
154        Self::from_str(&name)
155    }
156}
157
158impl Eq for QualifiedProcedureName {}
159
160impl PartialEq for QualifiedProcedureName {
161    fn eq(&self, other: &Self) -> bool {
162        self.path == other.path
163    }
164}
165
166impl Ord for QualifiedProcedureName {
167    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
168        self.path.cmp(&other.path)
169    }
170}
171
172impl PartialOrd for QualifiedProcedureName {
173    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
174        Some(self.cmp(other))
175    }
176}
177
178impl From<QualifiedProcedureName> for miette::SourceSpan {
179    fn from(fqn: QualifiedProcedureName) -> Self {
180        fqn.span.into()
181    }
182}
183
184impl Spanned for QualifiedProcedureName {
185    fn span(&self) -> SourceSpan {
186        self.span
187    }
188}
189
190impl fmt::Debug for QualifiedProcedureName {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192        f.debug_struct("QualifiedProcedureName").field("path", &self.path).finish()
193    }
194}
195
196impl crate::prettier::PrettyPrint for QualifiedProcedureName {
197    fn render(&self) -> miden_core::prettier::Document {
198        use crate::prettier::*;
199
200        display(self)
201    }
202}
203
204impl fmt::Display for QualifiedProcedureName {
205    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206        fmt::Display::fmt(&self.path, f)
207    }
208}
209
210// PROCEDURE NAME
211// ================================================================================================
212
213/// Procedure name.
214///
215/// The symbol represented by this type must comply with the following rules:
216///
217/// - It must consist only of alphanumeric characters, or ASCII graphic characters.
218/// - If it starts with a non-alphabetic character, it must contain at least one alphanumeric
219///   character, e.g. `_`, `$_` are not valid procedure symbols, but `_a` or `$_a` are.
220///
221/// NOTE: In Miden Assembly source files, a procedure name must be quoted in double-quotes if it
222/// contains any characters other than ASCII alphanumerics, or `_`. See examples below.
223///
224/// ## Examples
225///
226/// ```masm,ignore
227/// # All ASCII alphanumeric, bare identifier
228/// proc foo
229///   ...
230/// end
231///
232/// # All ASCII alphanumeric, leading underscore
233/// proc _foo
234///   ...
235/// end
236///
237/// # A symbol which contains `::`, which would be treated as a namespace operator, so requires
238/// # quoting
239/// proc "miden::core::foo"
240///   ...
241/// end
242///
243/// # A complex procedure name representing a monomorphized Rust function, requires quoting
244/// proc "alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>"
245///   ...
246/// end
247/// ```
248#[derive(Debug, Clone)]
249pub struct ProcedureName(Ident);
250
251impl ProcedureName {
252    /// Reserved name for a main procedure.
253    pub const MAIN_PROC_NAME: &'static str = Ident::MAIN;
254
255    /// Creates a [ProcedureName] from `name`.
256    pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
257        name.as_ref().parse()
258    }
259
260    /// Creates a [ProcedureName] from `name`
261    pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
262        name.as_ref().parse::<Self>().map(|name| name.with_span(span))
263    }
264
265    /// Sets the span for this [ProcedureName].
266    pub fn with_span(self, span: SourceSpan) -> Self {
267        Self(self.0.with_span(span))
268    }
269
270    /// Creates a [ProcedureName] from its raw components.
271    ///
272    /// It is expected that the caller has already validated that the name meets all validity
273    /// criteria for procedure names, for example, the parser only lexes/parses valid identifiers,
274    /// so by construction all such identifiers are valid.
275    ///
276    /// NOTE: This function is perma-unstable, it may be removed or modified at any time.
277    pub fn from_raw_parts(name: Ident) -> Self {
278        Self(name)
279    }
280
281    /// Obtains a procedure name representing the reserved name for the executable entrypoint
282    /// (i.e., `main`).
283    pub fn main() -> Self {
284        let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
285        Self(Ident::from_raw_parts(Span::unknown(name)))
286    }
287
288    /// Is this the reserved name for the executable entrypoint (i.e. `main`)?
289    pub fn is_main(&self) -> bool {
290        self.0.as_str() == Self::MAIN_PROC_NAME
291    }
292
293    /// Returns a string reference for this procedure name.
294    pub fn as_str(&self) -> &str {
295        self.as_ref()
296    }
297
298    /// Returns the underlying [Ident] representation
299    pub fn as_ident(&self) -> Ident {
300        self.0.clone()
301    }
302}
303
304impl Eq for ProcedureName {}
305
306impl PartialEq for ProcedureName {
307    fn eq(&self, other: &Self) -> bool {
308        self.0 == other.0
309    }
310}
311
312impl Ord for ProcedureName {
313    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
314        self.0.cmp(&other.0)
315    }
316}
317
318impl PartialOrd for ProcedureName {
319    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
320        Some(self.cmp(other))
321    }
322}
323
324impl Hash for ProcedureName {
325    fn hash<H: Hasher>(&self, state: &mut H) {
326        self.0.hash(state);
327    }
328}
329
330impl Spanned for ProcedureName {
331    fn span(&self) -> SourceSpan {
332        self.0.span()
333    }
334}
335
336impl From<ProcedureName> for miette::SourceSpan {
337    fn from(name: ProcedureName) -> Self {
338        name.span().into()
339    }
340}
341
342impl core::ops::Deref for ProcedureName {
343    type Target = str;
344
345    #[inline(always)]
346    fn deref(&self) -> &Self::Target {
347        self.0.as_str()
348    }
349}
350
351impl AsRef<Ident> for ProcedureName {
352    #[inline(always)]
353    fn as_ref(&self) -> &Ident {
354        &self.0
355    }
356}
357
358impl AsRef<str> for ProcedureName {
359    #[inline(always)]
360    fn as_ref(&self) -> &str {
361        self.0.as_str()
362    }
363}
364
365impl From<ProcedureName> for Ident {
366    #[inline(always)]
367    fn from(name: ProcedureName) -> Self {
368        name.0
369    }
370}
371
372impl PartialEq<str> for ProcedureName {
373    fn eq(&self, other: &str) -> bool {
374        self.0.as_str() == other
375    }
376}
377
378impl PartialEq<Ident> for ProcedureName {
379    fn eq(&self, other: &Ident) -> bool {
380        &self.0 == other
381    }
382}
383
384impl fmt::Display for ProcedureName {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        f.write_str(&self.0)
387    }
388}
389
390/// Parsing
391impl FromStr for ProcedureName {
392    type Err = IdentError;
393
394    fn from_str(s: &str) -> Result<Self, Self::Err> {
395        let extracted = Self::validate(s)?;
396
397        Ok(Self(Ident::from_raw_parts(Span::unknown(extracted.into()))))
398    }
399}
400
401impl ProcedureName {
402    fn validate(name: &str) -> Result<&str, IdentError> {
403        let mut chars = name.char_indices().peekable();
404
405        // peek the first char
406        match chars.peek() {
407            None => return Err(IdentError::Empty),
408            Some((_, '"')) => chars.next(),
409            Some((_, c)) if is_valid_unquoted_identifier_char(*c) => {
410                // All character for unqouted should be valid
411                let all_chars_valid =
412                    chars.all(|(_, char)| is_valid_unquoted_identifier_char(char));
413
414                if all_chars_valid {
415                    return Ok(name);
416                } else {
417                    return Err(IdentError::InvalidChars { ident: name.into() });
418                }
419            },
420            Some((_, c)) if c.is_ascii_uppercase() => {
421                return Err(IdentError::Casing(CaseKindError::Snake));
422            },
423            Some(_) => return Err(IdentError::InvalidChars { ident: name.into() }),
424        };
425
426        // parsing the qouted identifier
427        while let Some((pos, char)) = chars.next() {
428            match char {
429                '"' => {
430                    if chars.next().is_some() {
431                        return Err(IdentError::InvalidChars { ident: name.into() });
432                    }
433                    return Ok(&name[1..pos]);
434                },
435                c => {
436                    // if char is not alphanumeric or asciigraphic then return err
437                    if !(c.is_alphanumeric() || c.is_ascii_graphic()) {
438                        return Err(IdentError::InvalidChars { ident: name.into() });
439                    }
440                },
441            }
442        }
443
444        // if while loop has not returned then the qoute was not closed
445        Err(IdentError::InvalidChars { ident: name.into() })
446    }
447}
448
449// FROM STR HELPER
450fn is_valid_unquoted_identifier_char(c: char) -> bool {
451    c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '$' | '.')
452}
453
454// ARBITRARY IMPLEMENTATION
455// ================================================================================================
456
457#[cfg(feature = "arbitrary")]
458impl proptest::prelude::Arbitrary for ProcedureName {
459    type Parameters = ();
460
461    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
462        use proptest::prelude::*;
463
464        prop_oneof![
465            1 => crate::arbitrary::ident::ident_any_random_length(),
466            2 => crate::arbitrary::ident::bare_ident_any_random_length(),
467        ]
468        .prop_map(ProcedureName)
469        .boxed()
470    }
471
472    type Strategy = proptest::prelude::BoxedStrategy<Self>;
473}