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