use gazebo::prelude::*;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote_spanned};
use syn::{parse_macro_input, spanned::Spanned, Fields, ItemEnum, Variant};
struct BcOpcodeEnum {
span: Span,
variants: Vec<Ident>,
}
impl BcOpcodeEnum {
fn parse_variant(input: &Variant) -> Ident {
assert!(
input.discriminant.is_none(),
"BcOpcode relies on sequential numbering of opcodes"
);
assert!(
matches!(input.fields, Fields::Unit),
"BcOpcode variants must be unit"
);
input.ident.clone()
}
fn parse(input: ItemEnum) -> BcOpcodeEnum {
BcOpcodeEnum {
span: input.span(),
variants: input
.variants
.iter()
.map(BcOpcodeEnum::parse_variant)
.collect(),
}
}
fn render_dispatch_variant(&self, variant: &Ident) -> TokenStream {
let instr = format_ident!("Instr{}", variant);
quote_spanned! {
variant.span()=>
BcOpcode::#variant => handler.handle::<#instr>(),
}
}
fn render_dispatch(&self) -> TokenStream {
let variants = self.variants.map(|v| self.render_dispatch_variant(v));
quote_spanned! {
self.span=>
impl BcOpcode {
#[inline(always)]
fn do_dispatch<R>(self, handler: impl BcOpcodeHandler<R>) -> R {
match self {
#(#variants)*
}
}
}
}
}
fn render_dispatch_all_variant(&self, variant: &Ident) -> TokenStream {
let instr = format_ident!("Instr{}", variant);
quote_spanned! {
variant.span()=>
handler.handle::<#instr>(BcOpcode::#variant);
}
}
fn render_dispatch_all(&self) -> TokenStream {
let variants = self.variants.map(|v| self.render_dispatch_all_variant(v));
quote_spanned! {
self.span=>
impl BcOpcode {
#[inline(always)]
fn do_dispatch_all(handler: &mut impl BcOpcodeAllHandler) {
#(#variants)*
}
}
}
}
}
pub(crate) fn starlark_internal_bc(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input_clone = input.clone();
let bc_opcode_enum = parse_macro_input!(input_clone as ItemEnum);
let bc_opcode_enum = BcOpcodeEnum::parse(bc_opcode_enum);
let input = TokenStream::from(input);
let dispatch = bc_opcode_enum.render_dispatch();
let dispatch_all = bc_opcode_enum.render_dispatch_all();
proc_macro::TokenStream::from(quote_spanned! {
input.span()=>
#input
#dispatch
#dispatch_all
})
}