miden_assembly/ast/
ident.rs

1use 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/// Represents the types of errors that can occur when parsing/validating an [Ident]
11#[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/// Represents the various types of casing errors that can occur, e.g. using an identifier
28/// with `SCREAMING_CASE` where one with `snake_case` is expected.
29#[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/// Represents a generic identifier in Miden Assembly source code.
46///
47/// This type is used internally by all other specialized identifier types, e.g.
48/// [super::ProcedureName], and enforces the baseline rules for bare identifiers in Miden Assembly.
49/// Higher-level types, such as `ProcedureName`, can implement their own variations on these rules,
50/// and construct an [Ident] using [Ident::new_unchecked].
51///
52/// All identifiers are associated with a source span, and are interned to the extent possible, i.e.
53/// rather than allocating a new `String` for every use of the same identifier, we attempt to have
54/// all such uses share a single reference-counted allocation. This interning is not perfect or
55/// guaranteed globally, but generally holds within a given module. In the future we may make these
56/// actually interned strings with a global interner, but for now it is simply best-effort.
57#[derive(Clone)]
58pub struct Ident {
59    /// The source span associated with this identifier.
60    ///
61    /// NOTE: To make use of this span, we need to know the context in which it was used, i.e.,
62    /// either the containing module or procedure, both of which have a source file which we can
63    /// use to render a source snippet for this span.
64    ///
65    /// If a span is not known, the default value is used, which has zero-length and thus will not
66    /// be rendered as a source snippet.
67    span: SourceSpan,
68    /// The actual content of the identifier
69    name: Arc<str>,
70}
71
72impl Ident {
73    /// Parses an [Ident] from `source`.
74    pub fn new(source: impl AsRef<str>) -> Result<Self, IdentError> {
75        source.as_ref().parse()
76    }
77
78    /// Parses an [Ident] from `source`.
79    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    /// Sets the span for this identifier.
84    pub fn with_span(mut self, span: SourceSpan) -> Self {
85        self.span = span;
86        self
87    }
88
89    /// This allows constructing an [Ident] directly from a ref-counted string that is known to be
90    /// a valid identifier, and so does not require re-parsing/re-validating. This must _not_ be
91    /// used to bypass validation when you have an identifier that is not valid, and such
92    /// identifiers will be caught during compilation and result in a panic being raised.
93    ///
94    /// NOTE: This function is perma-unstable, it may be removed or modified at any time.
95    pub fn new_unchecked(name: Span<Arc<str>>) -> Self {
96        let (span, name) = name.into_parts();
97        Self { span, name }
98    }
99
100    /// Unwraps this [Ident], extracting the inner [`Arc<str>`].
101    pub fn into_inner(self) -> Arc<str> {
102        self.name
103    }
104
105    /// Returns the content of this identifier as a `str`.
106    pub fn as_str(&self) -> &str {
107        self.name.as_ref()
108    }
109
110    /// Applies the default [Ident] validation rules to `source`.
111    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}