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)]
40#[cfg_attr(
41    all(feature = "serde", feature = "arbitrary", test),
42    miden_serde_test_macros::serde_test
43)]
44pub enum LibraryNamespace {
45    /// A reserved namespace for kernel modules
46    Kernel = 0,
47    /// A reserved namespace for executable modules
48    Exec,
49    /// A reserved namespace assigned to anonymous libraries with no path
50    #[default]
51    Anon,
52    /// A user-defined namespace
53    User(Arc<str>),
54}
55
56// ------------------------------------------------------------------------------------------------
57/// Constants
58impl LibraryNamespace {
59    /// Namespaces must be 255 bytes or less
60    pub const MAX_LENGTH: usize = u8::MAX as usize;
61
62    /// Base kernel path.
63    pub const KERNEL_PATH: &'static str = "$kernel";
64
65    /// Path for an executable module.
66    pub const EXEC_PATH: &'static str = "$exec";
67
68    /// Path for a module without library path.
69    pub const ANON_PATH: &'static str = "$anon";
70}
71
72// ------------------------------------------------------------------------------------------------
73/// Constructors
74impl LibraryNamespace {
75    /// Construct a new [LibraryNamespace] from `source`
76    pub fn new<S>(source: S) -> Result<Self, LibraryNamespaceError>
77    where
78        S: AsRef<str>,
79    {
80        source.as_ref().parse()
81    }
82
83    /// Construct a new [LibraryNamespace] from a previously-validated [Ident].
84    ///
85    /// NOTE: The caller must ensure that the given identifier is a valid namespace name.
86    pub fn from_ident_unchecked(name: Ident) -> Self {
87        match name.as_str() {
88            Self::KERNEL_PATH => Self::Kernel,
89            Self::EXEC_PATH => Self::Exec,
90            Self::ANON_PATH => Self::Anon,
91            _ => Self::User(name.into_inner()),
92        }
93    }
94
95    /// Parse a [LibraryNamespace] by taking the prefix of the given path string, and returning
96    /// the namespace and remaining string if successful.
97    pub fn strip_path_prefix(path: &str) -> Result<(Self, &str), LibraryNamespaceError> {
98        match path.split_once("::") {
99            Some((ns, rest)) => ns.parse().map(|ns| (ns, rest)),
100            None => path.parse().map(|ns| (ns, "")),
101        }
102    }
103}
104
105// ------------------------------------------------------------------------------------------------
106/// Public accessors
107impl LibraryNamespace {
108    /// Returns true if this namespace is a reserved namespace.
109    pub fn is_reserved(&self) -> bool {
110        !matches!(self, Self::User(_))
111    }
112
113    /// Checks if `source` is a valid [LibraryNamespace]
114    ///
115    /// The rules for valid library namespaces are:
116    ///
117    /// * Must be lowercase
118    /// * Must start with an ASCII alphabetic character, with the exception of reserved special
119    ///   namespaces
120    /// * May only contain alphanumeric unicode characters, or a character from the ASCII graphic
121    ///   set, see [char::is_ascii_graphic].
122    pub fn validate(source: impl AsRef<str>) -> Result<(), LibraryNamespaceError> {
123        let source = source.as_ref();
124        if source.is_empty() {
125            return Err(LibraryNamespaceError::Empty);
126        }
127        if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) {
128            return Ok(());
129        }
130        if source.len() > Self::MAX_LENGTH {
131            return Err(LibraryNamespaceError::Length);
132        }
133        if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) {
134            return Err(LibraryNamespaceError::InvalidStart);
135        }
136        if !source.chars().all(|c| c.is_ascii_graphic() || c.is_alphanumeric()) {
137            return Err(LibraryNamespaceError::InvalidChars);
138        }
139        Ok(())
140    }
141}
142
143// ------------------------------------------------------------------------------------------------
144/// Conversions
145impl LibraryNamespace {
146    /// Get the string representation of this namespace.
147    pub fn as_str(&self) -> &str {
148        match self {
149            Self::Kernel => Self::KERNEL_PATH,
150            Self::Exec => Self::EXEC_PATH,
151            Self::Anon => Self::ANON_PATH,
152            Self::User(path) => path,
153        }
154    }
155
156    /// Get an [`Arc<str>`] representing this namespace.
157    pub fn as_refcounted_str(&self) -> Arc<str> {
158        match self {
159            Self::User(path) => path.clone(),
160            other => Arc::from(other.as_str().to_string().into_boxed_str()),
161        }
162    }
163
164    /// Create a [LibraryPath] representing this [LibraryNamespace].
165    pub fn to_path(&self) -> LibraryPath {
166        LibraryPath::from(self.clone())
167    }
168
169    /// Create an [Ident] representing this namespace.
170    pub fn to_ident(&self) -> Ident {
171        Ident::from_raw_parts(Span::unknown(self.as_refcounted_str()))
172    }
173
174    #[cfg(feature = "serde")]
175    const fn tag(&self) -> u8 {
176        // SAFETY: This is safe because we have given this enum a
177        // primitive representation with #[repr(u8)], with the first
178        // field of the underlying union-of-structs the discriminant
179        //
180        // See the section on "accessing the numeric value of the discriminant"
181        // here: https://doc.rust-lang.org/std/mem/fn.discriminant.html
182        unsafe { *(self as *const Self).cast::<u8>() }
183    }
184}
185
186impl core::ops::Deref for LibraryNamespace {
187    type Target = str;
188
189    fn deref(&self) -> &Self::Target {
190        self.as_str()
191    }
192}
193
194impl AsRef<str> for LibraryNamespace {
195    fn as_ref(&self) -> &str {
196        self.as_str()
197    }
198}
199
200impl fmt::Display for LibraryNamespace {
201    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202        f.write_str(self.as_str())
203    }
204}
205
206impl FromStr for LibraryNamespace {
207    type Err = LibraryNamespaceError;
208
209    fn from_str(s: &str) -> Result<Self, Self::Err> {
210        match s {
211            Self::KERNEL_PATH => Ok(Self::Kernel),
212            Self::EXEC_PATH => Ok(Self::Exec),
213            Self::ANON_PATH => Ok(Self::Anon),
214            other => {
215                Self::validate(other)?;
216                Ok(Self::User(Arc::from(other.to_string().into_boxed_str())))
217            },
218        }
219    }
220}
221
222impl TryFrom<Ident> for LibraryNamespace {
223    type Error = LibraryNamespaceError;
224    fn try_from(ident: Ident) -> Result<Self, Self::Error> {
225        match ident.as_str() {
226            Self::KERNEL_PATH => Ok(Self::Kernel),
227            Self::EXEC_PATH => Ok(Self::Exec),
228            Self::ANON_PATH => Ok(Self::Anon),
229            other => Self::new(other),
230        }
231    }
232}
233
234// SERIALIZATION / DESERIALIZATION
235// ------------------------------------------------------------------------------------------------
236
237impl Serializable for LibraryNamespace {
238    fn write_into<W: ByteWriter>(&self, target: &mut W) {
239        // Catch any situations where a namespace was incorrectly constructed
240        let bytes = self.as_bytes();
241        assert!(bytes.len() <= u8::MAX as usize, "namespace too long");
242
243        target.write_u8(bytes.len() as u8);
244        target.write_bytes(bytes);
245    }
246}
247
248impl Deserializable for LibraryNamespace {
249    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
250        let num_bytes = source.read_u8()? as usize;
251        let name = source.read_slice(num_bytes)?;
252        let name =
253            str::from_utf8(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
254        Self::new(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
255    }
256}
257
258#[cfg(feature = "serde")]
259impl serde::Serialize for LibraryNamespace {
260    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
261    where
262        S: serde::Serializer,
263    {
264        if serializer.is_human_readable() {
265            match self {
266                Self::Kernel => serializer.serialize_str(Self::KERNEL_PATH),
267                Self::Exec => serializer.serialize_str(Self::EXEC_PATH),
268                Self::Anon => serializer.serialize_str(Self::ANON_PATH),
269                Self::User(ns) => serializer.serialize_str(ns),
270            }
271        } else {
272            use serde::ser::SerializeTupleVariant;
273            let tag = self.tag() as u32;
274            match self {
275                Self::Kernel => {
276                    serializer.serialize_unit_variant("LibraryNamespace", tag, "Kernel")
277                },
278                Self::Exec => serializer.serialize_unit_variant("LibraryNamespace", tag, "Exec"),
279                Self::Anon => serializer.serialize_unit_variant("LibraryNamespace", tag, "Anon"),
280                Self::User(custom) => {
281                    let mut tuple =
282                        serializer.serialize_tuple_variant("LibraryNamespace", tag, "User", 1)?;
283                    tuple.serialize_field(&custom.as_ref())?;
284                    tuple.end()
285                },
286            }
287        }
288    }
289}
290
291#[cfg(feature = "serde")]
292impl<'de> serde::Deserialize<'de> for LibraryNamespace {
293    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
294    where
295        D: serde::Deserializer<'de>,
296    {
297        use serde::de::{SeqAccess, Unexpected};
298
299        if deserializer.is_human_readable() {
300            let name = <&'de str as serde::Deserialize>::deserialize(deserializer)?;
301            match name {
302                Self::KERNEL_PATH => Ok(Self::Kernel),
303                Self::EXEC_PATH => Ok(Self::Exec),
304                Self::ANON_PATH => Ok(Self::Anon),
305                other => Self::new(other).map_err(serde::de::Error::custom),
306            }
307        } else {
308            const KERNEL: u8 = LibraryNamespace::Kernel.tag();
309            const EXEC: u8 = LibraryNamespace::Exec.tag();
310            const ANON: u8 = LibraryNamespace::Anon.tag();
311            const USER: u8 = ANON + 1;
312
313            serde_untagged::UntaggedEnumVisitor::new()
314                .expecting("a valid section id")
315                .string(|s| Self::from_str(s).map_err(serde::de::Error::custom))
316                .u8(|tag| match tag {
317                    KERNEL => Ok(Self::Kernel),
318                    EXEC => Ok(Self::Exec),
319                    ANON => Ok(Self::Anon),
320                    USER => {
321                        Err(serde::de::Error::custom("expected a user-defined library namespace"))
322                    },
323                    other => Err(serde::de::Error::invalid_value(
324                        Unexpected::Unsigned(other as u64),
325                        &"a valid library namespace variant",
326                    )),
327                })
328                .seq(|mut seq| {
329                    let tag = seq.next_element::<u8>()?.ok_or_else(|| {
330                        serde::de::Error::invalid_length(0, &"a valid library namespace variant")
331                    })?;
332                    match tag {
333                        KERNEL => Ok(Self::Kernel),
334                        EXEC => Ok(Self::Exec),
335                        ANON => Ok(Self::Anon),
336                        USER => seq
337                            .next_element::<&str>()?
338                            .ok_or_else(|| {
339                                serde::de::Error::invalid_length(
340                                    1,
341                                    &"a user-defined library namespace",
342                                )
343                            })
344                            .and_then(|s| Self::new(s).map_err(serde::de::Error::custom)),
345                        other => Err(serde::de::Error::invalid_value(
346                            Unexpected::Unsigned(other as u64),
347                            &"a valid library namespace variant",
348                        )),
349                    }
350                })
351                .deserialize(deserializer)
352        }
353    }
354}
355
356#[cfg(all(feature = "std", any(test, feature = "arbitrary")))]
357impl proptest::prelude::Arbitrary for LibraryNamespace {
358    type Parameters = ();
359
360    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
361        use proptest::prelude::*;
362        prop_oneof![
363            Just(LibraryNamespace::Kernel),
364            Just(LibraryNamespace::Exec),
365            Just(LibraryNamespace::Anon),
366            prop::string::string_regex(r"[a-z][a-z0-9_]*")
367                .unwrap()
368                .prop_map(|s| { LibraryNamespace::User(Arc::from(s)) }),
369        ]
370        .boxed()
371    }
372
373    type Strategy = proptest::prelude::BoxedStrategy<Self>;
374}