miden_assembly/library/
namespace.rs

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