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)]
181pub struct ProcedureName(Ident);
182
183impl ProcedureName {
184 pub const MAIN_PROC_NAME: &'static str = "#main";
186
187 pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
189 name.as_ref().parse()
190 }
191
192 pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
194 name.as_ref().parse::<Self>().map(|name| name.with_span(span))
195 }
196
197 pub fn with_span(self, span: SourceSpan) -> Self {
199 Self(self.0.with_span(span))
200 }
201
202 pub fn new_unchecked(name: Ident) -> Self {
210 Self(name)
211 }
212
213 pub fn main() -> Self {
216 let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
217 Self(Ident::new_unchecked(Span::unknown(name)))
218 }
219
220 pub fn is_main(&self) -> bool {
222 self.0.as_str() == Self::MAIN_PROC_NAME
223 }
224
225 pub fn as_str(&self) -> &str {
227 self.as_ref()
228 }
229}
230
231impl Eq for ProcedureName {}
232
233impl PartialEq for ProcedureName {
234 fn eq(&self, other: &Self) -> bool {
235 self.0 == other.0
236 }
237}
238
239impl Ord for ProcedureName {
240 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
241 self.0.cmp(&other.0)
242 }
243}
244
245impl PartialOrd for ProcedureName {
246 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
247 Some(self.cmp(other))
248 }
249}
250
251impl Hash for ProcedureName {
252 fn hash<H: Hasher>(&self, state: &mut H) {
253 self.0.hash(state);
254 }
255}
256
257impl Spanned for ProcedureName {
258 fn span(&self) -> SourceSpan {
259 self.0.span()
260 }
261}
262
263impl From<ProcedureName> for miette::SourceSpan {
264 fn from(name: ProcedureName) -> Self {
265 name.span().into()
266 }
267}
268
269impl core::ops::Deref for ProcedureName {
270 type Target = str;
271
272 #[inline(always)]
273 fn deref(&self) -> &Self::Target {
274 self.0.as_str()
275 }
276}
277
278impl AsRef<Ident> for ProcedureName {
279 #[inline(always)]
280 fn as_ref(&self) -> &Ident {
281 &self.0
282 }
283}
284
285impl AsRef<str> for ProcedureName {
286 #[inline(always)]
287 fn as_ref(&self) -> &str {
288 self.0.as_str()
289 }
290}
291
292impl PartialEq<str> for ProcedureName {
293 fn eq(&self, other: &str) -> bool {
294 self.0.as_ref() == other
295 }
296}
297
298impl PartialEq<Ident> for ProcedureName {
299 fn eq(&self, other: &Ident) -> bool {
300 &self.0 == other
301 }
302}
303
304impl fmt::Display for ProcedureName {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 f.write_str(&self.0)
307 }
308}
309
310impl FromStr for ProcedureName {
312 type Err = IdentError;
313
314 fn from_str(s: &str) -> Result<Self, Self::Err> {
315 let mut chars = s.char_indices();
316 let raw = match chars.next() {
317 None => Err(IdentError::Empty),
318 Some((_, '"')) => loop {
319 if let Some((pos, c)) = chars.next() {
320 match c {
321 '"' => {
322 if chars.next().is_some() {
323 break Err(IdentError::InvalidChars { ident: s.into() });
324 }
325 let tok = &s[1..pos];
326 break Ok(Arc::from(tok.to_string().into_boxed_str()));
327 },
328 c if c.is_alphanumeric() => continue,
329 '_' | '$' | '-' | '!' | '?' | '<' | '>' | ':' | '.' => continue,
330 _ => break Err(IdentError::InvalidChars { ident: s.into() }),
331 }
332 } else {
333 break Err(IdentError::InvalidChars { ident: s.into() });
334 }
335 },
336 Some((_, c))
337 if c.is_ascii_lowercase() || c == '_' || c == '-' || c == '$' || c == '.' =>
338 {
339 if chars.as_str().contains(|c| match c {
340 c if c.is_ascii_alphanumeric() => false,
341 '_' | '-' | '$' | '.' => false,
342 _ => true,
343 }) {
344 Err(IdentError::InvalidChars { ident: s.into() })
345 } else {
346 Ok(Arc::from(s.to_string().into_boxed_str()))
347 }
348 },
349 Some((_, c)) if c.is_ascii_uppercase() => Err(IdentError::Casing(CaseKindError::Snake)),
350 Some(_) => Err(IdentError::InvalidChars { ident: s.into() }),
351 }?;
352 Ok(Self(Ident::new_unchecked(Span::unknown(raw))))
353 }
354}
355
356impl Serializable for ProcedureName {
357 fn write_into<W: ByteWriter>(&self, target: &mut W) {
358 self.as_str().write_into(target)
359 }
360}
361
362impl Deserializable for ProcedureName {
363 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
364 let str: String = source.read()?;
365 let proc_name = ProcedureName::new(str)
366 .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
367 Ok(proc_name)
368 }
369}
370
371#[cfg(feature = "testing")]
375impl proptest::prelude::Arbitrary for ProcedureName {
376 type Parameters = ();
377
378 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
379 use proptest::prelude::*;
380 let all_possible_chars_in_mangled_name =
382 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
383 let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
384 let plain = ProcedureName::new("user_func").unwrap();
385 let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
386 prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
387 }
388
389 type Strategy = proptest::prelude::BoxedStrategy<Self>;
390}
391
392#[cfg(test)]
397mod tests {
398 use proptest::prelude::*;
399 use vm_core::utils::{Deserializable, Serializable};
400
401 use super::ProcedureName;
402
403 proptest! {
404 #[test]
405 fn procedure_name_serialization_roundtrip(path in any::<ProcedureName>()) {
406 let bytes = path.to_bytes();
407 let deserialized = ProcedureName::read_from_bytes(&bytes).unwrap();
408 assert_eq!(path, deserialized);
409 }
410 }
411}