#[cfg(feature = "arbitrary")]
use alloc::vec;
use alloc::{
borrow::{Cow, ToOwned},
format,
string::ToString,
};
use core::{fmt, str::FromStr};
use miden_assembly_syntax::DisplayHex;
use miden_core::serde::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};
#[cfg(feature = "arbitrary")]
use proptest::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
#[repr(transparent)]
pub struct SectionId(Cow<'static, str>);
impl SectionId {
pub const DEBUG_TYPES: Self = Self(Cow::Borrowed("debug_types"));
pub const DEBUG_SOURCES: Self = Self(Cow::Borrowed("debug_sources"));
pub const DEBUG_FUNCTIONS: Self = Self(Cow::Borrowed("debug_functions"));
pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
pub const PROJECT_SOURCE_PROVENANCE: Self = Self(Cow::Borrowed("project_source_provenance"));
pub const KERNEL: Self = Self(Cow::Borrowed("kernel"));
pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
let name = name.as_ref();
if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
return Err(InvalidSectionIdError::InvalidStart);
}
if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
return Err(InvalidSectionIdError::InvalidCharacter);
}
Ok(Self(name.to_string().into()))
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
}
#[derive(Debug, thiserror::Error)]
pub enum InvalidSectionIdError {
#[error("invalid section id: cannot be empty")]
Empty,
#[error(
"invalid section id: contains invalid characters, only the set [a-z0-9._-] are allowed"
)]
InvalidCharacter,
#[error("invalid section id: must start with a character in the set [a-z_]")]
InvalidStart,
}
impl FromStr for SectionId {
type Err = InvalidSectionIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"debug_types" => Ok(Self::DEBUG_TYPES),
"debug_sources" => Ok(Self::DEBUG_SOURCES),
"debug_functions" => Ok(Self::DEBUG_FUNCTIONS),
"account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
"project_source_provenance" => Ok(Self::PROJECT_SOURCE_PROVENANCE),
"kernel" => Ok(Self::KERNEL),
custom => Self::custom(custom),
}
}
}
impl fmt::Display for SectionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Section {
pub id: SectionId,
pub data: Cow<'static, [u8]>,
}
impl fmt::Debug for Section {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let verbose = f.alternate();
let mut builder = f.debug_struct("Section");
builder.field("id", &format_args!("{}", &self.id));
if verbose {
builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
} else {
builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
}
}
}
impl Section {
pub fn new<B>(id: SectionId, data: B) -> Self
where
B: Into<Cow<'static, [u8]>>,
{
Self { id, data: data.into() }
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn len(&self) -> usize {
self.data.len()
}
}
impl Serializable for Section {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let id = self.id.as_str();
target.write_usize(id.len());
target.write_bytes(id.as_bytes());
target.write_usize(self.len());
target.write_bytes(&self.data);
}
}
impl Deserializable for Section {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let id_len = source.read_usize()?;
let id_bytes = source.read_slice(id_len)?;
let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
})?;
let id = SectionId(Cow::Owned(id_str.to_owned()));
let len = source.read_usize()?;
let bytes = source.read_slice(len)?;
Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
}
}
#[cfg(feature = "arbitrary")]
impl Arbitrary for SectionId {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
use alloc::string::String;
let builtins = proptest::sample::select(vec![
Self::DEBUG_TYPES,
Self::DEBUG_SOURCES,
Self::DEBUG_FUNCTIONS,
Self::ACCOUNT_COMPONENT_METADATA,
Self::PROJECT_SOURCE_PROVENANCE,
Self::KERNEL,
]);
let custom = (
proptest::prop_oneof![
proptest::char::range('a', 'z'),
proptest::char::range('A', 'Z'),
Just('_'),
],
proptest::collection::vec(
proptest::prop_oneof![
proptest::char::range('a', 'z'),
proptest::char::range('A', 'Z'),
proptest::char::range('0', '9'),
Just('.'),
Just('_'),
Just('-'),
],
0..31,
),
)
.prop_map(|(first, rest)| {
let mut name = String::with_capacity(rest.len() + 1);
name.push(first);
name.extend(rest);
Self::custom(name).expect("generated custom section ids are valid")
});
proptest::prop_oneof![builtins, custom].boxed()
}
}