miden_assembly_syntax/ast/attribute/
mod.rs

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