miden_assembly/library/
namespace.rs

1use alloc::{string::ToString, sync::Arc};
2use core::{
3    fmt,
4    str::{self, FromStr},
5};
6
7use crate::{
8    ast::Ident, diagnostics::Diagnostic, ByteReader, ByteWriter, Deserializable,
9    DeserializationError, LibraryPath, Serializable, Span,
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("invalid character in library namespace: expected lowercase ascii-alphanumeric character or '_'")]
25    #[diagnostic()]
26    InvalidChars,
27    #[error(
28        "invalid library namespace name: must start with lowercase ascii-alphabetic character"
29    )]
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    pub fn validate(source: impl AsRef<str>) -> Result<(), LibraryNamespaceError> {
108        let source = source.as_ref();
109        if source.is_empty() {
110            return Err(LibraryNamespaceError::Empty);
111        }
112        if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) {
113            return Ok(());
114        }
115        if source.len() > Self::MAX_LENGTH {
116            return Err(LibraryNamespaceError::Length);
117        }
118        if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) {
119            return Err(LibraryNamespaceError::InvalidStart);
120        }
121        if !source.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '_')) {
122            return Err(LibraryNamespaceError::InvalidChars);
123        }
124        Ok(())
125    }
126}
127
128// ------------------------------------------------------------------------------------------------
129/// Conversions
130impl LibraryNamespace {
131    /// Get the string representation of this namespace.
132    pub fn as_str(&self) -> &str {
133        match self {
134            Self::Kernel => Self::KERNEL_PATH,
135            Self::Exec => Self::EXEC_PATH,
136            Self::Anon => Self::ANON_PATH,
137            Self::User(ref path) => path,
138        }
139    }
140
141    /// Get an [`Arc<str>`] representing this namespace.
142    pub fn as_refcounted_str(&self) -> Arc<str> {
143        match self {
144            Self::User(ref path) => path.clone(),
145            other => Arc::from(other.as_str().to_string().into_boxed_str()),
146        }
147    }
148
149    /// Create a [LibraryPath] representing this [LibraryNamespace].
150    pub fn to_path(&self) -> LibraryPath {
151        LibraryPath::from(self.clone())
152    }
153
154    /// Create an [Ident] representing this namespace.
155    pub fn to_ident(&self) -> Ident {
156        Ident::new_unchecked(Span::unknown(self.as_refcounted_str()))
157    }
158}
159
160impl core::ops::Deref for LibraryNamespace {
161    type Target = str;
162
163    fn deref(&self) -> &Self::Target {
164        self.as_str()
165    }
166}
167
168impl AsRef<str> for LibraryNamespace {
169    fn as_ref(&self) -> &str {
170        self.as_str()
171    }
172}
173
174impl fmt::Display for LibraryNamespace {
175    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176        f.write_str(self.as_str())
177    }
178}
179
180impl FromStr for LibraryNamespace {
181    type Err = LibraryNamespaceError;
182
183    fn from_str(s: &str) -> Result<Self, Self::Err> {
184        match s {
185            Self::KERNEL_PATH => Ok(Self::Kernel),
186            Self::EXEC_PATH => Ok(Self::Exec),
187            Self::ANON_PATH => Ok(Self::Anon),
188            other => {
189                Self::validate(other)?;
190                Ok(Self::User(Arc::from(other.to_string().into_boxed_str())))
191            },
192        }
193    }
194}
195
196impl TryFrom<Ident> for LibraryNamespace {
197    type Error = LibraryNamespaceError;
198    fn try_from(ident: Ident) -> Result<Self, Self::Error> {
199        match ident.as_str() {
200            Self::KERNEL_PATH => Ok(Self::Kernel),
201            Self::EXEC_PATH => Ok(Self::Exec),
202            Self::ANON_PATH => Ok(Self::Anon),
203            other => Self::new(other),
204        }
205    }
206}
207
208// SERIALIZATION / DESERIALIZATION
209// ------------------------------------------------------------------------------------------------
210
211impl Serializable for LibraryNamespace {
212    fn write_into<W: ByteWriter>(&self, target: &mut W) {
213        // Catch any situations where a namespace was incorrectly constructed
214        let bytes = self.as_bytes();
215        assert!(bytes.len() <= u8::MAX as usize, "namespace too long");
216
217        target.write_u8(bytes.len() as u8);
218        target.write_bytes(bytes);
219    }
220}
221
222impl Deserializable for LibraryNamespace {
223    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
224        let num_bytes = source.read_u8()? as usize;
225        let name = source.read_slice(num_bytes)?;
226        let name =
227            str::from_utf8(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
228        Self::new(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
229    }
230}