miden_assembly/ast/attribute/
mod.rs

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