miden_assembly_syntax/ast/attribute/
mod.rs

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