cog_gen_rust/
lib.rs

1mod format;
2
3use std::fmt::Write;
4
5use {
6    heck::CamelCase,
7    proc_macro2::{Ident, Span, TokenStream},
8    quote::quote,
9};
10
11use cog_idl::{Function, Interface, Module, Parameter, Type, TypeModifier, TypeName};
12
13use crate::format::format;
14
15pub fn write_generated_header(code: &mut String) {
16    writeln!(
17        code,
18        "// This file is automatically generated by cog-gen-c."
19    )
20    .unwrap();
21    writeln!(code, "// It is not intended for manual editing.").unwrap();
22    writeln!(code).unwrap();
23}
24
25/// Generates an unformatted rust module string.
26pub fn generate_module(module: &Module) -> String {
27    let tokens = generate_module_tokens(module);
28
29    let mut code = String::new();
30    write_generated_header(&mut code);
31    write!(code, "{}", tokens).unwrap();
32
33    code
34}
35
36/// Generates a rust module string formatted with a flawed internal formatter.
37/// This function is meant for platforms without rustfmt, or if rustfmt isn't installed.
38pub fn generate_module_formatted(module: &Module) -> String {
39    let tokens = generate_module_tokens(module);
40
41    let mut code = String::new();
42    write_generated_header(&mut code);
43
44    format(&mut code, tokens, 0);
45
46    code
47}
48
49fn generate_module_tokens(module: &Module) -> TokenStream {
50    let doc_code = if let Some(doc) = &module.documentation {
51        quote! { #![doc = #doc] }
52    } else {
53        quote! {}
54    };
55
56    let interfaces_code: Vec<_> = module
57        .interfaces
58        .iter()
59        .map(|i| generate_interface(i))
60        .collect();
61
62    quote! {
63        #doc_code
64        #(#interfaces_code)*
65    }
66}
67
68fn generate_interface(interface: &Interface) -> TokenStream {
69    let ident = Ident::new(&interface.name.to_camel_case(), Span::call_site());
70    let doc_code = generate_doc(&interface.documentation);
71
72    let function_fields_code: Vec<_> = interface
73        .functions
74        .iter()
75        .map(|f| generate_function_field(f))
76        .collect();
77
78    let function_helpers_code: Vec<_> = interface
79        .functions
80        .iter()
81        .map(|f| generate_function_helper(f))
82        .collect();
83
84    quote! {
85        #[repr(C)]
86        #doc_code
87        pub struct #ident {
88            #(#function_fields_code),*
89        }
90
91        impl #ident {
92            #(#function_helpers_code)*
93        }
94    }
95}
96
97fn generate_function_field(function: &Function) -> TokenStream {
98    let ident = Ident::new(&function.name, Span::call_site());
99
100    let parameters_code: Vec<_> = function
101        .parameters
102        .iter()
103        .map(|p| generate_function_field_parameter(p))
104        .collect();
105
106    let return_code = function
107        .return_type
108        .as_ref()
109        .map(|r| {
110            let return_type = generate_c_type(r);
111            quote! { -> #return_type }
112        })
113        .unwrap_or(TokenStream::new());
114
115    quote! {
116        pub #ident: unsafe extern "C" fn( #(#parameters_code),* ) #return_code
117    }
118}
119
120fn generate_function_field_parameter(parameter: &Parameter) -> TokenStream {
121    let ident = Ident::new(&parameter.name, Span::call_site());
122    let type_ = generate_c_type(&parameter.type_);
123
124    quote! { #ident: #type_ }
125}
126
127fn generate_c_type(type_: &Type) -> TokenStream {
128    // C types don't technically know about references, so use a pointer just to be sure
129    let modifier = match type_.modifier {
130        TypeModifier::None => unimplemented!(),
131        TypeModifier::Pointer => quote! { *const },
132        TypeModifier::Reference => quote! { *const },
133    };
134
135    match type_.type_name {
136        TypeName::CString => quote! { #modifier std::os::raw::c_char },
137        TypeName::Any => quote! { #modifier std::ffi::c_void },
138    }
139}
140
141fn generate_function_helper(function: &Function) -> TokenStream {
142    let ident = Ident::new(&function.name, Span::call_site());
143    let doc_code = generate_doc(&function.documentation);
144
145    let parameters_code: Vec<_> = function
146        .parameters
147        .iter()
148        .map(|p| generate_function_helper_parameter(p))
149        .collect();
150
151    let return_code = function
152        .return_type
153        .as_ref()
154        .map(|r| {
155            let return_type = generate_rust_type(r);
156            quote! { -> #return_type }
157        })
158        .unwrap_or(TokenStream::new());
159
160    let conversions_code: Vec<_> = function
161        .parameters
162        .iter()
163        .filter_map(|p| generate_conversion(p))
164        .collect();
165
166    let arguments_code: Vec<_> = function
167        .parameters
168        .iter()
169        .map(|p| Ident::new(&p.name, Span::call_site()))
170        .collect();
171
172    quote! {
173        #doc_code
174        pub fn #ident(&self, #(#parameters_code),*) #return_code {
175            unsafe {
176                #(#conversions_code)*
177
178                (self.#ident)(#(#arguments_code),*)
179            }
180        }
181    }
182}
183fn generate_function_helper_parameter(parameter: &Parameter) -> TokenStream {
184    let ident = Ident::new(&parameter.name, Span::call_site());
185    let type_ = generate_rust_type(&parameter.type_);
186
187    quote! { #ident: #type_ }
188}
189
190fn generate_rust_type(type_: &Type) -> TokenStream {
191    let modifier = match type_.modifier {
192        TypeModifier::None => unimplemented!(),
193        TypeModifier::Pointer => quote! { *const },
194        TypeModifier::Reference => quote! { & },
195    };
196
197    match type_.type_name {
198        TypeName::CString => quote! { #modifier std::ffi::CStr },
199        TypeName::Any => quote! { #modifier std::ffi::c_void },
200    }
201}
202
203fn generate_conversion(parameter: &Parameter) -> Option<TokenStream> {
204    let ident = Ident::new(&parameter.name, Span::call_site());
205
206    if parameter.type_.type_name == TypeName::CString {
207        Some(quote! { let #ident = #ident.as_ptr(); })
208    } else {
209        None
210    }
211}
212
213fn generate_doc(doc: &Option<String>) -> TokenStream {
214    if let Some(doc) = doc {
215        quote! { #[doc = #doc] }
216    } else {
217        quote! {}
218    }
219}