Skip to content

Commit

Permalink
feat: implement procedure annotation syntax
Browse files Browse the repository at this point in the history
This commit introduces the concept of attributes/annotations to Miden
Assembly procedures. These can be used to represent interesting/useful
bits of metadata associated with specific procedures in three different
forms:

* Marker attributes, e.g. `@inline`, a name with no associated data
* List attributes, e.g. `@inline(always)`, i.e. a parameterized
  marker; multiple values can be provided as comma-delimited metadata
  expressions.
* Key-value attributes, e.g. `@props(<key> = <value>, ...)`, where
  `<key>` must be a valid identifier, and `<value>` must be a valid
  metadata expression. Multiple key-value attributes can be set at once,
  or you can specify the same key-value attribute multiple times, so
  long as each instance does not have any keys that conflict with
  previous instances.

Metadata expressions come in three possible types:

* bare identifier, e.g. `foo`
* quoted string, e.g. `"some text"`
* integer value, either decimal or hexadecimal format, e.g. `1` or
  `0x01`

Attributes will provide the foundation for upcoming changes that will
rely on being able to attach metadata to procedures. For now, attributes
may _only_ be attached to procedure definitions, not re-exports or any
other syntactic construct.

NOTE: This does not yet act on any attributes, nor store them anywhere
when assembling to MAST. For now, they are simply parsed, made available
in the AST, and ignored. Future PRs will introduce these as needed.

Closes #1434
  • Loading branch information
bitwalker committed Sep 25, 2024
1 parent 097f76d commit 8762dae
Show file tree
Hide file tree
Showing 16 changed files with 1,499 additions and 26 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 0.11.0 (TBD)

#### Enhancements

- Added support for procedure annotation (attribute) syntax to Miden Assembly

#### Changes

- [BREAKING] Wrapped `MastForest`s in `Program` and `Library` structs in `Arc` (#1465).
Expand Down
253 changes: 253 additions & 0 deletions assembly/src/ast/attribute/meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
mod expr;
mod kv;
mod list;

use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
use core::fmt;

pub use self::{expr::MetaExpr, kv::MetaKeyValue, list::MetaList};
use crate::{ast::Ident, parser::HexEncodedValue, Felt, SourceSpan, Span};

/// Represents the metadata provided as arguments to an attribute.
#[derive(Clone, PartialEq, Eq)]
pub enum Meta {
/// Represents empty metadata, e.g. `@foo`
Unit,
/// A list of metadata expressions, e.g. `@foo(a, "some text", 0x01)`
///
/// The list should always have at least one element, and this is guaranteed by the parser.
List(Vec<MetaExpr>),
/// A set of uniquely-named metadata expressions, e.g. `@foo(letter = a, text = "some text")`
///
/// The set should always have at least one key-value pair, and this is guaranteed by the
/// parser.
KeyValue(BTreeMap<Ident, MetaExpr>),
}
impl Meta {
/// Borrow the metadata without unwrapping the specific type
///
/// Returns `None` if there is no meaningful metadata
#[inline]
pub fn borrow(&self) -> Option<BorrowedMeta<'_>> {
match self {
Self::Unit => None,
Self::List(ref list) => Some(BorrowedMeta::List(list)),
Self::KeyValue(ref kv) => Some(BorrowedMeta::KeyValue(kv)),
}
}
}
impl FromIterator<MetaItem> for Meta {
#[inline]
fn from_iter<T: IntoIterator<Item = MetaItem>>(iter: T) -> Self {
let mut iter = iter.into_iter();
match iter.next() {
None => Self::Unit,
Some(MetaItem::Expr(expr)) => Self::List(
core::iter::once(expr)
.chain(iter.map(|item| match item {
MetaItem::Expr(expr) => expr,
MetaItem::KeyValue(..) => unsafe { core::hint::unreachable_unchecked() },
}))
.collect(),
),
Some(MetaItem::KeyValue(k, v)) => Self::KeyValue(
core::iter::once((k, v))
.chain(iter.map(|item| match item {
MetaItem::KeyValue(k, v) => (k, v),
MetaItem::Expr(_) => unsafe { core::hint::unreachable_unchecked() },
}))
.collect(),
),
}
}
}

impl FromIterator<MetaExpr> for Meta {
#[inline]
fn from_iter<T: IntoIterator<Item = MetaExpr>>(iter: T) -> Self {
Self::List(iter.into_iter().collect())
}
}

impl FromIterator<(Ident, MetaExpr)> for Meta {
#[inline]
fn from_iter<T: IntoIterator<Item = (Ident, MetaExpr)>>(iter: T) -> Self {
Self::KeyValue(iter.into_iter().collect())
}
}

impl<'a> FromIterator<(&'a str, MetaExpr)> for Meta {
#[inline]
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (&'a str, MetaExpr)>,
{
Self::KeyValue(
iter.into_iter()
.map(|(k, v)| {
let k = Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(k)));
(k, v)
})
.collect(),
)
}
}

impl<I, V> From<I> for Meta
where
Meta: FromIterator<V>,
I: IntoIterator<Item = V>,
{
#[inline]
fn from(iter: I) -> Self {
Self::from_iter(iter)
}
}

/// Represents a reference to the metadata for an [super::Attribute]
///
/// See [Meta] for what metadata is represented, and its syntax.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum BorrowedMeta<'a> {
/// A list of metadata expressions
List(&'a [MetaExpr]),
/// A list of uniquely-named metadata expressions
KeyValue(&'a BTreeMap<Ident, MetaExpr>),
}
impl fmt::Debug for BorrowedMeta<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::List(items) => write!(f, "{items:#?}"),
Self::KeyValue(items) => write!(f, "{items:#?}"),
}
}
}

/// Represents a single metadata item provided as an argument to an attribute.
///
/// For example, the `foo` attribute in `@foo(bar, baz)` has two metadata items, both of `Expr`
/// type, which compose a `
#[derive(Clone, PartialEq, Eq)]
pub enum MetaItem {
/// A metadata expression, e.g. `"some text"` in `@foo("some text")`
///
/// This represents the element type for `Meta::List`-based attributes.
Expr(MetaExpr),
/// A named metadata expression, e.g. `letter = a` in `@foo(letter = a)`
///
/// This represents the element type for `Meta::KeyValue`-based attributes.
KeyValue(Ident, MetaExpr),
}

impl MetaItem {
/// Unwrap this item to extract the contained [MetaExpr].
///
/// Panics if this item is not the `Expr` variant.
#[inline]
#[track_caller]
pub fn unwrap_expr(self) -> MetaExpr {
match self {
Self::Expr(expr) => expr,
Self::KeyValue(..) => unreachable!("tried to unwrap key-value as expression"),
}
}

/// Unwrap this item to extract the contained key-value pair.
///
/// Panics if this item is not the `KeyValue` variant.
#[inline]
#[track_caller]
pub fn unwrap_key_value(self) -> (Ident, MetaExpr) {
match self {
Self::KeyValue(k, v) => (k, v),
Self::Expr(_) => unreachable!("tried to unwrap expression as key-value"),
}
}
}

impl From<Ident> for MetaItem {
fn from(value: Ident) -> Self {
Self::Expr(MetaExpr::Ident(value))
}
}

impl From<&str> for MetaItem {
fn from(value: &str) -> Self {
Self::Expr(MetaExpr::String(Ident::new_unchecked(Span::new(
SourceSpan::UNKNOWN,
Arc::from(value),
))))
}
}

impl From<String> for MetaItem {
fn from(value: String) -> Self {
Self::Expr(MetaExpr::String(Ident::new_unchecked(Span::new(
SourceSpan::UNKNOWN,
Arc::from(value.into_boxed_str()),
))))
}
}

impl From<u8> for MetaItem {
fn from(value: u8) -> Self {
Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U8(value))))
}
}

impl From<u16> for MetaItem {
fn from(value: u16) -> Self {
Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U16(value))))
}
}

impl From<u32> for MetaItem {
fn from(value: u32) -> Self {
Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U32(value))))
}
}

impl From<Felt> for MetaItem {
fn from(value: Felt) -> Self {
Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Felt(value))))
}
}

impl From<[Felt; 4]> for MetaItem {
fn from(value: [Felt; 4]) -> Self {
Self::Expr(MetaExpr::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Word(value))))
}
}

impl<V> From<(Ident, V)> for MetaItem
where
V: Into<MetaExpr>,
{
fn from(entry: (Ident, V)) -> Self {
let (key, value) = entry;
Self::KeyValue(key, value.into())
}
}

impl<V> From<(&str, V)> for MetaItem
where
V: Into<MetaExpr>,
{
fn from(entry: (&str, V)) -> Self {
let (key, value) = entry;
let key = Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(key)));
Self::KeyValue(key, value.into())
}
}

impl<V> From<(String, V)> for MetaItem
where
V: Into<MetaExpr>,
{
fn from(entry: (String, V)) -> Self {
let (key, value) = entry;
let key =
Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(key.into_boxed_str())));
Self::KeyValue(key, value.into())
}
}
86 changes: 86 additions & 0 deletions assembly/src/ast/attribute/meta/expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use alloc::{string::String, sync::Arc};

use crate::{ast::Ident, parser::HexEncodedValue, prettier, Felt, SourceSpan, Span, Spanned};

/// Represents a metadata expression of an [crate::ast::Attribute]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MetaExpr {
/// An identifier/keyword, e.g. `inline`
Ident(Ident),
/// A decimal or hexadecimal integer value
Int(Span<HexEncodedValue>),
/// A quoted string or identifier
String(Ident),
}

impl prettier::PrettyPrint for MetaExpr {
fn render(&self) -> prettier::Document {
use prettier::*;

match self {
Self::Ident(id) => text(id),
Self::Int(value) => text(value),
Self::String(id) => text(format!("\"{}\"", id.as_str().escape_default())),
}
}
}

impl From<Ident> for MetaExpr {
fn from(value: Ident) -> Self {
Self::Ident(value)
}
}

impl From<&str> for MetaExpr {
fn from(value: &str) -> Self {
Self::String(Ident::new_unchecked(Span::new(SourceSpan::UNKNOWN, Arc::from(value))))
}
}

impl From<String> for MetaExpr {
fn from(value: String) -> Self {
Self::String(Ident::new_unchecked(Span::new(
SourceSpan::UNKNOWN,
Arc::from(value.into_boxed_str()),
)))
}
}

impl From<u8> for MetaExpr {
fn from(value: u8) -> Self {
Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U8(value)))
}
}

impl From<u16> for MetaExpr {
fn from(value: u16) -> Self {
Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U16(value)))
}
}

impl From<u32> for MetaExpr {
fn from(value: u32) -> Self {
Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::U32(value)))
}
}

impl From<Felt> for MetaExpr {
fn from(value: Felt) -> Self {
Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Felt(value)))
}
}

impl From<[Felt; 4]> for MetaExpr {
fn from(value: [Felt; 4]) -> Self {
Self::Int(Span::new(SourceSpan::UNKNOWN, HexEncodedValue::Word(value)))
}
}

impl Spanned for MetaExpr {
fn span(&self) -> SourceSpan {
match self {
Self::Ident(spanned) | Self::String(spanned) => spanned.span(),
Self::Int(spanned) => spanned.span(),
}
}
}
Loading

0 comments on commit 8762dae

Please sign in to comment.