miden_assembly_syntax/library/
namespace.rs1use 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#[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#[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 Kernel = 0,
47 Exec,
49 #[default]
51 Anon,
52 User(Arc<str>),
54}
55
56impl LibraryNamespace {
59 pub const MAX_LENGTH: usize = u8::MAX as usize;
61
62 pub const KERNEL_PATH: &'static str = "$kernel";
64
65 pub const EXEC_PATH: &'static str = "$exec";
67
68 pub const ANON_PATH: &'static str = "$anon";
70}
71
72impl LibraryNamespace {
75 pub fn new<S>(source: S) -> Result<Self, LibraryNamespaceError>
77 where
78 S: AsRef<str>,
79 {
80 source.as_ref().parse()
81 }
82
83 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 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
105impl LibraryNamespace {
108 pub fn is_reserved(&self) -> bool {
110 !matches!(self, Self::User(_))
111 }
112
113 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
143impl LibraryNamespace {
146 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 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 pub fn to_path(&self) -> LibraryPath {
166 LibraryPath::from(self.clone())
167 }
168
169 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 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
234impl Serializable for LibraryNamespace {
238 fn write_into<W: ByteWriter>(&self, target: &mut W) {
239 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}