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();
315 let raw = match chars.next() {
316 None => Err(IdentError::Empty),
317 Some((_, '"')) => loop {
318 if let Some((pos, c)) = chars.next() {
319 match c {
320 '"' => {
321 if chars.next().is_some() {
322 break Err(IdentError::InvalidChars { ident: s.into() });
323 }
324 let tok = &s[1..pos];
325 break Ok(Arc::from(tok.to_string().into_boxed_str()));
326 },
327 c if c.is_alphanumeric() || c.is_ascii_graphic() => continue,
328 _ => break Err(IdentError::InvalidChars { ident: s.into() }),
329 }
330 } else {
331 break Err(IdentError::InvalidChars { ident: s.into() });
332 }
333 },
334 Some((_, c))
335 if c.is_ascii_lowercase() || c == '_' || c == '-' || c == '$' || c == '.' =>
336 {
337 if chars.as_str().contains(|c| match c {
338 c if c.is_ascii_alphanumeric() => false,
339 '_' | '-' | '$' | '.' => false,
340 _ => true,
341 }) {
342 Err(IdentError::InvalidChars { ident: s.into() })
343 } else {
344 Ok(Arc::from(s.to_string().into_boxed_str()))
345 }
346 },
347 Some((_, c)) if c.is_ascii_uppercase() => Err(IdentError::Casing(CaseKindError::Snake)),
348 Some(_) => Err(IdentError::InvalidChars { ident: s.into() }),
349 }?;
350 Ok(Self(Ident::from_raw_parts(Span::unknown(raw))))
351 }
352}
353
354impl Serializable for ProcedureName {
355 fn write_into<W: ByteWriter>(&self, target: &mut W) {
356 self.as_str().write_into(target)
357 }
358}
359
360impl Deserializable for ProcedureName {
361 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
362 let str: String = source.read()?;
363 let proc_name = ProcedureName::new(str)
364 .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
365 Ok(proc_name)
366 }
367}
368
369#[cfg(feature = "testing")]
373impl proptest::prelude::Arbitrary for ProcedureName {
374 type Parameters = ();
375
376 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
377 use proptest::prelude::*;
378 let all_possible_chars_in_mangled_name =
380 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
381 let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
382 let plain = ProcedureName::new("user_func").unwrap();
383 let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
384 prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
385 }
386
387 type Strategy = proptest::prelude::BoxedStrategy<Self>;
388}
389
390#[cfg(test)]
395mod tests {
396 use proptest::prelude::*;
397 use vm_core::utils::{Deserializable, Serializable};
398
399 use super::ProcedureName;
400
401 proptest! {
402 #[test]
403 fn procedure_name_serialization_roundtrip(path in any::<ProcedureName>()) {
404 let bytes = path.to_bytes();
405 let deserialized = ProcedureName::read_from_bytes(&bytes).unwrap();
406 assert_eq!(path, deserialized);
407 }
408 }
409}