miden_assembly/ast/
ident.rs1use alloc::{string::ToString, sync::Arc};
2use core::{
3 fmt,
4 hash::{Hash, Hasher},
5 str::FromStr,
6};
7
8use crate::{SourceSpan, Span, Spanned};
9
10#[derive(Debug, thiserror::Error)]
12pub enum IdentError {
13 #[error("invalid identifier: cannot be empty")]
14 Empty,
15 #[error(
16 "invalid identifier '{ident}': must contain only lowercase, ascii alphanumeric characters, or underscores"
17 )]
18 InvalidChars { ident: Arc<str> },
19 #[error("invalid identifier: must start with lowercase ascii alphabetic character")]
20 InvalidStart,
21 #[error("invalid identifier: length exceeds the maximum of {max} bytes")]
22 InvalidLength { max: usize },
23 #[error("invalid identifier: {0}")]
24 Casing(CaseKindError),
25}
26
27#[derive(Debug, thiserror::Error)]
30pub enum CaseKindError {
31 #[error(
32 "only uppercase characters or underscores are allowed, and must start with an alphabetic character"
33 )]
34 Screaming,
35 #[error(
36 "only lowercase characters or underscores are allowed, and must start with an alphabetic character"
37 )]
38 Snake,
39 #[error(
40 "only alphanumeric characters are allowed, and must start with a lowercase alphabetic character"
41 )]
42 Camel,
43}
44
45#[derive(Clone)]
58pub struct Ident {
59 span: SourceSpan,
68 name: Arc<str>,
70}
71
72impl Ident {
73 pub fn new(source: impl AsRef<str>) -> Result<Self, IdentError> {
75 source.as_ref().parse()
76 }
77
78 pub fn new_with_span(span: SourceSpan, source: impl AsRef<str>) -> Result<Self, IdentError> {
80 source.as_ref().parse::<Self>().map(|id| id.with_span(span))
81 }
82
83 pub fn with_span(mut self, span: SourceSpan) -> Self {
85 self.span = span;
86 self
87 }
88
89 pub fn new_unchecked(name: Span<Arc<str>>) -> Self {
96 let (span, name) = name.into_parts();
97 Self { span, name }
98 }
99
100 pub fn into_inner(self) -> Arc<str> {
102 self.name
103 }
104
105 pub fn as_str(&self) -> &str {
107 self.name.as_ref()
108 }
109
110 pub fn validate(source: impl AsRef<str>) -> Result<(), IdentError> {
112 let source = source.as_ref();
113 if source.is_empty() {
114 return Err(IdentError::Empty);
115 }
116 if !source.starts_with(|c: char| c.is_ascii_alphabetic()) {
117 return Err(IdentError::InvalidStart);
118 }
119 if !source
120 .chars()
121 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | ':' | '/' | '@' | '.'))
122 {
123 return Err(IdentError::InvalidChars { ident: source.into() });
124 }
125 Ok(())
126 }
127}
128
129impl fmt::Debug for Ident {
130 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 f.debug_tuple("Ident").field(&self.name).finish()
132 }
133}
134
135impl Eq for Ident {}
136
137impl PartialEq for Ident {
138 fn eq(&self, other: &Self) -> bool {
139 self.name == other.name
140 }
141}
142
143impl Ord for Ident {
144 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
145 self.name.cmp(&other.name)
146 }
147}
148
149impl PartialOrd for Ident {
150 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
151 Some(self.cmp(other))
152 }
153}
154
155impl Hash for Ident {
156 fn hash<H: Hasher>(&self, state: &mut H) {
157 self.name.hash(state);
158 }
159}
160
161impl Spanned for Ident {
162 fn span(&self) -> SourceSpan {
163 self.span
164 }
165}
166
167impl core::ops::Deref for Ident {
168 type Target = str;
169
170 fn deref(&self) -> &Self::Target {
171 self.name.as_ref()
172 }
173}
174
175impl AsRef<str> for Ident {
176 #[inline]
177 fn as_ref(&self) -> &str {
178 &self.name
179 }
180}
181
182impl fmt::Display for Ident {
183 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184 fmt::Display::fmt(&self.name, f)
185 }
186}
187
188impl FromStr for Ident {
189 type Err = IdentError;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 Self::validate(s)?;
193 let name = Arc::from(s.to_string().into_boxed_str());
194 Ok(Self { span: SourceSpan::default(), name })
195 }
196}