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 start with an ASCII alphabetic character, or one of: `_`, `.`, or `$`
149/// - If it starts with a non-ASCII alphabetic character, it must contain at least one ASCII
150///   alphabetic character, e.g. `_`, `$_` are not valid symbols, but `_a` or `$_a` are.
151/// - Otherwise, the name may consist of any number of printable ASCII characters, e.g.
152///   alphanumerics, punctuation, etc. Control characters and the like are explicitly not allowed.
153///
154/// NOTE: In Miden Assembly source files, a procedure name must be quoted in double-quotes if it
155/// contains any characters other than ASCII alphanumerics, or `_`. See examples below.
156///
157/// ## Examples
158///
159/// ```masm,ignore
160/// # All ASCII alphanumeric, bare identifier
161/// proc.foo
162///   ...
163/// end
164///
165/// # All ASCII alphanumeric, leading underscore
166/// proc._foo
167///   ...
168/// end
169///
170/// # A symbol which contains `::`, which would be treated as a namespace operator, so requires quoting
171/// proc."std::foo"
172///   ...
173/// end
174///
175/// # A complex procedure name representing a monomorphized Rust function, requires quoting
176/// proc."alloc::alloc::box_free::<dyn alloc::boxed::FnBox<(), Output = ()>>"
177///   ...
178/// end
179/// ```
180#[derive(Debug, Clone)]
181pub struct ProcedureName(Ident);
182
183impl ProcedureName {
184    /// Reserved name for a main procedure.
185    pub const MAIN_PROC_NAME: &'static str = "#main";
186
187    /// Creates a [ProcedureName] from `name`.
188    pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
189        name.as_ref().parse()
190    }
191
192    /// Creates a [ProcedureName] from `name`
193    pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
194        name.as_ref().parse::<Self>().map(|name| name.with_span(span))
195    }
196
197    /// Sets the span for this [ProcedureName].
198    pub fn with_span(self, span: SourceSpan) -> Self {
199        Self(self.0.with_span(span))
200    }
201
202    /// Creates a [ProcedureName] from its raw components.
203    ///
204    /// It is expected that the caller has already validated that the name meets all validity
205    /// criteria for procedure names, for example, the parser only lexes/parses valid identifiers,
206    /// so by construction all such identifiers are valid.
207    ///
208    /// NOTE: This function is perma-unstable, it may be removed or modified at any time.
209    pub fn new_unchecked(name: Ident) -> Self {
210        Self(name)
211    }
212
213    /// Obtains a procedure name representing the reserved name for the executable entrypoint
214    /// (i.e., `main`).
215    pub fn main() -> Self {
216        let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
217        Self(Ident::new_unchecked(Span::unknown(name)))
218    }
219
220    /// Is this the reserved name for the executable entrypoint (i.e. `main`)?
221    pub fn is_main(&self) -> bool {
222        self.0.as_str() == Self::MAIN_PROC_NAME
223    }
224
225    /// Returns a string reference for this procedure name.
226    pub fn as_str(&self) -> &str {
227        self.as_ref()
228    }
229}
230
231impl Eq for ProcedureName {}
232
233impl PartialEq for ProcedureName {
234    fn eq(&self, other: &Self) -> bool {
235        self.0 == other.0
236    }
237}
238
239impl Ord for ProcedureName {
240    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
241        self.0.cmp(&other.0)
242    }
243}
244
245impl PartialOrd for ProcedureName {
246    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
247        Some(self.cmp(other))
248    }
249}
250
251impl Hash for ProcedureName {
252    fn hash<H: Hasher>(&self, state: &mut H) {
253        self.0.hash(state);
254    }
255}
256
257impl Spanned for ProcedureName {
258    fn span(&self) -> SourceSpan {
259        self.0.span()
260    }
261}
262
263impl From<ProcedureName> for miette::SourceSpan {
264    fn from(name: ProcedureName) -> Self {
265        name.span().into()
266    }
267}
268
269impl core::ops::Deref for ProcedureName {
270    type Target = str;
271
272    #[inline(always)]
273    fn deref(&self) -> &Self::Target {
274        self.0.as_str()
275    }
276}
277
278impl AsRef<Ident> for ProcedureName {
279    #[inline(always)]
280    fn as_ref(&self) -> &Ident {
281        &self.0
282    }
283}
284
285impl AsRef<str> for ProcedureName {
286    #[inline(always)]
287    fn as_ref(&self) -> &str {
288        self.0.as_str()
289    }
290}
291
292impl PartialEq<str> for ProcedureName {
293    fn eq(&self, other: &str) -> bool {
294        self.0.as_ref() == other
295    }
296}
297
298impl PartialEq<Ident> for ProcedureName {
299    fn eq(&self, other: &Ident) -> bool {
300        &self.0 == other
301    }
302}
303
304impl fmt::Display for ProcedureName {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.write_str(&self.0)
307    }
308}
309
310/// Parsing
311impl FromStr for ProcedureName {
312    type Err = IdentError;
313
314    fn from_str(s: &str) -> Result<Self, Self::Err> {
315        let mut chars = s.char_indices();
316        let raw = match chars.next() {
317            None => Err(IdentError::Empty),
318            Some((_, '"')) => loop {
319                if let Some((pos, c)) = chars.next() {
320                    match c {
321                        '"' => {
322                            if chars.next().is_some() {
323                                break Err(IdentError::InvalidChars { ident: s.into() });
324                            }
325                            let tok = &s[1..pos];
326                            break Ok(Arc::from(tok.to_string().into_boxed_str()));
327                        },
328                        c if c.is_alphanumeric() => continue,
329                        '_' | '$' | '-' | '!' | '?' | '<' | '>' | ':' | '.' => continue,
330                        _ => break Err(IdentError::InvalidChars { ident: s.into() }),
331                    }
332                } else {
333                    break Err(IdentError::InvalidChars { ident: s.into() });
334                }
335            },
336            Some((_, c))
337                if c.is_ascii_lowercase() || c == '_' || c == '-' || c == '$' || c == '.' =>
338            {
339                if chars.as_str().contains(|c| match c {
340                    c if c.is_ascii_alphanumeric() => false,
341                    '_' | '-' | '$' | '.' => false,
342                    _ => true,
343                }) {
344                    Err(IdentError::InvalidChars { ident: s.into() })
345                } else {
346                    Ok(Arc::from(s.to_string().into_boxed_str()))
347                }
348            },
349            Some((_, c)) if c.is_ascii_uppercase() => Err(IdentError::Casing(CaseKindError::Snake)),
350            Some(_) => Err(IdentError::InvalidChars { ident: s.into() }),
351        }?;
352        Ok(Self(Ident::new_unchecked(Span::unknown(raw))))
353    }
354}
355
356impl Serializable for ProcedureName {
357    fn write_into<W: ByteWriter>(&self, target: &mut W) {
358        self.as_str().write_into(target)
359    }
360}
361
362impl Deserializable for ProcedureName {
363    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
364        let str: String = source.read()?;
365        let proc_name = ProcedureName::new(str)
366            .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
367        Ok(proc_name)
368    }
369}
370
371// ARBITRARY IMPLEMENTATION
372// ================================================================================================
373
374#[cfg(feature = "testing")]
375impl proptest::prelude::Arbitrary for ProcedureName {
376    type Parameters = ();
377
378    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
379        use proptest::prelude::*;
380        // see https://doc.rust-lang.org/rustc/symbol-mangling/v0.html#symbol-grammar-summary
381        let all_possible_chars_in_mangled_name =
382            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
383        let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
384        let plain = ProcedureName::new("user_func").unwrap();
385        let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
386        prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
387    }
388
389    type Strategy = proptest::prelude::BoxedStrategy<Self>;
390}
391
392// TESTS
393// ================================================================================================
394
395/// Tests
396#[cfg(test)]
397mod tests {
398    use proptest::prelude::*;
399    use vm_core::utils::{Deserializable, Serializable};
400
401    use super::ProcedureName;
402
403    proptest! {
404        #[test]
405        fn procedure_name_serialization_roundtrip(path in any::<ProcedureName>()) {
406            let bytes = path.to_bytes();
407            let deserialized = ProcedureName::read_from_bytes(&bytes).unwrap();
408            assert_eq!(path, deserialized);
409        }
410    }
411}