Skip to content

Commit

Permalink
pliron-derive: Provide attr directive for format_op.
Browse files Browse the repository at this point in the history
This directive allows specifying the concrete Rust type of
an Op's attribute, thus making it not necessary to have the
attribute-id in the syntax (which otherwise would be necessary
for parsing).

Also add some documentation.

Fixes #46
  • Loading branch information
vaivaswatha committed Jan 27, 2025
1 parent 6f83c66 commit 259f607
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 22 deletions.
56 changes: 56 additions & 0 deletions pliron-derive/src/derive_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,15 @@ impl PrintableBuilder<OpPrinterState> for DeriveOpPrintable {
} else {
return err;
}
} else if d.name == "attr" {
let (attr_name_str, attr_type_path) = parse_attr_directive_args(d, input)?;
Ok(quote! {
let self_op = self.get_operation().deref(ctx);
let attr = self_op.attributes.get::<#attr_type_path>(
&::pliron::identifier::Identifier::try_from(#attr_name_str).unwrap()
).expect("Missing attribute");
::pliron::printable::Printable::fmt(attr, ctx, state, fmt)?;
})
} else {
unimplemented!("Unknown directive {}", d.name)
}
Expand Down Expand Up @@ -863,6 +872,18 @@ impl ParsableBuilder<OpParserState> for DeriveOpParsable {
} else {
return err;
}
} else if d.name == "attr" {
let (attr_name_str, attr_type_path) = parse_attr_directive_args(d, input)?;
let attr_name_ident = format_ident!("{}", attr_name_str);
state
.attributes
.insert(attr_name_str.clone(), attr_name_ident.clone());
Ok(quote! {
let #attr_name_ident = Box::new(#attr_type_path::parser(())
.parse_stream(state_stream)
.into_result()?
.0);
})
} else {
unimplemented!("Unknown directive {}", d.name)
}
Expand Down Expand Up @@ -917,3 +938,38 @@ impl ParsableBuilder<()> for DeriveTypeParsable {
}
}
}

fn parse_attr_directive_args(d: &Directive, input: &FmtInput) -> Result<(String, syn::Type)> {
if d.args.len() != 2 {
return Err(syn::Error::new_spanned(
input.ident.clone(),
"The `attr` directive takes two arguments,
the first is attribute name, and second attribute type"
.to_string(),
));
}
let Elem::Var(Var {
name: attr_name, ..
}) = &d.args[0]
else {
return Err(syn::Error::new_spanned(
input.ident.clone(),
"The first argument to `attr` directive must be a named variable representing its name"
.to_string(),
));
};
let attr_type = match &d.args[1] {
Elem::Var(Var { name, .. }) => name.clone(),
Elem::Lit(lit) => {
lit.lit.clone()
}
_ =>
return Err(syn::Error::new_spanned(
input.ident.clone(),
"The second argument to `attr` directive must be a named variable or a literal representing its type".to_string(),
))
};
let attr_type_path = syn::parse_str::<syn::Type>(&attr_type)?;
let attr_name_str = attr_name.to_string();
Ok((attr_name_str, attr_type_path))
}
87 changes: 78 additions & 9 deletions pliron-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,45 @@ pub fn def_op(args: TokenStream, input: TokenStream) -> TokenStream {

/// Derive [Format](../pliron/irfmt/trait.Format.html) for Rust types.
/// Use this is for types other than `Op`, `Type` and `Attribute`s.
/// 1. A named variable $name specifies a named struct field.
/// 2. An unnamed variable $i specifies the i'th field of a tuple struct.
/// 1. A named variable `$name` specifies a named struct field.
/// 2. An unnamed variable `$i` specifies the i'th field of a tuple struct.
///
/// Examples:
/// 1. Derive for a struct, with no format string (default format):
/// (Note that the field u64 has both `Printable` and `Parsable` implemented).
/// ```
/// use pliron::derive::format;
/// #[format]
/// struct IntWrapper {
/// inner: u64,
/// }
/// ```
/// 2. An example with a custom format string:
/// ```
/// use pliron::derive::format;
/// #[format("`BubbleWrap` `[` $inner `]`")]
/// struct IntWrapperCustom {
/// inner: u64,
/// }
/// ```
/// 3. An example for an enum (custom format strings are allowed for the variants only).
/// ```
/// use pliron::derive::format;
/// use pliron::{builtin::types::IntegerType, r#type::TypePtr};
/// #[format]
/// enum Enum {
/// A(TypePtr<IntegerType>),
/// B {
/// one: TypePtr<IntegerType>,
/// two: u64,
/// },
/// C,
/// #[format("`<` $upper `/` $lower `>`")]
/// Op {
/// upper: u64,
/// lower: u64,
/// },
/// }
#[proc_macro_attribute]
pub fn format(args: TokenStream, input: TokenStream) -> TokenStream {
to_token_stream(derive_format::derive(
Expand All @@ -127,23 +164,53 @@ pub fn format(args: TokenStream, input: TokenStream) -> TokenStream {

/// Derive [Format](../pliron/irfmt/trait.Format.html) for [Op](../pliron/op/trait.Op.html)s
/// This derive only supports a syntax in which results appear before the opid:
/// res1, ... = opid ...
/// `res1, ... = opid ...`
/// The format string specifies what comes after the opid.
/// 1. A named variable $name specifies a named attribute of the operation.
/// 2. An unnamed variable $i specifies `operands[i]`, except when inside some directives.
/// 1. A named variable `$name` specifies a named attribute of the operation.
/// 2. An unnamed variable `$i` specifies `operands[i]`, except when inside some directives.
/// 3. The "type" directive specifies that a type must be parsed. It takes one argument,
/// which is an unnamed variable `$i` with `i` specifying `result[i]`.
/// 4. The "region" directive specifies that a region must be parsed. It takes one argument,
/// which is an unnamed variable `$i` with `i` specifying `region[i]`.
/// 5. The "attr" directive can be used to specify attribute on an `Op` when the attribute's
/// rust type is fixed at compile time. It takes two arguments, the first is the attribute name
/// and the second is the concrete rust type of the attribute. This second argument can be a
/// named variable `$Name` (with `Name` being in scope) or a literal string denoting the path
/// to a rust type (e.g. `` `::pliron::builtin::attributes::IntegerAttr` ``).
/// The advantage over specifying the attribute as a named variable is that the attribute-id
/// is not a part of the syntax here, allowing it to be more succint.
///
/// Examples:
/// 1. Derive for a struct, with no format string (default format):
/// ```
/// use pliron::derive::{def_op, format_op};
/// use pliron::impl_verify_succ;
/// #[format_op]
/// #[def_op("test.myop")]
/// struct MyOp;
/// impl_verify_succ!(MyOp);
/// ```
/// 2. An example with a custom format string:
/// ```
/// use pliron::derive::{def_op, format_op, derive_op_interface_impl};
/// use pliron::impl_verify_succ;
/// use pliron::{op::Op, builtin::op_interfaces::{OneOpdInterface, OneResultInterface}};
/// #[format_op("$0 `<` $attr `>` `:` type($0)")]
/// #[def_op("test.one_result_one_operand")]
/// #[derive_op_interface_impl(OneOpdInterface, OneResultInterface)]
/// struct OneResultOneOperandOp;
/// impl_verify_succ!(OneResultOneOperandOp);
#[proc_macro_attribute]
pub fn format_op(args: TokenStream, input: TokenStream) -> TokenStream {
to_token_stream(derive_format::derive(args, input, DeriveIRObject::Op))
}

/// Derive [Format](../pliron/irfmt/trait.Format.html) for
/// [Attribute](../pliron/attribute/trait.Attribute.html)s
/// 1. A named variable $name specifies a named struct field.
/// 2. An unnamed variable $i specifies the i'th field of a tuple struct.
/// 1. A named variable `$name` specifies a named struct field.
/// 2. An unnamed variable `$i` specifies the i'th field of a tuple struct.
///
/// See [macro@format] for examples.
#[proc_macro_attribute]
pub fn format_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
to_token_stream(derive_format::derive(
Expand All @@ -154,8 +221,10 @@ pub fn format_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
}
/// Derive [Format](../pliron/irfmt/trait.Format.html) for
/// [Type](../pliron/type/trait.Type.html)s
/// 1. A named variable $name specifies a named struct field.
/// 2. An unnamed variable $i specifies the i'th field of a tuple struct.
/// 1. A named variable `$name` specifies a named struct field.
/// 2. An unnamed variable `$i` specifies the i'th field of a tuple struct.
///
/// See [macro@format] for examples.
#[proc_macro_attribute]
pub fn format_type(args: TokenStream, input: TokenStream) -> TokenStream {
to_token_stream(derive_format::derive(args, input, DeriveIRObject::Type))
Expand Down
102 changes: 89 additions & 13 deletions pliron-derive/tests/format_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ fn two_result_two_operands() {
assert!(res.verify(ctx).is_ok());
}

#[format_op("$attr `:` type($0)")]
use pliron::builtin::attributes::IntegerAttr;

#[format_op("attr($attr, $IntegerAttr) `:` type($0)")]
#[def_op("test.attr_op")]
struct AttrOp {}
impl_verify_succ!(AttrOp);
Expand All @@ -182,7 +184,81 @@ fn attr_op() {

let printed = "builtin.func @testfunc: builtin.function <() -> ()> {
^entry():
res0 = test.attr_op builtin.integer <0x0: builtin.int <si64>> :builtin.int <si64>;
res0 = test.attr_op <0x0: builtin.int <si64>> :builtin.int <si64>;
test.return res0
}";

let state_stream = state_stream_from_iterator(
printed.chars(),
parsable::State::new(ctx, location::Source::InMemory),
);

let (res, _) = Operation::parser(())
.parse(state_stream)
.expect("AttrOp parser failed");

expect![[r#"
builtin.func @testfunc: builtin.function <() -> ()>
{
^entry_block_1v1():
res0_op_3v1_res0 = test.attr_op <0x0: builtin.int <si64>>:builtin.int <si64>;
test.return res0_op_3v1_res0
}"#]]
.assert_eq(&res.disp(ctx).to_string());

assert!(res.verify(ctx).is_ok());
}

#[format_op("attr($attr, `pliron::builtin::attributes::StringAttr`) `:` type($0)")]
#[def_op("test.attr_op2")]
struct AttrOp2 {}
impl_verify_succ!(AttrOp2);

#[test]
fn attr_op2() {
let ctx = &mut setup_context_dialects();
AttrOp2::register(ctx, AttrOp2::parser_fn);

let printed = "builtin.func @testfunc: builtin.function <() -> ()> {
^entry():
res0 = test.attr_op2 \"Hello World\" :builtin.int <si64>;
test.return res0
}";

let state_stream = state_stream_from_iterator(
printed.chars(),
parsable::State::new(ctx, location::Source::InMemory),
);

let (res, _) = Operation::parser(())
.parse(state_stream)
.expect("AttrOp parser failed");

expect![[r#"
builtin.func @testfunc: builtin.function <() -> ()>
{
^entry_block_1v1():
res0_op_3v1_res0 = test.attr_op2 "Hello World":builtin.int <si64>;
test.return res0_op_3v1_res0
}"#]]
.assert_eq(&res.disp(ctx).to_string());

assert!(res.verify(ctx).is_ok());
}

#[format_op("$attr `:` type($0)")]
#[def_op("test.attr_op3")]
struct AttrOp3 {}
impl_verify_succ!(AttrOp3);

#[test]
fn attr_op3() {
let ctx = &mut setup_context_dialects();
AttrOp3::register(ctx, AttrOp3::parser_fn);

let printed = "builtin.func @testfunc: builtin.function <() -> ()> {
^entry():
res0 = test.attr_op3 builtin.integer <0x0: builtin.int <si64>> :builtin.int <si64>;
test.return res0
}";

Expand All @@ -199,7 +275,7 @@ fn attr_op() {
builtin.func @testfunc: builtin.function <() -> ()>
{
^entry_block_1v1():
res0_op_3v1_res0 = test.attr_op builtin.integer <0x0: builtin.int <si64>>:builtin.int <si64>;
res0_op_3v1_res0 = test.attr_op3 builtin.integer <0x0: builtin.int <si64>>:builtin.int <si64>;
test.return res0_op_3v1_res0
}"#]]
.assert_eq(&res.disp(ctx).to_string());
Expand All @@ -221,10 +297,10 @@ fn if_op() {

let printed = "builtin.func @testfunc: builtin.function <() -> ()> {
^entry():
res0 = test.attr_op builtin.integer <0x0: builtin.int <si64>> :builtin.int <si64>;
res0 = test.attr_op <0x0: builtin.int <si64>> :builtin.int <si64>;
test.if_op (res0) {
^then():
res1 = test.attr_op builtin.integer <0x1: builtin.int <si64>> :builtin.int <si64>;
res1 = test.attr_op <0x1: builtin.int <si64>> :builtin.int <si64>;
test.return res1
};
test.return res0
Expand All @@ -243,11 +319,11 @@ fn if_op() {
builtin.func @testfunc: builtin.function <() -> ()>
{
^entry_block_2v1():
res0_op_3v1_res0 = test.attr_op builtin.integer <0x0: builtin.int <si64>>:builtin.int <si64>;
res0_op_3v1_res0 = test.attr_op <0x0: builtin.int <si64>>:builtin.int <si64>;
test.if_op (res0_op_3v1_res0)
{
^then_block_1v1():
res1_op_4v1_res0 = test.attr_op builtin.integer <0x1: builtin.int <si64>>:builtin.int <si64>;
res1_op_4v1_res0 = test.attr_op <0x1: builtin.int <si64>>:builtin.int <si64>;
test.return res1_op_4v1_res0
};
test.return res0_op_3v1_res0
Expand All @@ -271,14 +347,14 @@ fn if_else_op() {

let printed = "builtin.func @testfunc: builtin.function <() -> ()> {
^entry():
res0 = test.attr_op builtin.integer <0x0: builtin.int <si64>> :builtin.int <si64>;
res0 = test.attr_op <0x0: builtin.int <si64>> :builtin.int <si64>;
test.if_else_op (res0) {
^then():
res1 = test.attr_op builtin.integer <0x1: builtin.int <si64>> :builtin.int <si64>;
res1 = test.attr_op <0x1: builtin.int <si64>> :builtin.int <si64>;
test.return res1
} else {
^else():
res2 = test.attr_op builtin.integer <0x2: builtin.int <si64>> :builtin.int <si64>;
res2 = test.attr_op <0x2: builtin.int <si64>> :builtin.int <si64>;
test.return res2
};
test.return res0
Expand All @@ -297,16 +373,16 @@ fn if_else_op() {
builtin.func @testfunc: builtin.function <() -> ()>
{
^entry_block_3v1():
res0_op_3v1_res0 = test.attr_op builtin.integer <0x0: builtin.int <si64>>:builtin.int <si64>;
res0_op_3v1_res0 = test.attr_op <0x0: builtin.int <si64>>:builtin.int <si64>;
test.if_else_op (res0_op_3v1_res0)
{
^then_block_1v1():
res1_op_4v1_res0 = test.attr_op builtin.integer <0x1: builtin.int <si64>>:builtin.int <si64>;
res1_op_4v1_res0 = test.attr_op <0x1: builtin.int <si64>>:builtin.int <si64>;
test.return res1_op_4v1_res0
}else
{
^else_block_2v1():
res2_op_6v1_res0 = test.attr_op builtin.integer <0x2: builtin.int <si64>>:builtin.int <si64>;
res2_op_6v1_res0 = test.attr_op <0x2: builtin.int <si64>>:builtin.int <si64>;
test.return res2_op_6v1_res0
};
test.return res0_op_3v1_res0
Expand Down

0 comments on commit 259f607

Please sign in to comment.