-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate_from_gir.py
More file actions
2132 lines (1904 loc) · 80.6 KB
/
generate_from_gir.py
File metadata and controls
2132 lines (1904 loc) · 80.6 KB
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
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""
Generate libflatpak bindings from GObject Introspection (GIR) file.
This script parses Flatpak-1.0.gir and generates:
1. C++ N-API wrapper functions in src/flatpak.cc
2. JavaScript wrapper classes in index.js
3. TypeScript definitions in index.d.ts
"""
import argparse
import os
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple
# -----------------------------------------------------------------------------
# Type mappings
# -----------------------------------------------------------------------------
GIR_TO_CPP_TYPES = {
"gboolean": "bool",
"gint": "int",
"guint": "unsigned int",
"gint8": "int8_t",
"guint8": "uint8_t",
"gint16": "int16_t",
"guint16": "uint16_t",
"gint32": "int32_t",
"guint32": "uint32_t",
"gint64": "int64_t",
"guint64": "uint64_t",
"glong": "long",
"gulong": "unsigned long",
"gshort": "short",
"gushort": "unsigned short",
"gsize": "size_t",
"gssize": "ssize_t",
"gdouble": "double",
"gfloat": "float",
"utf8": "const char*",
"filename": "const char*",
"gpointer": "void*",
"none": "void",
"GLib.Quark": "GQuark",
"GLib.Bytes": "GBytes*",
"GLib.HashTable": "GHashTable*",
"GLib.KeyFile": "GKeyFile*",
"GLib.Variant": "GVariant*",
"GLib.List": "GList*",
"GLib.PtrArray": "GPtrArray*",
"GLib.Strv": "char**",
}
GIR_TO_JS_TYPES = {
"gboolean": "boolean",
"gint": "number",
"guint": "number",
"gint8": "number",
"guint8": "number",
"gint16": "number",
"guint16": "number",
"gint32": "number",
"guint32": "number",
"gint64": "number",
"guint64": "number",
"glong": "number",
"gulong": "number",
"gshort": "number",
"gushort": "number",
"gsize": "number",
"gssize": "number",
"gdouble": "number",
"gfloat": "number",
"utf8": "string",
"filename": "string",
"gpointer": "External",
"none": "void",
"GLib.Quark": "number",
"GLib.Bytes": "External",
"GLib.HashTable": "External",
"GLib.KeyFile": "External",
"GLib.Variant": "External",
"GLib.List": "External",
"GLib.PtrArray": "External",
"GLib.Strv": "External",
}
CPP_TO_NAPI_TYPES = {
"bool": "Boolean",
"int": "Number",
"unsigned int": "Number",
"int8_t": "Number",
"uint8_t": "Number",
"int16_t": "Number",
"uint16_t": "Number",
"int32_t": "Number",
"uint32_t": "Number",
"int64_t": "Number",
"uint64_t": "Number",
"long": "Number",
"unsigned long": "Number",
"short": "Number",
"unsigned short": "Number",
"size_t": "Number",
"ssize_t": "Number",
"double": "Number",
"float": "Number",
"const char*": "String",
"char*": "String",
"void": "Undefined",
}
# -----------------------------------------------------------------------------
# Data structures
# -----------------------------------------------------------------------------
@dataclass
class Parameter:
name: str
gir_type: str
c_type: str
js_type: str
transfer: str = "none"
nullable: bool = False
direction: str = "in"
is_instance: bool = False
caller_allocates: bool = False
def is_pointer(self) -> bool:
return "*" in self.c_type
def is_gobject(self) -> bool:
# Exclude enum types from being treated as GObjects
if self.is_enum():
return False
return (
self.gir_type.startswith("Flatpak.")
or "Flatpak" in self.c_type
or self.gir_type
in [
"Gio.File",
"Gio.Cancellable",
"Gio.FileMonitor",
"GLib.Bytes",
"GLib.HashTable",
"GLib.KeyFile",
"GLib.Variant",
"GLib.List",
"GLib.PtrArray",
"GLib.Strv",
]
)
def is_enum(self) -> bool:
# Check if this is an enum type
# Check gir_type first
if (
self.gir_type.endswith("Type")
or self.gir_type.endswith("Flags")
or self.gir_type.endswith("Kind")
):
return True
# Check c_type for Flatpak enums
if "Flatpak" in self.c_type:
c_type_lower = self.c_type.lower()
if (
c_type_lower.endswith("kind")
or c_type_lower.endswith("type")
or c_type_lower.endswith("flags")
):
return True
return False
def is_error_param(self) -> bool:
return self.name == "error" and self.gir_type == "GLib.Error"
@dataclass
class ReturnValue:
gir_type: str
c_type: str
js_type: str
transfer: str = "none"
nullable: bool = False
element_type: str = ""
def is_pointer(self) -> bool:
return "*" in self.c_type
def is_gobject(self) -> bool:
# Exclude enum types from being treated as GObjects
if self.is_enum():
return False
return (
self.gir_type.startswith("Flatpak.")
or "Flatpak" in self.c_type
or self.gir_type
in [
"Gio.File",
"Gio.Cancellable",
"Gio.FileMonitor",
"GLib.Bytes",
"GLib.HashTable",
"GLib.KeyFile",
"GLib.Variant",
"GLib.List",
"GLib.PtrArray",
"GLib.Strv",
]
)
def is_enum(self) -> bool:
# Check if this is an enum type
# Check gir_type first
if (
self.gir_type.endswith("Type")
or self.gir_type.endswith("Flags")
or self.gir_type.endswith("Kind")
):
return True
# Check c_type for Flatpak enums
if "Flatpak" in self.c_type:
c_type_lower = self.c_type.lower()
if (
c_type_lower.endswith("kind")
or c_type_lower.endswith("type")
or c_type_lower.endswith("flags")
):
return True
return False
@dataclass
class Function:
name: str
c_name: str
parameters: List[Parameter]
return_value: ReturnValue
is_method: bool = False
is_constructor: bool = False
is_static: bool = False
throws: bool = False
def has_error_param(self) -> bool:
return any(p.is_error_param() for p in self.parameters)
def js_name(self) -> str:
if self.is_constructor:
return "new"
# First handle hyphenated names
if "-" in self.name:
# Convert hyphenated-name to camelCase
parts = self.name.split("-")
if self.is_method and parts[0] in ["get", "set", "is"]:
# Keep get/set/is prefix
return parts[0] + "".join(p.capitalize() for p in parts[1:] if p)
elif self.is_method:
return parts[0] + "".join(p.capitalize() for p in parts[1:] if p)
else:
# Standalone function
return parts[0] + "".join(p.capitalize() for p in parts[1:] if p)
# Convert snake_case to camelCase
parts = self.name.split("_")
if self.is_method and parts[0] in ["get", "set", "is"]:
# Keep get/set/is prefix
return parts[0] + "".join(p.capitalize() for p in parts[1:])
elif self.is_method:
return parts[0] + "".join(p.capitalize() for p in parts[1:])
else:
# Standalone function
return parts[0] + "".join(p.capitalize() for p in parts[1:])
@dataclass
class Property:
name: str
gir_type: str
c_type: str
js_type: str
readable: bool = True
writable: bool = False
construct: bool = False
def getter_name(self) -> str:
if self.name.startswith("is_"):
return self.name
# Handle hyphenated property names
if "-" in self.name:
return f"get_{self.name.replace('-', '_')}"
return f"get_{self.name}"
def setter_name(self) -> str:
# Handle hyphenated property names
if "-" in self.name:
return f"set_{self.name.replace('-', '_')}"
return f"set_{self.name}"
@dataclass
class Class:
name: str
c_name: str
parent: Optional[str] = None
functions: List[Function] = field(default_factory=list)
properties: List[Property] = field(default_factory=list)
@dataclass
class Namespace:
name: str
classes: List[Class] = field(default_factory=list)
functions: List[Function] = field(default_factory=list)
class GIRParser:
def __init__(self, gir_file: str):
self.gir_file = gir_file
self.tree = ET.parse(gir_file)
self.root = self.tree.getroot()
self.ns = {
"gi": "http://www.gtk.org/introspection/core/1.0",
"c": "http://www.gtk.org/introspection/c/1.0",
"glib": "http://www.gtk.org/introspection/glib/1.0",
}
def parse(self) -> Namespace:
"""Parse the entire GIR file"""
namespace = Namespace(name="Flatpak")
seen_c_names = set()
# Find all classes
for class_elem in self.root.findall(".//gi:class", self.ns):
cls = self.parse_class(class_elem)
if cls:
namespace.classes.append(cls)
# Find all standalone functions
for func_elem in self.root.findall(".//gi:function", self.ns):
func = self.parse_function(func_elem)
if func and func.c_name not in seen_c_names:
namespace.functions.append(func)
seen_c_names.add(func.c_name)
return namespace
def parse_class(self, class_elem) -> Optional[Class]:
"""Parse a class element"""
name = class_elem.get("name")
if not name:
return None
c_name = class_elem.get(f"{{{self.ns['c']}}}type")
if not c_name:
c_name = f"Flatpak{name}"
parent = class_elem.get("parent")
# Normalize parent class name by stripping namespace prefix
if parent:
# Extract simple class name after last dot
if "." in parent:
parent = parent.split(".")[-1]
cls = Class(name=name, c_name=c_name, parent=parent)
# Parse constructors
for constr_elem in class_elem.findall(".//gi:constructor", self.ns):
func = self.parse_function(constr_elem, is_constructor=True)
if func:
cls.functions.append(func)
# Parse methods
for method_elem in class_elem.findall(".//gi:method", self.ns):
func = self.parse_function(method_elem, is_method=True)
if func:
cls.functions.append(func)
# Parse static methods
for static_elem in class_elem.findall(".//gi:static-method", self.ns):
func = self.parse_function(static_elem, is_method=True, is_static=True)
if func:
cls.functions.append(func)
# Parse properties
for prop_elem in class_elem.findall(".//gi:property", self.ns):
prop = self.parse_property(prop_elem)
if prop:
cls.properties.append(prop)
return cls
def parse_function(
self,
func_elem,
is_method: bool = False,
is_constructor: bool = False,
is_static: bool = False,
) -> Optional[Function]:
"""Parse a function element"""
name = func_elem.get("name")
if not name:
return None
c_name = func_elem.get(f"{{{self.ns['c']}}}identifier")
if not c_name:
c_name = name
# Check if function throws errors
throws = func_elem.get("throws", "0") == "1"
# Parse parameters
parameters = []
has_callback = False
has_array_param = False
# Look for parameters under gi:parameters container
params_container = func_elem.find("gi:parameters", self.ns)
if params_container is not None:
for param_elem in params_container.findall("gi:parameter", self.ns):
# Check for array parameters
if param_elem.find("gi:array", self.ns) is not None:
has_array_param = True
param = self.parse_parameter(param_elem, is_instance=False)
if param:
# Check for callback parameters
if (
"callback" in param.gir_type.lower()
or "Callback" in param.gir_type
):
has_callback = True
parameters.append(param)
else:
# Fallback to searching all parameter elements
for param_elem in func_elem.findall(".//gi:parameter", self.ns):
# Check for array parameters
if param_elem.find("gi:array", self.ns) is not None:
has_array_param = True
param = self.parse_parameter(param_elem, is_instance=False)
if param:
# Check for callback parameters
if (
"callback" in param.gir_type.lower()
or "Callback" in param.gir_type
):
has_callback = True
parameters.append(param)
# Parse return value
return_elem = func_elem.find("gi:return-value", self.ns)
if return_elem is not None:
return_value = self.parse_return_value(return_elem)
else:
return_value = ReturnValue(
gir_type="none", c_type="void", js_type="void", transfer="none"
)
# For methods (non-static), check for instance-parameter
if is_method and not is_static:
instance_param_elem = func_elem.find("gi:instance-parameter", self.ns)
if instance_param_elem is not None:
param = self.parse_parameter(instance_param_elem, is_instance=True)
if param:
parameters.insert(0, param)
# Skip functions with callback parameters (too complex for initial version)
if has_callback:
return None
# Skip functions with array parameters (too complex for initial version)
if has_array_param:
return None
return Function(
name=name,
c_name=c_name,
parameters=parameters,
return_value=return_value,
is_method=is_method,
is_constructor=is_constructor,
is_static=is_static,
throws=throws,
)
def parse_parameter(self, param_elem, is_instance=False) -> Optional[Parameter]:
"""Parse a parameter element"""
name = param_elem.get("name", "")
if not name:
return None
# Get type info
type_elem = param_elem.find("gi:type", self.ns)
if type_elem is None:
return None
gir_type = type_elem.get("name", "")
c_type = type_elem.get(f"{{{self.ns['c']}}}type")
if c_type is None:
c_type = ""
# Get attributes
transfer = param_elem.get("transfer-ownership", "none")
nullable = param_elem.get("nullable", "0") == "1"
direction = param_elem.get("direction", "in")
caller_allocates = param_elem.get("caller-allocates", "0") == "1"
# Detect output parameters by name convention and type
if direction == "in":
# Check for common output parameter naming patterns
if (
name.endswith("_out")
or name.endswith("_inout")
or name.startswith("out_")
):
direction = "out"
# Check for pointer-to-pointer types (common for output parameters)
elif c_type.count("*") == 2: # e.g., FlatpakInstance**
direction = "out"
# Also check for pointer types with output naming patterns
elif (
name.endswith("_out")
or name.endswith("_inout")
or name.startswith("out_")
) and "*" in c_type:
direction = "out"
# Map to JS type
js_type = self.map_gir_to_js_type(gir_type)
return Parameter(
name=name,
gir_type=gir_type,
c_type=c_type,
js_type=js_type,
transfer=transfer,
nullable=nullable,
direction=direction,
is_instance=is_instance,
caller_allocates=caller_allocates,
)
def parse_return_value(self, return_elem) -> ReturnValue:
"""Parse a return value element"""
# Check for array type first
array_elem = return_elem.find("gi:array", self.ns)
if array_elem is not None:
# Handle array return type
c_type = array_elem.get(f"{{{self.ns['c']}}}type", "")
array_name = array_elem.get("name", "")
element_type = ""
# Get the element type inside the array
elem_type_elem = array_elem.find("gi:type", self.ns)
if elem_type_elem is not None:
element_type = elem_type_elem.get("name", "")
# Check for known array types
if array_name == "GLib.PtrArray" or "GPtrArray*" in c_type:
gir_type = "GLib.PtrArray"
if not c_type:
c_type = "GPtrArray*"
elif array_name == "GLib.List":
gir_type = "GLib.List"
if not c_type:
c_type = "GList*"
else:
if elem_type_elem is not None:
gir_type = element_type
# For string arrays, use GLib.Strv
if gir_type == "utf8":
gir_type = "GLib.Strv"
if not c_type:
c_type = "char**"
else:
# Generic array type
gir_type = f"{gir_type}[]"
else:
gir_type = "unknown[]"
transfer = return_elem.get("transfer-ownership", "none")
nullable = return_elem.get("nullable", "0") == "1"
js_type = self.map_gir_to_js_type(gir_type)
return ReturnValue(
gir_type=gir_type,
c_type=c_type,
js_type=js_type,
transfer=transfer,
nullable=nullable,
element_type=element_type,
)
# Check for regular type
type_elem = return_elem.find("gi:type", self.ns)
if type_elem is None:
# Default to void
return ReturnValue(
gir_type="none", c_type="void", js_type="void", transfer="none"
)
gir_type = type_elem.get("name", "none")
c_type = type_elem.get(f"{{{self.ns['c']}}}type")
if c_type is None:
# If c_type is not provided, try to map from gir_type
if gir_type == "none":
c_type = "void"
elif gir_type in GIR_TO_CPP_TYPES:
c_type = GIR_TO_CPP_TYPES[gir_type]
elif gir_type == "GLib.Strv":
c_type = "char**"
else:
c_type = "void"
transfer = return_elem.get("transfer-ownership", "none")
nullable = return_elem.get("nullable", "0") == "1"
js_type = self.map_gir_to_js_type(gir_type)
return ReturnValue(
gir_type=gir_type,
c_type=c_type,
js_type=js_type,
transfer=transfer,
nullable=nullable,
element_type="",
)
def parse_property(self, prop_elem) -> Optional[Property]:
"""Parse a property element"""
name = prop_elem.get("name")
if not name:
return None
# Get type info
type_elem = prop_elem.find("gi:type", self.ns)
if type_elem is None:
return None
gir_type = type_elem.get("name", "")
c_type = type_elem.get(f"{{{self.ns['c']}}}type")
if c_type is None:
c_type = ""
readable = prop_elem.get("readable", "1") == "1"
writable = prop_elem.get("writable", "0") == "1"
construct = prop_elem.get("construct", "0") == "1"
js_type = self.map_gir_to_js_type(gir_type)
return Property(
name=name,
gir_type=gir_type,
c_type=c_type,
js_type=js_type,
readable=readable,
writable=writable,
construct=construct,
)
def map_gir_to_js_type(self, gir_type: str) -> str:
"""Map GIR type to JavaScript type"""
# Check for Flatpak types
if gir_type.startswith("Flatpak."):
# Check for enum types (end with Type or Flags)
if gir_type.endswith("Type") or gir_type.endswith("Flags"):
return "number"
return "External"
# Check for Flatpak enum types without Flatpak. prefix (e.g., RefKind)
if (
gir_type.endswith("Kind")
or gir_type.endswith("Type")
or gir_type.endswith("Flags")
):
return "number"
# Check for array types
if gir_type == "GLib.PtrArray":
return "Array"
# Check for GObject types
if gir_type in [
"Gio.File",
"Gio.Cancellable",
"Gio.FileMonitor",
"GLib.Bytes",
"GLib.HashTable",
"GLib.KeyFile",
"GLib.Variant",
"GLib.List",
"GLib.PtrArray",
"GLib.Strv",
]:
return "External"
# Check for arrays
if gir_type.endswith("[]"):
return "Array"
# Check for basic types
if gir_type in GIR_TO_JS_TYPES:
return GIR_TO_JS_TYPES[gir_type]
# Default to any
return "any"
class CppGenerator:
def __init__(self, namespace: Namespace):
self.namespace = namespace
self.output = []
def generate(self) -> str:
"""Generate C++ wrapper code"""
self.output = []
self.output.append("// Generated by generate_from_gir.py")
self.output.append("// DO NOT EDIT THIS FILE DIRECTLY")
self.output.append("")
self.output.append("#include <flatpak/flatpak.h>")
self.output.append("#include <glib.h>")
self.output.append("#include <memory>")
self.output.append("#include <napi.h>")
self.output.append("#include <string>")
self.output.append("#include <vector>")
self.output.append("")
self.generate_class_forward_decls()
self.output.append("")
self.generate_class_wrappers()
self.output.append("")
self.generate_init_function()
return "\n".join(self.output)
def generate_class_forward_decls(self):
"""Generate forward declarations for wrapper functions"""
for cls in self.namespace.classes:
for func in cls.functions:
if func.is_constructor:
self.output.append(
f"Napi::Value Wrap_{cls.name}_{func.name}(const Napi::CallbackInfo& info);"
)
elif func.is_static:
self.output.append(
f"Napi::Value Wrap_{cls.name}_{func.name}(const Napi::CallbackInfo& info);"
)
else:
self.output.append(
f"Napi::Value Wrap_{cls.name}_{func.name}(const Napi::CallbackInfo& info);"
)
for func in self.namespace.functions:
self.output.append(
f"Napi::Value Wrap_{func.c_name}(const Napi::CallbackInfo& info);"
)
def generate_class_wrappers(self):
"""Generate wrapper functions for each class"""
for cls in self.namespace.classes:
for func in cls.functions:
if func.is_constructor:
self.generate_constructor_wrapper(cls, func)
elif func.is_static:
self.generate_static_method_wrapper(cls, func)
else:
self.generate_method_wrapper(cls, func)
for func in self.namespace.functions:
self.generate_function_wrapper(func)
def generate_constructor_wrapper(self, cls: Class, func: Function):
"""Generate wrapper for a constructor"""
self.output.append(
f"Napi::Value Wrap_{cls.name}_{func.name}(const Napi::CallbackInfo& info) {{"
)
self.output.append(" Napi::Env env = info.Env();")
self.output.append("")
# Handle instance parameter (implicit 'this')
cpp_params = []
# Generate parameter extraction code
# JavaScript parameters start at index 0 (no instance parameter for constructors)
js_param_index = 0
for param in func.parameters:
if param.is_instance:
# Skip instance parameter for constructors
continue
self.generate_parameter_code(param, js_param_index, cpp_params)
js_param_index += 1
# Handle error parameter
error_param_name = None
for param in func.parameters:
if param.is_error_param():
error_param_name = param.name
break
# If function throws but no error param found, add one
if func.throws and not error_param_name:
error_param_name = "error"
if error_param_name:
self.output.append(f" GError* {error_param_name} = NULL;")
# Generate function call
if func.return_value.c_type == "void":
call_line = f" {func.c_name}("
call_line += ", ".join(cpp_params)
if error_param_name:
if cpp_params:
call_line += f", &{error_param_name}"
else:
call_line += f"&{error_param_name}"
call_line += ");"
self.output.append(call_line)
self.output.append("")
result_var = None
else:
result_var = "result"
call_line = f" {func.return_value.c_type} {result_var} = {func.c_name}("
call_line += ", ".join(cpp_params)
if error_param_name:
if cpp_params:
call_line += f", &{error_param_name}"
else:
call_line += f"&{error_param_name}"
call_line += ");"
self.output.append(call_line)
self.output.append("")
# Error handling
if error_param_name:
self.output.append(f" if ({error_param_name}) {{")
self.output.append(
f" Napi::Error::New(env, {error_param_name}->message).ThrowAsJavaScriptException();"
)
self.output.append(f" g_error_free({error_param_name});")
self.output.append(" return env.Null();")
self.output.append(" }")
self.output.append("")
# Return conversion
if result_var is not None:
self.generate_return_conversion(func.return_value, result_var)
else:
self.generate_return_conversion(func.return_value, "")
self.output.append("}")
self.output.append("")
def generate_method_wrapper(self, cls: Class, func: Function):
"""Generate wrapper for an instance method"""
self.output.append(
f"Napi::Value Wrap_{cls.name}_{func.name}(const Napi::CallbackInfo& info) {{"
)
self.output.append(" Napi::Env env = info.Env();")
self.output.append("")
# First parameter is the instance (this)
self.output.append(" if (info.Length() < 1 || !info[0].IsExternal()) {")
self.output.append(
f' Napi::TypeError::New(env, "Expected {cls.name} instance").ThrowAsJavaScriptException();'
)
self.output.append(" return env.Null();")
self.output.append(" }")
self.output.append(
f" {cls.c_name}* self = info[0].As<Napi::External<{cls.c_name}>>().Data();"
)
self.output.append("")
self.output.append(" if (!self) {")
self.output.append(
f' Napi::Error::New(env, "Invalid {cls.name} instance (null pointer)").ThrowAsJavaScriptException();'
)
self.output.append(" return env.Null();")
self.output.append(" }")
self.output.append("")
# Generate parameter extraction code (skip first param for instance)
cpp_params = ["self"]
# JavaScript parameters start at index 1 (index 0 is the instance)
js_param_index = 1
for param in func.parameters:
if param.is_instance:
# Skip instance parameter
continue
self.generate_parameter_code(param, js_param_index, cpp_params)
js_param_index += 1
# Handle error parameter
error_param_name = None
for param in func.parameters:
if param.is_error_param():
error_param_name = param.name
break
# If function throws but no error param found, add one
if func.throws and not error_param_name:
error_param_name = "error"
if error_param_name:
self.output.append(f" GError* {error_param_name} = NULL;")
# Generate function call
if func.return_value.c_type == "void":
call_line = f" {func.c_name}("
call_line += ", ".join(cpp_params)
if error_param_name:
if cpp_params:
call_line += f", &{error_param_name}"
else:
call_line += f"&{error_param_name}"
call_line += ");"
self.output.append(call_line)
self.output.append("")
result_var = None
else:
result_var = "result"
call_line = f" {func.return_value.c_type} {result_var} = {func.c_name}("
call_line += ", ".join(cpp_params)
if error_param_name:
if cpp_params:
call_line += f", &{error_param_name}"
else:
call_line += f"&{error_param_name}"
call_line += ");"
self.output.append(call_line)
self.output.append("")
# Error handling
if error_param_name:
self.output.append(f" if ({error_param_name}) {{")
self.output.append(
f" Napi::Error::New(env, {error_param_name}->message).ThrowAsJavaScriptException();"
)
self.output.append(f" g_error_free({error_param_name});")
self.output.append(" return env.Null();")
self.output.append(" }")
self.output.append("")
# Return conversion
if result_var is not None:
self.generate_return_conversion(func.return_value, result_var)
else:
self.generate_return_conversion(func.return_value, "")
self.output.append("}")
self.output.append("")
def generate_static_method_wrapper(self, cls: Class, func: Function):
"""Generate wrapper for a static method"""
self.output.append(
f"Napi::Value Wrap_{cls.name}_{func.name}(const Napi::CallbackInfo& info) {{"
)
self.output.append(" Napi::Env env = info.Env();")
self.output.append("")
# Generate parameter extraction code
cpp_params = []
# JavaScript parameters start at index 0 for static methods
for i, param in enumerate(func.parameters):
self.generate_parameter_code(param, i, cpp_params)
# Handle error parameter
error_param_name = None
for param in func.parameters:
if param.is_error_param():
error_param_name = param.name
break
# If function throws but no error param found, add one
if func.throws and not error_param_name:
error_param_name = "error"
if error_param_name:
self.output.append(f" GError* {error_param_name} = NULL;")
# Generate function call
if func.return_value.c_type == "void":
call_line = f" {func.c_name}("
call_line += ", ".join(cpp_params)
if error_param_name:
if cpp_params:
call_line += f", &{error_param_name}"
else:
call_line += f"&{error_param_name}"
call_line += ");"
self.output.append(call_line)
self.output.append("")
result_var = None
else:
result_var = "result"
call_line = f" {func.return_value.c_type} {result_var} = {func.c_name}("
call_line += ", ".join(cpp_params)
if error_param_name: