blob: e0a1b3645be47254af5def7a7b7276c9f20024f1 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use proc_macro2::TokenStream;
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Variant};
/// Derives the `CommandDispatch` trait for a NewType enum.
///
/// Derive this on purely interior nodes in the opentitantool command hierarchy
/// (that is: nodes whose only purpose is to dispatch down to the next layer
/// in the command heirarchy). The automatic derivation simply calls the
/// `run` method on the next layer in the command hierarchy.
///
/// Imagine that you have structs `World` and `People` which implement
/// "Hello World" and "Hello People" commands. You would create Newtype
/// variants inside of a `Hello` enum and derive `CommandDispatch` to
/// generate the dispatch layer for this interior node in the command hierarchy:
///
/// ```
/// #[derive(StructOpt, CommandDispatch)]
/// pub enum Hello {
/// World(World),
/// People(People),
/// }
///
/// // The derived code is as follows:
/// impl opentitanlib::app::command::CommandDispatch for Hello {
/// fn run(
/// &self,
/// context: &dyn std::any::Any,
/// backend: &mut dyn opentitanlib::transport::Transport
/// ) -> anyhow::Result<Option<Box<dyn serde_annotate::Annotate>>> {
/// match self {
/// Hello::World(ref __field) => __field.run(context, backend),
/// Hello::People(ref __field) => __field.run(context, backend),
/// }
/// }
/// }
/// ```
#[proc_macro_derive(CommandDispatch)]
#[proc_macro_error]
pub fn derive_command_dispatch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match &input.data {
Data::Enum(e) => dispatch_enum(&input.ident, e).into(),
_ => abort!(
input.ident.span(),
"CommandDispatch is only implemented for enums"
),
}
}
fn dispatch_enum(name: &Ident, e: &DataEnum) -> TokenStream {
let arms = e
.variants
.iter()
.map(|variant| dispatch_variant(name, variant))
.collect::<Vec<_>>();
// We wrap the derived code inside an anonymous const block to give the
// `extern crate` references a local namespace that wont pollute the
// global namespace.
quote! {
const _: () = {
extern crate opentitanlib;
extern crate anyhow;
extern crate erased_serde;
impl opentitanlib::app::command::CommandDispatch for #name {
fn run(
&self,
context: &dyn std::any::Any,
backend: &opentitanlib::app::TransportWrapper,
) -> anyhow::Result<Option<Box<dyn serde_annotate::Annotate>>> {
match self {
#(#arms),*
}
}
}
};
}
}
fn dispatch_variant(name: &Ident, variant: &Variant) -> TokenStream {
let ident = &variant.ident;
let unnamed_len = match &variant.fields {
Fields::Unnamed(u) => u.unnamed.len(),
_ => abort!(
variant.ident.span(),
"CommandDispatch is only implemented for Newtype variants"
),
};
if unnamed_len != 1 {
abort!(
variant.ident.span(),
"CommandDispatch is only implemented for Newtype variants"
);
}
quote! {
#name::#ident(ref __field) =>
opentitanlib::app::command::CommandDispatch::run(__field, context, backend)
}
}