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