From 6b6f25357e25a07d234ae683c1b58b1929b8483e Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 19 Feb 2023 17:16:54 +0100 Subject: feat: add project & for_each_opengl_command macro --- src/lib.rs | 76 +++++++++++++++ src/repeat.rs | 217 +++++++++++++++++++++++++++++++++++++++++++ src/str.rs | 61 ++++++++++++ src/token_stream.rs | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 617 insertions(+) create mode 100644 src/lib.rs create mode 100644 src/repeat.rs create mode 100644 src/str.rs create mode 100644 src/token_stream.rs (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..35d975f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,76 @@ +//! Macros utilizing the [OpenGL API and Extension Registry]. +//! +//! [OpenGL API and Extension Registry]: https://github.com/KhronosGroup/OpenGL-Registry +#![deny(clippy::all, clippy::pedantic, missing_docs)] +use once_cell::sync::Lazy; +use opengl_registry::Registry; +use proc_macro::TokenStream; + +use crate::repeat::for_each_opengl_command_impl; + +mod repeat; +mod str; +mod token_stream; + +const OPENGL_CMD_IDENT_REPLACE: &str = "_gl_command_"; +const OPENGL_CMD_RET_TYPE_REPLACE: &str = "_gl_command_ret_type_"; +const OPENGL_CMD_ARGS_REPLACE: &str = "_gl_command_args_"; +const OPENGL_CMD_ARG_NAMES_REPLACE: &str = "_gl_command_arg_names_"; +const OPENGL_CMD_ARG_TYPES_REPLACE: &str = "_gl_command_arg_types_"; + +static OPENGL_REGISTRY: Lazy = Lazy::new(|| Registry::retrieve().unwrap()); + +/// Repeats the input for each command in the [OpenGL API and Extension Registry]. +/// +/// # Replacements +/// This macro will replace some specific identifiers. These identifiers are to be put +/// between underscores. +/// +/// ### The following identifiers will be replaced +/// +/// **`gl_command`**
+/// Will be replaced by the command name. +/// +/// **`gl_command_ret_type`**
+/// Will be replaced by the command return type. +/// +/// **`gl_command_args`**
+/// Will be replaced by the command arguments formatted as `name: type`. +/// +/// **`gl_command_arg_names`**
+/// Will be replaced by the command argument names. +/// +/// **`gl_command_arg_types`**
+/// Will be replaced by the command argument types. +/// +/// # Examples +/// ``` +/// # use opengl_registry_macros::for_each_opengl_command; +/// for_each_opengl_command! { +/// fn _gl_command_() +/// { +/// println!("Hello from {}", stringify!(_gl_command_)); +/// } +/// } +/// ``` +/// +/// Would expand to the following if the only OpenGL commands were `glCreateShader` and +/// `glBindBuffer`. +/// +/// ``` +/// fn glCreateShader() +/// { +/// println!("Hello from {}", stringify!(glCreateShader)); +/// } +/// fn glBindBuffer() +/// { +/// println!("Hello from {}", stringify!(glBindBuffer)); +/// } +/// ``` +/// +/// [OpenGL API and Extension Registry]: https://github.com/KhronosGroup/OpenGL-Registry +#[proc_macro] +pub fn for_each_opengl_command(input_stream: TokenStream) -> TokenStream +{ + for_each_opengl_command_impl(&input_stream.into(), &OPENGL_REGISTRY).into() +} diff --git a/src/repeat.rs b/src/repeat.rs new file mode 100644 index 0000000..2f55ee8 --- /dev/null +++ b/src/repeat.rs @@ -0,0 +1,217 @@ +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 gl_func_ident_index_paths = input_stream + .find_all_ident(&Ident::new(OPENGL_CMD_IDENT_REPLACE, Span::call_site())); + + let gl_func_ret_type_index_paths = input_stream + .find_all_ident(&Ident::new(OPENGL_CMD_RET_TYPE_REPLACE, Span::call_site())); + + let out: TokenStream = opengl_registry + .commands() + .iter() + .map(|gl_command| { + let stream = input_stream.replace_tokens( + &gl_func_ident_index_paths, + &TokenTree::Ident(Ident::new( + gl_command.prototype().name(), + Span::call_site(), + )), + ); + + let return_type = gl_command.prototype().return_type(); + + let stream = stream.replace_tokens( + &gl_func_ret_type_index_paths, + &TokenTree::Group(Group::new( + Delimiter::None, + TokenStream::from_str(&fix_type(return_type)).unwrap(), + )), + ); + + let gl_func_args_paths = stream + .find_all_ident(&Ident::new(OPENGL_CMD_ARGS_REPLACE, Span::call_site())); + + let stream = stream.replace_tokens( + &gl_func_args_paths, + &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 gl_func_arg_names_paths = stream.find_all_ident(&Ident::new( + OPENGL_CMD_ARG_NAMES_REPLACE, + Span::call_site(), + )); + + let stream = stream.replace_tokens( + &gl_func_arg_names_paths, + &TokenTree::Group(Group::new( + Delimiter::None, + TokenStream::from_str( + &gl_command + .parameters() + .iter() + .map(|param| clean_param_name(param.name())) + .collect::>() + .join(","), + ) + .unwrap(), + )), + ); + + let gl_func_arg_types_paths = stream.find_all_ident(&Ident::new( + OPENGL_CMD_ARG_TYPES_REPLACE, + Span::call_site(), + )); + + stream.replace_tokens( + &gl_func_arg_types_paths, + &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 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")], + ), + ]); + + assert_eq!( + for_each_opengl_command_impl( + "e! { + unsafe { + functions::_gl_command_ = FunctionPtr::new_initialized( + get_proc_addr(stringify!(_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 _gl_command_(_gl_command_args_) -> _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(), + ); + } +} diff --git a/src/str.rs b/src/str.rs new file mode 100644 index 0000000..90f3600 --- /dev/null +++ b/src/str.rs @@ -0,0 +1,61 @@ +use once_cell::sync::Lazy; +use regex::{Captures, Regex}; + +static C_PTR_RE: Lazy = + Lazy::new(|| Regex::new(r"(const )?([a-zA-Z0-9_]+)\s?\*(.*)").unwrap()); + +static REST_C_PTR_RE: Lazy = Lazy::new(|| Regex::new(r"(const)?\s?\*").unwrap()); + +pub fn c_ptr_to_rust_ptr(c_ptr: &str) -> String +{ + C_PTR_RE + .replace(c_ptr, |captures: &Captures| { + let const_or_mut = captures.get(1).map_or_else(|| "mut", |_| "const"); + + let type_name = captures.get(2).unwrap().as_str(); + + let rest = captures.get(3).unwrap(); + + let rest_ptr_matches = REST_C_PTR_RE.captures_iter(rest.as_str()); + + let rest_ptrs = rest_ptr_matches + .map(|capts| { + let const_or_mut = capts.get(1).map_or_else(|| "mut", |_| "const"); + + format!("*{const_or_mut} ") + }) + .collect::>(); + + format!("{}*{const_or_mut} {type_name}", rest_ptrs.join(" ")) + }) + .to_string() +} + +#[cfg(test)] +mod tests +{ + use pretty_assertions::assert_str_eq; + + use super::*; + + #[test] + fn c_ptr_to_rust_ptr_works() + { + assert_str_eq!(c_ptr_to_rust_ptr("void *"), "*mut void"); + assert_str_eq!(c_ptr_to_rust_ptr("const void*"), "*const void"); + assert_str_eq!(c_ptr_to_rust_ptr("GLint *"), "*mut GLint"); + assert_str_eq!(c_ptr_to_rust_ptr("const GLint*"), "*const GLint"); + assert_str_eq!(c_ptr_to_rust_ptr("const GLint *"), "*const GLint"); + assert_str_eq!(c_ptr_to_rust_ptr("const GLint **"), "*mut *const GLint"); + assert_str_eq!(c_ptr_to_rust_ptr("const GLint * *"), "*mut *const GLint"); + assert_str_eq!(c_ptr_to_rust_ptr("GLint **"), "*mut *mut GLint"); + assert_str_eq!(c_ptr_to_rust_ptr("GLuint * const*"), "*const *mut GLuint"); + + assert_str_eq!( + c_ptr_to_rust_ptr("const GLuint * const*"), + "*const *const GLuint" + ); + + assert_str_eq!(c_ptr_to_rust_ptr("GLchar"), "GLchar"); + } +} diff --git a/src/token_stream.rs b/src/token_stream.rs new file mode 100644 index 0000000..043de89 --- /dev/null +++ b/src/token_stream.rs @@ -0,0 +1,263 @@ +use proc_macro2::{Group, Ident, TokenStream, TokenTree}; + +pub use self::index_path::IndexPath; + +#[allow(clippy::module_name_repetitions)] +pub trait TokenStreamExt +{ + fn find_all_ident(&self, target_ident: &Ident) -> Vec; + + fn replace_tokens( + &self, + index_paths_to_replace: &[IndexPath], + substitution: &TokenTree, + ) -> TokenStream; +} + +impl TokenStreamExt for TokenStream +{ + fn find_all_ident(&self, target_ident: &Ident) -> Vec + { + let mut found_indices = Vec::new(); + + recurse_find_all_ident( + self.clone(), + target_ident, + &mut found_indices, + &IndexPath::new(), + ); + + found_indices + } + + fn replace_tokens( + &self, + index_paths_to_replace: &[IndexPath], + substitution: &TokenTree, + ) -> TokenStream + { + self.clone() + .into_iter() + .enumerate() + .map(|(index, mut token_tree)| { + for index_path in index_paths_to_replace + .iter() + .filter(|path| path.indices()[0] == index) + { + token_tree = match token_tree { + TokenTree::Ident(_) => substitution.clone(), + TokenTree::Group(group) => TokenTree::Group(Group::new( + group.delimiter(), + recurse_replace_tokens( + group.stream(), + substitution, + &index_path.indices()[1..], + ), + )), + tt => tt, + } + } + + token_tree + }) + .collect() + } +} + +fn recurse_find_all_ident( + input_stream: TokenStream, + target_ident: &Ident, + found_indices: &mut Vec, + current_index_path: &IndexPath, +) +{ + for (index, token_tree) in input_stream.into_iter().enumerate() { + match token_tree { + TokenTree::Ident(ident) if &ident == target_ident => { + let mut index_path = current_index_path.clone(); + + index_path.push_index(index); + + found_indices.push(index_path); + } + TokenTree::Group(group) => { + let mut index_path = current_index_path.clone(); + + index_path.push_index(index); + + recurse_find_all_ident( + group.stream(), + target_ident, + found_indices, + &index_path, + ); + } + _ => {} + } + } +} + +fn recurse_replace_tokens( + token_stream: TokenStream, + substitution: &TokenTree, + indices: &[usize], +) -> TokenStream +{ + token_stream + .into_iter() + .enumerate() + .map(|(index, token_tree)| match token_tree { + TokenTree::Ident(_) if index == indices[0] => substitution.clone(), + TokenTree::Group(group) if index == indices[0] => { + TokenTree::Group(Group::new( + group.delimiter(), + recurse_replace_tokens(group.stream(), substitution, &indices[1..]), + )) + } + tt => tt, + }) + .collect() +} + +mod index_path +{ + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct IndexPath + { + indices: Vec, + } + + impl IndexPath + { + pub fn new() -> Self + { + Self { + indices: Vec::new(), + } + } + + pub fn push_index(&mut self, index: usize) + { + self.indices.push(index); + } + + pub fn indices(&self) -> &[usize] + { + &self.indices + } + } + + impl From for IndexPath + where + IntoIter: IntoIterator, + { + fn from(value: IntoIter) -> Self + { + Self { + indices: value.into_iter().collect(), + } + } + } +} + +#[cfg(test)] +mod tests +{ + use proc_macro2::Span; + use quote::quote; + + use super::*; + + #[test] + fn find_all_ident_works() + { + assert_eq!( + quote! { + let abc = xyz; + } + .find_all_ident(&Ident::new("xyz", Span::call_site())), + vec![IndexPath::from([3])] + ); + + assert_eq!( + quote! { + let abc = (xyz, "123"); + } + .find_all_ident(&Ident::new("xyz", Span::call_site())), + vec![IndexPath::from([3, 0])] + ); + + assert_eq!( + quote! { + return ("123", (yo, 180, xyz)); + } + .find_all_ident(&Ident::new("xyz", Span::call_site())), + vec![IndexPath::from([1, 2, 4])] + ); + } + + #[test] + fn find_all_ident_works_with_multiple() + { + assert_eq!( + quote! { + unsafe { + functions::xyz = FunctionPtr::new_initialized( + get_proc_addr(stringify!(xyz)) + ); + } + } + .find_all_ident(&Ident::new("xyz", Span::call_site()),), + vec![IndexPath::from([1, 3]), IndexPath::from([1, 9, 1, 2, 0])] + ); + } + + #[test] + fn recurse_replace_tokens_works() + { + assert_eq!( + recurse_replace_tokens( + quote! { + let abc = xyz; + }, + &TokenTree::Ident(Ident::new("foo", Span::call_site())), + &[3] + ) + .to_string(), + quote! { + let abc = foo; + } + .to_string() + ); + + assert_eq!( + recurse_replace_tokens( + quote! { + let abc = (xyz, "123"); + }, + &TokenTree::Ident(Ident::new("foo", Span::call_site())), + &[3, 0] + ) + .to_string(), + quote! { + let abc = (foo, "123"); + } + .to_string() + ); + + assert_eq!( + recurse_replace_tokens( + quote! { + let abc = (hello, "123").iter_map(|_| xyz); + }, + &TokenTree::Ident(Ident::new("foo", Span::call_site())), + &[6, 3] + ) + .to_string(), + quote! { + let abc = (hello, "123").iter_map(|_| foo); + } + .to_string() + ); + } +} -- cgit v1.2.3-18-g5258