use std::str::FromStr; use convert_case::{Case, Casing}; use opengl_registry::Registry; use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use crate::str::c_ptr_to_rust_ptr; use crate::token_stream::TokenStreamExt; use crate::{ OPENGL_CMD_ARGS_REPLACE, OPENGL_CMD_ARG_NAMES_REPLACE, OPENGL_CMD_ARG_TYPES_REPLACE, OPENGL_CMD_IDENT_REPLACE, OPENGL_CMD_RET_TYPE_REPLACE, }; pub fn for_each_opengl_command_impl( input_stream: &TokenStream, opengl_registry: &Registry, ) -> TokenStream { let out: TokenStream = opengl_registry .commands() .iter() .map(|gl_command| { let stream = input_stream.replace_ident( &Ident::new(OPENGL_CMD_IDENT_REPLACE, Span::call_site()), &TokenTree::Ident(Ident::new( gl_command.prototype().name(), Span::call_site(), )), ); let return_type = gl_command.prototype().return_type(); let stream = stream.replace_ident( &Ident::new(OPENGL_CMD_RET_TYPE_REPLACE, Span::call_site()), &TokenTree::Group(Group::new( Delimiter::None, TokenStream::from_str(&fix_type(return_type)).unwrap(), )), ); let stream = stream.replace_ident( &Ident::new(OPENGL_CMD_ARGS_REPLACE, Span::call_site()), &TokenTree::Group(Group::new( Delimiter::None, TokenStream::from_str( &gl_command .parameters() .iter() .map(|param| { let param_name = clean_param_name(param.name()); let param_type = fix_type(param.get_type()); format!("{param_name}: {param_type}") }) .collect::>() .join(","), ) .unwrap(), )), ); let stream = stream.replace_ident( &Ident::new(OPENGL_CMD_ARG_NAMES_REPLACE, Span::call_site()), &TokenTree::Group(Group::new( Delimiter::None, TokenStream::from_str( &gl_command .parameters() .iter() .map(|param| clean_param_name(param.name())) .collect::>() .join(","), ) .unwrap(), )), ); stream.replace_ident( &Ident::new(OPENGL_CMD_ARG_TYPES_REPLACE, Span::call_site()), &TokenTree::Group(Group::new( Delimiter::None, TokenStream::from_str( &gl_command .parameters() .iter() .map(|param| fix_type(param.get_type())) .collect::>() .join(","), ) .unwrap(), )), ) }) .collect(); out } fn clean_param_name(param_name: &str) -> String { match param_name { "ref" => "reference", "type" => "ty", "in" => "inside", "box" => "a_box", name => name, } .to_case(Case::Snake) } fn fix_type(ty: &str) -> String { let ty = ty.replace("struct", ""); let ty = c_ptr_to_rust_ptr(&ty); ty.replace("void", "std::ffi::c_void") } #[cfg(test)] mod tests { use opengl_registry::command::{Command, Parameter, Prototype}; use pretty_assertions::assert_eq; use proc_macro2::{Punct, Spacing}; use quote::quote; use super::*; #[test] fn for_each_opengl_command_impl_works() { let registry = Registry::new(vec![ Command::new(Prototype::new("glBindBuffer", "void"), vec![]), Command::new( Prototype::new("glActiveTexture", "void"), vec![ Parameter::new("abc", "GLuint"), Parameter::new("xyz", "GLshort"), ], ), Command::new( Prototype::new("glDrawArrays", "GLint"), vec![Parameter::new("foo", "GLubyte")], ), ]); let ht = Punct::new('#', Spacing::Alone); assert_eq!( for_each_opengl_command_impl( "e! { unsafe { functions::#ht gl_command = FunctionPtr::new_initialized( get_proc_addr(stringify!(#ht gl_command)) ); } }, ®istry, ) .to_string(), quote! { unsafe { functions::glBindBuffer = FunctionPtr::new_initialized( get_proc_addr(stringify!(glBindBuffer)) ); } unsafe { functions::glActiveTexture = FunctionPtr::new_initialized( get_proc_addr(stringify!(glActiveTexture)) ); } unsafe { functions::glDrawArrays = FunctionPtr::new_initialized( get_proc_addr(stringify!(glDrawArrays)) ); } } .to_string() ); assert_eq!( for_each_opengl_command_impl( "e! { fn #ht gl_command(#ht gl_command_args) -> #ht gl_command_ret_type {} }, ®istry, ) .to_string(), quote! { fn glBindBuffer() -> std::ffi::c_void {} fn glActiveTexture(abc: GLuint, xyz: GLshort) -> std::ffi::c_void {} fn glDrawArrays(foo: GLubyte) -> GLint {} } .to_string(), ); } }