1use crate::backend::dotnet::conversion::*;
2use crate::backend::dotnet::doc::*;
3use crate::backend::dotnet::formatting::*;
4use crate::backend::dotnet::*;
5
6pub(crate) fn generate(
7 f: &mut dyn Printer,
8 lib: &Library,
9 st: &StructType<Validated>,
10) -> FormattingResult<()> {
11 match st {
12 StructType::FunctionArg(x) => {
13 generate_skeleton(f, x, lib, true, &|f| generate_to_native_conversions(f, x))
14 }
15 StructType::FunctionReturn(x) => {
16 generate_skeleton(f, x, lib, false, &|f| generate_to_dotnet_conversions(f, x))
17 }
18 StructType::CallbackArg(x) => {
19 generate_skeleton(f, x, lib, false, &|f| generate_to_dotnet_conversions(f, x))
20 }
21 StructType::Universal(x) => generate_skeleton(f, x, lib, true, &|f| {
22 generate_to_native_conversions(f, x)?;
23 generate_to_dotnet_conversions(f, x)
24 }),
25 }
26}
27
28trait DotNetVisibility {
29 fn to_str(&self) -> &str;
30}
31
32impl DotNetVisibility for Visibility {
33 fn to_str(&self) -> &str {
34 match self {
35 Visibility::Public => "public",
36 Visibility::Private => "internal",
37 }
38 }
39}
40
41fn get_field_value<T>(
42 field: &StructField<T, Validated>,
43 constructor: &Initializer<Validated>,
44) -> String
45where
46 T: StructFieldType,
47{
48 match constructor.values.iter().find(|x| x.name == field.name) {
49 Some(x) => match &x.value {
50 ValidatedDefaultValue::Bool(x) => x.to_string(),
51 ValidatedDefaultValue::Number(x) => match x {
52 NumberValue::U8(x) => x.to_string(),
53 NumberValue::S8(x) => x.to_string(),
54 NumberValue::U16(x) => x.to_string(),
55 NumberValue::S16(x) => x.to_string(),
56 NumberValue::U32(x) => x.to_string(),
57 NumberValue::S32(x) => x.to_string(),
58 NumberValue::U64(x) => x.to_string(),
59 NumberValue::S64(x) => x.to_string(),
60 NumberValue::Float(x) => format!("{x}F"),
61 NumberValue::Double(x) => x.to_string(),
62 },
63 ValidatedDefaultValue::Duration(t, x) => match t {
64 DurationType::Milliseconds => {
65 format!("TimeSpan.FromMilliseconds({})", t.get_value_string(*x))
66 }
67 DurationType::Seconds => {
68 format!("TimeSpan.FromSeconds({})", t.get_value_string(*x))
69 }
70 },
71 ValidatedDefaultValue::Enum(x, variant) => {
72 format!("{}.{}", x.name.camel_case(), variant.camel_case())
73 }
74 ValidatedDefaultValue::String(x) => format!("\"{x}\""),
75 ValidatedDefaultValue::DefaultStruct(handle, _, _) => {
76 format!("new {}()", handle.name().camel_case())
77 }
78 },
79 None => field.name.mixed_case(),
80 }
81}
82
83fn write_static_constructor<T>(
84 f: &mut dyn Printer,
85 handle: &Struct<T, Validated>,
86 constructor: &Handle<Initializer<Validated>>,
87) -> FormattingResult<()>
88where
89 T: StructFieldType + TypeInfo,
90{
91 write_constructor_documentation(f, handle, constructor, true)?;
92
93 let invocation_args = handle
94 .fields()
95 .map(|sf| get_field_value(sf, constructor))
96 .collect::<Vec<String>>()
97 .join(", ");
98
99 f.writeln(&format!(
100 "public static {} {}({})",
101 handle.name().camel_case(),
102 constructor.name.camel_case(),
103 constructor_parameters(handle, constructor)
104 ))?;
105
106 blocked(f, |f| {
107 f.writeln(&format!(
108 "return new {}({});",
109 handle.declaration.name().camel_case(),
110 invocation_args
111 ))
112 })
113}
114
115fn get_default_value_doc(x: &ValidatedDefaultValue) -> String {
116 match x {
117 ValidatedDefaultValue::Bool(x) => x.to_string(),
118 ValidatedDefaultValue::Number(x) => x.to_string(),
119 ValidatedDefaultValue::Duration(DurationType::Milliseconds, x) => {
120 format!("{}ms", x.as_millis())
121 }
122 ValidatedDefaultValue::Duration(DurationType::Seconds, x) => format!("{}s", x.as_secs()),
123 ValidatedDefaultValue::Enum(handle, variant) => format!(
124 "<see cref=\"{}.{}\" />",
125 handle.name.camel_case(),
126 variant.camel_case()
127 ),
128 ValidatedDefaultValue::String(x) => format!("\"{x}\""),
129 ValidatedDefaultValue::DefaultStruct(x, _, _) => {
130 format!("Default <see cref=\"{}\" />", x.name().camel_case())
131 }
132 }
133}
134
135fn write_constructor_documentation<T>(
136 f: &mut dyn Printer,
137 handle: &Struct<T, Validated>,
138 constructor: &Handle<Initializer<Validated>>,
139 write_return_info: bool,
140) -> FormattingResult<()>
141where
142 T: StructFieldType + TypeInfo,
143{
144 documentation(f, |f| {
145 xmldoc_print(f, &constructor.doc)?;
146
147 if !constructor.values.is_empty() {
148 f.newline()?;
149 f.writeln("<remarks>")?;
150 f.writeln("Default values:")?;
151 f.writeln("<list type=\"bullet\">")?;
152 for init_value in constructor.values.iter() {
153 f.writeln(&format!(
154 "<item><description><see cref=\"{}.{}\" />: {}</description></item>",
155 handle.name().camel_case(),
156 init_value.name.camel_case(),
157 get_default_value_doc(&init_value.value)
158 ))?;
159 }
160 f.writeln("</list>")?;
161 f.writeln("</remarks>")?;
162 }
163
164 f.newline()?;
165
166 for arg in handle.initializer_args(constructor.clone()) {
167 f.writeln(&format!("<param name=\"{}\">", arg.name.mixed_case()))?;
168 docstring_print(f, &arg.doc.brief)?;
169 f.write("</param>")?;
170 }
171
172 if write_return_info {
173 f.writeln(&format!(
174 "<returns>Initialized <see cref=\"{}\" /> instance </returns>",
175 handle.name().camel_case()
176 ))?;
177 }
178
179 Ok(())
180 })
181}
182
183fn constructor_parameters<T>(
184 handle: &Struct<T, Validated>,
185 constructor: &Handle<Initializer<Validated>>,
186) -> String
187where
188 T: StructFieldType + TypeInfo,
189{
190 handle
191 .initializer_args(constructor.clone())
192 .map(|sf| {
193 format!(
194 "{} {}",
195 sf.field_type.get_dotnet_type(),
196 sf.name.mixed_case()
197 )
198 })
199 .collect::<Vec<String>>()
200 .join(", ")
201}
202
203fn write_constructor<T>(
204 f: &mut dyn Printer,
205 visibility: Visibility,
206 handle: &Struct<T, Validated>,
207 constructor: &Handle<Initializer<Validated>>,
208) -> FormattingResult<()>
209where
210 T: StructFieldType + TypeInfo,
211{
212 if visibility == Visibility::Public && handle.visibility == Visibility::Public {
213 write_constructor_documentation(f, handle, constructor, false)?;
214 }
215
216 let visibility = match visibility {
217 Visibility::Public => handle.visibility,
218 Visibility::Private => Visibility::Private,
219 };
220
221 f.writeln(&format!(
222 "{} {}({})",
223 visibility.to_str(),
224 handle.name().camel_case(),
225 constructor_parameters(handle, constructor)
226 ))?;
227 blocked(f, |f| {
228 for field in &handle.fields {
229 indented(f, |f| {
230 f.writeln(&format!(
231 "this.{} = {};",
232 field.name.camel_case(),
233 get_field_value(field, constructor)
234 ))
235 })?;
236 }
237 Ok(())
238 })?;
239 Ok(())
240}
241
242fn generate_to_native_conversions<T>(
243 f: &mut dyn Printer,
244 handle: &Struct<T, Validated>,
245) -> FormattingResult<()>
246where
247 T: StructFieldType + ConvertToNative,
248{
249 let struct_name = handle.name().camel_case();
250 let struct_native_name = format!("{struct_name}Native");
251
252 f.newline()?;
253
254 f.writeln(&format!(
256 "internal static {struct_native_name} ToNative({struct_name} self)"
257 ))?;
258 blocked(f, |f| {
259 f.writeln(&format!("{struct_native_name} result;"))?;
260 for el in handle.fields() {
261 let el_name = el.name.camel_case();
262
263 let conversion = el
264 .field_type
265 .convert_to_native(&format!("self.{el_name}"))
266 .unwrap_or(format!("self.{el_name}"));
267 f.writeln(&format!("result.{el_name} = {conversion};"))?;
268 }
269 f.writeln("return result;")
270 })?;
271
272 f.newline()?;
273
274 f.writeln(&format!(
276 "internal static IntPtr ToNativeRef({struct_name} self)"
277 ))?;
278 blocked(f, |f| {
279 f.writeln("var handle = IntPtr.Zero;")?;
280 f.writeln("if (self != null)")?;
281 blocked(f, |f| {
282 f.writeln("var nativeStruct = ToNative(self);")?;
283 f.writeln("handle = Marshal.AllocHGlobal(Marshal.SizeOf(nativeStruct));")?;
284 f.writeln("Marshal.StructureToPtr(nativeStruct, handle, false);")?;
285 f.writeln("nativeStruct.Dispose();")
286 })?;
287 f.writeln("return handle;")
288 })?;
289
290 f.newline()?;
291
292 f.writeln("internal void Dispose()")?;
294 blocked(f, |f| {
295 for el in handle.fields() {
296 let el_name = el.name.camel_case();
297
298 if let Some(cleanup) = el.field_type.cleanup_native(&format!("this.{el_name}")) {
299 f.writeln(&cleanup)?;
300 }
301 }
302 Ok(())
303 })
304}
305
306fn generate_to_dotnet_conversions<T>(
307 f: &mut dyn Printer,
308 handle: &Struct<T, Validated>,
309) -> FormattingResult<()>
310where
311 T: StructFieldType + ConvertToDotNet,
312{
313 let struct_name = handle.name().camel_case();
314 let struct_native_name = format!("{struct_name}Native");
315
316 f.newline()?;
317
318 f.writeln(&format!(
320 "internal static {struct_name} FromNative({struct_native_name} native)"
321 ))?;
322 blocked(f, |f| {
323 f.writeln(&format!("return new {struct_name}"))?;
324 f.writeln("{")?;
325 indented(f, |f| {
326 for el in handle.fields() {
327 let el_name = el.name.camel_case();
328
329 let conversion = el
330 .field_type
331 .convert_to_dotnet(&format!("native.{el_name}"))
332 .unwrap_or(format!("native.{el_name}"));
333 f.writeln(&format!("{el_name} = {conversion},"))?;
334 }
335 Ok(())
336 })?;
337 f.writeln("};")
338 })?;
339
340 f.newline()?;
341
342 f.writeln(&format!(
344 "internal static {struct_name} FromNativeRef(IntPtr native)"
345 ))?;
346 blocked(f, |f| {
347 f.writeln(&format!("{struct_name} handle = null;"))?;
348 f.writeln("if (native != IntPtr.Zero)")?;
349 blocked(f, |f| {
350 f.writeln(&format!(
351 "var nativeStruct = Marshal.PtrToStructure<{struct_native_name}>(native);"
352 ))?;
353 f.writeln("handle = FromNative(nativeStruct);")
354 })?;
355 f.writeln("return handle;")
356 })
357}
358
359fn generate_skeleton<T>(
360 f: &mut dyn Printer,
361 handle: &Struct<T, Validated>,
362 lib: &Library,
363 generate_builder_methods: bool,
364 conversions: &dyn Fn(&mut dyn Printer) -> FormattingResult<()>,
365) -> FormattingResult<()>
366where
367 T: StructFieldType + TypeInfo,
368{
369 let struct_name = handle.name().camel_case();
370 let struct_native_name = format!("{struct_name}Native");
371
372 print_license(f, &lib.info.license_description)?;
373 print_imports(f)?;
374 f.newline()?;
375
376 let doc = match handle.visibility {
377 Visibility::Public => handle.doc.clone(),
378 Visibility::Private => handle
379 .doc
380 .clone()
381 .warning("This class is an opaque handle and cannot be constructed by user code"),
382 };
383
384 namespaced(f, &lib.settings.name, |f| {
385 documentation(f, |f| {
386 xmldoc_print(f, &doc)
388 })?;
389
390 f.writeln(&format!("public class {struct_name}"))?;
391 blocked(f, |f| {
392 for field in handle.fields() {
394 documentation(f, |f| {
395 xmldoc_print(f, &field.doc)?;
397 Ok(())
398 })?;
399
400 f.writeln(&format!(
401 "{} {} {};",
402 handle.visibility.to_str(),
403 field.field_type.get_dotnet_type(),
404 field.name.camel_case()
405 ))?;
406 }
407
408 if handle.visibility == Visibility::Public && generate_builder_methods {
410 f.newline()?;
411
412 for field in handle.fields() {
413 documentation(f, |f| {
414 xmldoc_print(f, &field.doc)?;
416 Ok(())
417 })?;
418
419 f.writeln(&format!(
420 "public {} With{}({} value)",
421 struct_name,
422 field.name.camel_case(),
423 field.field_type.get_dotnet_type(),
424 ))?;
425 blocked(f, |f| {
426 f.writeln(&format!("this.{} = value;", field.name.camel_case(),))?;
427 f.writeln("return this;")
428 })?;
429 }
430 }
431
432 for c in &handle.initializers {
433 match c.initializer_type {
434 InitializerType::Normal => {
435 f.newline()?;
436 write_constructor(f, Visibility::Public, handle, c)?;
437 }
438 InitializerType::Static => {
439 f.newline()?;
440 write_static_constructor(f, handle, c)?;
441 }
442 }
443 }
444
445 if !handle.has_full_initializer() {
447 let constructor = Handle::new(Initializer::full(InitializerType::Normal, doc));
448
449 f.newline()?;
450 write_constructor(f, Visibility::Private, handle, &constructor)?;
451 }
452
453 if !handle.has_default_initializer() {
454 f.newline()?;
456 f.writeln(&format!("internal {}() {{ }}", handle.name().camel_case()))?;
457 }
458
459 Ok(())
460 })?;
461
462 f.newline()?;
463
464 f.writeln("[StructLayout(LayoutKind.Sequential)]")?;
466 f.writeln(&format!("internal struct {struct_native_name}"))?;
467 blocked(f, |f| {
468 for el in handle.fields() {
470 f.writeln(&format!(
471 "{} {};",
472 el.field_type.get_native_type(),
473 el.name.camel_case()
474 ))?;
475 }
476
477 conversions(f)?;
478
479 f.newline()?;
480
481 f.writeln("internal static void NativeRefCleanup(IntPtr native)")?;
483 blocked(f, |f| {
484 f.writeln("if (native != IntPtr.Zero)")?;
485 blocked(f, |f| f.writeln("Marshal.FreeHGlobal(native);"))
486 })
487 })
488 })
489}