-
Notifications
You must be signed in to change notification settings - Fork 12
/
dear_bindings.py
792 lines (700 loc) · 45.1 KB
/
dear_bindings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# Dear Bindings Version v0.11 WIP
# Generates C-language headers for Dear ImGui
# Developed by Ben Carter (e-mail: ben AT shironekolabs.com, github: @ShironekoBen)
# Example command-line:
# python dear_bindings.py --output dcimgui ../imgui/imgui.h
# python dear_bindings.py --output dcimgui_internal --include ../imgui/imgui.h ../imgui/imgui_internal.h
# Example Input:
# imgui.h : a C++ header file (aiming to also support imgui_internal.h, implot.h etc.: support is not complete yet).
# Example output:
# dcimgui.h : a C header file for compilation by a modern C compiler, including full comments from original header file.
# dcimgui.cpp : a CPP implementation file which can to be linked into a C program.
# dcimgui.json : full metadata to reconstruct bindings for other programming languages, including full comments.
import os
from pathlib import Path
from src import code_dom
from src import c_lexer
from src import utils
import argparse
import sys
import traceback
from src.modifiers import *
from src.generators import *
from src.type_comprehension import *
# Insert a single header template file, complaining if it does not exist
# Replaces any expansions in the expansions dictionary with the given result
def insert_single_template(dest_file, template_file, expansions):
if not os.path.isfile(template_file):
print("Template file " + template_file + " could not be found (note that template file names are "
"expected to match source file names, so if you have "
"renamed imgui.h you will need to rename the template as "
"well). The common template file is included regardless of source "
"file name.")
sys.exit(2)
with open(template_file, "r") as src_file:
for line in src_file.readlines():
for before, after in expansions.items():
line = line.replace(before, after)
dest_file.write(line)
# Insert the contents of the appropriate header template file(s)
def insert_header_templates(dest_file, template_dir, src_file_name, dest_file_ext, expansions):
# Include the common template file
insert_single_template(dest_file,
os.path.join(template_dir, "common-header-template" + dest_file_ext),
expansions)
# Include the specific template for the file we are generating
insert_single_template(dest_file,
os.path.join(template_dir, src_file_name + "-header-template" + dest_file_ext),
expansions)
def parse_single_header(src_file, context):
print("Parsing " + src_file)
with open(src_file, "r") as f:
file_content = f.read()
# Tokenize file and then convert into a DOM
stream = c_lexer.tokenize(file_content)
if False: # Debug dump tokens
while True:
tok = stream.get_token()
if not tok:
break # No more input
print(tok)
return
return code_dom.DOMHeaderFile.parse(context, stream, os.path.split(src_file)[1])
# Parse the C++ header found in src_file, and write a C header to dest_file_no_ext.h, with binding implementation in
# dest_file_no_ext.cpp. Metadata will be written to dest_file_no_ext.json. implementation_header should point to a file
# containing the initial header block for the implementation (provided in the templates/ directory).
def convert_header(
src_file,
include_files,
dest_file_no_ext,
template_dir,
no_struct_by_value_arguments,
no_generate_default_arg_functions,
generate_unformatted_functions,
is_backend,
imgui_include_dir,
backend_include_dir,
emit_combined_json_metadata,
prefix_replacements
):
# Set up context and DOM root
context = code_dom.ParseContext()
dom_root = code_dom.DOMHeaderFileSet()
# Check if we'll do some special treatment for imgui_internal.h
is_imgui_internal = os.path.basename(src_file) == "imgui_internal.h"
# Parse any configuration include files and add them to the DOM
for include_file in include_files:
dom_root.add_child(parse_single_header(include_file, context))
# Parse and add the main header
main_src_root = parse_single_header(src_file, context)
dom_root.add_child(main_src_root)
# Assign a destination filename based on the output file
dest_file_name_only = os.path.basename(dest_file_no_ext)
_, main_src_root.dest_filename = os.path.split(dest_file_no_ext)
main_src_root.dest_filename += ".h" # Presume the primary output file is the .h
dom_root.validate_hierarchy()
# dom_root.dump()
print("Storing unmodified DOM")
dom_root.save_unmodified_clones()
print("Applying modifiers")
# Apply modifiers
# Add headers we need and remove those we don't
if not is_backend:
mod_add_includes.apply(dom_root, ["<stdbool.h>"]) # We need stdbool.h to get bool defined
mod_add_includes.apply(dom_root, ["<stdint.h>"]) # We need stdint.h to get int32_t
mod_remove_includes.apply(dom_root, ["<float.h>",
"<string.h>"])
if is_backend:
# Backends need to reference dcimgui.h, not imgui.h
mod_change_includes.apply(dom_root, {"\"imgui.h\"": "\"dcimgui.h\""})
# Backends need a forward-declaration for ImDrawData so that the code generator understands
# that it is an ImGui type and needs conversion
mod_add_forward_declarations.apply(main_src_root, ["struct ImDrawData;"])
# Look for "ImGui_ImplWin32_WndProcHandler" and rewrite the #if on it (this is a bit of a hack)
mod_rewrite_containing_preprocessor_conditional.apply(dom_root, "ImGui_ImplWin32_WndProcHandler",
"0",
"IMGUI_BACKEND_HAS_WINDOWS_H",
True)
mod_attach_preceding_comments.apply(dom_root)
mod_remove_function_bodies.apply(dom_root)
mod_assign_anonymous_type_names.apply(dom_root)
# Remove ImGuiOnceUponAFrame for now as it needs custom fiddling to make it usable from C
# Remove ImNewDummy/ImNewWrapper as it's a helper for C++ new (and C dislikes empty structs)
mod_remove_structs.apply(dom_root, ["ImGuiOnceUponAFrame",
"ImNewDummy", # ImGui <1.82
"ImNewWrapper", # ImGui >=1.82
# Templated stuff in imgui_internal.h
"ImBitArray", # template with two parameters, not supported
"ImSpanAllocator",
])
# Remove all functions from certain types, as they're not really useful
mod_remove_all_functions_from_classes.apply(dom_root, ["ImVector", "ImSpan", "ImChunkStream"])
# Remove all functions from ImPool, since we can't handle nested template functions yet
mod_remove_all_functions_from_classes.apply(dom_root, ["ImPool"])
# Remove Value() functions which are dumb helpers over Text(), would need custom names otherwise
mod_remove_functions.apply(dom_root, ["ImGui::Value"])
# Remove ImQsort() functions as modifiers on function pointers seem to emit a "anachronism used: modifiers on data are ignored" warning.
mod_remove_functions.apply(dom_root, ["ImQsort"])
# FIXME: Remove incorrectly parsed constructor due to "explicit" keyword.
mod_remove_functions.apply(dom_root, ["ImVec2ih::ImVec2ih"])
# Remove ErrorLogCallbackToDebugLog() from imgui_internal.h as there isn't a ErrorLogCallbackToDebugLogV() version for the bindings to call right now
mod_remove_functions.apply(dom_root, ["ImGui::ErrorLogCallbackToDebugLog"])
# Remove some templated functions from imgui_internal.h that we don't want and cause trouble
mod_remove_functions.apply(dom_root, ["ImGui::ScaleRatioFromValueT",
"ImGui::ScaleValueFromRatioT",
"ImGui::DragBehaviorT",
"ImGui::SliderBehaviorT",
"ImGui::RoundScalarWithFormatT",
"ImGui::CheckboxFlagsT"])
mod_remove_functions.apply(dom_root, ["ImGui::GetInputTextState",
"ImGui::DebugNodeInputTextState"])
mod_add_prefix_to_loose_functions.apply(dom_root, "c")
if not is_backend:
# Add helper functions to create/destroy ImVectors
# Implementation code for these can be found in templates/imgui-header.cpp
mod_add_manual_helper_functions.apply(dom_root,
[
"void ImVector_Construct(void* vector); // Construct a "
"zero-size ImVector<> (of any type). This is primarily "
"useful when calling "
"ImFontGlyphRangesBuilder_BuildRanges()",
"void ImVector_Destruct(void* vector); // Destruct an "
"ImVector<> (of any type). Important: Frees the vector "
"memory but does not call destructors on contained objects "
"(if they have them)",
])
# ImStr conversion helper, only enabled if IMGUI_HAS_IMSTR is on
mod_add_manual_helper_functions.apply(dom_root,
[
"ImStr ImStr_FromCharStr(const char* b); // Build an ImStr "
"from a regular const char* (no data is copied, so you need to make "
"sure the original char* isn't altered as long as you are using the "
"ImStr)."
],
# This weirdness is because we want this to compile cleanly even if
# IMGUI_HAS_IMSTR wasn't defined
["defined(IMGUI_HAS_IMSTR)", "IMGUI_HAS_IMSTR"])
# Add a note to ImFontGlyphRangesBuilder_BuildRanges() pointing people at the helpers
mod_add_function_comment.apply(dom_root,
"ImFontGlyphRangesBuilder::BuildRanges",
"(ImVector_Construct()/ImVector_Destruct() can be used to safely "
"construct out_ranges)")
mod_set_arguments_as_nullable.apply(dom_root, ["fmt"], False) # All arguments called "fmt" are non-nullable
mod_remove_operators.apply(dom_root)
mod_remove_heap_constructors_and_destructors.apply(dom_root)
mod_convert_references_to_pointers.apply(dom_root)
if no_struct_by_value_arguments:
mod_convert_by_value_struct_args_to_pointers.apply(dom_root)
# Assume IM_VEC2_CLASS_EXTRA and IM_VEC4_CLASS_EXTRA are never defined as they are likely to just cause problems
# if anyone tries to use it
mod_flatten_conditionals.apply(dom_root, "IM_VEC2_CLASS_EXTRA", False)
mod_flatten_conditionals.apply(dom_root, "IM_VEC4_CLASS_EXTRA", False)
mod_flatten_namespaces.apply(dom_root, {'ImGui': 'ImGui_', 'ImStb': 'ImStb_'})
mod_flatten_nested_classes.apply(dom_root)
# The custom type fudge here is a workaround for how template parameters are expanded
mod_flatten_templates.apply(dom_root, custom_type_fudges={'const ImFont**': 'ImFont* const*'})
# Remove dangling unspecialized template that flattening didn't handle
mod_remove_structs.apply(dom_root, ["ImVector_T"])
# Mark all the ImVector_ instantiations as single-line definitions
mod_mark_structs_as_single_line_definition.apply(dom_root, ["ImVector_"])
# We treat certain types as by-value types
mod_mark_by_value_structs.apply(dom_root, by_value_structs=[
'ImVec1',
'ImVec2',
'ImVec2ih',
'ImVec4',
'ImColor',
'ImStr',
'ImRect',
'ImGuiListClipperRange'
])
mod_mark_internal_members.apply(dom_root)
mod_flatten_class_functions.apply(dom_root)
mod_flatten_inheritance.apply(dom_root)
mod_remove_nested_typedefs.apply(dom_root)
mod_remove_static_fields.apply(dom_root)
mod_remove_extern_fields.apply(dom_root)
mod_remove_constexpr.apply(dom_root)
mod_generate_imstr_helpers.apply(dom_root)
mod_remove_enum_forward_declarations.apply(dom_root)
mod_calculate_enum_values.apply(dom_root)
# Treat enum values ending with _ as internal, and _COUNT as being count values
mod_mark_special_enum_values.apply(dom_root, internal_suffixes=["_"], count_suffixes=["_COUNT"])
# Mark enums that end with Flags (or Flags_ for the internal ones) as being flag enums
mod_mark_flags_enums.apply(dom_root, ["Flags", "Flags_"])
# These two are special cases because there are now (deprecated) overloads that differ from the main functions
# only in the type of the callback function. The normal disambiguation system can't handle that, so instead we
# manually rename the older versions of those functions here.
mod_rename_function_by_signature.apply(dom_root,
'ImGui_Combo', # Function name
'old_callback', # Argument to look for to identify this function
'ImGui_ComboObsolete' # New name
)
mod_rename_function_by_signature.apply(dom_root,
'ImGui_ListBox', # Function name
'old_callback', # Argument to look for to identify this function
'ImGui_ListBoxObsolete' # New name
)
# The DirectX backends declare some DirectX types that need to not have _t appended to their typedef names
mod_mark_structs_as_using_unmodified_name_for_typedef.apply(dom_root,
["ID3D11Device",
"ID3D11DeviceContext",
"ID3D12Device",
"ID3D12DescriptorHeap",
"ID3D12GraphicsCommandList",
"D3D12_CPU_DESCRIPTOR_HANDLE",
"D3D12_GPU_DESCRIPTOR_HANDLE",
"IDirect3DDevice9",
"GLFWwindow",
"GLFWmonitor"
])
# These DirectX types are awkward and we need to use a pointer-based cast when converting them
mod_mark_types_for_pointer_cast.apply(dom_root, ["D3D12_CPU_DESCRIPTOR_HANDLE",
"D3D12_GPU_DESCRIPTOR_HANDLE"])
# SDL backend forward-declared types
mod_mark_structs_as_using_unmodified_name_for_typedef.apply(dom_root,
["SDL_Window",
"SDL_Renderer",
"SDL_Gamepad",
"_SDL_GameController"
])
if is_imgui_internal:
# Some functions in imgui_internal already have the Ex suffix,
# which wreaks havok on disambiguation
mod_rename_functions.apply(main_src_root, {
'ImGui_BeginMenuEx': 'ImGui_BeginMenuWithIcon',
'ImGui_MenuItemEx': 'ImGui_MenuItemWithIcon',
'ImGui_BeginTableEx': 'ImGui_BeginTableWithID',
'ImGui_ButtonEx': 'ImGui_ButtonWithFlags',
'ImGui_ImageButtonEx': 'ImGui_ImageButtonWithFlags',
'ImGui_InputTextEx': 'ImGui_InputTextWithHintAndSize',
'ImGui_RenderTextClippedEx': 'ImGui_RenderTextClippedWithDrawList',
})
mod_disambiguate_functions.apply(dom_root,
name_suffix_remaps={
# Some more user-friendly suffixes for certain types
'const char*': 'Str',
'char*': 'Str',
'unsigned int': 'Uint',
'unsigned int*': 'UintPtr',
'ImGuiID': 'ID',
'const void*': 'Ptr',
'void*': 'Ptr'},
# Functions that look like they have name clashes but actually don't
# thanks to preprocessor conditionals
functions_to_ignore=[
"cImFileOpen",
"cImFileClose",
"cImFileGetSize",
"cImFileRead",
"cImFileWrite"],
functions_to_rename_everything=[
"ImGui_CheckboxFlags" # This makes more sense as IntPtr/UIntPtr variants
],
type_priorities={
})
if not no_generate_default_arg_functions:
mod_generate_default_argument_functions.apply(dom_root,
# We ignore functions that don't get called often because in those
# cases the default helper doesn't add much value but does clutter
# up the header file
functions_to_ignore=[
# Main
'ImGui_CreateContext',
'ImGui_DestroyContext',
# Demo, Debug, Information
'ImGui_ShowDemoWindow',
'ImGui_ShowMetricsWindow',
'ImGui_ShowDebugLogWindow',
'ImGui_ShowStackToolWindow',
'ImGui_ShowAboutWindow',
'ImGui_ShowStyleEditor',
# Styles
'ImGui_StyleColorsDark',
'ImGui_StyleColorsLight',
'ImGui_StyleColorsClassic',
# Windows
'ImGui_Begin',
'ImGui_BeginChild',
'ImGui_BeginChildID',
'ImGui_SetNextWindowSizeConstraints',
# Scrolling
'ImGui_SetScrollHereX',
'ImGui_SetScrollHereY',
'ImGui_SetScrollFromPosX',
'ImGui_SetScrollFromPosY',
# Parameters stacks
'ImGui_PushTextWrapPos',
# Widgets
'ImGui_ProgressBar',
'ImGui_ColorPicker4',
'ImGui_TreePushPtr', # Ensure why core lib has this default to NULL?
'ImGui_BeginListBox',
'ImGui_ListBox',
'ImGui_MenuItemBoolPtr',
'ImGui_BeginPopupModal',
'ImGui_OpenPopupOnItemClick',
'ImGui_TableGetColumnName',
'ImGui_TableGetColumnFlags',
'ImGui_TableSetBgColor',
'ImGui_GetColumnWidth',
'ImGui_GetColumnOffset',
'ImGui_BeginTabItem',
# Misc
'ImGui_LogToTTY',
'ImGui_LogToFile',
'ImGui_LogToClipboard',
'ImGui_BeginDisabled',
# Inputs
'ImGui_IsMousePosValid',
'ImGui_IsMouseDragging',
'ImGui_GetMouseDragDelta',
'ImGui_CaptureKeyboardFromApp',
'ImGui_CaptureMouseFromApp',
# Settings
'ImGui_LoadIniSettingsFromDisk',
'ImGui_LoadIniSettingsFromMemory',
'ImGui_SaveIniSettingsToMemory',
'ImGui_SaveIniSettingsToMemory',
# Memory Allcators
'ImGui_SetAllocatorFunctions',
# Other types
'ImGuiIO_SetKeyEventNativeDataEx',
'ImGuiTextFilter_Draw',
'ImGuiTextFilter_PassFilter',
'ImGuiTextBuffer_append',
'ImGuiInputTextCallbackData_InsertChars',
'ImColor_SetHSV',
'ImColor_HSV',
'ImGuiListClipper_Begin',
# ImDrawList
# - all 'int num_segments = 0' made explicit
'ImDrawList_AddCircleFilled',
'ImDrawList_AddBezierCubic',
'ImDrawList_AddBezierQuadratic',
'ImDrawList_PathStroke',
'ImDrawList_PathArcTo',
'ImDrawList_PathBezierCubicCurveTo',
'ImDrawList_PathBezierQuadraticCurveTo',
'ImDrawList_PathRect',
'ImDrawList_AddBezierCurve',
'ImDrawList_PathBezierCurveTo',
'ImDrawList_PushClipRect',
# ImFont, ImFontGlyphRangesBuilder
'ImFontGlyphRangesBuilder_AddText',
'ImFont_AddRemapChar',
'ImFont_RenderText',
# Obsolete functions
'ImGui_ImageButtonImTextureID',
'ImGui_ListBoxHeaderInt',
'ImGui_ListBoxHeader',
'ImGui_OpenPopupContextItem',
],
function_prefixes_to_ignore=[
'ImGuiStorage_',
'ImFontAtlas_'
],
trivial_argument_types=[
'ImGuiCond'
],
trivial_argument_names=[
'flags',
'popup_flags'
])
# Do some special-case renaming of functions
mod_rename_functions.apply(dom_root, {
# We want the ImGuiCol version of GetColorU32 to be the primary one, but we can't use type_priorities on
# mod_disambiguate_functions to achieve that because it also has more arguments and thus naturally gets passed
# over. Rather than introducing yet another layer of knobs to try and control _that_, we just do some
# after-the-fact renaming here.
'ImGui_GetColorU32': 'ImGui_GetColorU32ImVec4',
'ImGui_GetColorU32ImGuiCol': 'ImGui_GetColorU32',
'ImGui_GetColorU32ImGuiColEx': 'ImGui_GetColorU32Ex',
# ImGui_IsRectVisible is kinda inobvious as it stands, since the two overrides take the exact same type but
# interpret it differently. Hence do some renaming to make it clearer.
'ImGui_IsRectVisible': 'ImGui_IsRectVisibleBySize',
'ImGui_IsRectVisibleImVec2': 'ImGui_IsRectVisible'
})
if generate_unformatted_functions:
mod_add_unformatted_functions.apply(dom_root,
functions_to_ignore=[
'ImGui_Text',
'ImGuiTextBuffer_appendf'
])
if is_imgui_internal:
mod_move_elements.apply(dom_root,
main_src_root,
[
# This terribleness is because those few type definitions needs to appear
# below the definitions of ImVector_ImGuiTable and ImVector_ImGuiTabBar
(code_dom.DOMClassStructUnion, 'ImGuiTextIndex'),
(code_dom.DOMClassStructUnion, 'ImPool_', True),
#
(code_dom.DOMClassStructUnion, 'ImVector_int'),
(code_dom.DOMClassStructUnion, 'ImVector_const_charPtr'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiColorMod'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiContextHook'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiDockNodeSettings'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiDockRequest'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiFocusScopeData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiGroupData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiID'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiInputEvent'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiItemFlags'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiKeyRoutingData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiListClipperData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiListClipperRange'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiNavTreeNodeData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiMultiSelectState'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiMultiSelectTempData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiOldColumnData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiOldColumns'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiPopupData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiPtrOrIndex'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiSettingsHandler'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiShrinkWidthItem'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiStackLevelInfo'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiStyleMod'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTabBar'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTabItem'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTable'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTableColumnSortSpecs'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTableHeaderData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTableInstanceData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTableTempData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiTreeNodeStackData'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiViewportPPtr'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiWindowPtr'),
(code_dom.DOMClassStructUnion, 'ImVector_ImGuiWindowStackData'),
(code_dom.DOMClassStructUnion, 'ImVector_unsigned_char'),
#
(code_dom.DOMClassStructUnion, 'ImChunkStream_ImGuiWindowSettings'),
(code_dom.DOMClassStructUnion, 'ImChunkStream_ImGuiTableSettings'),
# Fudge those typedefs to be at the top
(code_dom.DOMTypedef, 'ImGuiTableColumnIdx', False, True),
])
# Make all functions use CIMGUI_API/CIMGUI_IMPL_API
mod_make_all_functions_use_imgui_api.apply(dom_root)
# Rename the API defines
mod_rename_defines.apply(dom_root, {'IMGUI_API': 'CIMGUI_API', 'IMGUI_IMPL_API': 'CIMGUI_IMPL_API'})
# Remove these #defines as they don't make sense in C
mod_remove_defines.apply(dom_root, ["IM_PLACEMENT_NEW(_PTR)", "IM_NEW(_TYPE)"])
# Rewrite these defines to reference the new function names
# This could be done more generically but there are only three at present and there's a limit to how generic
# we can get (as there's all sorts of #define trickery that could break the general case), so for now we'll
# just do it the easy way
mod_rewrite_defines.apply(dom_root, [
"IM_ALLOC(_SIZE)",
"IM_FREE(_PTR)",
"IMGUI_CHECKVERSION()"
], {"ImGui::": "ImGui_"})
# Rename these to stop them generating compile warnings as they clash with those in imgui.h
mod_rename_defines.apply(dom_root, {
'IM_ALLOC(_SIZE)': 'CIM_ALLOC(_SIZE)',
'IM_FREE(_PTR)': 'CIM_FREE(_PTR)',
'IMGUI_CHECKVERSION()': 'CIMGUI_CHECKVERSION()'
})
mod_forward_declare_structs.apply(dom_root)
mod_wrap_with_extern_c.apply(main_src_root) # main_src_root here to avoid wrapping the config headers
# For now we leave #pragma once intact on the assumption that modern compilers all support it, but if necessary
# it can be replaced with a traditional #include guard by uncommenting the line below. If you find yourself needing
# this functionality in a significant way please let me know!
# mod_remove_pragma_once.apply(dom_root)
mod_remove_empty_conditionals.apply(dom_root)
mod_merge_blank_lines.apply(dom_root)
mod_remove_blank_lines.apply(dom_root)
mod_align_enum_values.apply(dom_root)
mod_align_function_names.apply(dom_root)
mod_align_structure_field_names.apply(dom_root)
mod_align_comments.apply(dom_root)
# Exclude some defines that aren't really useful from the metadata
mod_exclude_defines_from_metadata.apply(dom_root, [
"CIMGUI_IMPL_API",
"IM_COL32_WHITE",
"IM_COL32_BLACK",
"IM_COL32_BLACK_TRANS",
"ImDrawCallback_ResetRenderState"
])
mod_replace_typedef_with_opaque_buffer.apply(dom_root, [
("ImBitArrayForNamedKeys", 20) # template with two parameters, not supported
])
# Remove namespaced define
mod_remove_typedefs.apply(dom_root, [
"ImStbTexteditState"
])
# Replace the stb_textedit type reference with an opaque pointer
mod_change_class_field_type.apply(dom_root, "ImGuiInputTextState", "Stb", "void*")
# If the user requested a custom prefix, then rename everything here
if len(prefix_replacements) > 0:
mod_rename_prefix.apply(dom_root, prefix_replacements)
dom_root.validate_hierarchy()
# Test code
# dom_root.dump()
# Cases where the varargs list version of a function does not simply have a V added to the name and needs a
# custom suffix instead
custom_varargs_list_suffixes = {'appendf': 'v'}
# Get just the name portion of the source file, to use as the template name
src_file_name_only = os.path.splitext(os.path.basename(src_file))[0]
print("Writing output to " + dest_file_no_ext + "[.h/.cpp/.json]")
# If our output name ends with _internal, then generate a version of it without that on the assumption that
# this is probably imgui_internal.h and thus we need to know what imgui.h is (likely) called as well.
if is_imgui_internal:
dest_file_name_only_no_internal = dest_file_name_only[:-9]
else:
dest_file_name_only_no_internal = dest_file_name_only
# Expansions to be used when processing templates, to insert variables as required
expansions = {"%IMGUI_INCLUDE_DIR%": imgui_include_dir,
"%BACKEND_INCLUDE_DIR%": backend_include_dir,
"%OUTPUT_HEADER_NAME%": dest_file_name_only + ".h",
"%OUTPUT_HEADER_NAME_NO_INTERNAL%": dest_file_name_only_no_internal + ".h"}
with open(dest_file_no_ext + ".h", "w") as file:
insert_header_templates(file, template_dir, src_file_name_only, ".h", expansions)
write_context = code_dom.WriteContext()
write_context.for_c = True
write_context.for_backend = is_backend
main_src_root.write_to_c(file, context=write_context)
# Generate implementations
with open(dest_file_no_ext + ".cpp", "w") as file:
insert_header_templates(file, template_dir, src_file_name_only, ".cpp", expansions)
gen_struct_converters.generate(dom_root, file, indent=0)
# Extract custom types from everything we parsed,
# but generate only for the main header
imgui_custom_types = utils.get_imgui_custom_types(dom_root)
gen_function_stubs.generate(main_src_root, file, imgui_custom_types,
indent=0,
custom_varargs_list_suffixes=custom_varargs_list_suffixes,
is_backend=is_backend)
# Generate metadata
if emit_combined_json_metadata:
metadata_file_name = dest_file_no_ext + ".json"
with open(metadata_file_name, "w") as file:
# We intentionally generate JSON starting from the root here so that we emit metadata from all dependencies
gen_metadata.generate(dom_root, file)
else:
# Emit separate metadata files for each header
headers = dom_root.list_directly_contained_children_of_type(code_dom.DOMHeaderFile)
for header in headers:
if (header == main_src_root):
metadata_file_name = dest_file_no_ext
else:
metadata_file_name = dest_file_no_ext + "_" + str(Path(header.source_filename).with_suffix(""))
metadata_file_name = metadata_file_name + ".json"
with open(metadata_file_name, "w") as file:
gen_metadata.generate(header, file)
if __name__ == '__main__':
# Parse the C++ header found in src_file, and write a C header to dest_file_no_ext.h, with binding implementation in
# dest_file_no_ext.cpp. Metadata will be written to dest_file_no_ext.json. implementation_header should point to a
# file containing the initial header block for the implementation (provided in the templates/ directory).
print("Dear Bindings: parse Dear ImGui headers, convert to C and output metadata.")
# Debug code
#type_comprehender.get_type_description("void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd)").dump(0)
default_template_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "src", "templates")
parser = argparse.ArgumentParser(
add_help=True,
epilog='Result code 0 is returned on success, 1 on conversion failure and 2 on '
'parameter errors')
parser.add_argument('src',
help='Path to source header file to process (generally imgui.h)')
parser.add_argument('-o', '--output',
required=True,
help='Path to output files (generally dcimgui). This should have no extension, '
'as <output>.h, <output>.cpp and <output>.json will be written.')
parser.add_argument('-t', '--templatedir',
default=default_template_dir,
help='Path to the implementation template directory (default: ./src/templates)')
parser.add_argument('--nopassingstructsbyvalue',
action='store_true',
help='Convert any by-value struct arguments to pointers (for other language bindings)')
parser.add_argument('--nogeneratedefaultargfunctions',
action='store_true',
help='Do not generate function variants with implied default values')
parser.add_argument('--generateunformattedfunctions',
action='store_true',
help='Generate unformatted variants of format string supporting functions')
parser.add_argument('--backend',
action='store_true',
help='Indicates that the header being processed is a backend header (experimental)')
parser.add_argument('--imgui-include-dir',
default='',
help="Path to ImGui headers to use in emitted include files. Should include a trailing slash "
"(eg \"Imgui/\"). (default: blank)")
parser.add_argument('--backend-include-dir',
default='',
help="Path to ImGui backend headers to use in emitted files. Should include a trailing slash "
"(eg \"Imgui/Backends/\"). (default: same as --imgui-include-dir)")
parser.add_argument('--include',
help="Path to additional .h files to include (e.g. imgui.h if converting imgui_internal.h, "
"and/or the file you set IMGUI_USER_CONFIG to, if any)",
default=[],
action='append')
parser.add_argument('--imconfig-path',
help="Path to imconfig.h. If not specified, imconfig.h will be assumed to be in the same "
"directory as the source file, or the directory immediately above it if --backend "
"is specified")
parser.add_argument('--emit-combined-json-metadata',
action='store_true',
help="Emit a single combined metadata JSON file instead of emitting "
"separate metadata JSON files for each header",
default=False)
parser.add_argument('--custom-namespace-prefix',
help="Specify a custom prefix to use on emitted functions/etc in place of the usual "
"namespace-derived ImGui_")
parser.add_argument('--replace-prefix',
help="Specify a name prefix and something to replace it with as a pair of arguments of "
"the form <old prefix>=<new prefix>. For example, \"--replace-prefix ImFont_=if will\" "
"result in ImFont_FindGlyph() becoming ifFontGlyph() (and all other ImFont_ names "
"following suit)",
default=[],
action='append')
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(0)
args = parser.parse_args()
include_files = []
default_imconfig_path = os.path.dirname(os.path.realpath(args.src))
# If --backend was specified, assume imconfig.h is in the directory above (as that is where it will be in the
# standard layout
if args.backend:
default_imconfig_path = os.path.dirname(default_imconfig_path)
# Add imconfig.h to the include list to get any #defines set in that
imconfig_path = args.imconfig_path if args.imconfig_path is not None else (
os.path.join(default_imconfig_path, "imconfig.h"))
include_files.append(imconfig_path)
# Build a map from all the requested prefix replacements
prefix_replacements = {}
for replacement_str in args.replace_prefix:
if '=' not in replacement_str:
print("--replace-prefix \"" + replacement_str + "\" is not of the form <old prefix>=<new prefix>")
sys.exit(1)
index = replacement_str.index('=')
old_prefix = replacement_str[:index]
new_prefix = replacement_str[(index+1):]
prefix_replacements[old_prefix] = new_prefix
# --custom-namespace-prefix is just handled as a handy short form for --replace-prefix ImGui_=<something>
if args.custom_namespace_prefix is not None:
prefix_replacements["ImGui_"] = args.custom_namespace_prefix
# Add any user-supplied config file as well
for include in args.include:
include_files.append(os.path.realpath(include))
# Perform conversion
try:
convert_header(
os.path.realpath(args.src),
include_files,
args.output,
args.templatedir,
args.nopassingstructsbyvalue,
args.nogeneratedefaultargfunctions,
args.generateunformattedfunctions,
args.backend,
args.imgui_include_dir,
args.backend_include_dir if args.backend_include_dir is not None else args.imgui_include_dir,
args.emit_combined_json_metadata,
prefix_replacements
)
except: # noqa - suppress warning about broad exception clause as it's intentionally broad
print("Exception during conversion:")
traceback.print_exc()
sys.exit(1)
print("Done")
sys.exit(0)