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 ops::Deref,
9 str::FromStr,
10};
11
12use miden_debug_types::{SourceSpan, Span, Spanned};
13use miden_utils_diagnostics::{IntoDiagnostic, Report, miette};
14
15use crate::{
16 Path, PathBuf,
17 ast::{CaseKindError, Ident, IdentError},
18};
19
20#[derive(Clone)]
29#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
30pub struct QualifiedProcedureName {
31 #[cfg_attr(feature = "arbitrary", proptest(value = "SourceSpan::default()"))]
33 span: SourceSpan,
34 #[cfg_attr(
35 feature = "arbitrary",
36 proptest(strategy = "crate::arbitrary::path::path_random_length(2)")
37 )]
38 path: Arc<Path>,
39}
40
41impl QualifiedProcedureName {
42 pub fn new(module: impl AsRef<Path>, name: ProcedureName) -> Self {
45 let span = name.span();
46 let path = module.as_ref().join(name).into();
47 Self { span, path }
48 }
49
50 #[inline(always)]
51 pub fn with_span(mut self, span: SourceSpan) -> Self {
52 self.span = span;
53 self
54 }
55
56 pub fn namespace(&self) -> &Path {
58 self.path.parent().unwrap()
59 }
60
61 pub fn name(&self) -> &str {
63 self.path.last().unwrap()
64 }
65
66 #[inline]
68 pub fn as_path(&self) -> &Path {
69 &self.path
70 }
71
72 #[inline]
74 pub fn to_spanned_path(&self) -> Span<&Path> {
75 Span::new(self.span, self.as_path())
76 }
77
78 #[inline]
79 pub fn into_inner(self) -> Arc<Path> {
80 self.path
81 }
82}
83
84impl Deref for QualifiedProcedureName {
85 type Target = Path;
86
87 #[inline]
88 fn deref(&self) -> &Self::Target {
89 &self.path
90 }
91}
92
93impl From<Arc<Path>> for QualifiedProcedureName {
94 fn from(path: Arc<Path>) -> Self {
95 assert!(path.parent().is_some());
96 Self { span: SourceSpan::default(), path }
97 }
98}
99
100impl From<PathBuf> for QualifiedProcedureName {
101 fn from(path: PathBuf) -> Self {
102 assert!(path.parent().is_some());
103 Self {
104 span: SourceSpan::default(),
105 path: path.into(),
106 }
107 }
108}
109
110impl From<&Path> for QualifiedProcedureName {
111 fn from(path: &Path) -> Self {
112 assert!(path.parent().is_some());
113 Self {
114 span: SourceSpan::default(),
115 path: path.to_path_buf().into(),
116 }
117 }
118}
119
120impl From<QualifiedProcedureName> for Arc<Path> {
121 fn from(value: QualifiedProcedureName) -> Self {
122 value.path
123 }
124}
125
126impl FromStr for QualifiedProcedureName {
127 type Err = Report;
128
129 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 let path = PathBuf::new(s).into_diagnostic()?;
131 if path.parent().is_none() {
132 return Err(Report::msg("invalid procedure path: must be qualified with a namespace"));
133 }
134 ProcedureName::validate(path.last().unwrap()).into_diagnostic()?;
135 Ok(Self {
136 span: SourceSpan::default(),
137 path: path.into(),
138 })
139 }
140}
141
142impl TryFrom<&str> for QualifiedProcedureName {
143 type Error = Report;
144
145 fn try_from(name: &str) -> Result<Self, Self::Error> {
146 Self::from_str(name)
147 }
148}
149
150impl TryFrom<String> for QualifiedProcedureName {
151 type Error = Report;
152
153 fn try_from(name: String) -> Result<Self, Self::Error> {
154 Self::from_str(&name)
155 }
156}
157
158impl Eq for QualifiedProcedureName {}
159
160impl PartialEq for QualifiedProcedureName {
161 fn eq(&self, other: &Self) -> bool {
162 self.path == other.path
163 }
164}
165
166impl Ord for QualifiedProcedureName {
167 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
168 self.path.cmp(&other.path)
169 }
170}
171
172impl PartialOrd for QualifiedProcedureName {
173 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
174 Some(self.cmp(other))
175 }
176}
177
178impl From<QualifiedProcedureName> for miette::SourceSpan {
179 fn from(fqn: QualifiedProcedureName) -> Self {
180 fqn.span.into()
181 }
182}
183
184impl Spanned for QualifiedProcedureName {
185 fn span(&self) -> SourceSpan {
186 self.span
187 }
188}
189
190impl fmt::Debug for QualifiedProcedureName {
191 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192 f.debug_struct("QualifiedProcedureName").field("path", &self.path).finish()
193 }
194}
195
196impl crate::prettier::PrettyPrint for QualifiedProcedureName {
197 fn render(&self) -> miden_core::prettier::Document {
198 use crate::prettier::*;
199
200 display(self)
201 }
202}
203
204impl fmt::Display for QualifiedProcedureName {
205 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206 fmt::Display::fmt(&self.path, f)
207 }
208}
209
210#[derive(Debug, Clone)]
249pub struct ProcedureName(Ident);
250
251impl ProcedureName {
252 pub const MAIN_PROC_NAME: &'static str = Ident::MAIN;
254
255 pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
257 name.as_ref().parse()
258 }
259
260 pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
262 name.as_ref().parse::<Self>().map(|name| name.with_span(span))
263 }
264
265 pub fn with_span(self, span: SourceSpan) -> Self {
267 Self(self.0.with_span(span))
268 }
269
270 pub fn from_raw_parts(name: Ident) -> Self {
278 Self(name)
279 }
280
281 pub fn main() -> Self {
284 let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
285 Self(Ident::from_raw_parts(Span::unknown(name)))
286 }
287
288 pub fn is_main(&self) -> bool {
290 self.0.as_str() == Self::MAIN_PROC_NAME
291 }
292
293 pub fn as_str(&self) -> &str {
295 self.as_ref()
296 }
297
298 pub fn as_ident(&self) -> Ident {
300 self.0.clone()
301 }
302}
303
304impl Eq for ProcedureName {}
305
306impl PartialEq for ProcedureName {
307 fn eq(&self, other: &Self) -> bool {
308 self.0 == other.0
309 }
310}
311
312impl Ord for ProcedureName {
313 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
314 self.0.cmp(&other.0)
315 }
316}
317
318impl PartialOrd for ProcedureName {
319 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
320 Some(self.cmp(other))
321 }
322}
323
324impl Hash for ProcedureName {
325 fn hash<H: Hasher>(&self, state: &mut H) {
326 self.0.hash(state);
327 }
328}
329
330impl Spanned for ProcedureName {
331 fn span(&self) -> SourceSpan {
332 self.0.span()
333 }
334}
335
336impl From<ProcedureName> for miette::SourceSpan {
337 fn from(name: ProcedureName) -> Self {
338 name.span().into()
339 }
340}
341
342impl core::ops::Deref for ProcedureName {
343 type Target = str;
344
345 #[inline(always)]
346 fn deref(&self) -> &Self::Target {
347 self.0.as_str()
348 }
349}
350
351impl AsRef<Ident> for ProcedureName {
352 #[inline(always)]
353 fn as_ref(&self) -> &Ident {
354 &self.0
355 }
356}
357
358impl AsRef<str> for ProcedureName {
359 #[inline(always)]
360 fn as_ref(&self) -> &str {
361 self.0.as_str()
362 }
363}
364
365impl From<ProcedureName> for Ident {
366 #[inline(always)]
367 fn from(name: ProcedureName) -> Self {
368 name.0
369 }
370}
371
372impl PartialEq<str> for ProcedureName {
373 fn eq(&self, other: &str) -> bool {
374 self.0.as_str() == other
375 }
376}
377
378impl PartialEq<Ident> for ProcedureName {
379 fn eq(&self, other: &Ident) -> bool {
380 &self.0 == other
381 }
382}
383
384impl fmt::Display for ProcedureName {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 f.write_str(&self.0)
387 }
388}
389
390impl FromStr for ProcedureName {
392 type Err = IdentError;
393
394 fn from_str(s: &str) -> Result<Self, Self::Err> {
395 let extracted = Self::validate(s)?;
396
397 Ok(Self(Ident::from_raw_parts(Span::unknown(extracted.into()))))
398 }
399}
400
401impl ProcedureName {
402 fn validate(name: &str) -> Result<&str, IdentError> {
403 let mut chars = name.char_indices().peekable();
404
405 match chars.peek() {
407 None => return Err(IdentError::Empty),
408 Some((_, '"')) => chars.next(),
409 Some((_, c)) if is_valid_unquoted_identifier_char(*c) => {
410 let all_chars_valid =
412 chars.all(|(_, char)| is_valid_unquoted_identifier_char(char));
413
414 if all_chars_valid {
415 return Ok(name);
416 } else {
417 return Err(IdentError::InvalidChars { ident: name.into() });
418 }
419 },
420 Some((_, c)) if c.is_ascii_uppercase() => {
421 return Err(IdentError::Casing(CaseKindError::Snake));
422 },
423 Some(_) => return Err(IdentError::InvalidChars { ident: name.into() }),
424 };
425
426 while let Some((pos, char)) = chars.next() {
428 match char {
429 '"' => {
430 if chars.next().is_some() {
431 return Err(IdentError::InvalidChars { ident: name.into() });
432 }
433 return Ok(&name[1..pos]);
434 },
435 c => {
436 if !(c.is_alphanumeric() || c.is_ascii_graphic()) {
438 return Err(IdentError::InvalidChars { ident: name.into() });
439 }
440 },
441 }
442 }
443
444 Err(IdentError::InvalidChars { ident: name.into() })
446 }
447}
448
449fn is_valid_unquoted_identifier_char(c: char) -> bool {
451 c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '$' | '.')
452}
453
454#[cfg(feature = "arbitrary")]
458impl proptest::prelude::Arbitrary for ProcedureName {
459 type Parameters = ();
460
461 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
462 use proptest::prelude::*;
463
464 prop_oneof![
465 1 => crate::arbitrary::ident::ident_any_random_length(),
466 2 => crate::arbitrary::ident::bare_ident_any_random_length(),
467 ]
468 .prop_map(ProcedureName)
469 .boxed()
470 }
471
472 type Strategy = proptest::prelude::BoxedStrategy<Self>;
473}