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#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19use crate::{
20 LibraryNamespace, LibraryPath,
21 ast::{CaseKindError, Ident, IdentError},
22};
23
24#[derive(Clone)]
33#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
34#[cfg_attr(
35 all(feature = "arbitrary", test),
36 miden_test_serde_macros::serde_test(winter_serde(true))
37)]
38pub struct QualifiedProcedureName {
39 #[cfg_attr(feature = "arbitrary", proptest(value = "SourceSpan::default()"))]
41 pub span: SourceSpan,
42 pub module: LibraryPath,
44 pub name: ProcedureName,
46}
47
48impl QualifiedProcedureName {
49 pub fn new(module: LibraryPath, name: ProcedureName) -> Self {
52 Self {
53 span: SourceSpan::default(),
54 module,
55 name,
56 }
57 }
58
59 pub fn namespace(&self) -> &LibraryNamespace {
61 self.module.namespace()
62 }
63}
64
65impl FromStr for QualifiedProcedureName {
66 type Err = Report;
67
68 fn from_str(s: &str) -> Result<Self, Self::Err> {
69 match s.rsplit_once("::") {
70 None => Err(Report::msg("invalid fully-qualified procedure name, expected namespace")),
71 Some((path, name)) => {
72 let name = name.parse::<ProcedureName>().into_diagnostic()?;
73 let path = path.parse::<LibraryPath>().into_diagnostic()?;
74 Ok(Self::new(path, name))
75 },
76 }
77 }
78}
79
80impl TryFrom<&str> for QualifiedProcedureName {
81 type Error = Report;
82
83 fn try_from(name: &str) -> Result<Self, Self::Error> {
84 Self::from_str(name)
85 }
86}
87
88impl TryFrom<String> for QualifiedProcedureName {
89 type Error = Report;
90
91 fn try_from(name: String) -> Result<Self, Self::Error> {
92 Self::from_str(&name)
93 }
94}
95
96impl Eq for QualifiedProcedureName {}
97
98impl PartialEq for QualifiedProcedureName {
99 fn eq(&self, other: &Self) -> bool {
100 self.name == other.name && self.module == other.module
101 }
102}
103
104impl Ord for QualifiedProcedureName {
105 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
106 self.module.cmp(&other.module).then_with(|| self.name.cmp(&other.name))
107 }
108}
109
110impl PartialOrd for QualifiedProcedureName {
111 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
112 Some(self.cmp(other))
113 }
114}
115
116impl From<QualifiedProcedureName> for miette::SourceSpan {
117 fn from(fqn: QualifiedProcedureName) -> Self {
118 fqn.span.into()
119 }
120}
121
122impl Spanned for QualifiedProcedureName {
123 fn span(&self) -> SourceSpan {
124 self.span
125 }
126}
127
128impl fmt::Debug for QualifiedProcedureName {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 f.debug_struct("FullyQualifiedProcedureName")
131 .field("module", &self.module)
132 .field("name", &self.name)
133 .finish()
134 }
135}
136
137impl crate::prettier::PrettyPrint for QualifiedProcedureName {
138 fn render(&self) -> miden_core::prettier::Document {
139 use crate::prettier::*;
140
141 display(self)
142 }
143}
144
145impl fmt::Display for QualifiedProcedureName {
146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147 write!(f, "{}::{}", &self.module, &self.name)
148 }
149}
150
151impl Serializable for QualifiedProcedureName {
152 fn write_into<W: ByteWriter>(&self, target: &mut W) {
153 self.module.write_into(target);
154 self.name.write_into(target);
155 }
156}
157
158impl Deserializable for QualifiedProcedureName {
159 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
160 let module = LibraryPath::read_from(source)?;
161 let name = ProcedureName::read_from(source)?;
162 Ok(Self::new(module, name))
163 }
164}
165
166#[cfg(feature = "serde")]
167impl serde::Serialize for QualifiedProcedureName {
168 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
169 where
170 S: serde::Serializer,
171 {
172 if serializer.is_human_readable() {
173 let name = format!("{}", self);
174 serializer.serialize_str(&name)
175 } else {
176 use serde::ser::SerializeStruct;
177
178 let mut builder = serializer.serialize_struct("QualifiedProcedureName", 2)?;
179 builder.serialize_field("module", &self.module)?;
180 builder.serialize_field("name", &self.name)?;
181 builder.end()
182 }
183 }
184}
185
186#[cfg(feature = "serde")]
187impl<'de> serde::Deserialize<'de> for QualifiedProcedureName {
188 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189 where
190 D: serde::Deserializer<'de>,
191 {
192 use serde::de::Visitor;
193
194 if deserializer.is_human_readable() {
195 let name = <&'de str as serde::Deserialize>::deserialize(deserializer)?;
196 return Self::from_str(name).map_err(serde::de::Error::custom);
197 }
198
199 #[derive(Deserialize)]
200 #[serde(field_identifier, rename_all = "lowercase")]
201 enum Field {
202 Module,
203 Name,
204 }
205
206 struct QualifiedProcedureNameVisitor;
207 impl<'de> Visitor<'de> for QualifiedProcedureNameVisitor {
208 type Value = QualifiedProcedureName;
209
210 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
211 formatter.write_str("struct QualifiedProcedureName")
212 }
213
214 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
215 where
216 A: serde::de::SeqAccess<'de>,
217 {
218 let module = seq
219 .next_element()?
220 .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
221 let name = seq
222 .next_element()?
223 .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
224 Ok(QualifiedProcedureName::new(module, name))
225 }
226
227 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
228 where
229 A: serde::de::MapAccess<'de>,
230 {
231 let mut module = None;
232 let mut name = None;
233 while let Some(key) = map.next_key()? {
234 match key {
235 Field::Module => {
236 if module.is_some() {
237 return Err(serde::de::Error::duplicate_field("module"));
238 }
239 module = Some(map.next_value()?);
240 },
241 Field::Name => {
242 if name.is_some() {
243 return Err(serde::de::Error::duplicate_field("name"));
244 }
245 name = Some(map.next_value()?);
246 },
247 }
248 }
249 let module = module.ok_or_else(|| serde::de::Error::missing_field("module"))?;
250 let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
251 Ok(QualifiedProcedureName::new(module, name))
252 }
253 }
254
255 deserializer.deserialize_struct(
256 "QualifiedProcedureName",
257 &["module", "name"],
258 QualifiedProcedureNameVisitor,
259 )
260 }
261}
262
263#[derive(Debug, Clone)]
302#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
303#[cfg_attr(feature = "serde", serde(transparent))]
304#[cfg_attr(
305 all(feature = "arbitrary", test),
306 miden_test_serde_macros::serde_test(winter_serde(true))
307)]
308pub struct ProcedureName(Ident);
309
310impl ProcedureName {
311 pub const MAIN_PROC_NAME: &'static str = "$main";
313
314 pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
316 name.as_ref().parse()
317 }
318
319 pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
321 name.as_ref().parse::<Self>().map(|name| name.with_span(span))
322 }
323
324 pub fn with_span(self, span: SourceSpan) -> Self {
326 Self(self.0.with_span(span))
327 }
328
329 pub fn from_raw_parts(name: Ident) -> Self {
337 Self(name)
338 }
339
340 pub fn main() -> Self {
343 let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
344 Self(Ident::from_raw_parts(Span::unknown(name)))
345 }
346
347 pub fn is_main(&self) -> bool {
349 self.0.as_str() == Self::MAIN_PROC_NAME
350 }
351
352 pub fn as_str(&self) -> &str {
354 self.as_ref()
355 }
356}
357
358impl Eq for ProcedureName {}
359
360impl PartialEq for ProcedureName {
361 fn eq(&self, other: &Self) -> bool {
362 self.0 == other.0
363 }
364}
365
366impl Ord for ProcedureName {
367 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
368 self.0.cmp(&other.0)
369 }
370}
371
372impl PartialOrd for ProcedureName {
373 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
374 Some(self.cmp(other))
375 }
376}
377
378impl Hash for ProcedureName {
379 fn hash<H: Hasher>(&self, state: &mut H) {
380 self.0.hash(state);
381 }
382}
383
384impl Spanned for ProcedureName {
385 fn span(&self) -> SourceSpan {
386 self.0.span()
387 }
388}
389
390impl From<ProcedureName> for miette::SourceSpan {
391 fn from(name: ProcedureName) -> Self {
392 name.span().into()
393 }
394}
395
396impl core::ops::Deref for ProcedureName {
397 type Target = str;
398
399 #[inline(always)]
400 fn deref(&self) -> &Self::Target {
401 self.0.as_str()
402 }
403}
404
405impl AsRef<Ident> for ProcedureName {
406 #[inline(always)]
407 fn as_ref(&self) -> &Ident {
408 &self.0
409 }
410}
411
412impl AsRef<str> for ProcedureName {
413 #[inline(always)]
414 fn as_ref(&self) -> &str {
415 self.0.as_str()
416 }
417}
418
419impl PartialEq<str> for ProcedureName {
420 fn eq(&self, other: &str) -> bool {
421 self.0.as_ref() == other
422 }
423}
424
425impl PartialEq<Ident> for ProcedureName {
426 fn eq(&self, other: &Ident) -> bool {
427 &self.0 == other
428 }
429}
430
431impl fmt::Display for ProcedureName {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 f.write_str(&self.0)
434 }
435}
436
437impl FromStr for ProcedureName {
439 type Err = IdentError;
440
441 fn from_str(s: &str) -> Result<Self, Self::Err> {
442 let mut chars = s.char_indices().peekable();
443
444 match chars.peek() {
446 None => return Err(IdentError::Empty),
447 Some((_, '"')) => chars.next(),
448 Some((_, c)) if is_valid_unquoted_identifier_char(*c) => {
449 let all_chars_valid =
451 chars.all(|(_, char)| is_valid_unquoted_identifier_char(char));
452
453 if all_chars_valid {
454 return Ok(Self(Ident::from_raw_parts(Span::unknown(s.into()))));
455 } else {
456 return Err(IdentError::InvalidChars { ident: s.into() });
457 }
458 },
459 Some((_, c)) if c.is_ascii_uppercase() => {
460 return Err(IdentError::Casing(CaseKindError::Snake));
461 },
462 Some(_) => return Err(IdentError::InvalidChars { ident: s.into() }),
463 };
464
465 while let Some((pos, char)) = chars.next() {
467 match char {
468 '"' => {
469 if chars.next().is_some() {
470 return Err(IdentError::InvalidChars { ident: s.into() });
471 }
472 let token = &s[0..pos];
473 return Ok(Self(Ident::from_raw_parts(Span::unknown(token.into()))));
474 },
475 c => {
476 if !(c.is_alphanumeric() || c.is_ascii_graphic()) {
478 return Err(IdentError::InvalidChars { ident: s.into() });
479 }
480 },
481 }
482 }
483
484 Err(IdentError::InvalidChars { ident: s.into() })
486 }
487}
488
489fn is_valid_unquoted_identifier_char(c: char) -> bool {
491 c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '$' | '.')
492}
493
494impl Serializable for ProcedureName {
495 fn write_into<W: ByteWriter>(&self, target: &mut W) {
496 self.as_str().write_into(target)
497 }
498}
499
500impl Deserializable for ProcedureName {
501 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
502 let str: String = source.read()?;
503 let proc_name = ProcedureName::new(str)
504 .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
505 Ok(proc_name)
506 }
507}
508
509#[cfg(any(test, feature = "arbitrary"))]
513impl proptest::prelude::Arbitrary for ProcedureName {
514 type Parameters = ();
515
516 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
517 use proptest::prelude::*;
518 let all_possible_chars_in_mangled_name =
520 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
521 let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
522 let plain = ProcedureName::new("user_func").unwrap();
523 let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
524 prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
525 }
526
527 type Strategy = proptest::prelude::BoxedStrategy<Self>;
528}