ruby-bindgen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +7 -0
  3. data/LICENSE +25 -0
  4. data/README.md +68 -0
  5. data/Rakefile +8 -0
  6. data/bin/ruby-bindgen +133 -0
  7. data/docs/architecture.md +238 -0
  8. data/docs/c/c_bindings.md +65 -0
  9. data/docs/c/constants.md +21 -0
  10. data/docs/c/customizing.md +19 -0
  11. data/docs/c/filtering.md +24 -0
  12. data/docs/c/getting_started.md +56 -0
  13. data/docs/c/library_loading.md +53 -0
  14. data/docs/c/output.md +96 -0
  15. data/docs/c/types.md +61 -0
  16. data/docs/c/version_guards.md +105 -0
  17. data/docs/cmake/cmake_bindings.md +19 -0
  18. data/docs/cmake/filtering.md +26 -0
  19. data/docs/cmake/getting_started.md +52 -0
  20. data/docs/cmake/output.md +110 -0
  21. data/docs/configuration.md +351 -0
  22. data/docs/contributing.md +68 -0
  23. data/docs/cpp/buffers.md +24 -0
  24. data/docs/cpp/classes.md +139 -0
  25. data/docs/cpp/cpp_bindings.md +29 -0
  26. data/docs/cpp/customizing.md +124 -0
  27. data/docs/cpp/enums.md +42 -0
  28. data/docs/cpp/filtering.md +35 -0
  29. data/docs/cpp/getting_started.md +80 -0
  30. data/docs/cpp/iterators.md +94 -0
  31. data/docs/cpp/operators.md +170 -0
  32. data/docs/cpp/output.md +125 -0
  33. data/docs/cpp/templates.md +114 -0
  34. data/docs/examples.md +133 -0
  35. data/docs/index.md +133 -0
  36. data/docs/prior_art.md +37 -0
  37. data/docs/troubleshooting.md +243 -0
  38. data/docs/type_spelling.md +200 -0
  39. data/docs/updating_bindings.md +55 -0
  40. data/docs/version_guards.md +69 -0
  41. data/lib/ruby-bindgen/config.rb +63 -0
  42. data/lib/ruby-bindgen/generators/cmake/cmake.rb +171 -0
  43. data/lib/ruby-bindgen/generators/cmake/directory.erb +29 -0
  44. data/lib/ruby-bindgen/generators/cmake/guard.rb +55 -0
  45. data/lib/ruby-bindgen/generators/cmake/presets.erb +232 -0
  46. data/lib/ruby-bindgen/generators/cmake/project.erb +89 -0
  47. data/lib/ruby-bindgen/generators/ffi/callback.erb +1 -0
  48. data/lib/ruby-bindgen/generators/ffi/constant.erb +1 -0
  49. data/lib/ruby-bindgen/generators/ffi/enum_constant_decl.erb +1 -0
  50. data/lib/ruby-bindgen/generators/ffi/enum_decl.erb +4 -0
  51. data/lib/ruby-bindgen/generators/ffi/enum_decl_anonymous.erb +4 -0
  52. data/lib/ruby-bindgen/generators/ffi/ffi.rb +687 -0
  53. data/lib/ruby-bindgen/generators/ffi/field_decl.erb +1 -0
  54. data/lib/ruby-bindgen/generators/ffi/function.erb +5 -0
  55. data/lib/ruby-bindgen/generators/ffi/library.erb +39 -0
  56. data/lib/ruby-bindgen/generators/ffi/project.erb +18 -0
  57. data/lib/ruby-bindgen/generators/ffi/struct.erb +6 -0
  58. data/lib/ruby-bindgen/generators/ffi/translation_unit.erb +9 -0
  59. data/lib/ruby-bindgen/generators/ffi/typedef_decl.erb +1 -0
  60. data/lib/ruby-bindgen/generators/ffi/union.erb +6 -0
  61. data/lib/ruby-bindgen/generators/ffi/variable.erb +1 -0
  62. data/lib/ruby-bindgen/generators/ffi/version.erb +9 -0
  63. data/lib/ruby-bindgen/generators/ffi/version_method.erb +5 -0
  64. data/lib/ruby-bindgen/generators/generator.rb +52 -0
  65. data/lib/ruby-bindgen/generators/rice/auto_generated_base_class.erb +5 -0
  66. data/lib/ruby-bindgen/generators/rice/class.erb +31 -0
  67. data/lib/ruby-bindgen/generators/rice/class_template.erb +9 -0
  68. data/lib/ruby-bindgen/generators/rice/class_template_specialization.erb +10 -0
  69. data/lib/ruby-bindgen/generators/rice/constant.erb +9 -0
  70. data/lib/ruby-bindgen/generators/rice/constructor.erb +1 -0
  71. data/lib/ruby-bindgen/generators/rice/conversion_function.erb +4 -0
  72. data/lib/ruby-bindgen/generators/rice/cxx_iterator_method.erb +1 -0
  73. data/lib/ruby-bindgen/generators/rice/cxx_method.erb +6 -0
  74. data/lib/ruby-bindgen/generators/rice/enum_constant_decl.erb +7 -0
  75. data/lib/ruby-bindgen/generators/rice/enum_decl.erb +6 -0
  76. data/lib/ruby-bindgen/generators/rice/field_decl.erb +8 -0
  77. data/lib/ruby-bindgen/generators/rice/function.erb +6 -0
  78. data/lib/ruby-bindgen/generators/rice/function_pointer.rb +68 -0
  79. data/lib/ruby-bindgen/generators/rice/incomplete_class.erb +3 -0
  80. data/lib/ruby-bindgen/generators/rice/iterator_alias.erb +1 -0
  81. data/lib/ruby-bindgen/generators/rice/iterator_collector.rb +159 -0
  82. data/lib/ruby-bindgen/generators/rice/namespace.erb +5 -0
  83. data/lib/ruby-bindgen/generators/rice/non_member_operator_binary.erb +4 -0
  84. data/lib/ruby-bindgen/generators/rice/non_member_operator_inspect.erb +6 -0
  85. data/lib/ruby-bindgen/generators/rice/non_member_operator_unary.erb +4 -0
  86. data/lib/ruby-bindgen/generators/rice/operator[].erb +4 -0
  87. data/lib/ruby-bindgen/generators/rice/project.cpp.erb +18 -0
  88. data/lib/ruby-bindgen/generators/rice/project.hpp.erb +13 -0
  89. data/lib/ruby-bindgen/generators/rice/reference_qualifier.rb +495 -0
  90. data/lib/ruby-bindgen/generators/rice/rice.rb +1724 -0
  91. data/lib/ruby-bindgen/generators/rice/rice_include.hpp.erb +7 -0
  92. data/lib/ruby-bindgen/generators/rice/signature_builder.rb +230 -0
  93. data/lib/ruby-bindgen/generators/rice/template_resolver.rb +585 -0
  94. data/lib/ruby-bindgen/generators/rice/translation_unit.cpp.erb +40 -0
  95. data/lib/ruby-bindgen/generators/rice/translation_unit.hpp.erb +7 -0
  96. data/lib/ruby-bindgen/generators/rice/translation_unit.ipp.erb +3 -0
  97. data/lib/ruby-bindgen/generators/rice/type_index.rb +117 -0
  98. data/lib/ruby-bindgen/generators/rice/type_speller.rb +509 -0
  99. data/lib/ruby-bindgen/generators/rice/union.erb +5 -0
  100. data/lib/ruby-bindgen/generators/rice/variable.erb +5 -0
  101. data/lib/ruby-bindgen/inputter.rb +54 -0
  102. data/lib/ruby-bindgen/name_mapper.rb +65 -0
  103. data/lib/ruby-bindgen/namer.rb +138 -0
  104. data/lib/ruby-bindgen/outputter.rb +40 -0
  105. data/lib/ruby-bindgen/parser.rb +82 -0
  106. data/lib/ruby-bindgen/refinements/cursor.rb +57 -0
  107. data/lib/ruby-bindgen/refinements/string.rb +41 -0
  108. data/lib/ruby-bindgen/symbol_candidates.rb +282 -0
  109. data/lib/ruby-bindgen/symbol_entry.rb +21 -0
  110. data/lib/ruby-bindgen/symbols.rb +107 -0
  111. data/lib/ruby-bindgen/type_pointer_formatter.rb +35 -0
  112. data/lib/ruby-bindgen/version.rb +3 -0
  113. data/lib/ruby-bindgen.rb +19 -0
  114. data/ruby-bindgen.gemspec +52 -0
  115. metadata +260 -0
@@ -0,0 +1,117 @@
1
+ module RubyBindgen
2
+ module Generators
3
+ class Rice
4
+ # Builds shared lookups for typedef resolution and simple-name qualification.
5
+ # Rice uses this index while walking the AST instead of threading raw hashes
6
+ # through unrelated qualification and inheritance code paths.
7
+ class TypeIndex
8
+ def initialize
9
+ clear
10
+ end
11
+
12
+ # Reset both lookup tables before a fresh AST walk.
13
+ #
14
+ # `@typedefs` maps canonical type spellings to the typedef/alias cursor
15
+ # that should win for that canonical type, for example:
16
+ # "int" => cursor for `using MyInt = int`
17
+ #
18
+ # `@qualified_names` maps a simple name to the preferred qualified name:
19
+ # "Box" => "Example::Box"
20
+ def clear
21
+ @typedefs = {}
22
+ @qualified_names = {}
23
+ end
24
+
25
+ # Walk the translation unit once and collect the shared type lookups used
26
+ # by the Rice generator.
27
+ #
28
+ # Only top-level typedefs/aliases are indexed. Member aliases are skipped
29
+ # because they are only valid through the owning class scope and cannot be
30
+ # reused as generic replacements elsewhere in generated code.
31
+ def build!(cursor)
32
+ clear
33
+
34
+ cursor.find_by_kind(true, :cursor_typedef_decl, :cursor_type_alias_decl,
35
+ :cursor_class_template, :cursor_class_decl, :cursor_struct) do |child|
36
+ record_type(child)
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ # Find the preferred typedef/alias cursor for a canonical type spelling.
43
+ #
44
+ # Example:
45
+ # typedef_for("int")
46
+ # returns the cursor for `using MyInt = int`
47
+ def typedef_for(canonical_spelling)
48
+ @typedefs[canonical_spelling]
49
+ end
50
+
51
+ # Find the preferred qualified name for a simple type name.
52
+ #
53
+ # Example:
54
+ # qualified_name_for("Box")
55
+ # returns "Example::Box"
56
+ def qualified_name_for(simple_name)
57
+ @qualified_names[simple_name]
58
+ end
59
+
60
+ # Record one discovered cursor into the appropriate lookup table.
61
+ #
62
+ # Typedefs and aliases populate both:
63
+ # - canonical type -> preferred typedef cursor
64
+ # - simple name -> preferred qualified spelling
65
+ #
66
+ # Class/template declarations only populate the simple-name lookup because
67
+ # they do not stand in for another canonical type.
68
+ def record_type(child)
69
+ case child.kind
70
+ when :cursor_typedef_decl, :cursor_type_alias_decl
71
+ parent_kind = child.semantic_parent.kind
72
+ return if parent_kind == :cursor_class_decl || parent_kind == :cursor_struct ||
73
+ parent_kind == :cursor_class_template || parent_kind == :cursor_class_template_partial_specialization
74
+
75
+ canonical = child.underlying_type.canonical.spelling
76
+ existing = @typedefs[canonical]
77
+ if existing.nil? || prefer_replacement?(existing.qualified_name, child.qualified_name)
78
+ @typedefs[canonical] = child
79
+ end
80
+
81
+ record_qualified_name(child.spelling, child.qualified_name)
82
+ when :cursor_class_template, :cursor_class_decl, :cursor_struct
83
+ return if child.spelling.empty?
84
+
85
+ record_qualified_name(child.spelling, child.qualified_name, prefer_existing: true)
86
+ end
87
+ end
88
+
89
+ # Store the preferred qualified spelling for a simple name.
90
+ #
91
+ # `prefer_existing: true` is used for class declarations so an earlier
92
+ # alias like `Example::Box` is not overwritten later by the class/template
93
+ # declaration for the same simple name.
94
+ def record_qualified_name(simple_name, qualified_name, prefer_existing: false)
95
+ return if simple_name.nil? || simple_name.empty?
96
+ return if simple_name == qualified_name
97
+
98
+ existing = @qualified_names[simple_name]
99
+ return if prefer_existing && existing
100
+
101
+ if existing.nil? || prefer_replacement?(existing, qualified_name)
102
+ @qualified_names[simple_name] = qualified_name
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Prefer user/library names over `std::...` when both map to the same
109
+ # simple name. This keeps project-local aliases such as `cv::String`
110
+ # instead of replacing them with `std::string`.
111
+ def prefer_replacement?(existing_qualified_name, new_qualified_name)
112
+ existing_qualified_name.start_with?("std::") && !new_qualified_name.start_with?("std::")
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,509 @@
1
+ require_relative '../../type_pointer_formatter'
2
+
3
+ module RubyBindgen
4
+ module Generators
5
+ class Rice
6
+ # Builds fully qualified C++ type spellings for generated Rice code and
7
+ # applies the extra template/class qualification rules that libclang does
8
+ # not handle on its own for emitted binding code.
9
+ class TypeSpeller
10
+ attr_writer :printing_policy
11
+
12
+ def initialize(type_index:)
13
+ @type_index = type_index
14
+ clear
15
+ end
16
+
17
+ def clear
18
+ @class_template_typedefs = {}
19
+ @class_static_members = {}
20
+ end
21
+
22
+ # Get the fully qualified class/struct type used in generated bindings.
23
+ #
24
+ # Examples:
25
+ # outer::Locale::Facet<Locale>
26
+ # becomes
27
+ # outer::Locale::Facet<outer::Locale>
28
+ #
29
+ # This goes through type_spelling(cursor.type) instead of cursor-specific
30
+ # string surgery. A class cursor's first child can be an unrelated type_ref,
31
+ # which makes first-match substitution double-qualify nested specializations
32
+ # like outer::outer::Locale::Facet<outer::Locale>.
33
+ def qualified_class_name(cursor)
34
+ type_spelling(cursor.type)
35
+ end
36
+
37
+ # Get qualified display name for a cursor.
38
+ # Qualifies any template arguments that need namespace prefixes.
39
+ # Used for generating fully qualified names in enum constants, etc.
40
+ def qualified_display_name(cursor)
41
+ if cursor&.kind == :cursor_class_template
42
+ template_arguments = template_parameter_arguments(cursor)
43
+ return "#{cursor.qualified_name}<#{template_arguments.join(', ')}>" unless template_arguments.empty?
44
+ end
45
+
46
+ display_name = qualify_template_parameter_packs(cursor.qualified_display_name, cursor)
47
+
48
+ # For members of template specializations, use the parent's type for qualification
49
+ # e.g., TypeTraits<lowercase_type>::type needs lowercase_type qualified,
50
+ # but cursor.type is just 'const int' which has no template args
51
+ type = cursor.type
52
+ parent = cursor.semantic_parent
53
+ if parent && parent.type.num_template_arguments > 0
54
+ type = parent.type
55
+ end
56
+ qualify_template_args(display_name, type,
57
+ ignored_names: template_parameter_names(cursor))
58
+ end
59
+
60
+ def type_spellings(cursor)
61
+ cursor.type.arg_types.map do |arg_type|
62
+ type_spelling(arg_type)
63
+ end
64
+ end
65
+
66
+ # Returns a fully-qualified C++ type spelling suitable for use in generated Rice bindings.
67
+ # Most type kinds are handled by Type#fully_qualified_name. This method only
68
+ # intercepts declared types that need generator-level context:
69
+ # - Class template types: fqn resolves template params, but template builders need them generic
70
+ # - Typedefs inside class templates: need 'typename' keyword for dependent types
71
+ # - Template instantiations: need qualify_template_args post-processing with TypeIndex
72
+ def type_spelling(type)
73
+ case type.kind
74
+ when :type_pointer
75
+ type_spelling_pointer(type)
76
+ when :type_lvalue_ref
77
+ "#{type_spelling(type.non_reference_type)} &"
78
+ when :type_rvalue_ref
79
+ "#{type_spelling(type.non_reference_type)} &&"
80
+ when :type_constant_array
81
+ "#{type_spelling(type.element_type)}[#{type.size}]"
82
+ when :type_incomplete_array
83
+ "#{type_spelling(type.element_type)}[]"
84
+ when :type_elaborated
85
+ type_spelling_declared(type)
86
+ when :type_typedef
87
+ type_spelling_declared(type)
88
+ when :type_unexposed
89
+ type_spelling_unexposed(type)
90
+ else
91
+ type.fully_qualified_name(@printing_policy)
92
+ end
93
+ end
94
+
95
+ # Qualify nested typedefs from a class template in a type spelling
96
+ # e.g., "std::reverse_iterator<iterator>" -> "std::reverse_iterator<cv::Mat_<_Tp>::iterator>"
97
+ def qualify_class_template_typedefs(spelling, class_template)
98
+ return spelling unless class_template&.kind == :cursor_class_template
99
+
100
+ cache_key = class_template.usr
101
+ typedef_info = @class_template_typedefs[cache_key] ||= begin
102
+ names = []
103
+ class_template.each(false) do |child|
104
+ child_kind = child.kind
105
+ if child_kind == :cursor_typedef_decl || child_kind == :cursor_type_alias_decl
106
+ names << child.spelling
107
+ end
108
+ end
109
+ { names: names, qualified_parent: class_template.qualified_display_name }
110
+ end
111
+
112
+ return spelling if typedef_info[:names].empty?
113
+
114
+ result = spelling.dup
115
+ qualified_parent = typedef_info[:qualified_parent]
116
+ qualified_name = class_template.qualified_name
117
+ display_name = class_template.display_name
118
+ simple_name = class_template.spelling
119
+ typedef_info[:names].each do |name|
120
+ fully_qualified = /(?<![:\w])(?:typename\s+)?#{Regexp.escape(qualified_parent)}::#{Regexp.escape(name)}(?![:\w])/
121
+ result = result.gsub(fully_qualified, "typename #{qualified_parent}::#{name}")
122
+
123
+ if qualified_name && !qualified_name.empty? && qualified_name != qualified_parent
124
+ qualified_without_args = /(?<![:\w])#{Regexp.escape(qualified_name)}::#{Regexp.escape(name)}(?![:\w])/
125
+ result = result.gsub(qualified_without_args, "typename #{qualified_parent}::#{name}")
126
+ end
127
+
128
+ if display_name && !display_name.empty? && display_name != qualified_parent
129
+ partially_qualified = /(?<![:\w])#{Regexp.escape(display_name)}::#{Regexp.escape(name)}(?![:\w])/
130
+ result = result.gsub(partially_qualified, "typename #{qualified_parent}::#{name}")
131
+ end
132
+
133
+ if simple_name && !simple_name.empty?
134
+ uninstantiated_class = /(?<![:\w])#{Regexp.escape(simple_name)}::#{Regexp.escape(name)}(?![:\w])/
135
+ result = result.gsub(uninstantiated_class, "typename #{qualified_parent}::#{name}")
136
+ end
137
+
138
+ unqualified = /(?<![:\w])#{Regexp.escape(name)}(?![:\w])/
139
+ result = result.gsub(unqualified, "typename #{qualified_parent}::#{name}")
140
+ end
141
+
142
+ result
143
+ end
144
+
145
+ # Qualify bare class members used as non-type template args.
146
+ # Within a class like GPCPatchDescriptor, a member can write
147
+ # Vec<double, nFeatures> but the generated binding code is outside the
148
+ # class, so it needs Vec<double, GPCPatchDescriptor::nFeatures>.
149
+ # qualify_template_args then handles qualifying GPCPatchDescriptor to
150
+ # cv::optflow::GPCPatchDescriptor.
151
+ #
152
+ # Unscoped enum constants need the same treatment:
153
+ # FixedBuffer<int, Size>
154
+ # becomes
155
+ # FixedBuffer<int, Tests::EnumSized<N>::Size>
156
+ #
157
+ # Class templates need their template parameters preserved:
158
+ # FixedBuffer<int, Size>
159
+ # becomes
160
+ # FixedBuffer<int, Tests::StaticSized<N>::Size>
161
+ def qualify_class_static_members(spelling, class_cursor)
162
+ return spelling unless class_cursor
163
+
164
+ parent_kind = class_cursor.kind
165
+ return spelling unless parent_kind == :cursor_class_decl ||
166
+ parent_kind == :cursor_struct ||
167
+ parent_kind == :cursor_class_template
168
+
169
+ cache_key = class_cursor.usr
170
+ member_info = @class_static_members[cache_key] ||= begin
171
+ names = []
172
+ class_cursor.each(false) do |child|
173
+ if child.kind == :cursor_variable
174
+ names << child.spelling
175
+ elsif child.kind == :cursor_enum_decl && !child.enum_scoped?
176
+ child.each(false) do |enum_child|
177
+ names << enum_child.spelling if enum_child.kind == :cursor_enum_constant_decl
178
+ end
179
+ end
180
+ end
181
+ qualified_parent = if parent_kind == :cursor_class_template
182
+ qualified_display_name(class_cursor)
183
+ else
184
+ class_cursor.qualified_name
185
+ end
186
+ { names: names, qualified_parent: qualified_parent }
187
+ end
188
+
189
+ return spelling if member_info[:names].empty?
190
+
191
+ result = spelling.dup
192
+ qualified_parent = member_info[:qualified_parent]
193
+ display_name = class_cursor.display_name
194
+ member_info[:names].each do |name|
195
+ if display_name && !display_name.empty? && display_name != qualified_parent
196
+ partially_qualified = /(?<![:\w])#{Regexp.escape(display_name)}::#{Regexp.escape(name)}(?![:\w])/
197
+ result = result.gsub(partially_qualified, "#{qualified_parent}::#{name}")
198
+ end
199
+
200
+ unqualified = /(?<![:\w])#{Regexp.escape(name)}(?![:\w])/
201
+ result = result.gsub(unqualified, "#{qualified_parent}::#{name}")
202
+ end
203
+
204
+ result
205
+ end
206
+
207
+ def preserve_template_parameter_names(spelling, template_cursor)
208
+ return spelling unless template_cursor&.kind == :cursor_class_template
209
+
210
+ result = spelling.dup
211
+ template_parameter_names(template_cursor).each do |name|
212
+ qualified_name = @type_index.qualified_name_for(name)
213
+ next if qualified_name.nil? || qualified_name == name
214
+
215
+ result = result.gsub(/(?<![:\w])#{Regexp.escape(qualified_name)}(?![:\w])/, name)
216
+ end
217
+ result
218
+ end
219
+
220
+ private
221
+
222
+ TEMPLATE_PARAMETER_KINDS = [:cursor_template_type_parameter,
223
+ :cursor_non_type_template_parameter,
224
+ :cursor_template_template_parameter].freeze
225
+
226
+ def type_spelling_pointer(type)
227
+ RubyBindgen::TypePointerFormatter.pointer_spelling(type) do |child_type|
228
+ type_spelling(child_type)
229
+ end
230
+ end
231
+
232
+ # Qualify template arguments in a type spelling
233
+ # e.g., DataType<hfloat> -> DataType<cv::hfloat>
234
+ # Collects qualified names from both original and canonical types
235
+ def qualify_template_args(spelling, type, ignored_names: [])
236
+ return spelling if spelling.nil? || !spelling.include?('<')
237
+
238
+ qualifications = {}
239
+ if type
240
+ collect_type_qualifications(type, qualifications)
241
+ collect_type_qualifications(type.canonical, qualifications)
242
+ end
243
+
244
+ spelling.scan(/(?<![:\w])([A-Z_a-z]\w*)(?!\w)/) do |match|
245
+ simple_name = match[0]
246
+ next if ignored_names.include?(simple_name)
247
+ next if qualifications.key?(simple_name)
248
+
249
+ qualified_name = @type_index.qualified_name_for(simple_name)
250
+ qualifications[simple_name] = qualified_name if qualified_name && simple_name != qualified_name
251
+ end
252
+
253
+ result = spelling.dup
254
+ qualifications.each do |simple_name, qualified_name|
255
+ next if simple_name == qualified_name
256
+
257
+ qualified_segments = qualified_name.split('::')
258
+ if qualified_segments.length > 2
259
+ (1...(qualified_segments.length - 1)).each do |index|
260
+ partial_name = qualified_segments[index..].join('::')
261
+ result = result.gsub(/(?<![:\w])#{Regexp.escape(partial_name)}(?![:\w])/, qualified_name)
262
+ end
263
+ end
264
+
265
+ result = result.gsub(/(?<![:\w])#{Regexp.escape(simple_name)}(?!\w)/, qualified_name)
266
+ end
267
+
268
+ result
269
+ end
270
+
271
+ def template_parameter_names(cursor)
272
+ return [] unless cursor
273
+
274
+ cursor.find_by_kind(false, *TEMPLATE_PARAMETER_KINDS)
275
+ .map(&:spelling)
276
+ .reject(&:empty?)
277
+ end
278
+
279
+ def template_parameter_arguments(cursor)
280
+ return [] unless cursor
281
+
282
+ cursor.find_by_kind(false, *TEMPLATE_PARAMETER_KINDS)
283
+ .filter_map do |template_parameter|
284
+ name = template_parameter.spelling
285
+ next if name.empty?
286
+
287
+ declaration = template_parameter.extent.text
288
+ declaration&.match?(/\.\.\.\s*#{Regexp.escape(name)}\b/) ? "#{name}..." : name
289
+ end
290
+ end
291
+
292
+ def qualify_template_parameter_packs(spelling, cursor)
293
+ return spelling unless cursor
294
+
295
+ template_cursor = if cursor.kind == :cursor_class_template
296
+ cursor
297
+ elsif cursor.semantic_parent&.kind == :cursor_class_template
298
+ cursor.semantic_parent
299
+ end
300
+ return spelling unless template_cursor
301
+
302
+ pack_names = template_parameter_arguments(template_cursor)
303
+ .select { |argument| argument.end_with?('...') }
304
+ .map { |argument| argument.delete_suffix('...') }
305
+ return spelling if pack_names.empty?
306
+
307
+ result = spelling.dup
308
+ pack_names.each do |name|
309
+ result = result.gsub(/(?<![:\w])#{Regexp.escape(name)}(?!\.\.\.|[:\w])/, "#{name}...")
310
+ end
311
+ result
312
+ end
313
+
314
+ # Recursively collect simple_name -> qualified_name mappings from a type's template arguments
315
+ def collect_type_qualifications(type, qualifications)
316
+ return if type.nil? || type.kind == :type_invalid
317
+ return unless type.num_template_arguments > 0
318
+
319
+ type.num_template_arguments.times do |i|
320
+ arg_type = type.template_argument_type(i)
321
+ next if arg_type.kind == :type_invalid
322
+
323
+ check_type = arg_type
324
+ check_type = check_type.pointee while check_type.kind == :type_pointer
325
+
326
+ decl = check_type.declaration
327
+ if decl.kind != :cursor_no_decl_found
328
+ simple_name = decl.spelling
329
+ if TEMPLATE_PARAMETER_KINDS.include?(decl.kind)
330
+ qualifications[simple_name] = simple_name unless simple_name.empty?
331
+ else
332
+ qualified_name = if decl.kind == :cursor_typedef_decl && decl.semantic_parent.kind == :cursor_class_template
333
+ "#{decl.semantic_parent.qualified_display_name}::#{simple_name}"
334
+ else
335
+ decl.qualified_name
336
+ end
337
+ if !simple_name.empty? && simple_name != qualified_name
338
+ qualifications[simple_name] = qualified_name
339
+ end
340
+ end
341
+ end
342
+
343
+ collect_type_qualifications(arg_type, qualifications)
344
+ end
345
+ end
346
+
347
+ # Qualify dependent types within template arguments
348
+ # e.g., "cv::Point_<typename DataType<_Tp>::channel_type>"
349
+ # -> "cv::Point_<typename cv::DataType<_Tp>::channel_type>"
350
+ # Also handles nested template args such as:
351
+ # "typename Outer_<Outer_<T>>::type"
352
+ # -> "typename Tests::Outer_<Tests::Outer_<T>>::type"
353
+ #
354
+ # The source range APIs are not available here because libclang only gives us
355
+ # a type spelling string at this point, so we do a small balanced parse of the
356
+ # dependent base name and then qualify the nested template args recursively.
357
+ def qualify_dependent_types_in_template_args(spelling)
358
+ return spelling unless spelling.include?('typename')
359
+
360
+ result = String.new
361
+ index = 0
362
+
363
+ while (match = /\btypename\b/.match(spelling, index))
364
+ result << spelling[index...match.end(0)]
365
+ index = match.end(0)
366
+
367
+ while index < spelling.length && spelling[index].match?(/\s/)
368
+ result << spelling[index]
369
+ index += 1
370
+ end
371
+
372
+ identifier_match = /\A([A-Za-z_][A-Za-z0-9_]*)/.match(spelling[index..])
373
+ unless identifier_match
374
+ result << spelling[index]
375
+ index += 1
376
+ next
377
+ end
378
+
379
+ class_name = identifier_match[1]
380
+ name_end = index + class_name.length
381
+ template_part, after_template = balanced_template_suffix(spelling, name_end)
382
+ unless spelling[after_template, 2] == '::'
383
+ result << spelling[index...after_template]
384
+ index = after_template
385
+ next
386
+ end
387
+
388
+ qualified_name = @type_index.qualified_name_for(class_name) || class_name
389
+ qualified_template_part = if template_part.empty?
390
+ ""
391
+ else
392
+ qualify_template_args(qualify_dependent_types_in_template_args(template_part), nil)
393
+ end
394
+
395
+ result << "#{qualified_name}#{qualified_template_part}::"
396
+ index = after_template + 2
397
+ end
398
+
399
+ result << spelling[index..] if index < spelling.length
400
+ result
401
+ end
402
+
403
+ def type_spelling_declared(type)
404
+ decl = type.declaration
405
+
406
+ case decl.kind
407
+ when :cursor_class_template
408
+ spelling = type.spelling
409
+ outer_name = spelling.sub(/\s*<.*/, '')
410
+ qualified = outer_name.match?(/\w+::/) ? spelling : spelling.sub(decl.spelling, decl.qualified_name)
411
+ qualify_template_args(qualify_dependent_types_in_template_args(qualified), type)
412
+
413
+ when :cursor_typedef_decl, :cursor_type_alias_decl
414
+ if decl.semantic_parent.kind == :cursor_class_template
415
+ const_prefix = type.const_qualified? ? "const " : ""
416
+ parent = decl.semantic_parent
417
+ display = qualified_display_name(parent)
418
+ qualified = parent.qualified_name
419
+ full_parent = if display.include?('<') && !display.start_with?(qualified)
420
+ "#{qualified}#{display[display.index('<')..]}"
421
+ else
422
+ display
423
+ end
424
+ "#{const_prefix}typename #{full_parent}::#{decl.spelling}"
425
+ else
426
+ type.fully_qualified_name(@printing_policy)
427
+ end
428
+
429
+ when :cursor_no_decl_found
430
+ spelling = type.spelling
431
+ if spelling.include?('::')
432
+ # Preserve already-qualified public aliases such as std::exception_ptr
433
+ # instead of emitting canonical implementation-detail spellings.
434
+ qualify_template_args(qualify_dependent_types_in_template_args(spelling), type)
435
+ else
436
+ qualify_template_args(type.fully_qualified_name(@printing_policy), type)
437
+ end
438
+
439
+ else
440
+ qualify_template_args(type.fully_qualified_name(@printing_policy), type)
441
+ end
442
+ end
443
+
444
+ def type_spelling_unexposed(type)
445
+ decl = type.declaration
446
+ spelling = type.spelling
447
+
448
+ if decl.kind == :cursor_class_template && template_arguments_need_type_spelling?(type)
449
+ arg_spellings = (0...type.num_template_arguments).map do |index|
450
+ type_spelling(type.template_argument_type(index))
451
+ end
452
+ const_prefix = type.const_qualified? ? "const " : ""
453
+ return "#{const_prefix}#{decl.qualified_name}<#{arg_spellings.join(', ')}>"
454
+ end
455
+
456
+ if decl.kind == :cursor_no_decl_found && spelling.include?('::')
457
+ qualify_template_args(qualify_dependent_types_in_template_args(spelling), type)
458
+ else
459
+ qualify_template_args(qualify_dependent_types_in_template_args(type.fully_qualified_name(@printing_policy)), type)
460
+ end
461
+ end
462
+
463
+ def template_arguments_need_type_spelling?(type)
464
+ return false unless type.num_template_arguments > 0
465
+
466
+ type.num_template_arguments.times.any? do |index|
467
+ template_argument_needs_type_spelling?(type.template_argument_type(index))
468
+ end
469
+ end
470
+
471
+ def template_argument_needs_type_spelling?(type)
472
+ return false if type.nil? || type.kind == :type_invalid
473
+
474
+ case type.kind
475
+ when :type_typedef, :type_elaborated
476
+ # LLVM 21 can still surface dependent alias/tag args as :type_elaborated.
477
+ # LLVM 22 usually surfaces the same cases as bare :type_typedef.
478
+ true
479
+ when :type_pointer
480
+ template_argument_needs_type_spelling?(type.pointee)
481
+ when :type_lvalue_ref, :type_rvalue_ref
482
+ template_argument_needs_type_spelling?(type.non_reference_type)
483
+ else
484
+ template_arguments_need_type_spelling?(type)
485
+ end
486
+ end
487
+
488
+ def balanced_template_suffix(text, start_index)
489
+ return ["", start_index] unless text[start_index] == '<'
490
+
491
+ depth = 0
492
+ index = start_index
493
+ while index < text.length
494
+ case text[index]
495
+ when '<'
496
+ depth += 1
497
+ when '>'
498
+ depth -= 1
499
+ return [text[start_index..index], index + 1] if depth == 0
500
+ end
501
+ index += 1
502
+ end
503
+
504
+ ["", start_index]
505
+ end
506
+ end
507
+ end
508
+ end
509
+ end
@@ -0,0 +1,5 @@
1
+ <%- if under -%>
2
+ Rice::Data_Type<<%= cpp_type %>> <%= cursor.cruby_name %> = define_class_under<<%= cpp_type %>>(<%= under.cruby_name %>, "<%= ruby_name %>")<%= children %>
3
+ <%- else -%>
4
+ Rice::Data_Type<<%= cpp_type %>> <%= cursor.cruby_name %> = define_class<<%= cpp_type %>>("<%= ruby_name %>")<%= children %>
5
+ <%- end -%>
@@ -0,0 +1,5 @@
1
+ <%- if cursor.semantic_parent.kind == :cursor_translation_unit || cursor.semantic_parent.kind == :cursor_namespace -%>
2
+ <%= cursor.semantic_parent.cruby_name %>.define_singleton_attr("<%= cursor.ruby_name %>", &<%= cursor.qualified_name %>);
3
+ <%- else -%>
4
+ define_singleton_attr("<%= cursor.ruby_name %>", &<%= qualified_parent %>::<%= cursor.spelling %>)
5
+ <%- end -%>
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'find'
4
+ require 'pathname'
5
+
6
+ module RubyBindgen
7
+ class Inputter
8
+ include Enumerable
9
+ attr_reader :base_path, :globs, :exclude_glob
10
+
11
+ def initialize(base_path, globs=nil, exclude_glob=[])
12
+ @base_path = base_path
13
+ @globs = Array(globs).empty? ? ["**/*.{h,hpp}"] : Array(globs)
14
+ @exclude_glob = exclude_glob
15
+
16
+ if RUBY_PLATFORM.match?(/mswin/) || RUBY_PLATFORM.match?(/mingw/)
17
+ @base_path = @base_path.gsub('\\', '/')
18
+ @globs = @globs.map { |g| g.gsub('\\', '/') }
19
+ @exclude_glob = @exclude_glob.map { |g| g.gsub('\\', '/') }
20
+ end
21
+ end
22
+
23
+ def each
24
+ raise(ArgumentError, "No block given") unless block_given?
25
+
26
+ yielded = 0
27
+ seen = Set.new
28
+ self.globs.each do |glob|
29
+ search = File.join(self.base_path, glob)
30
+ Dir.glob(search).each do |path|
31
+ next if exclude.include?(path)
32
+ next unless seen.add?(path)
33
+
34
+ yielded += 1
35
+ relative_path = Pathname.new(path).relative_path_from(self.base_path)
36
+ yield path, relative_path.to_path
37
+ end
38
+ end
39
+
40
+ # Silent empty output is one of the most common config mistakes. Warn
41
+ # so a typo'd match: glob doesn't look like a successful run.
42
+ if yielded.zero?
43
+ warn "ruby-bindgen: no input files matched #{self.globs.inspect} under #{self.base_path}"
44
+ end
45
+ end
46
+
47
+ def exclude
48
+ @exclude ||= self.exclude_glob.map do |exclude|
49
+ search = File.join(self.base_path, exclude)
50
+ Dir.glob(search)
51
+ end.flatten.uniq
52
+ end
53
+ end
54
+ end