miden_assembly_syntax/library/
namespace.rs

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