miden_assembly_syntax/ast/procedure/
name.rs1use 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
17use crate::{
18 LibraryNamespace, LibraryPath,
19 ast::{CaseKindError, Ident, IdentError},
20};
21
22#[derive(Clone)]
31#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
32pub struct QualifiedProcedureName {
33 #[cfg_attr(feature = "arbitrary", proptest(value = "SourceSpan::default()"))]
35 pub span: SourceSpan,
36 pub module: LibraryPath,
38 pub name: ProcedureName,
40}
41
42impl QualifiedProcedureName {
43 pub fn new(module: LibraryPath, name: ProcedureName) -> Self {
46 Self {
47 span: SourceSpan::default(),
48 module,
49 name,
50 }
51 }
52
53 pub fn namespace(&self) -> &LibraryNamespace {
55 self.module.namespace()
56 }
57}
58
59impl FromStr for QualifiedProcedureName {
60 type Err = Report;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 match s.rsplit_once("::") {
64 None => Err(Report::msg("invalid fully-qualified procedure name, expected namespace")),
65 Some((path, name)) => {
66 let name = name.parse::<ProcedureName>().into_diagnostic()?;
67 let path = path.parse::<LibraryPath>().into_diagnostic()?;
68 Ok(Self::new(path, name))
69 },
70 }
71 }
72}
73
74impl TryFrom<&str> for QualifiedProcedureName {
75 type Error = Report;
76
77 fn try_from(name: &str) -> Result<Self, Self::Error> {
78 Self::from_str(name)
79 }
80}
81
82impl TryFrom<String> for QualifiedProcedureName {
83 type Error = Report;
84
85 fn try_from(name: String) -> Result<Self, Self::Error> {
86 Self::from_str(&name)
87 }
88}
89
90impl Eq for QualifiedProcedureName {}
91
92impl PartialEq for QualifiedProcedureName {
93 fn eq(&self, other: &Self) -> bool {
94 self.name == other.name && self.module == other.module
95 }
96}
97
98impl Ord for QualifiedProcedureName {
99 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
100 self.module.cmp(&other.module).then_with(|| self.name.cmp(&other.name))
101 }
102}
103
104impl PartialOrd for QualifiedProcedureName {
105 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
106 Some(self.cmp(other))
107 }
108}
109
110impl From<QualifiedProcedureName> for miette::SourceSpan {
111 fn from(fqn: QualifiedProcedureName) -> Self {
112 fqn.span.into()
113 }
114}
115
116impl Spanned for QualifiedProcedureName {
117 fn span(&self) -> SourceSpan {
118 self.span
119 }
120}
121
122impl fmt::Debug for QualifiedProcedureName {
123 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124 f.debug_struct("FullyQualifiedProcedureName")
125 .field("module", &self.module)
126 .field("name", &self.name)
127 .finish()
128 }
129}
130
131impl crate::prettier::PrettyPrint for QualifiedProcedureName {
132 fn render(&self) -> miden_core::prettier::Document {
133 use crate::prettier::*;
134
135 display(self)
136 }
137}
138
139impl fmt::Display for QualifiedProcedureName {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 write!(f, "{}::{}", &self.module, &self.name)
142 }
143}
144
145impl Serializable for QualifiedProcedureName {
146 fn write_into<W: ByteWriter>(&self, target: &mut W) {
147 self.module.write_into(target);
148 self.name.write_into(target);
149 }
150}
151
152impl Deserializable for QualifiedProcedureName {
153 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
154 let module = LibraryPath::read_from(source)?;
155 let name = ProcedureName::read_from(source)?;
156 Ok(Self::new(module, name))
157 }
158}
159
160#[derive(Debug, Clone)]
199pub struct ProcedureName(Ident);
200
201impl ProcedureName {
202 pub const MAIN_PROC_NAME: &'static str = "$main";
204
205 pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
207 name.as_ref().parse()
208 }
209
210 pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
212 name.as_ref().parse::<Self>().map(|name| name.with_span(span))
213 }
214
215 pub fn with_span(self, span: SourceSpan) -> Self {
217 Self(self.0.with_span(span))
218 }
219
220 pub fn from_raw_parts(name: Ident) -> Self {
228 Self(name)
229 }
230
231 pub fn main() -> Self {
234 let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
235 Self(Ident::from_raw_parts(Span::unknown(name)))
236 }
237
238 pub fn is_main(&self) -> bool {
240 self.0.as_str() == Self::MAIN_PROC_NAME
241 }
242
243 pub fn as_str(&self) -> &str {
245 self.as_ref()
246 }
247}
248
249impl Eq for ProcedureName {}
250
251impl PartialEq for ProcedureName {
252 fn eq(&self, other: &Self) -> bool {
253 self.0 == other.0
254 }
255}
256
257impl Ord for ProcedureName {
258 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
259 self.0.cmp(&other.0)
260 }
261}
262
263impl PartialOrd for ProcedureName {
264 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
265 Some(self.cmp(other))
266 }
267}
268
269impl Hash for ProcedureName {
270 fn hash<H: Hasher>(&self, state: &mut H) {
271 self.0.hash(state);
272 }
273}
274
275impl Spanned for ProcedureName {
276 fn span(&self) -> SourceSpan {
277 self.0.span()
278 }
279}
280
281impl From<ProcedureName> for miette::SourceSpan {
282 fn from(name: ProcedureName) -> Self {
283 name.span().into()
284 }
285}
286
287impl core::ops::Deref for ProcedureName {
288 type Target = str;
289
290 #[inline(always)]
291 fn deref(&self) -> &Self::Target {
292 self.0.as_str()
293 }
294}
295
296impl AsRef<Ident> for ProcedureName {
297 #[inline(always)]
298 fn as_ref(&self) -> &Ident {
299 &self.0
300 }
301}
302
303impl AsRef<str> for ProcedureName {
304 #[inline(always)]
305 fn as_ref(&self) -> &str {
306 self.0.as_str()
307 }
308}
309
310impl PartialEq<str> for ProcedureName {
311 fn eq(&self, other: &str) -> bool {
312 self.0.as_ref() == other
313 }
314}
315
316impl PartialEq<Ident> for ProcedureName {
317 fn eq(&self, other: &Ident) -> bool {
318 &self.0 == other
319 }
320}
321
322impl fmt::Display for ProcedureName {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.write_str(&self.0)
325 }
326}
327
328impl FromStr for ProcedureName {
330 type Err = IdentError;
331
332 fn from_str(s: &str) -> Result<Self, Self::Err> {
333 let mut chars = s.char_indices().peekable();
334
335 match chars.peek() {
337 None => return Err(IdentError::Empty),
338 Some((_, '"')) => chars.next(),
339 Some((_, c)) if is_valid_unquoted_identifier_char(*c) => {
340 let all_chars_valid =
342 chars.all(|(_, char)| is_valid_unquoted_identifier_char(char));
343
344 if all_chars_valid {
345 return Ok(Self(Ident::from_raw_parts(Span::unknown(s.into()))));
346 } else {
347 return Err(IdentError::InvalidChars { ident: s.into() });
348 }
349 },
350 Some((_, c)) if c.is_ascii_uppercase() => {
351 return Err(IdentError::Casing(CaseKindError::Snake));
352 },
353 Some(_) => return Err(IdentError::InvalidChars { ident: s.into() }),
354 };
355
356 while let Some((pos, char)) = chars.next() {
358 match char {
359 '"' => {
360 if chars.next().is_some() {
361 return Err(IdentError::InvalidChars { ident: s.into() });
362 }
363 let token = &s[0..pos];
364 return Ok(Self(Ident::from_raw_parts(Span::unknown(token.into()))));
365 },
366 c => {
367 if !(c.is_alphanumeric() || c.is_ascii_graphic()) {
369 return Err(IdentError::InvalidChars { ident: s.into() });
370 }
371 },
372 }
373 }
374
375 Err(IdentError::InvalidChars { ident: s.into() })
377 }
378}
379
380fn is_valid_unquoted_identifier_char(c: char) -> bool {
382 c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '$' | '.')
383}
384
385impl Serializable for ProcedureName {
386 fn write_into<W: ByteWriter>(&self, target: &mut W) {
387 self.as_str().write_into(target)
388 }
389}
390
391impl Deserializable for ProcedureName {
392 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
393 let str: String = source.read()?;
394 let proc_name = ProcedureName::new(str)
395 .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
396 Ok(proc_name)
397 }
398}
399
400#[cfg(any(test, feature = "arbitrary"))]
404impl proptest::prelude::Arbitrary for ProcedureName {
405 type Parameters = ();
406
407 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
408 use proptest::prelude::*;
409 let all_possible_chars_in_mangled_name =
411 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
412 let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
413 let plain = ProcedureName::new("user_func").unwrap();
414 let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
415 prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
416 }
417
418 type Strategy = proptest::prelude::BoxedStrategy<Self>;
419}
420
421#[cfg(test)]
426mod tests {
427 use miden_core::utils::{Deserializable, Serializable};
428 use proptest::prelude::*;
429
430 use super::ProcedureName;
431
432 proptest! {
433 #[test]
434 fn procedure_name_serialization_roundtrip(path in any::<ProcedureName>()) {
435 let bytes = path.to_bytes();
436 let deserialized = ProcedureName::read_from_bytes(&bytes).unwrap();
437 assert_eq!(path, deserialized);
438 }
439 }
440}