miden_assembly/library/
namespace.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
use alloc::{string::ToString, sync::Arc};
use core::{
    fmt,
    str::{self, FromStr},
};

use crate::{
    ast::Ident, diagnostics::Diagnostic, ByteReader, ByteWriter, Deserializable,
    DeserializationError, LibraryPath, Serializable, Span,
};

// LIBRARY NAMESPACE
// ================================================================================================

/// Represents an error when parsing or validating a library namespace
#[derive(Debug, thiserror::Error, Diagnostic, PartialEq, Eq)]
pub enum LibraryNamespaceError {
    #[error("invalid library namespace name: cannot be a empty")]
    #[diagnostic()]
    Empty,
    #[error("invalid library namespace name: too many characters")]
    #[diagnostic()]
    Length,
    #[error("invalid character in library namespace: expected lowercase ascii-alphanumeric character or '_'")]
    #[diagnostic()]
    InvalidChars,
    #[error(
        "invalid library namespace name: must start with lowercase ascii-alphabetic character"
    )]
    #[diagnostic()]
    InvalidStart,
}

/// Represents the root component of a library path, akin to a Rust crate name
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum LibraryNamespace {
    /// A reserved namespace for kernel modules
    Kernel = 0,
    /// A reserved namespace for executable modules
    Exec,
    /// A reserved namespace assigned to anonymous libraries with no path
    #[default]
    Anon,
    /// A user-defined namespace
    User(Arc<str>),
}

// ------------------------------------------------------------------------------------------------
/// Constants
impl LibraryNamespace {
    /// Namespaces must be 255 bytes or less
    pub const MAX_LENGTH: usize = u8::MAX as usize;

    /// Base kernel path.
    pub const KERNEL_PATH: &'static str = "#sys";

    /// Path for an executable module.
    pub const EXEC_PATH: &'static str = "#exec";

    /// Path for a module without library path.
    pub const ANON_PATH: &'static str = "#anon";
}

// ------------------------------------------------------------------------------------------------
/// Constructors
impl LibraryNamespace {
    /// Construct a new [LibraryNamespace] from `source`
    pub fn new<S>(source: S) -> Result<Self, LibraryNamespaceError>
    where
        S: AsRef<str>,
    {
        source.as_ref().parse()
    }

    /// Construct a new [LibraryNamespace] from a previously-validated [Ident].
    ///
    /// NOTE: The caller must ensure that the given identifier is a valid namespace name.
    pub fn from_ident_unchecked(name: Ident) -> Self {
        match name.as_str() {
            Self::KERNEL_PATH => Self::Kernel,
            Self::EXEC_PATH => Self::Exec,
            Self::ANON_PATH => Self::Anon,
            _ => Self::User(name.into_inner()),
        }
    }

    /// Parse a [LibraryNamespace] by taking the prefix of the given path string, and returning
    /// the namespace and remaining string if successful.
    pub fn strip_path_prefix(path: &str) -> Result<(Self, &str), LibraryNamespaceError> {
        match path.split_once("::") {
            Some((ns, rest)) => ns.parse().map(|ns| (ns, rest)),
            None => path.parse().map(|ns| (ns, "")),
        }
    }
}

// ------------------------------------------------------------------------------------------------
/// Public accessors
impl LibraryNamespace {
    /// Returns true if this namespace is a reserved namespace.
    pub fn is_reserved(&self) -> bool {
        !matches!(self, Self::User(_))
    }

    /// Checks if `source` is a valid [LibraryNamespace]
    pub fn validate(source: impl AsRef<str>) -> Result<(), LibraryNamespaceError> {
        let source = source.as_ref();
        if source.is_empty() {
            return Err(LibraryNamespaceError::Empty);
        }
        if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) {
            return Ok(());
        }
        if source.as_bytes().len() > Self::MAX_LENGTH {
            return Err(LibraryNamespaceError::Length);
        }
        if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) {
            return Err(LibraryNamespaceError::InvalidStart);
        }
        if !source.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '_')) {
            return Err(LibraryNamespaceError::InvalidChars);
        }
        Ok(())
    }
}

// ------------------------------------------------------------------------------------------------
/// Conversions
impl LibraryNamespace {
    /// Get the string representation of this namespace.
    pub fn as_str(&self) -> &str {
        match self {
            Self::Kernel => Self::KERNEL_PATH,
            Self::Exec => Self::EXEC_PATH,
            Self::Anon => Self::ANON_PATH,
            Self::User(ref path) => path,
        }
    }

    /// Get an [`Arc<str>`] representing this namespace.
    pub fn as_refcounted_str(&self) -> Arc<str> {
        match self {
            Self::User(ref path) => path.clone(),
            other => Arc::from(other.as_str().to_string().into_boxed_str()),
        }
    }

    /// Create a [LibraryPath] representing this [LibraryNamespace].
    pub fn to_path(&self) -> LibraryPath {
        LibraryPath::from(self.clone())
    }

    /// Create an [Ident] representing this namespace.
    pub fn to_ident(&self) -> Ident {
        Ident::new_unchecked(Span::unknown(self.as_refcounted_str()))
    }
}

impl core::ops::Deref for LibraryNamespace {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.as_str()
    }
}

impl AsRef<str> for LibraryNamespace {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl fmt::Display for LibraryNamespace {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl FromStr for LibraryNamespace {
    type Err = LibraryNamespaceError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            Self::KERNEL_PATH => Ok(Self::Kernel),
            Self::EXEC_PATH => Ok(Self::Exec),
            Self::ANON_PATH => Ok(Self::Anon),
            other => {
                Self::validate(other)?;
                Ok(Self::User(Arc::from(other.to_string().into_boxed_str())))
            },
        }
    }
}

impl TryFrom<Ident> for LibraryNamespace {
    type Error = LibraryNamespaceError;
    fn try_from(ident: Ident) -> Result<Self, Self::Error> {
        match ident.as_str() {
            Self::KERNEL_PATH => Ok(Self::Kernel),
            Self::EXEC_PATH => Ok(Self::Exec),
            Self::ANON_PATH => Ok(Self::Anon),
            other => Self::new(other),
        }
    }
}

// SERIALIZATION / DESERIALIZATION
// ------------------------------------------------------------------------------------------------

impl Serializable for LibraryNamespace {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        // Catch any situations where a namespace was incorrectly constructed
        let bytes = self.as_bytes();
        assert!(bytes.len() <= u8::MAX as usize, "namespace too long");

        target.write_u8(bytes.len() as u8);
        target.write_bytes(bytes);
    }
}

impl Deserializable for LibraryNamespace {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let num_bytes = source.read_u8()? as usize;
        let name = source.read_slice(num_bytes)?;
        let name =
            str::from_utf8(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
        Self::new(name).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
    }
}