diff --git a/cranelift/peepmatic/crates/macro/Cargo.toml b/cranelift/peepmatic/crates/macro/Cargo.toml new file mode 100644 index 0000000000..e13cffd988 --- /dev/null +++ b/cranelift/peepmatic/crates/macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "peepmatic-macro" +version = "0.1.0" +authors = ["Nick Fitzgerald "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "1.0.9" +quote = "1.0.3" +syn = { version = "1.0.16", features = ['extra-traits'] } + +[lib] +proc_macro = true diff --git a/cranelift/peepmatic/crates/macro/src/child_nodes.rs b/cranelift/peepmatic/crates/macro/src/child_nodes.rs new file mode 100644 index 0000000000..c71ac409a0 --- /dev/null +++ b/cranelift/peepmatic/crates/macro/src/child_nodes.rs @@ -0,0 +1,110 @@ +use quote::quote; +use syn::DeriveInput; +use syn::{parse_quote, GenericParam, Generics, Result}; + +pub fn derive_child_nodes(input: &DeriveInput) -> Result { + let children = get_child_nodes(&input.data)?; + let name = &input.ident; + let generics = add_trait_bounds(input.generics.clone()); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + Ok(quote! { + impl #impl_generics ChildNodes<'a, 'a> for #name #ty_generics #where_clause { + fn child_nodes(&'a self, children: &mut impl Extend>) { + #children + } + } + }) +} + +fn get_child_nodes(data: &syn::Data) -> Result { + match data { + syn::Data::Struct(s) => { + let mut fields = vec![]; + + match &s.fields { + syn::Fields::Named(n) => { + for f in n.named.iter() { + let opts = crate::PeepmaticOpts::from_attrs(&mut f.attrs.clone())?; + + if opts.skip_child { + continue; + } + + let field_name = f.ident.as_ref().unwrap(); + if opts.flatten { + fields.push(quote! { + self.#field_name.iter().map(DynAstRef::from) + }); + } else { + fields.push(quote! { + std::iter::once(DynAstRef::from(&self.#field_name)) + }); + } + } + } + syn::Fields::Unnamed(u) => { + for (i, f) in u.unnamed.iter().enumerate() { + let opts = crate::PeepmaticOpts::from_attrs(&mut f.attrs.clone())?; + if opts.skip_child { + continue; + } + if opts.flatten { + return Err(syn::Error::new( + u.paren_token.span, + "#[peepmatic(flatten)] is only allowed with named fields", + )); + } + fields.push(quote! { + std::iter::once(DynAstRef::from(&self.#i)) + }); + } + } + syn::Fields::Unit => {} + } + + Ok(match fields.as_slice() { + [] => quote! { let _ = children; }, + [f, rest @ ..] => { + let rest = rest.iter().map(|f| { + quote! { + .chain(#f) + } + }); + quote! { + children.extend( #f #( #rest )* ); + } + } + }) + } + syn::Data::Enum(e) => { + let mut match_arms = vec![]; + for v in e.variants.iter() { + match v.fields { + syn::Fields::Unnamed(ref u) if u.unnamed.len() == 1 => { + let variant = &v.ident; + match_arms.push(quote! { + Self::#variant(x) => children.extend(Some(x.into())), + }); + } + _ => panic!("#[derive(ChildNodes)] only supports enums whose variants all ahve a single unnamed field") + } + } + Ok(quote! { + match self { + #( #match_arms )* + } + }) + } + syn::Data::Union(_) => panic!("#[derive(ChildNodes)] is not supported on unions"), + } +} + +fn add_trait_bounds(mut generics: Generics) -> Generics { + for param in &mut generics.params { + if let GenericParam::Type(type_param) = param { + type_param.bounds.push(parse_quote!(ChildNodes<'a, 'a>)); + } + } + generics +} diff --git a/cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs b/cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs new file mode 100644 index 0000000000..4cd993a72c --- /dev/null +++ b/cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs @@ -0,0 +1,23 @@ +use quote::quote; +use syn::DeriveInput; +use syn::Result; + +pub fn derive_into_dyn_ast_ref(input: &DeriveInput) -> Result { + let ty = &input.ident; + + let opts = crate::PeepmaticOpts::from_attrs(&mut input.attrs.clone())?; + if opts.no_into_dyn_node { + return Ok(quote! {}); + } + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + Ok(quote! { + impl #impl_generics From<&'a #ty #ty_generics> for DynAstRef<'a> #where_clause { + #[inline] + fn from(x: &'a #ty #ty_generics) -> Self { + Self::#ty(x) + } + } + }) +} diff --git a/cranelift/peepmatic/crates/macro/src/lib.rs b/cranelift/peepmatic/crates/macro/src/lib.rs new file mode 100644 index 0000000000..5375947c36 --- /dev/null +++ b/cranelift/peepmatic/crates/macro/src/lib.rs @@ -0,0 +1,156 @@ +extern crate proc_macro; + +use crate::proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::DeriveInput; +use syn::Error; +use syn::{parse_macro_input, Ident, Result}; + +mod child_nodes; +mod into_dyn_ast_ref; +mod operator; +mod span; + +#[proc_macro_derive(PeepmaticOperator, attributes(peepmatic))] +pub fn operator(input: TokenStream) -> TokenStream { + operator::derive_operator(input) +} + +#[proc_macro_derive(Ast, attributes(peepmatic))] +pub fn derive_ast(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let span_impl = match span::derive_span(&input) { + Ok(s) => s, + Err(e) => return e.to_compile_error().into(), + }; + + let child_nodes_impl = match child_nodes::derive_child_nodes(&input) { + Ok(c) => c, + Err(e) => return e.to_compile_error().into(), + }; + + let into_dyn_ast_ref_impl = match into_dyn_ast_ref::derive_into_dyn_ast_ref(&input) { + Ok(n) => n, + Err(e) => return e.to_compile_error().into(), + }; + + let expanded = quote! { + #span_impl + #child_nodes_impl + #into_dyn_ast_ref_impl + }; + + TokenStream::from(expanded) +} + +#[derive(Default)] +pub(crate) struct PeepmaticOpts { + // `ChildNodes` options. + skip_child: bool, + flatten: bool, + + // `From<&'a Self> for DynAstRef<'a>` options. + no_into_dyn_node: bool, + + // Peepmatic operator options. + immediates_paren: syn::token::Paren, + immediates: Vec, + params_paren: syn::token::Paren, + params: Vec, + result: Option, +} + +impl Parse for PeepmaticOpts { + fn parse(input: ParseStream) -> Result { + enum Attr { + Immediates(syn::token::Paren, Vec), + Params(syn::token::Paren, Vec), + Result(syn::Ident), + NoIntoDynNode, + SkipChild, + Flatten, + } + + let attrs = Punctuated::<_, syn::token::Comma>::parse_terminated(input)?; + let mut ret = PeepmaticOpts::default(); + for attr in attrs { + match attr { + Attr::Immediates(paren, imms) => { + ret.immediates_paren = paren; + ret.immediates = imms; + } + Attr::Params(paren, ps) => { + ret.params_paren = paren; + ret.params = ps; + } + Attr::Result(r) => ret.result = Some(r), + Attr::NoIntoDynNode => ret.no_into_dyn_node = true, + Attr::SkipChild => ret.skip_child = true, + Attr::Flatten => ret.flatten = true, + } + } + + return Ok(ret); + + impl Parse for Attr { + fn parse(input: ParseStream) -> Result { + let attr: Ident = input.parse()?; + if attr == "immediates" { + let inner; + let paren = syn::parenthesized!(inner in input); + let imms = Punctuated::<_, syn::token::Comma>::parse_terminated(&inner)?; + return Ok(Attr::Immediates(paren, imms.into_iter().collect())); + } + if attr == "params" { + let inner; + let paren = syn::parenthesized!(inner in input); + let params = Punctuated::<_, syn::token::Comma>::parse_terminated(&inner)?; + return Ok(Attr::Params(paren, params.into_iter().collect())); + } + if attr == "result" { + let inner; + syn::parenthesized!(inner in input); + return Ok(Attr::Result(syn::Ident::parse(&inner)?)); + } + if attr == "skip_child" { + return Ok(Attr::SkipChild); + } + if attr == "no_into_dyn_node" { + return Ok(Attr::NoIntoDynNode); + } + if attr == "flatten" { + return Ok(Attr::Flatten); + } + return Err(Error::new(attr.span(), "unexpected attribute")); + } + } + } +} + +fn peepmatic_attrs(attrs: &mut Vec) -> TokenStream { + let mut ret = proc_macro2::TokenStream::new(); + let ident = syn::Path::from(syn::Ident::new("peepmatic", Span::call_site())); + for i in (0..attrs.len()).rev() { + if attrs[i].path != ident { + continue; + } + let attr = attrs.remove(i); + let group = match attr.tokens.into_iter().next().unwrap() { + proc_macro2::TokenTree::Group(g) => g, + _ => panic!("#[peepmatic(...)] expected"), + }; + ret.extend(group.stream()); + ret.extend(quote! { , }); + } + return ret.into(); +} + +impl PeepmaticOpts { + pub(crate) fn from_attrs(attrs: &mut Vec) -> syn::Result { + syn::parse(peepmatic_attrs(attrs)) + } +} diff --git a/cranelift/peepmatic/crates/macro/src/operator.rs b/cranelift/peepmatic/crates/macro/src/operator.rs new file mode 100644 index 0000000000..0d53a1219d --- /dev/null +++ b/cranelift/peepmatic/crates/macro/src/operator.rs @@ -0,0 +1,325 @@ +//! Implementation of the `#[peepmatic]` macro for the `Operator` AST node. + +use crate::proc_macro::TokenStream; +use crate::PeepmaticOpts; +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::DeriveInput; +use syn::Error; +use syn::{parse_macro_input, Result}; + +pub fn derive_operator(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let variants = match get_enum_variants(&input) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + + let arity = match create_arity(&variants) { + Ok(a) => a, + Err(e) => return e.to_compile_error().into(), + }; + + let num_operators = variants.len(); + let type_methods = create_type_methods(&variants); + let parse_impl = create_parse_impl(&input.ident, &variants); + let display_impl = create_display_impl(&input.ident, &variants); + let try_from_u32_impl = create_try_from_u32_impl(&input.ident, &variants); + let ident = &input.ident; + + let expanded = quote! { + impl #ident { + #arity + #type_methods + + /// Get the total number of different operators. + pub const fn num_operators() -> usize { + #num_operators + } + } + + #display_impl + #try_from_u32_impl + #parse_impl + }; + + // eprintln!("{}", expanded); + TokenStream::from(expanded) +} + +fn get_enum_variants(input: &DeriveInput) -> Result> { + let en = match &input.data { + syn::Data::Enum(en) => en, + syn::Data::Struct(_) => { + panic!("can only put #[peepmatic] on an enum; found it on a struct") + } + syn::Data::Union(_) => panic!("can only put #[peepmatic] on an enum; found it on a union"), + }; + en.variants + .iter() + .cloned() + .map(|mut variant| { + Ok(OperatorVariant { + opts: PeepmaticOpts::from_attrs(&mut variant.attrs)?, + syn: variant, + }) + }) + .collect() +} + +struct OperatorVariant { + syn: syn::Variant, + opts: PeepmaticOpts, +} + +fn create_arity(variants: &[OperatorVariant]) -> Result { + let mut imm_arities = vec![]; + let mut params_arities = vec![]; + + for v in variants { + let variant = &v.syn.ident; + + let imm_arity = v.opts.immediates.len(); + if imm_arity > std::u8::MAX as usize { + return Err(Error::new( + v.opts.immediates_paren.span, + "cannot have more than u8::MAX immediates", + )); + } + let imm_arity = imm_arity as u8; + + imm_arities.push(quote! { + Self::#variant => #imm_arity, + }); + + let params_arity = v.opts.params.len(); + if params_arity > std::u8::MAX as usize { + return Err(Error::new( + v.opts.params_paren.span, + "cannot have more than u8::MAX params", + )); + } + let params_arity = params_arity as u8; + + params_arities.push(quote! { + Self::#variant => #params_arity, + }); + } + + Ok(quote! { + /// Get the number of immediates that this operator has. + pub fn immediates_arity(&self) -> u8 { + match *self { + #( #imm_arities )* + } + } + + /// Get the number of parameters that this operator takes. + pub fn params_arity(&self) -> u8 { + match *self { + #( #params_arities )* + } + } + }) +} + +fn create_type_methods(variants: &[OperatorVariant]) -> impl quote::ToTokens { + let mut result_types = vec![]; + let mut imm_types = vec![]; + let mut param_types = vec![]; + + for v in variants { + let variant = &v.syn.ident; + + let result_ty = v.opts.result.as_ref().unwrap_or_else(|| { + panic!( + "must define #[peepmatic(result(..))] on operator `{}`", + variant + ) + }); + result_types.push(quote! { + Self::#variant => { + context.#result_ty(span) + } + }); + + let imm_tys = match &v.opts.immediates[..] { + [] => quote! {}, + [ty, rest @ ..] => { + let rest = rest.iter().map(|ty| { + quote! { .chain(::std::iter::once(context.#ty(span))) } + }); + quote! { + types.extend(::std::iter::once(context.#ty(span))#( #rest )*); + } + } + }; + imm_types.push(quote! { + Self::#variant => { + #imm_tys + } + }); + + let param_tys = match &v.opts.params[..] { + [] => quote! {}, + [ty, rest @ ..] => { + let rest = rest.iter().map(|ty| { + quote! { .chain(::std::iter::once(context.#ty(span))) } + }); + quote! { + types.extend(::std::iter::once(context.#ty(span))#( #rest )*); + } + } + }; + param_types.push(quote! { + Self::#variant => { + #param_tys + } + }); + } + + quote! { + /// Get the result type of this operator. + #[cfg(feature = "construct")] + pub fn result_type<'a, C>( + &self, + context: &mut C, + span: wast::Span, + ) -> C::TypeVariable + where + C: 'a + TypingContext<'a>, + { + match *self { + #( #result_types )* + } + } + + /// Get the immediate types of this operator. + #[cfg(feature = "construct")] + pub fn immediate_types<'a, C>( + &self, + context: &mut C, + span: wast::Span, + types: &mut impl Extend, + ) + where + C: 'a + TypingContext<'a>, + { + match *self { + #( #imm_types )* + } + } + + /// Get the parameter types of this operator. + #[cfg(feature = "construct")] + pub fn param_types<'a, C>( + &self, + context: &mut C, + span: wast::Span, + types: &mut impl Extend, + ) + where + C: 'a + TypingContext<'a>, + { + match *self { + #( #param_types )* + } + } + } +} + +fn snake_case(s: &str) -> String { + let mut t = String::with_capacity(s.len() + 1); + for (i, ch) in s.chars().enumerate() { + if i != 0 && ch.is_uppercase() { + t.push('_'); + } + t.extend(ch.to_lowercase()); + } + t +} + +fn create_parse_impl(ident: &syn::Ident, variants: &[OperatorVariant]) -> impl quote::ToTokens { + let token_defs = variants.iter().map(|v| { + let tok = snake_case(&v.syn.ident.to_string()); + let tok = Ident::new(&tok, Span::call_site()); + quote! { + wast::custom_keyword!(#tok); + } + }); + + let parses = variants.iter().map(|v| { + let tok = snake_case(&v.syn.ident.to_string()); + let tok = Ident::new(&tok, Span::call_site()); + let ident = &v.syn.ident; + quote! { + if p.peek::<#tok>() { + p.parse::<#tok>()?; + return Ok(Self::#ident); + } + } + }); + + let expected = format!("expected {}", ident); + + quote! { + #[cfg(feature = "construct")] + impl<'a> wast::parser::Parse<'a> for #ident { + fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result { + #( #token_defs )* + + #( #parses )* + + Err(p.error(#expected)) + } + } + } +} + +fn create_display_impl(ident: &syn::Ident, variants: &[OperatorVariant]) -> impl quote::ToTokens { + let displays = variants.iter().map(|v| { + let variant = &v.syn.ident; + let snake = snake_case(&v.syn.ident.to_string()); + quote! { + Self::#variant => write!(f, #snake), + } + }); + + quote! { + impl std::fmt::Display for #ident { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + #( #displays )* + } + } + } + } +} + +fn create_try_from_u32_impl( + ident: &syn::Ident, + variants: &[OperatorVariant], +) -> impl quote::ToTokens { + let matches = variants.iter().map(|v| { + let variant = &v.syn.ident; + quote! { + x if Self::#variant as u32 == x => Ok(Self::#variant), + } + }); + + let error_msg = format!("value is not an `{}`", ident); + + quote! { + impl std::convert::TryFrom for #ident { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + match value { + #( #matches )* + _ => Err(#error_msg) + } + } + } + } +} diff --git a/cranelift/peepmatic/crates/macro/src/span.rs b/cranelift/peepmatic/crates/macro/src/span.rs new file mode 100644 index 0000000000..f994920dff --- /dev/null +++ b/cranelift/peepmatic/crates/macro/src/span.rs @@ -0,0 +1,50 @@ +use quote::quote; +use syn::DeriveInput; +use syn::{parse_quote, GenericParam, Generics, Result}; + +pub fn derive_span(input: &DeriveInput) -> Result { + let ty = &input.ident; + + let body = match &input.data { + syn::Data::Struct(_) => quote! { self.span }, + syn::Data::Enum(e) => { + let variants = e.variants.iter().map(|v| match v.fields { + syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let variant = &v.ident; + quote! { #ty::#variant(x) => x.span(), } + } + _ => panic!( + "derive(Ast) on enums only supports variants with a single, unnamed field" + ), + }); + quote! { + match self { + #( #variants )* + } + } + } + syn::Data::Union(_) => panic!("derive(Ast) can only be used with structs and enums, not unions"), + }; + + let generics = add_span_trait_bounds(input.generics.clone()); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + Ok(quote! { + impl #impl_generics Span for #ty #ty_generics #where_clause { + #[inline] + fn span(&self) -> wast::Span { + #body + } + } + }) +} + +// Add a bound `T: Span` to every type parameter `T`. +fn add_span_trait_bounds(mut generics: Generics) -> Generics { + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param.bounds.push(parse_quote!(Span)); + } + } + generics +}