use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use syn::{ItemMod, Type};
use crate::{
defs::{
AllowedType, AllowedTypes, ArgSource, Defs, MicrocodeOp, MicrocodeSubtype,
MicrocodeTypeDef, StackInfo,
},
get_crate,
};
pub fn generate_microcode_type(def: &Defs) -> TokenStream {
let enum_def = generate_enum_def(&def.microcode_type, &def.module.ident);
let allowed_types_def = generate_allowed_types_def(&def.allowed_types);
let descriptor_def = generate_descriptor_def(&def.microcode_type, &def.allowed_types);
let descriptors_impl =
generate_descriptors_impl(&def.microcode_type, &def.allowed_types, &def.module);
let module_def = &def.module;
quote! {
#enum_def
#allowed_types_def
#descriptor_def
#descriptors_impl
#module_def
}
}
fn generate_enum_def(
MicrocodeTypeDef {
name,
vis,
docs,
ops,
..
}: &MicrocodeTypeDef,
module_name: &Ident,
) -> TokenStream {
let member_defs = ops.iter().map(|op| generate_member_def(op, module_name));
quote! {
#(#docs)*
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#vis enum #name {
#(#member_defs)*
}
}
}
fn generate_member_def(
MicrocodeOp {
name,
docs,
args,
returns,
sig,
subtype,
func_name,
..
}: &MicrocodeOp,
module_name: &Ident,
) -> TokenStream {
let fields: Vec<_> = args
.iter()
.filter_map(|arg| match arg.source {
ArgSource::Field(ref field_info) => Some((&arg.ty, field_info)),
ArgSource::Stack(_) => None,
})
.collect();
let content = if fields.is_empty() {
None
} else {
let field_defs = fields.iter().map(|(ty, field_info)| {
let field_name = &field_info.name;
let docs = &field_info.docs;
quote! {
#(#docs)*
#field_name: #ty,
}
});
Some(quote!({ #(#field_defs)* }))
};
let extern_doc = match subtype {
MicrocodeSubtype::Extern => "This instruction is an extern.".into(),
MicrocodeSubtype::Function => format!(
"This instruction is defined by the function [`{}::{}`].",
module_name, func_name
),
};
let sig_doc = format!("**Signature:** `{}`", sig.to_token_stream().to_string());
let pops_doc: Vec<_> = args
.iter()
.filter_map(|arg| match &arg.source {
ArgSource::Field(_) => None,
ArgSource::Stack(StackInfo { name }) => {
let ty = &arg.ty;
Some(match name {
None => ty.to_token_stream().to_string(),
Some(name) => quote! { #name: #ty }.to_string(),
})
}
})
.collect();
let pops_doc = if pops_doc.is_empty() {
quote! {
#[doc = ""]
#[doc = "**Does not pop any values off of the stack.**"]
}
} else {
quote! {
#[doc = ""]
#[doc = "**Pops these values off of the stack:**"]
#[doc = "(in order from top of the stack to the bottom)"]
#[doc = "```text"]
#(#[doc = #pops_doc])*
#[doc = "```"]
}
};
let pushes_doc: Vec<_> = returns
.iter()
.rev()
.map(|ret| ret.ty.to_token_stream().to_string())
.collect();
let pushes_doc = if pushes_doc.is_empty() {
quote! {
#[doc = ""]
#[doc = "**Does not push any results onto the stack.**"]
}
} else {
quote! {
#[doc = ""]
#[doc = "**Pushes these values onto the stack:**"]
#[doc = "(in order from top of the stack to the bottom)"]
#[doc = "```text"]
#(#[doc = #pushes_doc])*
#[doc = "```"]
}
};
quote! {
#(#docs)*
#[doc = ""]
#[doc = #extern_doc]
#[doc = ""]
#[doc = #sig_doc]
#pops_doc
#pushes_doc
#name #content,
}
}
fn generate_allowed_types_def(
AllowedTypes {
name,
docs,
vis,
types,
}: &AllowedTypes,
) -> TokenStream {
let members = types.iter().map(|AllowedType { name, docs, .. }| {
quote! {
#(#docs)*
#name
}
});
quote! {
#(#docs)*
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#vis enum #name {
#(#members,)*
}
}
}
fn generate_descriptor_def(
MicrocodeTypeDef {
vis,
descriptor_name,
..
}: &MicrocodeTypeDef,
AllowedTypes {
name: allowed_types_name,
..
}: &AllowedTypes,
) -> TokenStream {
let doc = format!("Provides descriptive informaton about an instruction from the microcode.");
let feo3boy_opcodes = crate::get_crate("feo3boy-opcodes");
quote! {
#[doc = #doc]
#[derive(Debug, Clone)]
#vis struct #descriptor_name {
pub name: String,
pub args: Vec<#feo3boy_opcodes::compiler::args::Arg<#allowed_types_name>>,
pub returns: Vec<#allowed_types_name>,
pub optype: #feo3boy_opcodes::compiler::OperationType,
}
}
}
fn generate_descriptors_impl(
def @ MicrocodeTypeDef {
vis,
name,
descriptor_name,
ops,
..
}: &MicrocodeTypeDef,
allowed_types: &AllowedTypes,
module: &ItemMod,
) -> TokenStream {
let patterns = ops.iter().map(|op| {
let pat = generate_op_pattern(op);
let val = generate_descriptor_value(op, def, allowed_types, module);
quote! {
Self::#pat => #val
}
});
let doc = format!("Get the [`{}`] for this operation.", descriptor_name);
quote! {
impl #name {
#[doc = #doc]
#vis fn descriptor(self) -> #descriptor_name {
match self {
#(#patterns,)*
}
}
}
}
}
fn generate_op_pattern(op: &MicrocodeOp) -> TokenStream {
let fields: Vec<_> = op
.args
.iter()
.filter_map(|arg| match &arg.source {
ArgSource::Field(field) => Some(&field.name),
ArgSource::Stack(_) => None,
})
.collect();
let name = &op.name;
if fields.is_empty() {
quote! { #name }
} else {
quote! {
#name {
#(#fields,)*
}
}
}
}
fn generate_descriptor_value(
op: &MicrocodeOp,
def: &MicrocodeTypeDef,
allowed_types: &AllowedTypes,
module: &ItemMod,
) -> TokenStream {
let descriptor_name = &def.descriptor_name;
let allowed_types_name = &allowed_types.name;
let name = op.name.to_string();
let feo3boy_opcodes = get_crate("feo3boy-opcodes");
let args = op.args.iter().map(|arg| match &arg.source {
ArgSource::Field(field) => {
let field_name = &field.name;
quote! {
#feo3boy_opcodes::compiler::args::Arg::Literal(
#feo3boy_opcodes::compiler::args::DynLiteral::new(#field_name)
)
}
}
ArgSource::Stack(_) => {
let typename = rust_type_to_allowed_type(&arg.ty, allowed_types);
quote! {
#feo3boy_opcodes::compiler::args::Arg::StackValue(#allowed_types_name::#typename)
}
}
});
let returns = op.returns.iter().map(|ret| {
let typename = rust_type_to_allowed_type(&ret.ty, allowed_types);
quote! {
#allowed_types_name::#typename
}
});
let optype = match op.subtype {
MicrocodeSubtype::Extern => {
quote! {
#feo3boy_opcodes::compiler::OperationType::Extern
}
}
MicrocodeSubtype::Function => {
let modname = &module.ident;
let funcname = &op.func_name;
quote! {
#feo3boy_opcodes::compiler::OperationType::Function {
path: quote! { #modname::#funcname }
}
}
}
};
quote! {
#descriptor_name {
name: #name.into(),
args: vec![
#(#args,)*
],
returns: vec![
#(#returns,)*
],
optype: #optype,
}
}
}
fn rust_type_to_allowed_type<'a, 'b>(
rust_type: &'a Type,
allowed_types: &'b AllowedTypes,
) -> &'b Ident {
match rust_type {
Type::Path(path) => {
if path.qself.is_some() {
panic!("Expected an unqualified path");
}
match path.path.get_ident() {
Some(ident) => allowed_types
.types
.iter()
.find_map(|allowed| {
if ident == allowed.ty.to_string().as_str() {
Some(&allowed.name)
} else {
None
}
})
.expect("Matching allowed type not found"),
None => panic!("Expected a single-identifier path"),
}
}
_ => panic!("Expected a path type"),
}
}