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();
315        let raw = match chars.next() {
316            None => Err(IdentError::Empty),
317            Some((_, '"')) => loop {
318                if let Some((pos, c)) = chars.next() {
319                    match c {
320                        '"' => {
321                            if chars.next().is_some() {
322                                break Err(IdentError::InvalidChars { ident: s.into() });
323                            }
324                            let tok = &s[1..pos];
325                            break Ok(Arc::from(tok.to_string().into_boxed_str()));
326                        },
327                        c if c.is_alphanumeric() || c.is_ascii_graphic() => continue,
328                        _ => break Err(IdentError::InvalidChars { ident: s.into() }),
329                    }
330                } else {
331                    break Err(IdentError::InvalidChars { ident: s.into() });
332                }
333            },
334            Some((_, c))
335                if c.is_ascii_lowercase() || c == '_' || c == '-' || c == '$' || c == '.' =>
336            {
337                if chars.as_str().contains(|c| match c {
338                    c if c.is_ascii_alphanumeric() => false,
339                    '_' | '-' | '$' | '.' => false,
340                    _ => true,
341                }) {
342                    Err(IdentError::InvalidChars { ident: s.into() })
343                } else {
344                    Ok(Arc::from(s.to_string().into_boxed_str()))
345                }
346            },
347            Some((_, c)) if c.is_ascii_uppercase() => Err(IdentError::Casing(CaseKindError::Snake)),
348            Some(_) => Err(IdentError::InvalidChars { ident: s.into() }),
349        }?;
350        Ok(Self(Ident::from_raw_parts(Span::unknown(raw))))
351    }
352}
353
354impl Serializable for ProcedureName {
355    fn write_into<W: ByteWriter>(&self, target: &mut W) {
356        self.as_str().write_into(target)
357    }
358}
359
360impl Deserializable for ProcedureName {
361    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
362        let str: String = source.read()?;
363        let proc_name = ProcedureName::new(str)
364            .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
365        Ok(proc_name)
366    }
367}
368
369// ARBITRARY IMPLEMENTATION
370// ================================================================================================
371
372#[cfg(feature = "testing")]
373impl proptest::prelude::Arbitrary for ProcedureName {
374    type Parameters = ();
375
376    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
377        use proptest::prelude::*;
378        // see https://doc.rust-lang.org/rustc/symbol-mangling/v0.html#symbol-grammar-summary
379        let all_possible_chars_in_mangled_name =
380            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
381        let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
382        let plain = ProcedureName::new("user_func").unwrap();
383        let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
384        prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
385    }
386
387    type Strategy = proptest::prelude::BoxedStrategy<Self>;
388}
389
390// TESTS
391// ================================================================================================
392
393/// Tests
394#[cfg(test)]
395mod tests {
396    use proptest::prelude::*;
397    use vm_core::utils::{Deserializable, Serializable};
398
399    use super::ProcedureName;
400
401    proptest! {
402        #[test]
403        fn procedure_name_serialization_roundtrip(path in any::<ProcedureName>()) {
404            let bytes = path.to_bytes();
405            let deserialized = ProcedureName::read_from_bytes(&bytes).unwrap();
406            assert_eq!(path, deserialized);
407        }
408    }
409}