miden_assembly/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 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#[derive(Clone)]
28#[cfg_attr(feature = "testing", derive(proptest_derive::Arbitrary))]
29pub struct QualifiedProcedureName {
30 #[cfg_attr(feature = "testing", proptest(value = "SourceSpan::default()"))]
32 pub span: SourceSpan,
33 pub module: LibraryPath,
35 pub name: ProcedureName,
37}
38
39impl QualifiedProcedureName {
40 pub fn new(module: LibraryPath, name: ProcedureName) -> Self {
43 Self {
44 span: SourceSpan::default(),
45 module,
46 name,
47 }
48 }
49
50 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#[derive(Debug, Clone)]
180pub struct ProcedureName(Ident);
181
182impl ProcedureName {
183 pub const MAIN_PROC_NAME: &'static str = "#main";
185
186 pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
188 name.as_ref().parse()
189 }
190
191 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 pub fn with_span(self, span: SourceSpan) -> Self {
198 Self(self.0.with_span(span))
199 }
200
201 pub fn from_raw_parts(name: Ident) -> Self {
209 Self(name)
210 }
211
212 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 pub fn is_main(&self) -> bool {
221 self.0.as_str() == Self::MAIN_PROC_NAME
222 }
223
224 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
309impl 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().peekable();
315
316 match chars.peek() {
318 None => return Err(IdentError::Empty),
319 Some((_, '"')) => chars.next(),
320 Some((_, c)) if is_valid_unquoted_identifier_char(*c) => {
321 let all_chars_valid =
323 chars.all(|(_, char)| is_valid_unquoted_identifier_char(char));
324
325 if all_chars_valid {
326 return Ok(Self(Ident::from_raw_parts(Span::unknown(s.into()))));
327 } else {
328 return Err(IdentError::InvalidChars { ident: s.into() });
329 }
330 },
331 Some((_, c)) if c.is_ascii_uppercase() => {
332 return Err(IdentError::Casing(CaseKindError::Snake));
333 },
334 Some(_) => return Err(IdentError::InvalidChars { ident: s.into() }),
335 };
336
337 while let Some((pos, char)) = chars.next() {
339 match char {
340 '"' => {
341 if chars.next().is_some() {
342 return Err(IdentError::InvalidChars { ident: s.into() });
343 }
344 let token = &s[0..pos];
345 return Ok(Self(Ident::from_raw_parts(Span::unknown(token.into()))));
346 },
347 c => {
348 if !(c.is_alphanumeric() || c.is_ascii_graphic()) {
350 return Err(IdentError::InvalidChars { ident: s.into() });
351 }
352 },
353 }
354 }
355
356 Err(IdentError::InvalidChars { ident: s.into() })
358 }
359}
360
361fn is_valid_unquoted_identifier_char(c: char) -> bool {
363 c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '$' | '.')
364}
365
366impl Serializable for ProcedureName {
367 fn write_into<W: ByteWriter>(&self, target: &mut W) {
368 self.as_str().write_into(target)
369 }
370}
371
372impl Deserializable for ProcedureName {
373 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
374 let str: String = source.read()?;
375 let proc_name = ProcedureName::new(str)
376 .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
377 Ok(proc_name)
378 }
379}
380
381#[cfg(feature = "testing")]
385impl proptest::prelude::Arbitrary for ProcedureName {
386 type Parameters = ();
387
388 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
389 use proptest::prelude::*;
390 let all_possible_chars_in_mangled_name =
392 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
393 let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
394 let plain = ProcedureName::new("user_func").unwrap();
395 let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
396 prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
397 }
398
399 type Strategy = proptest::prelude::BoxedStrategy<Self>;
400}
401
402#[cfg(test)]
407mod tests {
408 use proptest::prelude::*;
409 use vm_core::utils::{Deserializable, Serializable};
410
411 use super::ProcedureName;
412
413 proptest! {
414 #[test]
415 fn procedure_name_serialization_roundtrip(path in any::<ProcedureName>()) {
416 let bytes = path.to_bytes();
417 let deserialized = ProcedureName::read_from_bytes(&bytes).unwrap();
418 assert_eq!(path, deserialized);
419 }
420 }
421}