Skip to main content

miden_assembly_syntax/ast/attribute/
mod.rs

1mod meta;
2mod set;
3
4use core::fmt;
5
6use miden_core::serde::{
7    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
8};
9use miden_debug_types::{SourceSpan, Spanned};
10
11pub use self::{
12    meta::{BorrowedMeta, Meta, MetaExpr, MetaItem, MetaKeyValue, MetaList},
13    set::{AttributeSet, AttributeSetEntry},
14};
15use crate::{ast::Ident, prettier};
16
17/// An [Attribute] represents some named metadata attached to a Miden Assembly procedure.
18///
19/// An attribute has no predefined structure per se, but syntactically there are three types:
20///
21/// * Marker attributes, i.e. just a name and no associated data. Attributes of this type are used
22///   to "mark" the item they are attached to with some unique trait or behavior implied by the
23///   name. For example, `@inline`. NOTE: `@inline()` is not valid syntax.
24///
25/// * List attributes, i.e. a name and one or more comma-delimited expressions. Attributes of this
26///   type are used for cases where you want to parameterize a marker-like trait. To use a Rust
27///   example, `#[derive(Trait)]` is a list attribute, where `derive` is the marker, but we want to
28///   instruct whatever processes derives, what traits it needs to derive. The equivalent syntax in
29///   Miden Assembly would be `@derive(Trait)`. Lists must always have at least one item.
30///
31/// * Key-value attributes, i.e. a name and a value. Attributes of this type are used to attach
32///   named properties to an item. For example, `@storage(offset = 1)`. Possible value types are:
33///   bare identifiers, decimal or hexadecimal integers, and quoted strings.
34///
35/// There are no restrictions on what attributes can exist or be used. However, there are a set of
36/// attributes that the assembler knows about, and acts on, which will be stripped during assembly.
37/// Any remaining attributes we don't explicitly handle in the assembler, will be passed along as
38/// metadata attached to the procedures in the MAST output by the assembler.
39#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[cfg_attr(
41    all(feature = "arbitrary", test),
42    miden_test_serialization_macros::serialization_test
43)]
44pub enum Attribute {
45    /// A named behavior, trait or action; e.g. `@inline`
46    Marker(Ident),
47    /// A parameterized behavior, trait or action; e.g. `@inline(always)` or `@derive(Foo, Bar)`
48    List(MetaList),
49    /// A named property; e.g. `@props(key = "value")`, `@props(a = 1, b = 0x1)`
50    KeyValue(MetaKeyValue),
51}
52
53impl Attribute {
54    /// Create a new [Attribute] with the given metadata.
55    ///
56    /// The metadata value must be convertible to [Meta].
57    ///
58    /// For marker attributes, you can either construct the `Marker` variant directly, or pass
59    /// either `Meta::Unit` or `None` as the metadata argument.
60    ///
61    /// If the metadata is empty, a `Marker` attribute will be produced, otherwise the type depends
62    /// on the metadata. If the metadata is _not_ key-value shaped, a `List` is produced, otherwise
63    /// a `KeyValue`.
64    pub fn new(name: Ident, metadata: impl Into<Meta>) -> Self {
65        let metadata = metadata.into();
66        match metadata {
67            Meta::Unit => Self::Marker(name),
68            Meta::List(items) => Self::List(MetaList { span: Default::default(), name, items }),
69            Meta::KeyValue(items) => {
70                Self::KeyValue(MetaKeyValue { span: Default::default(), name, items })
71            },
72        }
73    }
74
75    /// Create a new [Attribute] from an metadata-producing iterator.
76    ///
77    /// If the iterator is empty, a `Marker` attribute will be produced, otherwise the type depends
78    /// on the metadata. If the metadata is _not_ key-value shaped, a `List` is produced, otherwise
79    /// a `KeyValue`.
80    pub fn from_iter<V, I>(name: Ident, metadata: I) -> Self
81    where
82        Meta: FromIterator<V>,
83        I: IntoIterator<Item = V>,
84    {
85        Self::new(name, Meta::from_iter(metadata))
86    }
87
88    /// Set the source location for this attribute
89    pub fn with_span(self, span: SourceSpan) -> Self {
90        match self {
91            Self::Marker(id) => Self::Marker(id.with_span(span)),
92            Self::List(list) => Self::List(list.with_span(span)),
93            Self::KeyValue(kv) => Self::KeyValue(kv.with_span(span)),
94        }
95    }
96
97    /// Get the name of this attribute as a string
98    pub fn name(&self) -> &str {
99        match self {
100            Self::Marker(id) => id.as_str(),
101            Self::List(list) => list.name(),
102            Self::KeyValue(kv) => kv.name(),
103        }
104    }
105
106    /// Get the name of this attribute as an [Ident]
107    pub fn id(&self) -> Ident {
108        match self {
109            Self::Marker(id) => id.clone(),
110            Self::List(list) => list.id(),
111            Self::KeyValue(kv) => kv.id(),
112        }
113    }
114
115    /// Returns true if this is a marker attribute
116    pub fn is_marker(&self) -> bool {
117        matches!(self, Self::Marker(_))
118    }
119
120    /// Returns true if this is a list attribute
121    pub fn is_list(&self) -> bool {
122        matches!(self, Self::List(_))
123    }
124
125    /// Returns true if this is a key-value attribute
126    pub fn is_key_value(&self) -> bool {
127        matches!(self, Self::KeyValue(_))
128    }
129
130    /// Get the metadata for this attribute
131    ///
132    /// Returns `None` if this is a marker attribute, and thus has no metadata
133    pub fn metadata(&self) -> Option<BorrowedMeta<'_>> {
134        match self {
135            Self::Marker(_) => None,
136            Self::List(list) => Some(BorrowedMeta::List(&list.items)),
137            Self::KeyValue(kv) => Some(BorrowedMeta::KeyValue(&kv.items)),
138        }
139    }
140}
141
142impl fmt::Debug for Attribute {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            Self::Marker(id) => f.debug_tuple("Marker").field(&id).finish(),
146            Self::List(meta) => f
147                .debug_struct("List")
148                .field("name", &meta.name)
149                .field("items", &meta.items)
150                .finish(),
151            Self::KeyValue(meta) => f
152                .debug_struct("KeyValue")
153                .field("name", &meta.name)
154                .field("items", &meta.items)
155                .finish(),
156        }
157    }
158}
159
160impl fmt::Display for Attribute {
161    #[inline]
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        use prettier::PrettyPrint;
164        self.pretty_print(f)
165    }
166}
167
168impl prettier::PrettyPrint for Attribute {
169    fn render(&self) -> prettier::Document {
170        use prettier::*;
171        let doc = text(format!("@{}", self.name()));
172        match self {
173            Self::Marker(_) => doc,
174            Self::List(meta) => {
175                let singleline_items = meta
176                    .items
177                    .iter()
178                    .map(PrettyPrint::render)
179                    .reduce(|acc, item| acc + const_text(", ") + item)
180                    .unwrap_or(Document::Empty);
181                let multiline_items = indent(
182                    4,
183                    nl() + meta
184                        .items
185                        .iter()
186                        .map(PrettyPrint::render)
187                        .reduce(|acc, item| acc + nl() + item)
188                        .unwrap_or(Document::Empty),
189                ) + nl();
190                doc + const_text("(") + (singleline_items | multiline_items) + const_text(")")
191            },
192            Self::KeyValue(meta) => {
193                let singleline_items = meta
194                    .items
195                    .iter()
196                    .map(|(k, v)| text(k) + const_text(" = ") + v.render())
197                    .reduce(|acc, item| acc + const_text(", ") + item)
198                    .unwrap_or(Document::Empty);
199                let multiline_items = indent(
200                    4,
201                    nl() + meta
202                        .items
203                        .iter()
204                        .map(|(k, v)| text(k) + const_text(" = ") + v.render())
205                        .reduce(|acc, item| acc + nl() + item)
206                        .unwrap_or(Document::Empty),
207                ) + nl();
208                doc + const_text("(") + (singleline_items | multiline_items) + const_text(")")
209            },
210        }
211    }
212}
213
214impl Spanned for Attribute {
215    fn span(&self) -> SourceSpan {
216        match self {
217            Self::Marker(id) => id.span(),
218            Self::List(list) => list.span(),
219            Self::KeyValue(kv) => kv.span(),
220        }
221    }
222}
223
224impl From<Ident> for Attribute {
225    fn from(value: Ident) -> Self {
226        Self::Marker(value)
227    }
228}
229
230impl<K, V> From<(K, V)> for Attribute
231where
232    K: Into<Ident>,
233    V: Into<MetaExpr>,
234{
235    fn from(kv: (K, V)) -> Self {
236        let (key, value) = kv;
237        Self::List(MetaList {
238            span: SourceSpan::default(),
239            name: key.into(),
240            items: vec![value.into()],
241        })
242    }
243}
244
245impl From<MetaList> for Attribute {
246    fn from(value: MetaList) -> Self {
247        Self::List(value)
248    }
249}
250
251impl From<MetaKeyValue> for Attribute {
252    fn from(value: MetaKeyValue) -> Self {
253        Self::KeyValue(value)
254    }
255}
256
257#[cfg(feature = "arbitrary")]
258impl proptest::arbitrary::Arbitrary for Attribute {
259    type Parameters = ();
260
261    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
262        use proptest::{arbitrary::any, prop_oneof, strategy::Strategy};
263
264        prop_oneof![
265            any::<Ident>().prop_map(Self::Marker),
266            any::<MetaList>().prop_map(Self::List),
267            any::<MetaKeyValue>().prop_map(Self::KeyValue),
268        ]
269        .boxed()
270    }
271
272    type Strategy = proptest::prelude::BoxedStrategy<Self>;
273}
274
275impl Serializable for Attribute {
276    fn write_into<W: ByteWriter>(&self, target: &mut W) {
277        match self {
278            Self::Marker(name) => {
279                target.write_u8(0);
280                name.write_into(target);
281            },
282            Self::List(list) => {
283                target.write_u8(1);
284                list.write_into(target);
285            },
286            Self::KeyValue(kv) => {
287                target.write_u8(2);
288                kv.write_into(target);
289            },
290        }
291    }
292}
293
294impl Deserializable for Attribute {
295    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
296        match source.read_u8()? {
297            0 => Ident::read_from(source).map(Self::Marker),
298            1 => MetaList::read_from(source).map(Self::List),
299            2 => MetaKeyValue::read_from(source).map(Self::KeyValue),
300            n => Err(DeserializationError::InvalidValue(format!(
301                "unknown Attribute variant tag '{n}'"
302            ))),
303        }
304    }
305}