miden_assembly_syntax/library/
namespace.rs

1use alloc::{string::ToString, sync::Arc};
2use core::{
3    fmt,
4    str::{self, FromStr},
5};
6
7use miden_core::utils::{
8    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
9};
10use miden_debug_types::Span;
11use miden_utils_diagnostics::{Diagnostic, miette};
12
13use crate::{LibraryPath, ast::Ident};
14
15// LIBRARY NAMESPACE
16// ================================================================================================
17
18/// Represents an error when parsing or validating a library namespace
19#[derive(Debug, thiserror::Error, Diagnostic)]
20pub enum LibraryNamespaceError {
21    #[error("invalid library namespace name: cannot be empty")]
22    #[diagnostic()]
23    Empty,
24    #[error("invalid library namespace name: too many characters")]
25    #[diagnostic()]
26    Length,
27    #[error(
28        "invalid character in library namespace: expected lowercase ascii-alphanumeric character or '_'"
29    )]
30    #[diagnostic()]
31    InvalidChars,
32    #[error("invalid library namespace name: must start with lowercase ascii-alphabetic character")]
33    #[diagnostic()]
34    InvalidStart,
35}
36
37/// Represents the root component of a library path, akin to a Rust crate name
38#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[repr(u8)]
40pub enum LibraryNamespace {
41    /// A reserved namespace for kernel modules
42    Kernel = 0,
43    /// A reserved namespace for executable modules
44    Exec,
45    /// A reserved namespace assigned to anonymous libraries with no path
46    #[default]
47    Anon,
48    /// A user-defined namespace
49    User(Arc<str>),
50}
51
52// ------------------------------------------------------------------------------------------------
53/// Constants
54impl LibraryNamespace {
55    /// Namespaces must be 255 bytes or less
56    pub const MAX_LENGTH: usize = u8::MAX as usize;
57
58    /// Base kernel path.
59    pub const KERNEL_PATH: &'static str = "$kernel";
60
61    /// Path for an executable module.
62    pub const EXEC_PATH: &'static str = "$exec";
63
64    /// Path for a module without library path.
65    pub const ANON_PATH: &'static str = "$anon";
66}
67
68// ------------------------------------------------------------------------------------------------
69/// Constructors
70impl LibraryNamespace {
71    /// Construct a new [LibraryNamespace] from `source`
72    pub fn new<S>(source: S) -> Result<Self, LibraryNamespaceError>
73    where
74        S: AsRef<str>,
75    {
76        source.as_ref().parse()
77    }
78
79    /// Construct a new [LibraryNamespace] from a previously-validated [Ident].
80    ///
81    /// NOTE: The caller must ensure that the given identifier is a valid namespace name.
82    pub fn from_ident_unchecked(name: Ident) -> Self {
83        match name.as_str() {
84            Self::KERNEL_PATH => Self::Kernel,
85            Self::EXEC_PATH => Self::Exec,
86            Self::ANON_PATH => Self::Anon,
87            _ => Self::User(name.into_inner()),
88        }
89    }
90
91    /// Parse a [LibraryNamespace] by taking the prefix of the given path string, and returning
92    /// the namespace and remaining string if successful.
93    pub fn strip_path_prefix(path: &str) -> Result<(Self, &str), LibraryNamespaceError> {
94        match path.split_once("::") {
95            Some((ns, rest)) => ns.parse().map(|ns| (ns, rest)),
96            None => path.parse().map(|ns| (ns, "")),
97        }
98    }
99}
100
101// ------------------------------------------------------------------------------------------------
102/// Public accessors
103impl LibraryNamespace {
104    /// Returns true if this namespace is a reserved namespace.
105    pub fn is_reserved(&self) -> bool {
106        !matches!(self, Self::User(_))
107    }
108
109    /// Checks if `source` is a valid [LibraryNamespace]
110    ///
111    /// The rules for valid library namespaces are:
112    ///
113    /// * Must be lowercase
114    /// * Must start with an ASCII alphabetic character, with the exception of reserved special
115    ///   namespaces
116    /// * May only contain alphanumeric unicode characters, or a character from the ASCII graphic
117    ///   set, see [char::is_ascii_graphic].
118    pub fn validate(source: impl AsRef<str>) -> Result<(), LibraryNamespaceError> {
119        let source = source.as_ref();
120        if source.is_empty() {
121            return Err(LibraryNamespaceError::Empty);
122        }
123        if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) {
124            return Ok(());
125        }
126        if source.len() > Self::MAX_LENGTH {
127            return Err(LibraryNamespaceError::Length);
128        }
129        if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) {
130            return Err(LibraryNamespaceError::InvalidStart);
131        }
132        if !source.chars().all(|c| c.is_ascii_graphic() || c.is_alphanumeric()) {
133            return Err(LibraryNamespaceError::InvalidChars);
134        }
135        Ok(())
136    }
137}
138
139// ------------------------------------------------------------------------------------------------
140/// Conversions
141impl LibraryNamespace {
142    /// Get the string representation of this namespace.
143    pub fn as_str(&self) -> &str {
144        match self {
145            Self::Kernel => Self::KERNEL_PATH,
146            Self::Exec => Self::EXEC_PATH,
147            Self::Anon => Self::ANON_PATH,
148            Self::User(path) => path,
149        }
150    }
151
152    /// Get an [`Arc<str>`] representing this namespace.
153    pub fn as_refcounted_str(&self) -> Arc<str> {
154        match self {
155            Self::User(path) => path.clone(),
156            other => Arc::from(other.as_str().to_string().into_boxed_str()),
157        }
158    }
159
160    /// Create a [LibraryPath] representing this [LibraryNamespace].
161    pub fn to_path(&self) -> LibraryPath {
162        LibraryPath::from(self.clone())
163    }
164
165    /// Create an [Ident] representing this namespace.
166    pub fn to_ident(&self) -> Ident {
167        Ident::from_raw_parts(Span::unknown(self.as_refcounted_str()))
168    }
169}
170
171impl core::ops::Deref for LibraryNamespace {
172    type Target = str;
173
174    fn deref(&self) -> &Self::Target {
175        self.as_str()
176    }
177}
178
179impl AsRef<str> for LibraryNamespace {
180    fn as_ref(&self) -> &str {
181        self.as_str()
182    }
183}
184
185impl fmt::Display for LibraryNamespace {
186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187        f.write_str(self.as_str())
188    }
189}
190
191impl FromStr for LibraryNamespace {
192    type Err = LibraryNamespaceError;
193
194    fn from_str(s: &str) -> Result<Self, Self::Err> {
195        match s {
196            Self::KERNEL_PATH => Ok(Self::Kernel),
197            Self::EXEC_PATH => Ok(Self::Exec),
198            Self::ANON_PATH => Ok(Self::Anon),
199            other => {
200                Self::validate(other)?;
201                Ok(Self::User(Arc::from(other.to_string().into_boxed_str())))
202            },
203        }
204    }
205}
206
207impl TryFrom<Ident> for LibraryNamespace {
208    type Error = LibraryNamespaceError;
209    fn try_from(ident: Ident) -> Result<Self, Self::Error> {
210        match ident.as_str() {
211            Self::KERNEL_PATH => Ok(Self::Kernel),
212            Self::EXEC_PATH => Ok(Self::Exec),
213            Self::ANON_PATH => Ok(Self::Anon),
214            other => Self::new(other),
215        }
216    }
217}
218
219// SERIALIZATION / DESERIALIZATION
220// ------------------------------------------------------------------------------------------------
221
222impl Serializable for LibraryNamespace {
223    fn write_into<W: ByteWriter>(&self, target: &mut W) {
224        // Catch any situations where a namespace was incorrectly constructed
225        let bytes = self.as_bytes();
226        assert!(bytes.len() <= u8::MAX as usize, "namespace too long");
227
228        target.write_u8(bytes.len() as u8);
229        target.write_bytes(bytes);
230    }
231}
232
233impl Deserializable for LibraryNamespace {
234    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
235        let num_bytes = source.read_u8()? as usize;
236        let name = source.read_slice(num_bytes)?;
237        let name =
238            str::from_utf8(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
239        Self::new(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
240    }
241}