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 values.
* Key-value attributes, e.g. `#[key = <value>]`, where `<value>` can be
  a "meta expression" of three possible types: identifier, string, or
  integer (in either decimal or hexadecimal format).

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 23, 2024
1 parent 097f76d commit d855bcd
Show file tree
Hide file tree
Showing 10 changed files with 784 additions and 24 deletions.
473 changes: 473 additions & 0 deletions assembly/src/ast/attribute.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions assembly/src/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Abstract syntax tree (AST) components of Miden programs, modules, and procedures.

mod attribute;
mod block;
mod constants;
mod form;
Expand All @@ -16,6 +17,7 @@ mod tests;
pub mod visit;

pub use self::{
attribute::{Attribute, AttributeSet, MetaExpr, MetaKeyValue, MetaList},
block::Block,
constants::{Constant, ConstantExpr, ConstantOp},
form::Form,
Expand Down
13 changes: 12 additions & 1 deletion assembly/src/ast/procedure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ pub use self::{
procedure::{Procedure, Visibility},
resolver::{LocalNameResolver, ResolvedProcedure},
};
use crate::{ast::Invoke, SourceSpan, Span, Spanned};
use crate::{
ast::{AttributeSet, Invoke},
SourceSpan, Span, Spanned,
};

// EXPORT
// ================================================================================================
Expand Down Expand Up @@ -56,6 +59,14 @@ impl Export {
}
}

/// Returns the attributes for this procedure.
pub fn attributes(&self) -> Option<&AttributeSet> {
match self {
Self::Procedure(ref proc) => Some(proc.attributes()),
Self::Alias(_) => None,
}
}

/// Returns the visibility of this procedure (e.g. public or private).
///
/// See [Visibility] for more details on what visibilities are supported.
Expand Down
38 changes: 37 additions & 1 deletion assembly/src/ast/procedure/procedure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use core::fmt;

use super::ProcedureName;
use crate::{
ast::{Block, Invoke},
ast::{Attribute, AttributeSet, Block, Invoke},
SourceSpan, Span, Spanned,
};

Expand Down Expand Up @@ -55,6 +55,8 @@ pub struct Procedure {
span: SourceSpan,
/// The documentation attached to this procedure
docs: Option<Span<String>>,
/// The attributes attached to this procedure
attrs: AttributeSet,
/// The local name of this procedure
name: ProcedureName,
/// The visibility of this procedure (i.e. whether it is exported or not)
Expand All @@ -81,6 +83,7 @@ impl Procedure {
Self {
span,
docs: None,
attrs: Default::default(),
name,
visibility,
num_locals,
Expand All @@ -95,6 +98,15 @@ impl Procedure {
self
}

/// Adds attributes to this procedure definition
pub fn with_attributes<I>(mut self, attrs: I) -> Self
where
I: IntoIterator<Item = Attribute>,
{
self.attrs.extend(attrs);
self
}

/// Modifies the visibility of this procedure.
///
/// This is made crate-local as the visibility of a procedure is virtually always determined
Expand Down Expand Up @@ -134,6 +146,30 @@ impl Procedure {
self.docs.as_ref()
}

/// Get the attributes attached to this procedure
#[inline]
pub fn attributes(&self) -> &AttributeSet {
&self.attrs
}

/// Get the attributes attached to this procedure, mutably
#[inline]
pub fn attributes_mut(&mut self) -> &mut AttributeSet {
&mut self.attrs
}

/// Returns true if this procedure has an attribute named `name`
#[inline]
pub fn has_attribute(&self, name: impl AsRef<str>) -> bool {
self.attrs.has(name)
}

/// Returns the attribute named `name`, if present
#[inline]
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<&Attribute> {
self.attrs.get(name)
}

/// Returns a reference to the [Block] containing the body of this procedure.
pub fn body(&self) -> &Block {
&self.body
Expand Down
86 changes: 84 additions & 2 deletions assembly/src/ast/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ use crate::{
Felt, Span,
};

macro_rules! id {
($name:ident) => {
Ident::new(stringify!($name)).unwrap()
};
}

macro_rules! id_unchecked {
($name:literal) => {
Ident::new_unchecked(Span::new($crate::SourceSpan::UNKNOWN, Arc::from($name)))
};
}

macro_rules! inst {
($inst:ident($value:expr)) => {
Op::Inst(Span::unknown(Instruction::$inst($value)))
Expand Down Expand Up @@ -157,6 +169,20 @@ macro_rules! proc {
.with_docs(Some(Span::unknown($docs.to_string()))),
))
};

($docs:expr, $attrs:expr, $name:ident, $num_locals:literal, $body:expr) => {
Form::Procedure(Export::Procedure(
Procedure::new(
Default::default(),
Visibility::Private,
stringify!($name).parse().expect("invalid procedure name"),
$num_locals,
$body,
)
.with_docs($docs)
.with_attributes($attrs),
))
};
}

macro_rules! export {
Expand Down Expand Up @@ -569,7 +595,7 @@ fn test_ast_parsing_module_sequential_if() -> Result<(), Report> {
}

#[test]
fn parsed_while_if_body() {
fn test_ast_parsing_while_if_body() {
let context = TestContext::new();
let source = source_file!(
&context,
Expand Down Expand Up @@ -599,6 +625,62 @@ fn parsed_while_if_body() {
assert_forms!(context, source, forms);
}

#[test]
fn test_ast_parsing_attributes() -> Result<(), Report> {
let context = TestContext::new();

let source = source_file!(
&context,
r#"
# Simple marker attribute
#[inline]
proc.foo.1
loc_load.0
end
# List attribute
#[inline(always)]
proc.bar.2
padw
end
# Key value attributes of various kinds
#[decimal = 1]
#[hex = 0xdeadbeef]
#[id = foo]
#[string = "not a valid quoted identifier"]
proc.baz.2
padw
end
begin
exec.foo
exec.bar
exec.baz
end"#
);

let inline = Attribute::Marker(id!(inline));
let inline_always = Attribute::List(MetaList::new(id!(inline), [MetaExpr::Ident(id!(always))]));
let decimal = Attribute::KeyValue(MetaKeyValue::new(id!(decimal), 1u8));
let hex = Attribute::KeyValue(MetaKeyValue::new(id!(hex), 0xdeadbeefu32));
let id = Attribute::KeyValue(MetaKeyValue::new(id!(id), MetaExpr::Ident(id!(foo))));
let string = Attribute::KeyValue(MetaKeyValue::new(
id!(string),
MetaExpr::String(id_unchecked!("not a valid quoted identifier")),
));

let forms = module!(
proc!(None, [inline], foo, 1, block!(inst!(LocLoad(0u16.into())))),
proc!(None, [inline_always], bar, 2, block!(inst!(PadW))),
proc!(None, [decimal, hex, id, string], baz, 2, block!(inst!(PadW))),
begin!(exec!(foo), exec!(bar), exec!(baz))
);
assert_eq!(context.parse_forms(source)?, forms);

Ok(())
}

// PROCEDURE IMPORTS
// ================================================================================================

Expand Down Expand Up @@ -1078,6 +1160,6 @@ fn assert_parsing_line_unexpected_token() {
" : ^|^",
" : `-- found a mul here",
" `----",
r#" help: expected "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"#
r#" help: expected "!", or "begin", or "const", or "export", or "proc", or "use", or end of file, or doc comment"#
);
}
10 changes: 10 additions & 0 deletions assembly/src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ pub enum ParsingError {
#[label]
span: SourceSpan,
},
#[error("conflicting attributes for procedure definition")]
#[diagnostic()]
AttributeConflict {
#[label(
"conflict occurs because an attribute with the same name has already been defined"
)]
span: SourceSpan,
#[label("previously defined here")]
prev: SourceSpan,
},
}

impl ParsingError {
Expand Down
82 changes: 80 additions & 2 deletions assembly/src/parser/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ extern {
bare_ident => Token::Ident(<&'input str>),
const_ident => Token::ConstantIdent(<&'input str>),
quoted_ident => Token::QuotedIdent(<&'input str>),
quoted_string => Token::QuotedString(<&'input str>),
hex_value => Token::HexValue(<HexEncodedValue>),
bin_value => Token::BinValue(<BinEncodedValue>),
doc_comment => Token::DocComment(<DocumentationType>),
Expand Down Expand Up @@ -195,19 +196,32 @@ extern {
"!" => Token::Bang,
"::" => Token::ColonColon,
"." => Token::Dot,
"," => Token::Comma,
"=" => Token::Equal,
"(" => Token::Lparen,
"[" => Token::Lbracket,
"-" => Token::Minus,
"+" => Token::Plus,
"//" => Token::SlashSlash,
"/" => Token::Slash,
"*" => Token::Star,
")" => Token::Rparen,
"]" => Token::Rbracket,
"->" => Token::Rstab,
EOF => Token::Eof,
}
}


// comma-delimited with at least one element
#[inline]
CommaDelimited<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T> => {
v.push(e);
v
}
};

// dot-delimited with at least one element
#[inline]
DotDelimited<T>: Vec<T> = {
Expand Down Expand Up @@ -290,6 +304,23 @@ Begin: Form = {
}

Proc: Form = {
<annotations:Annotation*> <mut proc:ProcedureDef> =>? {
let attributes = proc.attributes_mut();
for attr in annotations {
if let Some(prev_attr) = attributes.get(attr.name()) {
return Err(ParseError::User {
error: ParsingError::AttributeConflict { span: attr.span(), prev: prev_attr.span() },
});
}
attributes.insert(attr);
}
Ok(Form::Procedure(Export::Procedure(proc)))
},
AliasDef => Form::Procedure(Export::Alias(<>)),
}

#[inline]
ProcedureDef: Procedure = {
<l:@L> <visibility:Visibility> "." <name:ProcedureName> <num_locals:MaybeParam<U16>> <body:Block> "end" <r:@R> =>? {
let num_locals = num_locals.unwrap_or(0);
let procedure = Procedure::new(
Expand All @@ -299,9 +330,12 @@ Proc: Form = {
num_locals,
body
);
Ok(Form::Procedure(Export::Procedure(procedure)))
Ok(procedure)
},
}

#[inline]
AliasDef: ProcedureAlias = {
<l:@L> "export" "." <name:MaybeQualifiedProcedurePath> <alias:("->" <ProcedureName>)?> <r:@R> =>? {
let span = span!(source_file.id(), l, r);
let alias = match name {
Expand Down Expand Up @@ -341,7 +375,7 @@ Proc: Form = {
ProcedureAlias::new(export_name, AliasTarget::AbsoluteProcedurePath(target))
}
};
Ok(Form::Procedure(Export::Alias(alias)))
Ok(alias)
}
}

Expand All @@ -351,6 +385,50 @@ Visibility: Visibility = {
"export" => Visibility::Public,
}

// ANNOTATIONS
// ================================================================================================

Annotation: Attribute = {
<l:@L> "!" "[" <attr:Attribute> "]" <r:@R> => attr.with_span(span!(source_file.id(), l, r)),
}

#[inline]
Attribute: Attribute = {
BareIdent => Attribute::Marker(<>),
MetaList => Attribute::List(<>),
MetaKeyValue => Attribute::KeyValue(<>),
}

MetaList: MetaList = {
<l:@L> <name:BareIdent> "(" <items:CommaDelimited<MetaExpr>> ")" <r:@R> => {
MetaList { span: span!(source_file.id(), l, r), name, items }
}
}

MetaKeyValue: MetaKeyValue = {
<l:@L> <key:BareIdent> "=" <value:MetaExpr> <r:@R> => {
MetaKeyValue { span: span!(source_file.id(), l, r), key, value }
}
}

MetaExpr: MetaExpr = {
BareIdent => MetaExpr::Ident(<>),
QuotedString => MetaExpr::String(<>),
<l:@L> <value:IntOrHex> <r:@R> => MetaExpr::Int(Span::new(span!(source_file.id(), l, r), value)),
}

#[inline]
QuotedString: Ident = {
<l:@L> <value:quoted_string> <r:@R> => {
let value = interned.get(value).cloned().unwrap_or_else(|| {
let value = Arc::<str>::from(value.to_string().into_boxed_str());
interned.insert(value.clone());
value
});
Ident::new_unchecked(Span::new(span!(source_file.id(), l, r), value))
}
}

// CODE BLOCKS
// ================================================================================================

Expand Down
Loading

0 comments on commit d855bcd

Please sign in to comment.