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
25pub 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
36pub 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(¶meter.name, Span::call_site());
122 let type_ = generate_c_type(¶meter.type_);
123
124 quote! { #ident: #type_ }
125}
126
127fn generate_c_type(type_: &Type) -> TokenStream {
128 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(¶meter.name, Span::call_site());
185 let type_ = generate_rust_type(¶meter.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(¶meter.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}