miden_assembly_syntax/ast/
ident.rs1use alloc::{string::ToString, sync::Arc};
2use core::{
3 fmt,
4 hash::{Hash, Hasher},
5 str::FromStr,
6};
7
8use miden_core::utils::{
9 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
10};
11use miden_debug_types::{SourceSpan, Span, Spanned};
12
13#[derive(Debug, thiserror::Error)]
15pub enum IdentError {
16 #[error("invalid identifier: cannot be empty")]
17 Empty,
18 #[error(
19 "invalid identifier '{ident}': must contain only unicode alphanumeric or ascii graphic characters"
20 )]
21 InvalidChars { ident: Arc<str> },
22 #[error("invalid identifier: length exceeds the maximum of {max} bytes")]
23 InvalidLength { max: usize },
24 #[error("invalid identifier: {0}")]
25 Casing(CaseKindError),
26}
27
28#[derive(Debug, thiserror::Error)]
31pub enum CaseKindError {
32 #[error(
33 "only uppercase characters or underscores are allowed, and must start with an alphabetic character"
34 )]
35 Screaming,
36 #[error(
37 "only lowercase characters or underscores are allowed, and must start with an alphabetic character"
38 )]
39 Snake,
40 #[error(
41 "only alphanumeric characters are allowed, and must start with a lowercase alphabetic character"
42 )]
43 Camel,
44}
45
46#[derive(Clone)]
57#[cfg_attr(
58 all(feature = "arbitrary", test),
59 miden_test_serde_macros::serde_test(winter_serde(true))
60)]
61pub struct Ident {
62 span: SourceSpan,
71 name: Arc<str>,
73}
74
75impl Ident {
76 pub const MAIN: &'static str = "$main";
78
79 pub fn new(source: impl AsRef<str>) -> Result<Self, IdentError> {
87 source.as_ref().parse()
88 }
89
90 pub fn new_with_span(span: SourceSpan, source: impl AsRef<str>) -> Result<Self, IdentError> {
98 source.as_ref().parse::<Self>().map(|id| id.with_span(span))
99 }
100
101 pub fn with_span(mut self, span: SourceSpan) -> Self {
103 self.span = span;
104 self
105 }
106
107 pub fn from_raw_parts(name: Span<Arc<str>>) -> Self {
116 let (span, name) = name.into_parts();
117 Self { span, name }
118 }
119
120 pub fn into_inner(self) -> Arc<str> {
122 self.name
123 }
124
125 pub fn as_str(&self) -> &str {
127 self.name.as_ref()
128 }
129
130 pub fn is_constant_ident(&self) -> bool {
132 self.name
133 .chars()
134 .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
135 }
136
137 pub fn requires_quoting(&self) -> bool {
139 !self.name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
140 }
141
142 pub fn validate(source: impl AsRef<str>) -> Result<(), IdentError> {
144 let source = source.as_ref();
145 if source.is_empty() {
146 return Err(IdentError::Empty);
147 }
148 if !source
149 .chars()
150 .all(|c| (c.is_ascii_graphic() || c.is_alphanumeric()) && c != '#')
151 {
152 return Err(IdentError::InvalidChars { ident: source.into() });
153 }
154 Ok(())
155 }
156}
157
158impl fmt::Debug for Ident {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 f.debug_tuple("Ident").field(&self.name).finish()
161 }
162}
163
164impl Eq for Ident {}
165
166impl PartialEq for Ident {
167 fn eq(&self, other: &Self) -> bool {
168 self.name == other.name
169 }
170}
171
172impl Ord for Ident {
173 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
174 self.name.cmp(&other.name)
175 }
176}
177
178impl PartialOrd for Ident {
179 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
180 Some(self.cmp(other))
181 }
182}
183
184impl Hash for Ident {
185 fn hash<H: Hasher>(&self, state: &mut H) {
186 self.name.hash(state);
187 }
188}
189
190impl Spanned for Ident {
191 fn span(&self) -> SourceSpan {
192 self.span
193 }
194}
195
196impl core::ops::Deref for Ident {
197 type Target = str;
198
199 fn deref(&self) -> &Self::Target {
200 self.name.as_ref()
201 }
202}
203
204impl AsRef<str> for Ident {
205 #[inline]
206 fn as_ref(&self) -> &str {
207 &self.name
208 }
209}
210
211impl fmt::Display for Ident {
212 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213 fmt::Display::fmt(&self.name, f)
214 }
215}
216
217impl FromStr for Ident {
218 type Err = IdentError;
219
220 fn from_str(s: &str) -> Result<Self, Self::Err> {
221 Self::validate(s)?;
222 let name = Arc::from(s.to_string().into_boxed_str());
223 Ok(Self { span: SourceSpan::default(), name })
224 }
225}
226
227impl From<Ident> for miden_utils_diagnostics::miette::SourceSpan {
228 fn from(value: Ident) -> Self {
229 value.span.into()
230 }
231}
232
233#[cfg(feature = "serde")]
234impl serde::Serialize for Ident {
235 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236 where
237 S: serde::Serializer,
238 {
239 serializer.serialize_str(self.as_str())
240 }
241}
242
243#[cfg(feature = "serde")]
244impl<'de> serde::Deserialize<'de> for Ident {
245 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246 where
247 D: serde::Deserializer<'de>,
248 {
249 let name = <&'de str as serde::Deserialize>::deserialize(deserializer)?;
250 Self::new(name).map_err(serde::de::Error::custom)
251 }
252}
253
254impl Serializable for Ident {
255 fn write_into<W: ByteWriter>(&self, target: &mut W) {
256 target.write_usize(self.len());
257 target.write_bytes(self.as_bytes());
258 }
259}
260
261impl Deserializable for Ident {
262 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
263 use alloc::string::ToString;
264
265 let len = source.read_usize()?;
266 let bytes = source.read_slice(len)?;
267 let id = core::str::from_utf8(bytes)
268 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
269 Self::new(id).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
270 }
271}
272
273#[cfg(feature = "arbitrary")]
274pub mod arbitrary {
275 use alloc::{borrow::Cow, string::String};
276
277 use proptest::{char::CharStrategy, collection::vec, prelude::*};
278
279 use super::*;
280
281 impl Arbitrary for Ident {
282 type Parameters = ();
283
284 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
285 ident_any_random_length().boxed()
286 }
287
288 type Strategy = BoxedStrategy<Self>;
289 }
290
291 const SPECIAL: [char; 32] = const {
294 let mut buf = ['a'; 32];
295 let mut idx = 0;
296 let mut range_idx = 0;
297 while range_idx < SPECIAL_RANGES.len() {
298 let range = &SPECIAL_RANGES[range_idx];
299 range_idx += 1;
300 let mut j = *range.start() as u32;
301 let end = *range.end() as u32;
302 while j <= end {
303 unsafe {
304 buf[idx] = char::from_u32_unchecked(j);
305 }
306 idx += 1;
307 j += 1;
308 }
309 }
310 buf
311 };
312
313 const SPECIAL_RANGES: &[core::ops::RangeInclusive<char>] =
314 &['!'..='/', ':'..='@', '['..='`', '{'..='~'];
315 const PREFERRED_RANGES: &[core::ops::RangeInclusive<char>] = &['a'..='z', 'A'..='Z'];
316 const EXTRA_RANGES: &[core::ops::RangeInclusive<char>] = &['0'..='9', 'à'..='ö', 'ø'..='ÿ'];
317
318 const PREFERRED_CONSTANT_RANGES: &[core::ops::RangeInclusive<char>] = &['A'..='Z'];
319 const EXTRA_CONSTANT_RANGES: &[core::ops::RangeInclusive<char>] = &['0'..='9'];
320
321 prop_compose! {
322 fn bare_ident_chars()
325 (c in CharStrategy::new_borrowed(
326 &['_'],
327 PREFERRED_RANGES,
328 &['0'..='9']
329 )) -> char {
330 c
331 }
332 }
333
334 prop_compose! {
335 fn ident_chars()
338 (c in CharStrategy::new_borrowed(
339 &SPECIAL,
340 PREFERRED_RANGES,
341 EXTRA_RANGES
342 )) -> char {
343 c
344 }
345 }
346
347 prop_compose! {
348 fn const_ident_chars()
350 (c in CharStrategy::new_borrowed(
351 &['_'],
352 PREFERRED_CONSTANT_RANGES,
353 EXTRA_CONSTANT_RANGES
354 )) -> char {
355 c
356 }
357 }
358
359 prop_compose! {
360 fn ident_raw_any(length: u32)
365 ((leading_char, rest) in (
366 proptest::char::ranges(Cow::Borrowed(&['a'..='z', '_'..='_'])),
367 vec(ident_chars(), 0..=(length as usize))
368 )) -> String {
369 let mut buf = String::with_capacity(length as usize);
370 buf.push(leading_char);
371 for c in rest {
372 if !buf.is_empty() && buf.len() + c.len_utf8() > length as usize {
373 break;
374 }
375 buf.push(c);
376 }
377 buf
378 }
379 }
380
381 prop_compose! {
382 fn bare_ident_raw_any(length: u32)
384 ((leading_char, rest) in (
385 proptest::char::range('a', 'z'),
386 vec(bare_ident_chars(), 0..=(length as usize))
387 )) -> String {
388 let mut buf = String::with_capacity(length as usize);
389 buf.push(leading_char);
390 for c in rest {
391 if !buf.is_empty() && buf.len() + c.len_utf8() > length as usize {
392 break;
393 }
394 buf.push(c);
395 }
396 buf
397 }
398 }
399
400 prop_compose! {
401 fn const_ident_raw_any(length: u32)
403 ((leading_char, rest) in (
404 proptest::char::range('A', 'Z'),
405 vec(const_ident_chars(), 0..=(length as usize))
406 )) -> String {
407 let mut buf = String::with_capacity(length as usize);
408 buf.push(leading_char);
409 for c in rest {
410 if !buf.is_empty() && buf.len() + c.len_utf8() > length as usize {
411 break;
412 }
413 buf.push(c);
414 }
415 buf
416 }
417 }
418
419 prop_compose! {
420 pub fn ident_any(length: u32)
422 (raw in ident_raw_any(length)
423 .prop_filter(
424 "identifiers must be valid",
425 |s| Ident::validate(s).is_ok()
426 )
427 ) -> Ident {
428 Ident::from_raw_parts(Span::new(SourceSpan::UNKNOWN, raw.into_boxed_str().into()))
429 }
430 }
431
432 prop_compose! {
433 pub fn bare_ident_any(length: u32)
436 (raw in bare_ident_raw_any(length)
437 .prop_filter(
438 "identifiers must be valid",
439 |s| Ident::validate(s).is_ok()
440 )
441 ) -> Ident {
442 Ident::from_raw_parts(Span::new(SourceSpan::UNKNOWN, raw.into_boxed_str().into()))
443 }
444 }
445
446 prop_compose! {
447 pub fn const_ident_any(length: u32)
450 (raw in const_ident_raw_any(length)
451 .prop_filter(
452 "identifiers must be valid",
453 |s| Ident::validate(s).is_ok()
454 )
455 ) -> Ident {
456 let id = Ident::from_raw_parts(Span::new(SourceSpan::UNKNOWN, raw.into_boxed_str().into()));
457 assert!(id.is_constant_ident());
458 id
459 }
460 }
461
462 prop_compose! {
463 pub fn builtin_type_any()
465 (name in prop_oneof![
466 Just(crate::ast::types::Type::I1),
467 Just(crate::ast::types::Type::I8),
468 Just(crate::ast::types::Type::U8),
469 Just(crate::ast::types::Type::I16),
470 Just(crate::ast::types::Type::U16),
471 Just(crate::ast::types::Type::I32),
472 Just(crate::ast::types::Type::U32),
473 Just(crate::ast::types::Type::I64),
474 Just(crate::ast::types::Type::U64),
475 Just(crate::ast::types::Type::I128),
476 Just(crate::ast::types::Type::U128),
477 Just(crate::ast::types::Type::Felt),
478 ]) -> Ident {
479 Ident::from_raw_parts(Span::new(SourceSpan::UNKNOWN, name.to_string().into_boxed_str().into()))
480 }
481 }
482
483 prop_compose! {
484 pub fn ident_any_random_length()
486 (length in 1..u8::MAX)
487 (id in ident_any(length as u32)) -> Ident {
488 id
489 }
490 }
491
492 prop_compose! {
493 pub fn bare_ident_any_random_length()
496 (length in 1..u8::MAX)
497 (id in ident_any(length as u32)) -> Ident {
498 id
499 }
500 }
501
502 prop_compose! {
503 pub fn const_ident_any_random_length()
506 (length in 1..u8::MAX)
507 (id in const_ident_any(length as u32)) -> Ident {
508 id
509 }
510 }
511}