From fbbeed8c10dc9c9bdb34f946d5b844b537ebad7a Mon Sep 17 00:00:00 2001 From: Ashelyn Rose Date: Sat, 7 Dec 2024 13:29:46 -0700 Subject: Proof of concept for macro opcode defs --- opcode_proc/Cargo.toml | 12 +++ opcode_proc/src/lib.rs | 212 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 opcode_proc/Cargo.toml create mode 100644 opcode_proc/src/lib.rs (limited to 'opcode_proc') diff --git a/opcode_proc/Cargo.toml b/opcode_proc/Cargo.toml new file mode 100644 index 0000000..9c17800 --- /dev/null +++ b/opcode_proc/Cargo.toml @@ -0,0 +1,12 @@ +[lib] +proc-macro = true + +[package] +name = "opcode_proc" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn = {version = "2.0.90", features = ["full"] } +proc-macro2= "1.0.92" +quote = "1.0" diff --git a/opcode_proc/src/lib.rs b/opcode_proc/src/lib.rs new file mode 100644 index 0000000..d777214 --- /dev/null +++ b/opcode_proc/src/lib.rs @@ -0,0 +1,212 @@ +extern crate proc_macro; +extern crate syn; +extern crate quote; +extern crate proc_macro2; + +use proc_macro::TokenStream; +use quote::quote; +use proc_macro2::Span; +use syn::{braced, bracketed, parenthesized, parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, token::{self, Paren}, Block, Error, PatType, Ident, LitInt, Result, Token, Type, TypeTuple}; + +#[proc_macro] +pub fn generate_opcodes(item: TokenStream) -> TokenStream { + let ops_def = parse_macro_input!(item as OpcodeBlock); + + let enum_name = ops_def.enum_name; + let field_names = ops_def.codes.iter().map(|field| &field.name).collect::>(); + + let getcode_match_arms = ops_def.codes.iter().map(|field| { + let code = &field.byte_code; + let variant = &field.name; + quote! { + #code => Self::#variant, + } + }).collect::>(); + + let getinput_match_arms = ops_def.codes.iter().map(|field| { + let variant = &field.name; + let num = &field.input_params.len(); + quote! { + #enum_name::#variant => #num, + } + }); + + let getoutput_match_arms = ops_def.codes.iter().map(|field| { + let variant = &field.name; + let num = &field.output_params.len(); + quote! { + #enum_name::#variant => #num, + } + }); + + let closure_match_arms = ops_def.codes.iter().map(|field| { + let variant = &field.name; + let closure_expr = &field.func_block; + let interpreter_arg = &field.interpreter_ident.clone().unwrap_or(Ident::new("_terp", Span::call_site())); + let input_args = &field.input_params; + let output_args = if field.output_params.len() == 1 { + let arg = field.output_params[0].clone(); + quote! { #arg } + } else { + let args = &field.output_params; + quote! { (#(#args),*) } + }; + + + let call_args = if field.input_params.len() > 0 { + let arg_expr = (0..field.input_params.len()).map(|index| quote! {input_params[#index]}).collect::>(); + quote! {interpreter, #(#arg_expr),*} + } else { + quote! {interpreter} + }; + + let return_vec = (0..field.output_params.len()).map(|index| quote! {out_tuple.#index}); + let call = match field.output_params.len() { + 0 => quote! { + closure(#call_args); + return Vec::new(); + }, + 1 => quote! { + let result = closure(#call_args); + return vec![ result ]; + }, + _ => quote! { + let out_tuple = closure(#call_args); + return vec![ #(#return_vec),* ]; + }, + }; + + quote! { + #enum_name::#variant => { + let closure = |#interpreter_arg: &mut crate::Interpreter, #(#input_args),*| -> #output_args + #closure_expr; + + #call + } + } + }); + + let tokens = quote! { + pub enum #enum_name { + #(#field_names),* + } + + impl #enum_name { + pub fn get_from_code(code: u32) -> Self { + match code { + #(#getcode_match_arms)* + _ => panic!("Unknown opcode 0x{code:03x}") + } + } + + pub fn get_input_arg_count(opcode: Self) -> usize { + match opcode { + #(#getinput_match_arms)* + } + } + + pub fn get_output_arg_count(opcode: Self) -> usize { + match opcode { + #(#getoutput_match_arms)* + } + } + + pub fn call_opcode(interpreter: &mut crate::Interpreter, opcode: Self, input_params: Vec) -> Vec { + match opcode { + #(#closure_match_arms)* + } + } + } + }; + + tokens.into() +} + +impl Parse for OpcodeBlock { + fn parse(input: ParseStream) -> Result { + let inner; + input.parse::()?; + Ok(OpcodeBlock { + enum_token: input.parse()?, + enum_name: input.parse()?, + brace_token: braced!(inner in input), + codes: inner.parse_terminated(SingleOpcodeDefinition::parse, Token![,])?, + }) + } +} + +impl Parse for SingleOpcodeDefinition { + fn parse(input: ParseStream) -> Result { + let attr; + input.parse::()?; + bracketed!(attr in input); + let code = attr.parse::()?; + if code != "code" { + return Err(Error::new(code.span(), "expected `code` attribute")); + } + let code; + parenthesized!(code in attr); + let byte_code = code.parse::()?; + + input.parse::()?; + let name = input.parse::()?; + + let params; + let mut interpreter_ident = None; + parenthesized!(params in input); + if params.peek(Token![&]) { + params.parse::()?; + params.parse::()?; + interpreter_ident = Some(params.parse::()?); + + if params.peek(Token![,]) { + params.parse::()?; + } + } + + let input_params = params.parse_terminated(PatType::parse, Token![,])? + .into_iter().map(|param| param).collect(); + + let output_params; + if input.peek(Token![->]) { + input.parse::]>()?; + + if input.peek(Paren) { + let params; + parenthesized!(params in input); + output_params = params.parse_terminated(Type::parse, Token![,])?.into_iter().map(|s| s).collect::>(); + } else { + output_params = vec![input.parse::()?] + } + } else { + output_params = Vec::new(); + } + + let func_block = input.parse::()?; + + Ok(SingleOpcodeDefinition { + byte_code, + name, + input_params, + interpreter_ident, + output_params, + func_block, + }) + } +} + +struct OpcodeBlock { + pub enum_token: token::Enum, + pub enum_name: Ident, + pub brace_token: token::Brace, + pub codes: Punctuated, +} + +struct SingleOpcodeDefinition { + pub byte_code: LitInt, + pub name: Ident, + pub interpreter_ident: Option, + pub input_params: Vec, + pub output_params: Vec, + pub func_block: Block, +} -- cgit 1.4.1