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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +25 -0
- data/README.md +68 -0
- data/Rakefile +8 -0
- data/bin/ruby-bindgen +133 -0
- data/docs/architecture.md +238 -0
- data/docs/c/c_bindings.md +65 -0
- data/docs/c/constants.md +21 -0
- data/docs/c/customizing.md +19 -0
- data/docs/c/filtering.md +24 -0
- data/docs/c/getting_started.md +56 -0
- data/docs/c/library_loading.md +53 -0
- data/docs/c/output.md +96 -0
- data/docs/c/types.md +61 -0
- data/docs/c/version_guards.md +105 -0
- data/docs/cmake/cmake_bindings.md +19 -0
- data/docs/cmake/filtering.md +26 -0
- data/docs/cmake/getting_started.md +52 -0
- data/docs/cmake/output.md +110 -0
- data/docs/configuration.md +351 -0
- data/docs/contributing.md +68 -0
- data/docs/cpp/buffers.md +24 -0
- data/docs/cpp/classes.md +139 -0
- data/docs/cpp/cpp_bindings.md +29 -0
- data/docs/cpp/customizing.md +124 -0
- data/docs/cpp/enums.md +42 -0
- data/docs/cpp/filtering.md +35 -0
- data/docs/cpp/getting_started.md +80 -0
- data/docs/cpp/iterators.md +94 -0
- data/docs/cpp/operators.md +170 -0
- data/docs/cpp/output.md +125 -0
- data/docs/cpp/templates.md +114 -0
- data/docs/examples.md +133 -0
- data/docs/index.md +133 -0
- data/docs/prior_art.md +37 -0
- data/docs/troubleshooting.md +243 -0
- data/docs/type_spelling.md +200 -0
- data/docs/updating_bindings.md +55 -0
- data/docs/version_guards.md +69 -0
- data/lib/ruby-bindgen/config.rb +63 -0
- data/lib/ruby-bindgen/generators/cmake/cmake.rb +171 -0
- data/lib/ruby-bindgen/generators/cmake/directory.erb +29 -0
- data/lib/ruby-bindgen/generators/cmake/guard.rb +55 -0
- data/lib/ruby-bindgen/generators/cmake/presets.erb +232 -0
- data/lib/ruby-bindgen/generators/cmake/project.erb +89 -0
- data/lib/ruby-bindgen/generators/ffi/callback.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/constant.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/enum_constant_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/enum_decl.erb +4 -0
- data/lib/ruby-bindgen/generators/ffi/enum_decl_anonymous.erb +4 -0
- data/lib/ruby-bindgen/generators/ffi/ffi.rb +687 -0
- data/lib/ruby-bindgen/generators/ffi/field_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/function.erb +5 -0
- data/lib/ruby-bindgen/generators/ffi/library.erb +39 -0
- data/lib/ruby-bindgen/generators/ffi/project.erb +18 -0
- data/lib/ruby-bindgen/generators/ffi/struct.erb +6 -0
- data/lib/ruby-bindgen/generators/ffi/translation_unit.erb +9 -0
- data/lib/ruby-bindgen/generators/ffi/typedef_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/union.erb +6 -0
- data/lib/ruby-bindgen/generators/ffi/variable.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/version.erb +9 -0
- data/lib/ruby-bindgen/generators/ffi/version_method.erb +5 -0
- data/lib/ruby-bindgen/generators/generator.rb +52 -0
- data/lib/ruby-bindgen/generators/rice/auto_generated_base_class.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/class.erb +31 -0
- data/lib/ruby-bindgen/generators/rice/class_template.erb +9 -0
- data/lib/ruby-bindgen/generators/rice/class_template_specialization.erb +10 -0
- data/lib/ruby-bindgen/generators/rice/constant.erb +9 -0
- data/lib/ruby-bindgen/generators/rice/constructor.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/conversion_function.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/cxx_iterator_method.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/cxx_method.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/enum_constant_decl.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/enum_decl.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/field_decl.erb +8 -0
- data/lib/ruby-bindgen/generators/rice/function.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/function_pointer.rb +68 -0
- data/lib/ruby-bindgen/generators/rice/incomplete_class.erb +3 -0
- data/lib/ruby-bindgen/generators/rice/iterator_alias.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/iterator_collector.rb +159 -0
- data/lib/ruby-bindgen/generators/rice/namespace.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_binary.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_inspect.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_unary.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/operator[].erb +4 -0
- data/lib/ruby-bindgen/generators/rice/project.cpp.erb +18 -0
- data/lib/ruby-bindgen/generators/rice/project.hpp.erb +13 -0
- data/lib/ruby-bindgen/generators/rice/reference_qualifier.rb +495 -0
- data/lib/ruby-bindgen/generators/rice/rice.rb +1724 -0
- data/lib/ruby-bindgen/generators/rice/rice_include.hpp.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/signature_builder.rb +230 -0
- data/lib/ruby-bindgen/generators/rice/template_resolver.rb +585 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.cpp.erb +40 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.hpp.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.ipp.erb +3 -0
- data/lib/ruby-bindgen/generators/rice/type_index.rb +117 -0
- data/lib/ruby-bindgen/generators/rice/type_speller.rb +509 -0
- data/lib/ruby-bindgen/generators/rice/union.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/variable.erb +5 -0
- data/lib/ruby-bindgen/inputter.rb +54 -0
- data/lib/ruby-bindgen/name_mapper.rb +65 -0
- data/lib/ruby-bindgen/namer.rb +138 -0
- data/lib/ruby-bindgen/outputter.rb +40 -0
- data/lib/ruby-bindgen/parser.rb +82 -0
- data/lib/ruby-bindgen/refinements/cursor.rb +57 -0
- data/lib/ruby-bindgen/refinements/string.rb +41 -0
- data/lib/ruby-bindgen/symbol_candidates.rb +282 -0
- data/lib/ruby-bindgen/symbol_entry.rb +21 -0
- data/lib/ruby-bindgen/symbols.rb +107 -0
- data/lib/ruby-bindgen/type_pointer_formatter.rb +35 -0
- data/lib/ruby-bindgen/version.rb +3 -0
- data/lib/ruby-bindgen.rb +19 -0
- data/ruby-bindgen.gemspec +52 -0
- metadata +260 -0
|
@@ -0,0 +1,1724 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
# Forward declaration so the helper files below can nest their classes
|
|
4
|
+
# under Rice (Ruby would otherwise see `class Rice` with no superclass
|
|
5
|
+
# in the helpers and TypeError when this file later says `< Generator`).
|
|
6
|
+
module RubyBindgen
|
|
7
|
+
module Generators
|
|
8
|
+
class Rice < Generator
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require_relative 'function_pointer'
|
|
14
|
+
require_relative 'iterator_collector'
|
|
15
|
+
require_relative 'reference_qualifier'
|
|
16
|
+
require_relative 'signature_builder'
|
|
17
|
+
require_relative 'template_resolver'
|
|
18
|
+
require_relative 'type_index'
|
|
19
|
+
require_relative 'type_speller'
|
|
20
|
+
|
|
21
|
+
module RubyBindgen
|
|
22
|
+
module Generators
|
|
23
|
+
class Rice < Generator
|
|
24
|
+
CURSOR_LITERALS = [:cursor_integer_literal, :cursor_floating_literal,
|
|
25
|
+
:cursor_imaginary_literal, :cursor_string_literal,
|
|
26
|
+
:cursor_character_literal, :cursor_cxx_bool_literal_expr,
|
|
27
|
+
:cursor_cxx_null_ptr_literal_expr, :cursor_fixed_point_literal,
|
|
28
|
+
:cursor_unary_operator]
|
|
29
|
+
|
|
30
|
+
CURSOR_CLASSES = [:cursor_class_decl, :cursor_class_template, :cursor_struct]
|
|
31
|
+
|
|
32
|
+
# Fundamental types that should use ArgBuffer/ReturnBuffer when passed/returned as pointers
|
|
33
|
+
# Mapping of C++ operators to Ruby method names per Rice documentation
|
|
34
|
+
# Keys use cursor spelling form (e.g., 'operator()') so they share the same
|
|
35
|
+
# key namespace as user rename_methods.
|
|
36
|
+
# Values can be:
|
|
37
|
+
# - String: direct mapping
|
|
38
|
+
# - Proc: called with cursor to determine mapping (for arity-dependent operators)
|
|
39
|
+
OPERATOR_MAPPINGS = RubyBindgen::NameMapper.new([
|
|
40
|
+
# Assignment operators - not overridable in Ruby
|
|
41
|
+
['operator=', 'assign'],
|
|
42
|
+
['operator+=', 'assign_plus'],
|
|
43
|
+
['operator-=', 'assign_minus'],
|
|
44
|
+
['operator*=', 'assign_multiply'],
|
|
45
|
+
['operator/=', 'assign_divide'],
|
|
46
|
+
['operator%=', 'assign_modulus'],
|
|
47
|
+
|
|
48
|
+
# Bitwise assignment operators - not overridable in Ruby
|
|
49
|
+
['operator&=', 'assign_and'],
|
|
50
|
+
['operator|=', 'assign_or'],
|
|
51
|
+
['operator^=', 'assign_xor'],
|
|
52
|
+
['operator<<=', 'assign_left_shift'],
|
|
53
|
+
['operator>>=', 'assign_right_shift'],
|
|
54
|
+
|
|
55
|
+
# Logical operators - && and || not overridable in Ruby
|
|
56
|
+
['operator&&', 'logical_and'],
|
|
57
|
+
['operator||', 'logical_or'],
|
|
58
|
+
|
|
59
|
+
# Function call operator
|
|
60
|
+
['operator()', 'call'],
|
|
61
|
+
|
|
62
|
+
# Member access through pointer (arrow operator)
|
|
63
|
+
['operator->', 'arrow'],
|
|
64
|
+
|
|
65
|
+
# Increment/decrement - arity-dependent (prefix=0 args, postfix=1 arg)
|
|
66
|
+
['operator++', ->(cursor) { cursor.type.args_size == 0 ? 'increment' : 'increment_post' }],
|
|
67
|
+
['operator--', ->(cursor) { cursor.type.args_size == 0 ? 'decrement' : 'decrement_post' }],
|
|
68
|
+
|
|
69
|
+
# Dereference vs multiply - arity-dependent (unary=0 args, binary=1 arg)
|
|
70
|
+
['operator*', ->(cursor) { cursor.type.args_size == 0 ? 'dereference' : '*' }],
|
|
71
|
+
|
|
72
|
+
# Unary plus/minus vs binary - arity-dependent
|
|
73
|
+
# Ruby uses +@ and -@ for unary operators, + and - for binary
|
|
74
|
+
# Member: unary=0 args, binary=1 arg
|
|
75
|
+
# Non-member: unary=1 arg, binary=2 args (but we check member arity here)
|
|
76
|
+
['operator+', ->(cursor) { cursor.type.args_size == 0 ? '+@' : '+' }],
|
|
77
|
+
['operator-', ->(cursor) { cursor.type.args_size == 0 ? '-@' : '-' }],
|
|
78
|
+
|
|
79
|
+
# Pass-through operators - Ruby supports these directly
|
|
80
|
+
['operator/', '/'],
|
|
81
|
+
['operator%', '%'],
|
|
82
|
+
['operator&', '&'],
|
|
83
|
+
['operator|', '|'],
|
|
84
|
+
['operator^', '^'],
|
|
85
|
+
['operator~', '~'],
|
|
86
|
+
['operator<<', '<<'],
|
|
87
|
+
['operator>>', '>>'],
|
|
88
|
+
['operator==', '=='],
|
|
89
|
+
['operator!=', '!='],
|
|
90
|
+
['operator<', '<'],
|
|
91
|
+
['operator>', '>'],
|
|
92
|
+
['operator<=', '<='],
|
|
93
|
+
['operator>=', '>='],
|
|
94
|
+
['operator!', '!'],
|
|
95
|
+
['operator[]', '[]'],
|
|
96
|
+
]).freeze
|
|
97
|
+
|
|
98
|
+
# Mapping of C++ type names to Ruby conversion method suffixes
|
|
99
|
+
CONVERSION_TYPE_MAPPINGS = RubyBindgen::NameMapper.new([
|
|
100
|
+
# Standard integer types
|
|
101
|
+
['int', 'i'],
|
|
102
|
+
['long', 'l'],
|
|
103
|
+
['long long', 'i64'],
|
|
104
|
+
['short', 'i16'],
|
|
105
|
+
['unsigned int', 'u'],
|
|
106
|
+
['unsigned long', 'ul'],
|
|
107
|
+
['unsigned long long', 'u64'],
|
|
108
|
+
['unsigned short', 'u16'],
|
|
109
|
+
# Fixed-width integer types
|
|
110
|
+
['int8_t', 'i8'],
|
|
111
|
+
['int16_t', 'i16'],
|
|
112
|
+
['int32_t', 'i32'],
|
|
113
|
+
['int64_t', 'i64'],
|
|
114
|
+
['uint8_t', 'u8'],
|
|
115
|
+
['uint16_t', 'u16'],
|
|
116
|
+
['uint32_t', 'u32'],
|
|
117
|
+
['uint64_t', 'u64'],
|
|
118
|
+
# Size type (platform-independent)
|
|
119
|
+
['size_t', 'size'],
|
|
120
|
+
# Floating point types
|
|
121
|
+
['float', 'f32'],
|
|
122
|
+
['double', 'f'],
|
|
123
|
+
['long double', 'ld'],
|
|
124
|
+
# Other types
|
|
125
|
+
['bool', 'bool'],
|
|
126
|
+
['std::string', 's'],
|
|
127
|
+
['std::__cxx11::basic_string<char>', 's'],
|
|
128
|
+
['std::basic_string<char>', 's'],
|
|
129
|
+
['basic_string<char>', 's'],
|
|
130
|
+
['char *', 's'],
|
|
131
|
+
['const char *', 's'],
|
|
132
|
+
]).freeze
|
|
133
|
+
|
|
134
|
+
# std:: types that Rice converts to native Ruby types (no Rice wrapper exists).
|
|
135
|
+
# string→String, string_view→String, complex→Complex, monostate→NilClass, tuple→Array.
|
|
136
|
+
# Checked by declaration spelling (not qualified_name) to avoid inline namespace issues
|
|
137
|
+
# (e.g., std::__cxx11::basic_string on libstdc++).
|
|
138
|
+
RICE_NATIVE_TYPES = Set.new(%w[basic_string basic_string_view complex monostate tuple]).freeze
|
|
139
|
+
|
|
140
|
+
FUNDAMENTAL_TYPES = [
|
|
141
|
+
:type_void, :type_bool,
|
|
142
|
+
:type_char_u, :type_uchar, :type_char16, :type_char32, :type_char_s,
|
|
143
|
+
:type_schar, :type_wchar,
|
|
144
|
+
:type_short, :type_ushort,
|
|
145
|
+
:type_int, :type_uint,
|
|
146
|
+
:type_long, :type_ulong,
|
|
147
|
+
:type_longlong, :type_ulonglong,
|
|
148
|
+
:type_int128, :type_uint128,
|
|
149
|
+
:type_float, :type_double, :type_longdouble,
|
|
150
|
+
:type_float128, :type_float16,
|
|
151
|
+
:type_nullptr
|
|
152
|
+
].freeze
|
|
153
|
+
|
|
154
|
+
# Directory containing the ERB templates used by the Rice generator.
|
|
155
|
+
def self.template_dir
|
|
156
|
+
__dir__
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Create the main generator plus the extracted helpers that own naming,
|
|
160
|
+
# qualification, template resolution, and signature construction.
|
|
161
|
+
def initialize(inputter, outputter, config)
|
|
162
|
+
super(inputter, outputter, config)
|
|
163
|
+
@include_header = config[:include]
|
|
164
|
+
@init_names = Hash.new
|
|
165
|
+
@namespaces = Set.new
|
|
166
|
+
@classes = Hash.new # Maps cruby_name -> C++ type for Data_Type<T> declarations
|
|
167
|
+
@reference_qualifier = ReferenceQualifier.new
|
|
168
|
+
@type_index = TypeIndex.new
|
|
169
|
+
@type_speller = TypeSpeller.new(type_index: @type_index)
|
|
170
|
+
@auto_generated_bases = Set.new
|
|
171
|
+
@symbols = RubyBindgen::Symbols.new(config[:symbols] || {})
|
|
172
|
+
@export_macros = config[:export_macros] || []
|
|
173
|
+
@version_check = config[:version_check]
|
|
174
|
+
raise ArgumentError, "version_check is required when symbols.versions is non-empty" if @symbols.has_versions? && !@version_check
|
|
175
|
+
|
|
176
|
+
# Build naming tables: merge operator defaults with user config
|
|
177
|
+
symbols_config = config[:symbols] || {}
|
|
178
|
+
rename_types = RubyBindgen::NameMapper.from_config(symbols_config[:rename_types] || [])
|
|
179
|
+
user_rename_methods = RubyBindgen::NameMapper.from_config(symbols_config[:rename_methods] || [])
|
|
180
|
+
rename_methods = OPERATOR_MAPPINGS.merge(user_rename_methods)
|
|
181
|
+
@namer = RubyBindgen::Namer.new(rename_types, rename_methods, CONVERSION_TYPE_MAPPINGS)
|
|
182
|
+
@template_resolver = TemplateResolver.new(reference_qualifier: @reference_qualifier,
|
|
183
|
+
type_speller: @type_speller,
|
|
184
|
+
namer: @namer)
|
|
185
|
+
@signature_builder = SignatureBuilder.new(type_speller: @type_speller,
|
|
186
|
+
reference_qualifier: @reference_qualifier,
|
|
187
|
+
cursor_literals: CURSOR_LITERALS,
|
|
188
|
+
fundamental_types: FUNDAMENTAL_TYPES)
|
|
189
|
+
# Non-member operators grouped by target class cruby_name
|
|
190
|
+
@non_member_operators = Hash.new { |h, k| h[k] = [] }
|
|
191
|
+
@iterator_collector = IteratorCollector.new
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Parse the configured inputs with libclang and stream the resulting
|
|
195
|
+
# translation units back through this visitor.
|
|
196
|
+
def generate
|
|
197
|
+
clang_args = @config[:clang_args] || []
|
|
198
|
+
parser = RubyBindgen::Parser.new(@inputter, clang_args, libclang: @config[:libclang])
|
|
199
|
+
::FFI::Clang::Cursor.namer = @namer
|
|
200
|
+
parser.generate(self)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Check if a type references a skipped symbol by examining its declaration.
|
|
204
|
+
# Unwraps pointers/references and checks template arguments recursively.
|
|
205
|
+
def type_references_skipped_symbol?(type)
|
|
206
|
+
type = type.intrinsic_type
|
|
207
|
+
|
|
208
|
+
# Check the type's own declaration (try both non-canonical and canonical
|
|
209
|
+
# since dependent types like SkippedClass<T> may not resolve canonically)
|
|
210
|
+
[type, type.canonical].each do |t|
|
|
211
|
+
decl = t.declaration
|
|
212
|
+
next if decl.kind == :cursor_no_decl_found
|
|
213
|
+
return true if @symbols.skip?(decl)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# For dependent/unexposed types where no declaration is found (e.g., SkippedClass<T>
|
|
217
|
+
# inside a class template), fall back to checking the type spelling
|
|
218
|
+
if type.declaration.kind == :cursor_no_decl_found && type.canonical.declaration.kind == :cursor_no_decl_found
|
|
219
|
+
return true if @symbols.skip_spelling?(type.spelling)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Check template arguments recursively
|
|
223
|
+
if type.num_template_arguments > 0
|
|
224
|
+
type.num_template_arguments.times do |i|
|
|
225
|
+
arg_type = type.template_argument_type(i)
|
|
226
|
+
next if arg_type.kind == :type_invalid
|
|
227
|
+
return true if type_references_skipped_symbol?(arg_type)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
false
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Check if any parameter type of a callable references a skipped symbol.
|
|
235
|
+
def has_skipped_param_type?(cursor)
|
|
236
|
+
(0...cursor.type.args_size).any? do |i|
|
|
237
|
+
type_references_skipped_symbol?(cursor.type.arg_type(i))
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Skip rvalue-reference parameters only when the underlying type is
|
|
242
|
+
# genuinely move-only. Copyable types such as std::vector<T> or T&&
|
|
243
|
+
# assignment operators compile and should remain available.
|
|
244
|
+
def has_unsupported_rice_param_type?(cursor)
|
|
245
|
+
(0...cursor.type.args_size).any? do |i|
|
|
246
|
+
type = cursor.type.arg_type(i)
|
|
247
|
+
unsupported_rice_rvalue_ref_type?(type)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Check if the return type of a callable references a skipped symbol.
|
|
252
|
+
def has_skipped_return_type?(cursor)
|
|
253
|
+
type_references_skipped_symbol?(cursor.type.result_type)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Check if a cursor should be skipped based on symbols config.
|
|
257
|
+
# Adds Rice-specific template-argument matching on top of basic lookup.
|
|
258
|
+
def skip_symbol?(cursor)
|
|
259
|
+
return true if @symbols.skip?(cursor)
|
|
260
|
+
|
|
261
|
+
# Check if any template argument type references a skipped symbol
|
|
262
|
+
type_references_skipped_symbol?(cursor.type)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Rice's std::function adapter is not reliable once callback signatures
|
|
266
|
+
# involve references or nested callbacks which themselves do. Skip those
|
|
267
|
+
# attrs instead of emitting uncompilable wrappers.
|
|
268
|
+
def unsupported_rice_attribute_type?(type)
|
|
269
|
+
type.reference?
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Preserve rvalue-reference bindings for copyable types. The only cases we
|
|
273
|
+
# still need to suppress here are move-only sinks such as unique_ptr&&
|
|
274
|
+
# where Rice has no usable from_ruby conversion.
|
|
275
|
+
def unsupported_rice_rvalue_ref_type?(type)
|
|
276
|
+
return false unless type.kind == :type_rvalue_ref
|
|
277
|
+
|
|
278
|
+
pointee = type.non_reference_type
|
|
279
|
+
move_only_std_type?(pointee) || !pointee.copyable?
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def move_only_std_type?(type)
|
|
283
|
+
canonical_spelling = type.canonical.spelling
|
|
284
|
+
canonical_spelling.start_with?("std::unique_ptr<")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def unsupported_rice_vector_element_type?(type)
|
|
288
|
+
type = type.non_reference_type if type.reference?
|
|
289
|
+
canonical = type.canonical
|
|
290
|
+
decl = canonical.declaration
|
|
291
|
+
return false if decl.kind == :cursor_no_decl_found
|
|
292
|
+
return false unless vector_like_type?(decl)
|
|
293
|
+
|
|
294
|
+
element_type = canonical.template_argument_type(0)
|
|
295
|
+
return false if element_type.nil? || element_type.kind == :type_invalid
|
|
296
|
+
|
|
297
|
+
!rice_equality_supported?(element_type)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def vector_like_type?(decl)
|
|
301
|
+
decl.spelling == "vector" || decl.qualified_name == "std::vector"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def variant_like_type?(decl)
|
|
305
|
+
decl.spelling == "variant" || decl.qualified_name == "std::variant"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def rice_equality_supported?(type)
|
|
309
|
+
type = type.non_reference_type if type.reference?
|
|
310
|
+
type = type.canonical
|
|
311
|
+
|
|
312
|
+
return true if FUNDAMENTAL_TYPES.include?(type.kind) || type.kind == :type_enum
|
|
313
|
+
return true if [:type_pointer, :type_member_pointer].include?(type.kind)
|
|
314
|
+
|
|
315
|
+
decl = type.declaration
|
|
316
|
+
return true if decl.kind == :cursor_no_decl_found
|
|
317
|
+
return true if comparable_std_type?(decl)
|
|
318
|
+
|
|
319
|
+
if variant_like_type?(decl)
|
|
320
|
+
return (0...type.num_template_arguments).all? do |i|
|
|
321
|
+
arg_type = type.template_argument_type(i)
|
|
322
|
+
next true if arg_type.kind == :type_invalid
|
|
323
|
+
|
|
324
|
+
rice_equality_supported?(arg_type)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
has_equality_operator?(decl)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def comparable_std_type?(decl)
|
|
332
|
+
["basic_string", "string", "monostate"].include?(decl.spelling) ||
|
|
333
|
+
["std::string", "std::monostate"].include?(decl.qualified_name)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def has_equality_operator?(decl)
|
|
337
|
+
return true if decl.find_by_kind(false, :cursor_cxx_method).any? do |method|
|
|
338
|
+
method.spelling == "operator==" && method.type.args_size == 1
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
@translation_unit_cursor.find_by_kind(true, :cursor_function, :cursor_function_template).any? do |function|
|
|
342
|
+
next false unless function.spelling == "operator=="
|
|
343
|
+
next false unless function.type.args_size == 2
|
|
344
|
+
|
|
345
|
+
arg_declarations = 2.times.map do |index|
|
|
346
|
+
function.type.arg_type(index).intrinsic_type.canonical.declaration
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
arg_declarations.all? do |arg_decl|
|
|
350
|
+
arg_decl.kind != :cursor_no_decl_found &&
|
|
351
|
+
(arg_decl == decl || arg_decl.qualified_name == decl.qualified_name)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def unsupported_rice_callback_type?(type)
|
|
357
|
+
type = type.non_reference_type if type.reference?
|
|
358
|
+
canonical = type.canonical
|
|
359
|
+
decl = canonical.declaration
|
|
360
|
+
return false if decl.kind == :cursor_no_decl_found
|
|
361
|
+
return false unless decl.qualified_name == "std::function"
|
|
362
|
+
|
|
363
|
+
callback_signature_unsupported?(canonical.template_argument_type(0))
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def callback_signature_unsupported?(type)
|
|
367
|
+
return false if type.nil? || type.kind == :type_invalid
|
|
368
|
+
return false unless [:type_function_proto, :type_function_no_proto].include?(type.kind)
|
|
369
|
+
|
|
370
|
+
return true if type.result_type.reference?
|
|
371
|
+
|
|
372
|
+
type.arg_types.any? do |arg_type|
|
|
373
|
+
arg_type.reference? || unsupported_rice_callback_type?(arg_type)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def implicit_default_constructor_available?(cursor)
|
|
378
|
+
cursor.find_by_kind(false, :cursor_field_decl).none? do |field|
|
|
379
|
+
field.type.reference?
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Reset any per-run caches before parsing begins.
|
|
384
|
+
def visit_start
|
|
385
|
+
# Clear caches from previous runs
|
|
386
|
+
@type_speller.clear
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def visit_parse_error(_path, relative_path, error)
|
|
390
|
+
warn "Warning: skipping #{relative_path} because it could not be parsed"
|
|
391
|
+
warn error.message
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Emit the shared include header and optional project wrapper once all
|
|
395
|
+
# translation units have been processed.
|
|
396
|
+
def visit_end
|
|
397
|
+
create_rice_include_header
|
|
398
|
+
create_project_files
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Returns the path to the Rice include header (user-specified or auto-generated)
|
|
402
|
+
def rice_include_header
|
|
403
|
+
@include_header || "#{@project || 'rice'}_include.hpp"
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Compute the .ipp path for a template defined in a different file.
|
|
407
|
+
def ipp_path_for_cursor(cursor)
|
|
408
|
+
template_file = cursor.file_location.file
|
|
409
|
+
relative = Pathname.new(template_file).relative_path_from(Pathname.new(@inputter.base_path)).to_s
|
|
410
|
+
File.join(File.dirname(relative), "#{File.basename(relative, '.*')}-rb.ipp")
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Generate default Rice include header if user didn't specify one.
|
|
414
|
+
# If the file already exists on disk, preserve it so user customizations are not lost.
|
|
415
|
+
def create_rice_include_header
|
|
416
|
+
return if @include_header # User specified their own header
|
|
417
|
+
|
|
418
|
+
header_path = rice_include_header
|
|
419
|
+
output_path = self.outputter.output_path(header_path)
|
|
420
|
+
if File.exist?(output_path)
|
|
421
|
+
STDOUT << " Preserving: " << header_path << "\n"
|
|
422
|
+
return
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
STDOUT << " Writing: " << header_path << "\n"
|
|
426
|
+
content = render_template("rice_include.hpp")
|
|
427
|
+
self.outputter.write(header_path, content)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Render one translation unit into its generated `-rb.hpp`, `-rb.cpp`, and
|
|
431
|
+
# optional `-rb.ipp` outputs.
|
|
432
|
+
def visit_translation_unit(translation_unit, path, relative_path)
|
|
433
|
+
@namespaces.clear
|
|
434
|
+
@classes.clear
|
|
435
|
+
@auto_generated_bases.clear
|
|
436
|
+
@non_member_operators.clear
|
|
437
|
+
@iterator_collector.clear
|
|
438
|
+
cursor = translation_unit.cursor
|
|
439
|
+
@translation_unit_cursor = cursor
|
|
440
|
+
@type_speller.printing_policy = cursor.printing_policy
|
|
441
|
+
|
|
442
|
+
# Build lookups for typedef resolution and simple-name qualification.
|
|
443
|
+
@type_index.build!(cursor)
|
|
444
|
+
|
|
445
|
+
# Figure out relative paths for generated header and cpp file
|
|
446
|
+
@basename = "#{File.basename(relative_path, ".*")}-rb"
|
|
447
|
+
@relative_dir = File.dirname(relative_path)
|
|
448
|
+
rice_header = File.join(@relative_dir, "#{@basename}.hpp")
|
|
449
|
+
rice_cpp = File.join(@relative_dir, "#{@basename}.cpp")
|
|
450
|
+
|
|
451
|
+
# Track init names - use relative path to avoid conflicts (e.g., core/version vs dnn/version)
|
|
452
|
+
path_parts = Pathname.new(relative_path).each_filename.to_a
|
|
453
|
+
path_parts.shift if path_parts.length >= 2 # Remove top-level directory (e.g., opencv2)
|
|
454
|
+
filename = Pathname.new(path_parts.pop).sub_ext('').to_s.camelize
|
|
455
|
+
dir_part = path_parts.map(&:camelize).join('_')
|
|
456
|
+
init_name = dir_part.empty? ? "Init_#{filename}" : "Init_#{dir_part}_#{filename}"
|
|
457
|
+
@init_names[rice_header] = init_name
|
|
458
|
+
|
|
459
|
+
@includes = Set.new
|
|
460
|
+
@includes << "#include <#{relative_path}>"
|
|
461
|
+
@includes << "#include \"#{@basename}.hpp\""
|
|
462
|
+
|
|
463
|
+
class_templates, has_builders = render_class_templates(cursor)
|
|
464
|
+
content = render_children(cursor, :indentation => 2)
|
|
465
|
+
|
|
466
|
+
# Render non-member operators grouped by class
|
|
467
|
+
non_member_ops = render_non_member_operators
|
|
468
|
+
unless non_member_ops.empty?
|
|
469
|
+
content = content + "\n\n " + non_member_ops
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# Generate .ipp file if builders exist (for reusability without duplicate Init symbols)
|
|
473
|
+
rice_ipp = nil
|
|
474
|
+
if has_builders
|
|
475
|
+
rice_ipp = File.join(File.dirname(relative_path), "#{@basename}.ipp")
|
|
476
|
+
STDOUT << " Writing: " << rice_ipp << "\n"
|
|
477
|
+
ipp_content = render_cursor(cursor, "translation_unit.ipp",
|
|
478
|
+
:class_templates => class_templates)
|
|
479
|
+
self.outputter.write(rice_ipp, ipp_content)
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Render C++ file
|
|
483
|
+
STDOUT << " Writing: " << rice_cpp << "\n"
|
|
484
|
+
content = render_cursor(cursor, "translation_unit.cpp",
|
|
485
|
+
:class_templates => class_templates,
|
|
486
|
+
:content => content,
|
|
487
|
+
:includes => @includes,
|
|
488
|
+
:init_name => init_name,
|
|
489
|
+
:rice_header => rice_header,
|
|
490
|
+
:incomplete_iterators => @iterator_collector.incomplete_iterators,
|
|
491
|
+
:rice_ipp => rice_ipp ? File.basename(rice_ipp) : nil)
|
|
492
|
+
self.outputter.write(rice_cpp, content)
|
|
493
|
+
|
|
494
|
+
# Render header file
|
|
495
|
+
STDOUT << " Writing: " << rice_header << "\n"
|
|
496
|
+
# Compute relative path from translation unit directory to the include header
|
|
497
|
+
relative_include = Pathname.new(rice_include_header).relative_path_from(File.dirname(relative_path)).to_s
|
|
498
|
+
content = render_cursor(cursor, "translation_unit.hpp",
|
|
499
|
+
:init_name => init_name,
|
|
500
|
+
:rice_include_header => relative_include)
|
|
501
|
+
self.outputter.write(rice_header, content)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Render a public, callable constructor into the Rice chain for its class.
|
|
505
|
+
def visit_constructor(cursor)
|
|
506
|
+
# Do not process class constructors defined outside of the class definition
|
|
507
|
+
return if cursor.lexical_parent != cursor.semantic_parent
|
|
508
|
+
|
|
509
|
+
# Do not process deleted constructors
|
|
510
|
+
return if cursor.deleted?
|
|
511
|
+
|
|
512
|
+
# Skip deprecated constructors (they may not be exported from library)
|
|
513
|
+
return if cursor.availability == :deprecated
|
|
514
|
+
|
|
515
|
+
# Skip explicitly listed constructors
|
|
516
|
+
return if skip_symbol?(cursor)
|
|
517
|
+
|
|
518
|
+
# Do not process move constructors
|
|
519
|
+
return if cursor.move_constructor?
|
|
520
|
+
|
|
521
|
+
# Do not construct abstract classes
|
|
522
|
+
return if cursor.semantic_parent.abstract?
|
|
523
|
+
|
|
524
|
+
# Skip constructors that take skipped types as parameters
|
|
525
|
+
return if has_skipped_param_type?(cursor)
|
|
526
|
+
return if has_unsupported_rice_param_type?(cursor)
|
|
527
|
+
|
|
528
|
+
signature = @signature_builder.constructor_signature(cursor)
|
|
529
|
+
args = @signature_builder.arguments(cursor)
|
|
530
|
+
|
|
531
|
+
return unless signature
|
|
532
|
+
|
|
533
|
+
self.render_cursor(cursor, "constructor",
|
|
534
|
+
:signature => signature, :args => args)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# Render a class or struct, including child members, anonymous enum
|
|
538
|
+
# constants, embedded types, and any auto-generated template bases needed
|
|
539
|
+
# before the class itself can be registered.
|
|
540
|
+
def visit_class_decl(cursor)
|
|
541
|
+
return if skip_namespace_forward_declaration?(cursor)
|
|
542
|
+
|
|
543
|
+
# Skip explicitly listed symbols
|
|
544
|
+
return if skip_symbol?(cursor)
|
|
545
|
+
|
|
546
|
+
# Skip anonymous types with no definer (no typedef, field, or variable name).
|
|
547
|
+
# These are unnameable in C++ and cannot be meaningfully bound.
|
|
548
|
+
return if cursor.anonymous? && !cursor.anonymous_definer
|
|
549
|
+
|
|
550
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
551
|
+
|
|
552
|
+
# Determine containing module
|
|
553
|
+
under = find_under(cursor)
|
|
554
|
+
|
|
555
|
+
# Is there a base class?
|
|
556
|
+
base = nil
|
|
557
|
+
auto_generated_base = ""
|
|
558
|
+
base_specifier = cursor.find_first_by_kind(false, :cursor_cxx_base_specifier)
|
|
559
|
+
if base_specifier
|
|
560
|
+
# Use canonical spelling for fully qualified type name with namespaces
|
|
561
|
+
base = base_specifier.type.canonical.spelling
|
|
562
|
+
|
|
563
|
+
# Check if base is a template instantiation that needs to be auto-generated
|
|
564
|
+
if base.include?('<') && !@auto_generated_bases.include?(base)
|
|
565
|
+
auto_generated_base = auto_generate_template_base_for_class(base_specifier, base, under)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Visit children
|
|
570
|
+
versions = visit_children(cursor,
|
|
571
|
+
exclude_kinds: Set[:cursor_class_decl, :cursor_struct, :cursor_enum_decl, :cursor_typedef_decl])
|
|
572
|
+
|
|
573
|
+
# Are there any constructors? If not, C++ will define one implicitly
|
|
574
|
+
# (but not for incomplete/opaque types which can't be instantiated)
|
|
575
|
+
constructors = cursor.find_by_kind(false, :cursor_constructor)
|
|
576
|
+
if !cursor.abstract? &&
|
|
577
|
+
!cursor.opaque_declaration? &&
|
|
578
|
+
constructors.none? &&
|
|
579
|
+
implicit_default_constructor_available?(cursor)
|
|
580
|
+
versions[nil].unshift(self.render_template("constructor",
|
|
581
|
+
:cursor => cursor,
|
|
582
|
+
:signature => @signature_builder.constructor_signature(cursor),
|
|
583
|
+
:args => []))
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# Add anonymous enum constants to the class chain (with per-constant versioning)
|
|
587
|
+
cursor.find_by_kind(false, :cursor_enum_decl) do |child_cursor|
|
|
588
|
+
next if child_cursor.private? || child_cursor.protected?
|
|
589
|
+
next unless child_cursor.anonymous?
|
|
590
|
+
constant_versions = visit_children(child_cursor)
|
|
591
|
+
constant_versions.each do |version, lines|
|
|
592
|
+
versions[version].concat(lines.map(&:strip))
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
children_content = merge_children(versions, indentation: 2, chain: true, terminate: true, strip: true)
|
|
597
|
+
|
|
598
|
+
# Collect forward-declared (incomplete) inner classes
|
|
599
|
+
# They must be registered with Rice before the parent class methods use them
|
|
600
|
+
incomplete_classes = []
|
|
601
|
+
cursor.find_by_kind(false, :cursor_class_decl, :cursor_struct) do |child_cursor|
|
|
602
|
+
next if child_cursor.private? || child_cursor.protected?
|
|
603
|
+
next unless child_cursor.opaque_declaration?
|
|
604
|
+
definition = child_cursor.definition
|
|
605
|
+
if definition &&
|
|
606
|
+
![:cursor_invalid_file, :cursor_no_decl_found].include?(definition.kind) &&
|
|
607
|
+
!definition.opaque_declaration? &&
|
|
608
|
+
translation_unit_file?(definition)
|
|
609
|
+
next
|
|
610
|
+
end
|
|
611
|
+
incomplete_classes << visit_incomplete_class(child_cursor, cursor)
|
|
612
|
+
end
|
|
613
|
+
incomplete_classes_content = merge_children({ nil => incomplete_classes })
|
|
614
|
+
|
|
615
|
+
# Auto-instantiate any class templates used as parameter types
|
|
616
|
+
auto_instantiated = auto_instantiate_parameter_templates(cursor, under)
|
|
617
|
+
result[nil] << auto_instantiated unless auto_instantiated.empty?
|
|
618
|
+
|
|
619
|
+
# Render class
|
|
620
|
+
cpp_type = @type_speller.qualified_class_name(cursor)
|
|
621
|
+
raw_class_name = cursor.type.spelling.split("::").last
|
|
622
|
+
ruby_class_name = @namer.apply_rename_types(raw_class_name, raw_class_name.camelize)
|
|
623
|
+
has_incomplete_classes = !incomplete_classes_content.to_s.empty?
|
|
624
|
+
@classes[cursor.cruby_name] = cpp_type
|
|
625
|
+
result[nil] << self.render_cursor(cursor, "class", :under => under, :base => base,
|
|
626
|
+
:auto_generated_base => auto_generated_base,
|
|
627
|
+
:incomplete_classes => incomplete_classes_content,
|
|
628
|
+
:children => children_content,
|
|
629
|
+
:cpp_type => cpp_type,
|
|
630
|
+
:ruby_class_name => ruby_class_name,
|
|
631
|
+
:has_incomplete_classes => has_incomplete_classes)
|
|
632
|
+
|
|
633
|
+
# Alias each_const to each if the class only has const iterators
|
|
634
|
+
if @iterator_collector.each_const_only?(cursor.cruby_name)
|
|
635
|
+
result[nil] << render_template("iterator_alias", :cruby_name => cursor.cruby_name)
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
# Define any complete embedded classes and structs
|
|
639
|
+
cursor.find_by_kind(false, :cursor_class_decl, :cursor_struct) do |child_cursor|
|
|
640
|
+
next if child_cursor.private? || child_cursor.protected?
|
|
641
|
+
next if child_cursor.forward_declaration?
|
|
642
|
+
next if child_cursor.opaque_declaration?
|
|
643
|
+
content = visit_class_decl(child_cursor)
|
|
644
|
+
next unless content
|
|
645
|
+
version = @symbols.version(child_cursor)
|
|
646
|
+
result[version] << content
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# Define any named embedded enums (anonymous enums are chained above)
|
|
650
|
+
cursor.find_by_kind(false, :cursor_enum_decl) do |child_cursor|
|
|
651
|
+
next if child_cursor.private? || child_cursor.protected?
|
|
652
|
+
next if child_cursor.anonymous?
|
|
653
|
+
version = @symbols.version(child_cursor)
|
|
654
|
+
result[version] << visit_enum_decl(child_cursor)
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
merge_children(result)
|
|
658
|
+
end
|
|
659
|
+
alias :visit_struct :visit_class_decl
|
|
660
|
+
|
|
661
|
+
# Scan a class's methods for class template parameters that need auto-instantiation.
|
|
662
|
+
def auto_instantiate_parameter_templates(cursor, under)
|
|
663
|
+
result = []
|
|
664
|
+
|
|
665
|
+
cursor.each(false) do |child, _|
|
|
666
|
+
next unless [:cursor_cxx_method, :cursor_constructor].include?(child.kind)
|
|
667
|
+
next if child.private? || child.protected?
|
|
668
|
+
|
|
669
|
+
child.num_arguments.times do |i|
|
|
670
|
+
param = child.argument(i)
|
|
671
|
+
type = param.type.intrinsic_type
|
|
672
|
+
|
|
673
|
+
# Skip if not a template instantiation or is from a system header (std::, etc.)
|
|
674
|
+
next unless type.num_template_arguments > 0
|
|
675
|
+
next if type.canonical.declaration.location.in_system_header?
|
|
676
|
+
|
|
677
|
+
# Find the class template declaration semantically so alias parameters
|
|
678
|
+
# like `using AliasContainer = Container<Item>` still auto-instantiate
|
|
679
|
+
# the underlying `Container<Item>` specialization.
|
|
680
|
+
decl, instantiated_type, instantiated_type_source = parameter_template_instantiation(type, param)
|
|
681
|
+
next unless decl && decl.kind == :cursor_class_template
|
|
682
|
+
next unless decl.location.file == cursor.location.file
|
|
683
|
+
|
|
684
|
+
# Auto-instantiate if no typedef exists
|
|
685
|
+
instantiated_type = @type_speller.qualify_class_static_members(instantiated_type, cursor)
|
|
686
|
+
next if @type_index.typedef_for(instantiated_type)
|
|
687
|
+
|
|
688
|
+
code = auto_instantiate_template(decl, instantiated_type, instantiated_type_source, under)
|
|
689
|
+
result << code unless code.empty?
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
merge_children({ nil => result })
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# Resolve the actual class template being instantiated for a parameter type.
|
|
697
|
+
# This prefers semantic specialization data so aliases such as
|
|
698
|
+
# `using AliasContainer = Container<Item>` still resolve to `Container`.
|
|
699
|
+
def parameter_template_instantiation(type, param)
|
|
700
|
+
declaration = type.declaration
|
|
701
|
+
specialized_template = declaration.specialized_template
|
|
702
|
+
unless specialized_template.kind == :cursor_invalid_file
|
|
703
|
+
return [specialized_template, @type_speller.type_spelling(type.unqualified_type), type]
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
canonical_type = type.canonical
|
|
707
|
+
canonical_declaration = canonical_type.declaration
|
|
708
|
+
specialized_template = canonical_declaration.specialized_template
|
|
709
|
+
unless specialized_template.kind == :cursor_invalid_file
|
|
710
|
+
return [specialized_template, @type_speller.type_spelling(canonical_type.unqualified_type), canonical_type]
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
template_ref = param.find_first_by_kind(true, :cursor_template_ref)
|
|
714
|
+
return [nil, nil, type] unless template_ref
|
|
715
|
+
|
|
716
|
+
[template_ref.referenced, @type_speller.type_spelling(type.unqualified_type), type]
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Visit a forward-declared (incomplete) inner class.
|
|
720
|
+
# These need to be registered with Rice so that types like Ptr<Impl> work.
|
|
721
|
+
# Must be registered BEFORE the parent class methods that use them (smart pointer issue).
|
|
722
|
+
def visit_incomplete_class(cursor, parent_cursor)
|
|
723
|
+
# Skip if already defined
|
|
724
|
+
return "" if @classes.key?(cursor.cruby_name)
|
|
725
|
+
|
|
726
|
+
@classes[cursor.cruby_name] = cursor.qualified_name
|
|
727
|
+
render_cursor(cursor, "incomplete_class", :under => parent_cursor)
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# Get base class spelling from a cursor's base specifier.
|
|
731
|
+
# Handles both template and non-template base classes.
|
|
732
|
+
# Returns nil if no base class exists.
|
|
733
|
+
def get_base_spelling(cursor)
|
|
734
|
+
base_specifier = cursor.find_first_by_kind(false, :cursor_cxx_base_specifier)
|
|
735
|
+
return nil unless base_specifier
|
|
736
|
+
|
|
737
|
+
base_declaration = base_specifier.type&.declaration
|
|
738
|
+
return nil unless base_declaration && base_declaration.kind != :cursor_no_decl_found
|
|
739
|
+
|
|
740
|
+
specialized_template = base_declaration.specialized_template
|
|
741
|
+
base_cursor = specialized_template.kind == :cursor_invalid_file ? base_declaration : specialized_template
|
|
742
|
+
|
|
743
|
+
# Skip system-header base classes (e.g., std::shared_ptr)
|
|
744
|
+
return nil if base_cursor.location.in_system_header?
|
|
745
|
+
|
|
746
|
+
@template_resolver.resolve_base_specifier_spelling(base_specifier)
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
# Render the reusable `_instantiate` helper for a class template so typedefs
|
|
750
|
+
# and alias specializations can bind that template later.
|
|
751
|
+
def visit_class_template_builder(cursor)
|
|
752
|
+
children_content = render_children(cursor,
|
|
753
|
+
only_kinds: [:cursor_cxx_method, :cursor_constructor, :cursor_field_decl, :cursor_variable,
|
|
754
|
+
:cursor_enum_decl, :cursor_conversion_function],
|
|
755
|
+
indentation: 4, chain: true,
|
|
756
|
+
terminate: true, strip: true)
|
|
757
|
+
|
|
758
|
+
base_spelling = get_base_spelling(cursor)
|
|
759
|
+
|
|
760
|
+
template_parameter_kinds = [:cursor_template_type_parameter,
|
|
761
|
+
:cursor_non_type_template_parameter,
|
|
762
|
+
:cursor_template_template_parameter]
|
|
763
|
+
|
|
764
|
+
raw_template_parameters = cursor.find_by_kind(false, *template_parameter_kinds)
|
|
765
|
+
|
|
766
|
+
# Filter out unnamed template parameters (e.g., `typename = void` default params)
|
|
767
|
+
# — they have empty spelling and produce invalid C++ like `<T, >`
|
|
768
|
+
template_parameters = raw_template_parameters.reject { |p| p.spelling.empty? }
|
|
769
|
+
return if template_parameters.empty? && raw_template_parameters.any?
|
|
770
|
+
template_signature = template_parameters.map { |template_parameter| @template_resolver.template_parameter_signature(template_parameter) }
|
|
771
|
+
.join(", ")
|
|
772
|
+
|
|
773
|
+
# Build fully qualified type using template params (e.g., Tests::Matrix<T, Rows, Columns>)
|
|
774
|
+
param_names = template_parameters.map { |template_parameter| @template_resolver.template_parameter_argument(template_parameter) }
|
|
775
|
+
.join(", ")
|
|
776
|
+
fully_qualified_type = "#{cursor.qualified_name}<#{param_names}>"
|
|
777
|
+
|
|
778
|
+
# Determine containing module
|
|
779
|
+
under = find_under(cursor)
|
|
780
|
+
|
|
781
|
+
# Render class
|
|
782
|
+
result = Array.new
|
|
783
|
+
result << self.render_cursor(cursor, "class_template", :under => under,
|
|
784
|
+
:template_signature => template_signature,
|
|
785
|
+
:fully_qualified_type => fully_qualified_type,
|
|
786
|
+
:base_spelling => base_spelling,
|
|
787
|
+
:children => children_content)
|
|
788
|
+
|
|
789
|
+
merge_children({ nil => result })
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# Find the nearest enclosing module (namespace, class, or struct), skipping
|
|
793
|
+
# inline namespaces which don't map to Ruby modules.
|
|
794
|
+
def find_under(cursor)
|
|
795
|
+
cursor.ancestors_by_kind(:cursor_class_decl, :cursor_struct, :cursor_namespace)
|
|
796
|
+
.find { |a| a.kind != :cursor_namespace || !a.inline_namespace? }
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# Check if cursor has one of the required export macros in its source text
|
|
800
|
+
# Used to filter out non-exported functions (e.g., only include CV_EXPORTS functions)
|
|
801
|
+
def has_export_macro?(cursor)
|
|
802
|
+
return true if @export_macros.empty?
|
|
803
|
+
|
|
804
|
+
source_text = cursor.extent.text
|
|
805
|
+
return false if source_text.nil?
|
|
806
|
+
@export_macros.any? { |macro| source_text.include?(macro) }
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
ITERATOR_METHODS = ["begin", "end", "cbegin", "cend", "rbegin", "rend", "crbegin", "crend"].freeze
|
|
810
|
+
|
|
811
|
+
# Common skip checks for functions and methods
|
|
812
|
+
def skip_callable?(cursor)
|
|
813
|
+
skip_symbol?(cursor) ||
|
|
814
|
+
cursor.availability == :deprecated ||
|
|
815
|
+
cursor.type.variadic?
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
# Render a class method, including special handling for iterator adapters
|
|
819
|
+
# and mutable `operator[]` setter support.
|
|
820
|
+
def visit_cxx_method(cursor)
|
|
821
|
+
# Do not process method definitions outside of classes (because we already processed them)
|
|
822
|
+
return if cursor.lexical_parent != cursor.semantic_parent
|
|
823
|
+
return if skip_callable?(cursor)
|
|
824
|
+
return if has_skipped_param_type?(cursor)
|
|
825
|
+
return if has_unsupported_rice_param_type?(cursor)
|
|
826
|
+
return if has_skipped_return_type?(cursor)
|
|
827
|
+
|
|
828
|
+
# Is this an iterator?
|
|
829
|
+
if ITERATOR_METHODS.include?(cursor.spelling)
|
|
830
|
+
return visit_cxx_iterator_method(cursor)
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
signature = @signature_builder.method_signature(cursor)
|
|
834
|
+
|
|
835
|
+
result = Array.new
|
|
836
|
+
|
|
837
|
+
name = cursor.ruby_name
|
|
838
|
+
args = @signature_builder.arguments(cursor)
|
|
839
|
+
|
|
840
|
+
# Check if return type should use ReturnBuffer
|
|
841
|
+
return_buffer = @signature_builder.buffer_type?(cursor.result_type)
|
|
842
|
+
|
|
843
|
+
is_template = cursor.semantic_parent.kind == :cursor_class_template
|
|
844
|
+
qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
|
|
845
|
+
result << self.render_cursor(cursor, "cxx_method",
|
|
846
|
+
:name => name,
|
|
847
|
+
:is_template => is_template,
|
|
848
|
+
:signature => signature,
|
|
849
|
+
:args => args,
|
|
850
|
+
:return_buffer => return_buffer,
|
|
851
|
+
:qualified_parent => qualified_parent)
|
|
852
|
+
|
|
853
|
+
# Special handling for implementing #[](index, value)
|
|
854
|
+
if cursor.spelling == "operator[]" && cursor.result_type.kind == :type_lvalue_ref &&
|
|
855
|
+
!cursor.result_type.non_reference_type.const_qualified? && !cursor.const?
|
|
856
|
+
index_param = cursor.find_by_kind(false, :cursor_parm_decl).first
|
|
857
|
+
index_type = @type_speller.type_spelling(cursor.type.arg_type(0))
|
|
858
|
+
index_name = index_param&.spelling.to_s.empty? ? "index" : index_param.spelling
|
|
859
|
+
value_type = @type_speller.type_spelling(cursor.result_type)
|
|
860
|
+
result << self.render_cursor(cursor, "operator[]",
|
|
861
|
+
:name => name,
|
|
862
|
+
:index_type => index_type,
|
|
863
|
+
:index_name => index_name,
|
|
864
|
+
:qualified_parent => qualified_parent,
|
|
865
|
+
:value_type => value_type)
|
|
866
|
+
end
|
|
867
|
+
result
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
def skip_namespace_forward_declaration?(cursor)
|
|
871
|
+
return false unless cursor.kind == :cursor_class_decl
|
|
872
|
+
return false unless cursor.opaque_declaration?
|
|
873
|
+
return false if [:cursor_class_decl, :cursor_struct].include?(cursor.semantic_parent.kind)
|
|
874
|
+
|
|
875
|
+
definition = cursor.definition
|
|
876
|
+
return false if [:cursor_invalid_file, :cursor_no_decl_found].include?(definition.kind)
|
|
877
|
+
return false if definition.opaque_declaration?
|
|
878
|
+
|
|
879
|
+
translation_unit_file?(definition)
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
# Generates Rice define_iterator calls for C++ iterator methods.
|
|
883
|
+
# In C++, cbegin/crbegin can be called on non-const objects but return
|
|
884
|
+
# const iterators while const begin/rbegin require a const receiver.
|
|
885
|
+
# Ruby has no such distinction, so the collector skips cbegin/crbegin
|
|
886
|
+
# to avoid emitting duplicate each_const / each_reverse_const methods.
|
|
887
|
+
def visit_cxx_iterator_method(cursor)
|
|
888
|
+
iterator_name = @iterator_collector.record(cursor)
|
|
889
|
+
return unless iterator_name
|
|
890
|
+
|
|
891
|
+
signature = @signature_builder.method_signature(cursor)
|
|
892
|
+
return unless signature
|
|
893
|
+
|
|
894
|
+
begin_method = cursor.spelling
|
|
895
|
+
end_method = begin_method.sub("begin", "end")
|
|
896
|
+
is_template = cursor.semantic_parent.kind == :cursor_class_template
|
|
897
|
+
qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
|
|
898
|
+
|
|
899
|
+
self.render_cursor(cursor, "cxx_iterator_method",
|
|
900
|
+
:name => iterator_name,
|
|
901
|
+
:begin_method => begin_method,
|
|
902
|
+
:end_method => end_method,
|
|
903
|
+
:signature => signature,
|
|
904
|
+
:is_template => is_template,
|
|
905
|
+
:qualified_parent => qualified_parent)
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
# Render a conversion operator such as `operator bool()` or `operator T*()`.
|
|
909
|
+
# Template-parameter conversions that cannot produce stable Ruby names are skipped.
|
|
910
|
+
def visit_conversion_function(cursor)
|
|
911
|
+
# For now only deal with member functions
|
|
912
|
+
return unless CURSOR_CLASSES.include?(cursor.lexical_parent.kind)
|
|
913
|
+
|
|
914
|
+
return if skip_callable?(cursor)
|
|
915
|
+
|
|
916
|
+
return unless cursor.type.args_size == 0
|
|
917
|
+
return if has_skipped_return_type?(cursor)
|
|
918
|
+
|
|
919
|
+
result_type = cursor.type.result_type
|
|
920
|
+
|
|
921
|
+
# Skip "safe bool idiom" conversion operators from pre-C++11.
|
|
922
|
+
# These are typedefs to member function pointers used before explicit operator bool().
|
|
923
|
+
# Pattern: typedef void (Class::*bool_type)() const; operator bool_type() const;
|
|
924
|
+
if result_type.declaration.kind == :cursor_typedef_decl
|
|
925
|
+
canonical = result_type.canonical
|
|
926
|
+
if canonical.kind == :type_member_pointer
|
|
927
|
+
# Check if the pointee is a function type
|
|
928
|
+
pointee = canonical.pointee
|
|
929
|
+
if pointee.kind == :type_function_proto || pointee.kind == :type_function_no_proto
|
|
930
|
+
return
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
result_type_spelling = @type_speller.type_spelling(result_type)
|
|
936
|
+
is_const = cursor.const?
|
|
937
|
+
|
|
938
|
+
# For class templates, the result type may contain "type-parameter-X_Y" which
|
|
939
|
+
# generates invalid Ruby method names. Use generic names instead.
|
|
940
|
+
if cursor.semantic_parent.kind == :cursor_class_template &&
|
|
941
|
+
result_type.canonical.spelling.include?("type-parameter-")
|
|
942
|
+
# Determine if this is a pointer conversion
|
|
943
|
+
if result_type.kind == :type_pointer
|
|
944
|
+
ruby_name = is_const ? "to_const_ptr" : "to_ptr"
|
|
945
|
+
else
|
|
946
|
+
# Skip other template parameter conversions for now
|
|
947
|
+
return
|
|
948
|
+
end
|
|
949
|
+
else
|
|
950
|
+
ruby_name = cursor.ruby_name
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
|
|
954
|
+
self.render_cursor(cursor, "conversion_function",
|
|
955
|
+
:ruby_name => ruby_name, :result_type => result_type_spelling,
|
|
956
|
+
:is_const => is_const,
|
|
957
|
+
:qualified_parent => qualified_parent)
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# Render a named enum as a Rice enum, or flatten anonymous enum constants
|
|
961
|
+
# into the surrounding class/namespace output.
|
|
962
|
+
def visit_enum_decl(cursor)
|
|
963
|
+
return if CURSOR_CLASSES.include?(cursor.semantic_parent.kind) && !cursor.public?
|
|
964
|
+
return if !cursor.anonymous? && skip_symbol?(cursor)
|
|
965
|
+
|
|
966
|
+
if cursor.anonymous? && CURSOR_CLASSES.include?(cursor.semantic_parent.kind)
|
|
967
|
+
# Anonymous enum inside a class — return constants as chainable strings
|
|
968
|
+
versions = visit_children(cursor)
|
|
969
|
+
return versions.values.flatten.map(&:strip)
|
|
970
|
+
elsif cursor.anonymous?
|
|
971
|
+
# Anonymous enum at namespace/TU level — return standalone constant definitions
|
|
972
|
+
return render_children(cursor, strip: true)
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
under = find_under(cursor)
|
|
976
|
+
children = render_children(cursor, indentation: 2, chain: true, terminate: true, strip: true)
|
|
977
|
+
self.render_cursor(cursor, "enum_decl", :under => under, :children => children)
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
# Render one enum constant, preserving whether it came from an anonymous
|
|
981
|
+
# enum and whether that enum lives inside a class scope.
|
|
982
|
+
def visit_enum_constant_decl(cursor)
|
|
983
|
+
return if skip_symbol?(cursor)
|
|
984
|
+
enum_parent = cursor.semantic_parent
|
|
985
|
+
enum_scope = enum_parent.semantic_parent
|
|
986
|
+
anonymous_parent = enum_parent.anonymous?
|
|
987
|
+
anonymous_class_scope = anonymous_parent &&
|
|
988
|
+
[:cursor_class_decl, :cursor_struct, :cursor_class_template].include?(enum_scope.kind)
|
|
989
|
+
qualified_name = "#{@type_speller.qualified_display_name(enum_scope)}::#{cursor.spelling}"
|
|
990
|
+
|
|
991
|
+
self.render_cursor(cursor, "enum_constant_decl",
|
|
992
|
+
:anonymous_parent => anonymous_parent,
|
|
993
|
+
:anonymous_class_scope => anonymous_class_scope,
|
|
994
|
+
:owner_cruby_name => enum_scope.cruby_name,
|
|
995
|
+
:qualified_name => qualified_name,
|
|
996
|
+
:value_name => cursor.qualified_display_name)
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
# Render a free function or non-member operator that survives the symbol,
|
|
1000
|
+
# export-macro, and parameter/return-type filters.
|
|
1001
|
+
def visit_function(cursor)
|
|
1002
|
+
# Skip `= delete`d free functions. clang_CXXMethod_isDeleted only
|
|
1003
|
+
# applies to class methods, so we use clang_getCursorAvailability,
|
|
1004
|
+
# which reports :not_available for deleted free functions.
|
|
1005
|
+
return if cursor.availability == :not_available
|
|
1006
|
+
# Can't return arrays in C++
|
|
1007
|
+
return if cursor.type.result_type.is_a?(::FFI::Clang::Types::Array)
|
|
1008
|
+
return if skip_callable?(cursor)
|
|
1009
|
+
return if has_skipped_param_type?(cursor)
|
|
1010
|
+
return if has_unsupported_rice_param_type?(cursor)
|
|
1011
|
+
return if has_skipped_return_type?(cursor)
|
|
1012
|
+
return unless has_export_macro?(cursor)
|
|
1013
|
+
|
|
1014
|
+
if cursor.spelling.start_with?('operator') && !cursor.spelling.match?(/^operator\w/)
|
|
1015
|
+
return self.visit_operator_non_member(cursor)
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
name = cursor.ruby_name
|
|
1019
|
+
args = @signature_builder.arguments(cursor)
|
|
1020
|
+
|
|
1021
|
+
signature = @signature_builder.method_signature(cursor)
|
|
1022
|
+
|
|
1023
|
+
# Check if return type should use ReturnBuffer
|
|
1024
|
+
return_buffer = @signature_builder.buffer_type?(cursor.type.result_type)
|
|
1025
|
+
|
|
1026
|
+
under = cursor.ancestors_by_kind(:cursor_namespace)
|
|
1027
|
+
.find { |a| !a.inline_namespace? }
|
|
1028
|
+
self.render_cursor(cursor, "function",
|
|
1029
|
+
:under => under,
|
|
1030
|
+
:name => name,
|
|
1031
|
+
:signature => signature,
|
|
1032
|
+
:args => args,
|
|
1033
|
+
:return_buffer => return_buffer)
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
# Render simple object-like macros as Ruby constants when the macro body is
|
|
1037
|
+
# exactly one literal token.
|
|
1038
|
+
def visit_macro_definition(cursor)
|
|
1039
|
+
tokens = cursor.translation_unit.tokenize(cursor.extent)
|
|
1040
|
+
return unless tokens.size == 2
|
|
1041
|
+
return unless tokens.tokens[0].kind == :identifier
|
|
1042
|
+
return unless tokens.tokens[1].kind == :literal
|
|
1043
|
+
return if skip_symbol?(cursor)
|
|
1044
|
+
|
|
1045
|
+
self.render_cursor(cursor, "constant",
|
|
1046
|
+
:name => tokens.tokens[0].spelling.upcase_first,
|
|
1047
|
+
:qualified_name => tokens.tokens[0].spelling)
|
|
1048
|
+
end
|
|
1049
|
+
|
|
1050
|
+
# Render a namespace as a Ruby module, except for inline namespaces which
|
|
1051
|
+
# are flattened into their enclosing namespace.
|
|
1052
|
+
def visit_namespace(cursor)
|
|
1053
|
+
# Skip anonymous namespaces - they're internal implementation details
|
|
1054
|
+
return if cursor.anonymous?
|
|
1055
|
+
|
|
1056
|
+
# Inline namespaces (e.g., std::__1, abseil's lts_*) should not create
|
|
1057
|
+
# Ruby modules — their members belong to the enclosing namespace.
|
|
1058
|
+
# Recurse into children without registering a new module.
|
|
1059
|
+
if cursor.inline_namespace?
|
|
1060
|
+
return self.render_children(cursor)
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
result = Array.new
|
|
1064
|
+
|
|
1065
|
+
# Don't redefine a namespace twice. It doesn't matter to Ruby, but C++ wrapper
|
|
1066
|
+
# will break with a redefinition error:
|
|
1067
|
+
# Module rb_mNamespace = define_module("namespace");
|
|
1068
|
+
# Module rb_mNamespace = define_module("namespace");
|
|
1069
|
+
qualified_display_name = cursor.qualified_display_name
|
|
1070
|
+
unless @namespaces.include?(qualified_display_name)
|
|
1071
|
+
@namespaces << qualified_display_name
|
|
1072
|
+
under = find_under(cursor)
|
|
1073
|
+
result << self.render_cursor(cursor, "namespace", :under => under)
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
result << self.render_children(cursor)
|
|
1077
|
+
|
|
1078
|
+
result.map { |s| s.chomp }.reject(&:empty?).join("\n\n")
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
# Render a public field as a Rice attribute on its containing class.
|
|
1082
|
+
def visit_field_decl(cursor)
|
|
1083
|
+
return unless cursor.public?
|
|
1084
|
+
return if skip_symbol?(cursor)
|
|
1085
|
+
return if cursor.availability == :deprecated
|
|
1086
|
+
return if unsupported_rice_attribute_type?(cursor.type)
|
|
1087
|
+
|
|
1088
|
+
qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
|
|
1089
|
+
self.render_cursor(cursor, "field_decl",
|
|
1090
|
+
:qualified_parent => qualified_parent)
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
# Record a free operator for later rendering onto the target class.
|
|
1094
|
+
# These are grouped and emitted after normal members so cross-file
|
|
1095
|
+
# `Data_Type<T>()` references can be handled in one pass.
|
|
1096
|
+
def visit_operator_non_member(cursor)
|
|
1097
|
+
# This is a stand-alone operator, such as:
|
|
1098
|
+
#
|
|
1099
|
+
# MatExpr operator + (const Mat& a, const Mat& b); # binary (2 args)
|
|
1100
|
+
# std::ostream& operator << (std::ostream& out, const Complex<_Tp>& c)
|
|
1101
|
+
# MatExpr operator ~(const Mat& m); # unary (1 arg)
|
|
1102
|
+
# MatExpr operator -(const Mat& m); # unary negation (1 arg)
|
|
1103
|
+
return if cursor.type.args_size < 1 || cursor.type.args_size > 2
|
|
1104
|
+
|
|
1105
|
+
arg0_type = cursor.type.arg_type(0).non_reference_type
|
|
1106
|
+
class_cursor = arg0_type.declaration
|
|
1107
|
+
|
|
1108
|
+
# Skip when the first argument is a fundamental type (e.g., double) or a
|
|
1109
|
+
# typedef to one (e.g., ptrdiff_t -> long long). There is no Rice wrapper
|
|
1110
|
+
# for these types so Data_Type<T>() would be invalid.
|
|
1111
|
+
return if class_cursor.kind == :cursor_no_decl_found
|
|
1112
|
+
return if FUNDAMENTAL_TYPES.include?(arg0_type.canonical.kind)
|
|
1113
|
+
# Rice already provides bitwise operators (&, |, ^, ~, <<, >>) for enums automatically
|
|
1114
|
+
return if class_cursor.kind == :cursor_enum_decl
|
|
1115
|
+
|
|
1116
|
+
# Skip types that Rice converts to native Ruby types (no Rice wrapper exists).
|
|
1117
|
+
# Note: std::vector, std::pair, etc. ARE wrapped by Rice.
|
|
1118
|
+
canonical_decl = arg0_type.canonical.declaration
|
|
1119
|
+
return if canonical_decl.kind != :cursor_no_decl_found &&
|
|
1120
|
+
canonical_decl.location.in_system_header? &&
|
|
1121
|
+
RICE_NATIVE_TYPES.include?(canonical_decl.spelling)
|
|
1122
|
+
|
|
1123
|
+
# Use the class cursor directly - operators should be attached to the actual class
|
|
1124
|
+
# (e.g., rb_cCvMat for cv::Mat), not to typedefs (e.g., rb_cMatND which is typedef for Mat)
|
|
1125
|
+
# Collect non-member operators to render grouped by class later
|
|
1126
|
+
@non_member_operators[class_cursor.cruby_name] << { cursor: cursor, class_cursor: class_cursor }
|
|
1127
|
+
nil
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
# Render the queued non-member operators grouped by their target class.
|
|
1131
|
+
# Includes ostream-based `inspect`, unary operators, and binary operators.
|
|
1132
|
+
# Each branch produces a [target_cursor, rendered_line] pair; we group
|
|
1133
|
+
# the lines by the target class's cruby_name and emit one chained
|
|
1134
|
+
# `class.define(...)...` block per class.
|
|
1135
|
+
def render_non_member_operators
|
|
1136
|
+
# cpp_type carries the qualified class name for cross-file Data_Type<T>() refs.
|
|
1137
|
+
grouped = Hash.new { |h, k| h[k] = { lines: [], cpp_type: nil } }
|
|
1138
|
+
|
|
1139
|
+
@non_member_operators.each_value do |operators|
|
|
1140
|
+
operators.each do |op|
|
|
1141
|
+
cursor = op[:cursor]
|
|
1142
|
+
class_cursor = op[:class_cursor]
|
|
1143
|
+
|
|
1144
|
+
target_cursor, line =
|
|
1145
|
+
if ostream_insertion?(cursor)
|
|
1146
|
+
render_ostream_inspect(cursor)
|
|
1147
|
+
elsif cursor.type.args_size == 1
|
|
1148
|
+
render_unary_operator(cursor, class_cursor)
|
|
1149
|
+
else
|
|
1150
|
+
render_binary_operator(cursor, class_cursor)
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
target_class = target_cursor.cruby_name
|
|
1154
|
+
grouped[target_class][:cpp_type] ||= @type_speller.qualified_class_name(target_cursor)
|
|
1155
|
+
grouped[target_class][:lines] << line
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
result = []
|
|
1160
|
+
grouped.each do |cruby_name, info|
|
|
1161
|
+
next if info[:lines].empty?
|
|
1162
|
+
# Local-defined classes use the variable; cross-file refs use Data_Type<T>()
|
|
1163
|
+
class_ref = @classes.key?(cruby_name) ? cruby_name : "Data_Type<#{info[:cpp_type]}>()"
|
|
1164
|
+
# Indent 4 spaces (2 for function body + 2 for method chain)
|
|
1165
|
+
content = merge_children({ nil => info[:lines] }, indentation: 4, chain: true, terminate: true, strip: true)
|
|
1166
|
+
result << "#{class_ref}#{content}"
|
|
1167
|
+
end
|
|
1168
|
+
result.join("\n \n ")
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
# `std::ostream& operator<<(std::ostream&, T&)` overloads get translated
|
|
1172
|
+
# to a Ruby `inspect` method on T's class.
|
|
1173
|
+
def ostream_insertion?(cursor)
|
|
1174
|
+
return false unless cursor.spelling.include?("<<")
|
|
1175
|
+
arg0_decl = cursor.type.arg_type(0).non_reference_type.declaration
|
|
1176
|
+
arg0_decl.location.in_system_header? && arg0_decl.spelling.end_with?("ostream")
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
def render_ostream_inspect(cursor)
|
|
1180
|
+
target_cursor = cursor.type.arg_type(1).non_reference_type.declaration
|
|
1181
|
+
arg_type = @type_speller.type_spelling(cursor.type.arg_type(1))
|
|
1182
|
+
line = render_template("non_member_operator_inspect", :arg_type => arg_type).strip
|
|
1183
|
+
[target_cursor, line]
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
# e.g. `operator~(const Mat& m)`, `operator-(const Mat& m)`.
|
|
1187
|
+
def render_unary_operator(cursor, class_cursor)
|
|
1188
|
+
op_symbol = cursor.spelling.sub(/^operator\s*/, '')
|
|
1189
|
+
# Ruby uses +@ and -@ for unary plus/minus; ~ and ! stay as-is.
|
|
1190
|
+
ruby_name = case op_symbol
|
|
1191
|
+
when '+' then '+@'
|
|
1192
|
+
when '-' then '-@'
|
|
1193
|
+
else op_symbol
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
line = render_template("non_member_operator_unary",
|
|
1197
|
+
:ruby_name => ruby_name,
|
|
1198
|
+
:arg0_type => @type_speller.type_spelling(cursor.type.arg_type(0)),
|
|
1199
|
+
:result_type => @type_speller.type_spelling(cursor.result_type),
|
|
1200
|
+
:op_symbol => op_symbol).strip
|
|
1201
|
+
[class_cursor, line]
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
# e.g. `operator+(const Mat& a, const Mat& b)`.
|
|
1205
|
+
def render_binary_operator(cursor, class_cursor)
|
|
1206
|
+
op_symbol = cursor.spelling.sub(/^operator\s*/, '')
|
|
1207
|
+
line = render_template("non_member_operator_binary",
|
|
1208
|
+
:ruby_name => cursor.ruby_name,
|
|
1209
|
+
:arg0_type => @type_speller.type_spelling(cursor.type.arg_type(0)),
|
|
1210
|
+
:arg1_type => @type_speller.type_spelling(cursor.type.arg_type(1)),
|
|
1211
|
+
:result_type => @type_speller.type_spelling(cursor.result_type),
|
|
1212
|
+
:return_stmt => binary_return_stmt(cursor, op_symbol)).strip
|
|
1213
|
+
[class_cursor, line]
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
# Pick the right return statement shape for a binary operator based on
|
|
1217
|
+
# what the operator returns: void, a reference to self, or a value.
|
|
1218
|
+
def binary_return_stmt(cursor, op_symbol)
|
|
1219
|
+
if cursor.result_type.kind == :type_void
|
|
1220
|
+
"self #{op_symbol} other;"
|
|
1221
|
+
elsif cursor.result_type.kind == :type_lvalue_ref &&
|
|
1222
|
+
cursor.result_type.non_reference_type == cursor.type.arg_type(0).non_reference_type
|
|
1223
|
+
# Returns reference to self (e.g., FileStorage& operator<<)
|
|
1224
|
+
"self #{op_symbol} other;\n return self;"
|
|
1225
|
+
else
|
|
1226
|
+
# Returns a value (e.g., bool, ptrdiff_t)
|
|
1227
|
+
"return self #{op_symbol} other;"
|
|
1228
|
+
end
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1231
|
+
# Resolve a top-level typedef or alias to the class template specialization
|
|
1232
|
+
# it names, then render the specialization binding.
|
|
1233
|
+
def visit_typedef_decl(cursor)
|
|
1234
|
+
return if cursor.semantic_parent.kind == :cursor_class_decl || cursor.semantic_parent.kind == :cursor_struct
|
|
1235
|
+
return if skip_symbol?(cursor)
|
|
1236
|
+
|
|
1237
|
+
# Skip if already processed (can happen when force-generating base classes)
|
|
1238
|
+
return if @classes.key?(cursor.cruby_name)
|
|
1239
|
+
|
|
1240
|
+
# Skip typedefs to std:: types - Rice handles these automatically
|
|
1241
|
+
canonical_decl = cursor.underlying_type.canonical.declaration
|
|
1242
|
+
return if canonical_decl.kind != :cursor_no_decl_found && canonical_decl.location.in_system_header?
|
|
1243
|
+
|
|
1244
|
+
template_specialization = template_specialization_target(cursor)
|
|
1245
|
+
return unless template_specialization
|
|
1246
|
+
|
|
1247
|
+
cursor_template, underlying_type = template_specialization
|
|
1248
|
+
return if skip_symbol?(cursor_template)
|
|
1249
|
+
|
|
1250
|
+
visit_template_specialization(cursor, cursor_template, underlying_type)
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
# Handle C++11 'using' type alias declarations the same as typedef
|
|
1254
|
+
def visit_type_alias_decl(cursor)
|
|
1255
|
+
visit_typedef_decl(cursor)
|
|
1256
|
+
end
|
|
1257
|
+
|
|
1258
|
+
# Resolve a typedef or type alias to the class template specialization it names.
|
|
1259
|
+
# Handles both direct specializations:
|
|
1260
|
+
# typedef Point_<int> Point2i;
|
|
1261
|
+
# and aliases through an existing typedef:
|
|
1262
|
+
# typedef Point2i Point;
|
|
1263
|
+
def template_specialization_target(cursor)
|
|
1264
|
+
type = cursor.underlying_type
|
|
1265
|
+
|
|
1266
|
+
loop do
|
|
1267
|
+
declaration = type.declaration
|
|
1268
|
+
return nil if declaration.kind == :cursor_no_decl_found
|
|
1269
|
+
|
|
1270
|
+
template_cursor = declaration.specialized_template
|
|
1271
|
+
return [template_cursor, type] unless template_cursor.kind == :cursor_invalid_file
|
|
1272
|
+
|
|
1273
|
+
return nil unless declaration.kind == :cursor_typedef_decl || declaration.kind == :cursor_type_alias_decl
|
|
1274
|
+
|
|
1275
|
+
type = declaration.underlying_type
|
|
1276
|
+
end
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
# Render one typedef/alias specialization of a class template and ensure
|
|
1280
|
+
# any inherited template bases are available first.
|
|
1281
|
+
def visit_template_specialization(cursor, cursor_template, underlying_type)
|
|
1282
|
+
under = find_under(cursor)
|
|
1283
|
+
# Get template arguments including any default values that were omitted in the typedef
|
|
1284
|
+
template_argument_values = @template_resolver.full_template_arguments(cursor, underlying_type, cursor_template)
|
|
1285
|
+
template_arguments = template_argument_values.join(", ")
|
|
1286
|
+
|
|
1287
|
+
result = ""
|
|
1288
|
+
|
|
1289
|
+
# Is there a base class?
|
|
1290
|
+
base_ref = cursor_template.find_first_by_kind(false, :cursor_cxx_base_specifier)
|
|
1291
|
+
base_spelling = nil
|
|
1292
|
+
if base_ref
|
|
1293
|
+
# Base class children can be a :cursor_type_ref or :cursor_template_ref
|
|
1294
|
+
type_ref = base_ref.find_first_by_kind(false, :cursor_type_ref, :cursor_template_ref)
|
|
1295
|
+
base = type_ref.definition
|
|
1296
|
+
|
|
1297
|
+
# Resolve the base class to its instantiated form (e.g., PtrStep<unsigned char>)
|
|
1298
|
+
base_spelling = @template_resolver.resolve_base_instantiation(cursor, underlying_type)
|
|
1299
|
+
|
|
1300
|
+
# Ensure the base class is generated before this class.
|
|
1301
|
+
# Skip std:: base classes — Rice handles standard library types automatically.
|
|
1302
|
+
# This prevents walking libstdc++ internals (e.g., cv::Ptr<T> inherits from
|
|
1303
|
+
# std::shared_ptr<T> → std::__shared_ptr<T> → std::__shared_ptr_access<T>).
|
|
1304
|
+
# Also check the base template's own namespace since resolve_base_instantiation
|
|
1305
|
+
# may incorrectly use the derived class's namespace (e.g., "cv::shared_ptr").
|
|
1306
|
+
base_template_decl = template_cursor_definition(base_ref.find_first_by_kind(false, :cursor_type_ref, :cursor_template_ref)&.referenced)
|
|
1307
|
+
base_in_std = base_template_decl&.location&.in_system_header?
|
|
1308
|
+
# Use cursor_template_ref specifically to get the base class template declaration
|
|
1309
|
+
# (cursor_type_ref may resolve to a template parameter like T instead)
|
|
1310
|
+
base_class_template = template_cursor_definition(base_ref.find_first_by_kind(false, :cursor_template_ref)&.referenced)
|
|
1311
|
+
base_in_current_file = base_class_template &&
|
|
1312
|
+
translation_unit_file?(base_class_template)
|
|
1313
|
+
if base_spelling && !base_in_std
|
|
1314
|
+
if base_in_current_file
|
|
1315
|
+
base_typedef = @type_index.typedef_for(base_spelling)
|
|
1316
|
+
if base_typedef
|
|
1317
|
+
# Base has a typedef - check if it has been generated yet
|
|
1318
|
+
unless @classes.key?(base_typedef.cruby_name)
|
|
1319
|
+
# Force generate the typedef first (recursively handles its bases)
|
|
1320
|
+
result = visit_typedef_decl(base_typedef) || ""
|
|
1321
|
+
end
|
|
1322
|
+
elsif !@auto_generated_bases.include?(base_spelling)
|
|
1323
|
+
# No typedef - auto-generate
|
|
1324
|
+
result = auto_generate_base_class(base_ref, base_spelling, under)
|
|
1325
|
+
end
|
|
1326
|
+
elsif base_class_template && !base_class_template.location.in_system_header?
|
|
1327
|
+
# Base template is from an included file — include its .ipp so the
|
|
1328
|
+
# _instantiate builder is available for the derived class
|
|
1329
|
+
builder_ipp = ipp_path_for_cursor(base_class_template)
|
|
1330
|
+
current_ipp = File.join(@relative_dir, "#{@basename}.ipp")
|
|
1331
|
+
if builder_ipp != current_ipp
|
|
1332
|
+
ipp_relative = Pathname.new(builder_ipp).relative_path_from(Pathname.new(@relative_dir)).to_s
|
|
1333
|
+
@includes << "#include \"#{ipp_relative}\""
|
|
1334
|
+
end
|
|
1335
|
+
end
|
|
1336
|
+
end
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1339
|
+
template_specialization = @template_resolver.specialization_spelling(cursor, underlying_type, cursor_template)
|
|
1340
|
+
|
|
1341
|
+
# If template is defined in a different file, include its .ipp for the _instantiate builder
|
|
1342
|
+
template_owner = template_cursor_definition(cursor_template)
|
|
1343
|
+
unless translation_unit_file?(template_owner)
|
|
1344
|
+
builder_ipp = ipp_path_for_cursor(template_owner)
|
|
1345
|
+
current_ipp = File.join(@relative_dir, "#{@basename}.ipp")
|
|
1346
|
+
if builder_ipp != current_ipp
|
|
1347
|
+
ipp_relative = Pathname.new(builder_ipp).relative_path_from(Pathname.new(@relative_dir)).to_s
|
|
1348
|
+
@includes << "#include \"#{ipp_relative}\""
|
|
1349
|
+
end
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
@classes[cursor.cruby_name] = template_specialization
|
|
1353
|
+
result + self.render_cursor(cursor, "class_template_specialization",
|
|
1354
|
+
:cursor_template => cursor_template,
|
|
1355
|
+
:template_specialization => template_specialization,
|
|
1356
|
+
:template_arguments => template_arguments,
|
|
1357
|
+
:base_ref => base_ref,
|
|
1358
|
+
:base => base,
|
|
1359
|
+
:base_spelling => base_spelling,
|
|
1360
|
+
:under => under)
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1363
|
+
# Auto-instantiate a class template used as a parameter type without a typedef.
|
|
1364
|
+
def auto_instantiate_template(cursor_template, instantiated_type, type, under)
|
|
1365
|
+
return "" unless cursor_template
|
|
1366
|
+
match = instantiated_type.match(/\<(.*)\>/)
|
|
1367
|
+
return "" unless match
|
|
1368
|
+
|
|
1369
|
+
# Skip if template arguments reference skipped symbols
|
|
1370
|
+
return "" if type_references_skipped_symbol?(type)
|
|
1371
|
+
|
|
1372
|
+
ruby_class_name = instantiated_type.gsub(/::|<|>|,|\s+/, ' ').split.map(&:capitalize).join
|
|
1373
|
+
ruby_class_name = @namer.apply_rename_types(ruby_class_name)
|
|
1374
|
+
cruby_name = "rb_c#{ruby_class_name}"
|
|
1375
|
+
return "" if @classes.key?(cruby_name)
|
|
1376
|
+
|
|
1377
|
+
@classes[cruby_name] = instantiated_type
|
|
1378
|
+
render_template("class_template_specialization",
|
|
1379
|
+
:cursor => cursor_template, :cursor_template => cursor_template,
|
|
1380
|
+
:template_specialization => instantiated_type, :template_arguments => match[1],
|
|
1381
|
+
:cruby_name => cruby_name, :base_ref => nil, :base => nil,
|
|
1382
|
+
:base_spelling => nil, :under => under)
|
|
1383
|
+
end
|
|
1384
|
+
|
|
1385
|
+
# Auto-generate a base class definition when no typedef exists for it.
|
|
1386
|
+
# When recursive: true, also generates any base classes of the base class.
|
|
1387
|
+
def auto_generate_base_class(base_ref, base_spelling, under, recursive: true)
|
|
1388
|
+
base_template_ref = base_ref.find_first_by_kind(false, :cursor_template_ref)
|
|
1389
|
+
return "" unless base_template_ref
|
|
1390
|
+
|
|
1391
|
+
base_declaration = base_ref.type.declaration
|
|
1392
|
+
unless base_declaration.kind == :cursor_no_decl_found
|
|
1393
|
+
specialized_template = base_declaration.specialized_template
|
|
1394
|
+
return "" if specialized_template.kind == :cursor_class_template_partial_specialization
|
|
1395
|
+
end
|
|
1396
|
+
|
|
1397
|
+
base_template = base_template_ref.referenced
|
|
1398
|
+
return "" if base_template.location.in_system_header?
|
|
1399
|
+
return "" if base_template.kind == :cursor_class_template_partial_specialization
|
|
1400
|
+
# Skip base templates from included files — their own output handles registration
|
|
1401
|
+
return "" unless translation_unit_file?(base_template)
|
|
1402
|
+
base_template_arguments_text = @template_resolver.template_argument_list_text(base_spelling)
|
|
1403
|
+
return "" unless base_template_arguments_text
|
|
1404
|
+
|
|
1405
|
+
base_template_arguments = @template_resolver.template_argument_texts(base_template_arguments_text)
|
|
1406
|
+
return "" if base_template_arguments.empty?
|
|
1407
|
+
|
|
1408
|
+
result = ""
|
|
1409
|
+
base_base_spelling = nil
|
|
1410
|
+
|
|
1411
|
+
# Check if this base class has its own base that needs auto-generation
|
|
1412
|
+
if recursive
|
|
1413
|
+
base_base_ref = base_template.find_first_by_kind(false, :cursor_cxx_base_specifier)
|
|
1414
|
+
if base_base_ref
|
|
1415
|
+
base_base_template_ref = base_base_ref.find_first_by_kind(false, :cursor_template_ref)
|
|
1416
|
+
if base_base_template_ref
|
|
1417
|
+
# Build substitution map from template params to actual values
|
|
1418
|
+
template_params = @template_resolver.template_parameters(base_template).map(&:spelling)
|
|
1419
|
+
template_arg_values = base_template_arguments
|
|
1420
|
+
subs = template_params.each_with_index.to_h { |param, i| [param, template_arg_values[i]] }
|
|
1421
|
+
|
|
1422
|
+
# Substitute template parameters with actual values
|
|
1423
|
+
base_base_spelling = @template_resolver.resolve_base_specifier_spelling(base_base_ref, substitutions: subs)
|
|
1424
|
+
|
|
1425
|
+
if base_base_spelling && !@type_index.typedef_for(base_base_spelling) && !@auto_generated_bases.include?(base_base_spelling)
|
|
1426
|
+
result = auto_generate_base_class(base_base_ref, base_base_spelling, under)
|
|
1427
|
+
end
|
|
1428
|
+
end
|
|
1429
|
+
end
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1432
|
+
@auto_generated_bases << base_spelling
|
|
1433
|
+
ruby_name = @template_resolver.ruby_name_from_template(base_spelling, base_template_arguments)
|
|
1434
|
+
cruby_name = "rb_c#{ruby_name}"
|
|
1435
|
+
|
|
1436
|
+
@classes[cruby_name] = base_spelling
|
|
1437
|
+
result + render_template("auto_generated_base_class",
|
|
1438
|
+
:cruby_name => cruby_name, :ruby_name => ruby_name,
|
|
1439
|
+
:base_spelling => base_spelling, :base_base_spelling => base_base_spelling,
|
|
1440
|
+
:base_template => base_template, :template_arguments => base_template_arguments_text,
|
|
1441
|
+
:under => under)
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
# Auto-generate a template base class for a non-template derived class.
|
|
1445
|
+
# For example: class PlaneWarper : public WarperBase<PlaneProjector>
|
|
1446
|
+
def auto_generate_template_base_for_class(base_specifier, base_spelling, under)
|
|
1447
|
+
auto_generate_base_class(base_specifier, base_spelling, under, recursive: false)
|
|
1448
|
+
end
|
|
1449
|
+
|
|
1450
|
+
# Render a union plus any embedded unions/structs that need to appear first.
|
|
1451
|
+
def visit_union(cursor)
|
|
1452
|
+
return if cursor.forward_declaration?
|
|
1453
|
+
return if cursor.anonymous?
|
|
1454
|
+
return if skip_symbol?(cursor)
|
|
1455
|
+
|
|
1456
|
+
result = Array.new
|
|
1457
|
+
|
|
1458
|
+
# Define any embedded unions (skip anonymous/skipped ones that return nil)
|
|
1459
|
+
cursor.find_by_kind(false, :cursor_union) do |union|
|
|
1460
|
+
content = visit_union(union)
|
|
1461
|
+
result << content if content
|
|
1462
|
+
end
|
|
1463
|
+
|
|
1464
|
+
# Define any embedded structures (skip anonymous/skipped ones that return nil)
|
|
1465
|
+
cursor.find_by_kind(false, :cursor_struct) do |struct|
|
|
1466
|
+
content = visit_struct(struct)
|
|
1467
|
+
result << content if content
|
|
1468
|
+
end
|
|
1469
|
+
|
|
1470
|
+
under = find_under(cursor)
|
|
1471
|
+
|
|
1472
|
+
children = render_children(cursor, indentation: 2, chain: true, terminate: true, strip: true,
|
|
1473
|
+
exclude_kinds: Set[:cursor_struct, :cursor_union])
|
|
1474
|
+
result << self.render_cursor(cursor, "union", :under => under, :children => children,
|
|
1475
|
+
:cpp_type => @type_speller.qualified_class_name(cursor),
|
|
1476
|
+
:ruby_name => cursor.ruby_name)
|
|
1477
|
+
result.map { |s| s.chomp }.join("\n\n")
|
|
1478
|
+
end
|
|
1479
|
+
|
|
1480
|
+
# Render a variable either as a Ruby constant or, for static class members,
|
|
1481
|
+
# as a singleton attribute on the owning Rice class.
|
|
1482
|
+
def visit_variable(cursor)
|
|
1483
|
+
if CURSOR_CLASSES.include?(cursor.semantic_parent.kind) &&
|
|
1484
|
+
!cursor.public?
|
|
1485
|
+
return
|
|
1486
|
+
end
|
|
1487
|
+
return if skip_symbol?(cursor)
|
|
1488
|
+
|
|
1489
|
+
# Skip compiler/cuda keywords like __device__ __forceinline__
|
|
1490
|
+
return if cursor.spelling.match(/^__.*__$/)
|
|
1491
|
+
|
|
1492
|
+
# Const variables become Ruby constants
|
|
1493
|
+
if cursor.type.const_qualified?
|
|
1494
|
+
visit_variable_constant(cursor)
|
|
1495
|
+
else
|
|
1496
|
+
parent_kind = cursor.semantic_parent.kind
|
|
1497
|
+
if parent_kind == :cursor_translation_unit || parent_kind == :cursor_namespace
|
|
1498
|
+
# Non-const variables at global/namespace scope become Ruby constants
|
|
1499
|
+
# Rice's define_singleton_attr only works on Data_Type<T>, not Class or Module
|
|
1500
|
+
visit_variable_constant(cursor)
|
|
1501
|
+
else
|
|
1502
|
+
# Static class fields use define_singleton_attr on Data_Type<T>
|
|
1503
|
+
qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
|
|
1504
|
+
self.render_cursor(cursor, "variable",
|
|
1505
|
+
:qualified_parent => qualified_parent)
|
|
1506
|
+
end
|
|
1507
|
+
end
|
|
1508
|
+
end
|
|
1509
|
+
|
|
1510
|
+
# Render one constant definition for an enum value, macro, or variable.
|
|
1511
|
+
def visit_variable_constant(cursor)
|
|
1512
|
+
self.render_cursor(cursor, "constant",
|
|
1513
|
+
:name => cursor.spelling.upcase_first,
|
|
1514
|
+
:qualified_name => @type_speller.qualified_display_name(cursor))
|
|
1515
|
+
end
|
|
1516
|
+
|
|
1517
|
+
# Render the optional project-level wrapper files that call every generated
|
|
1518
|
+
# per-header init function.
|
|
1519
|
+
def create_project_files
|
|
1520
|
+
return unless @project
|
|
1521
|
+
|
|
1522
|
+
# Create master hpp/cpp files to include all the files we generated
|
|
1523
|
+
basename = "#{project}-rb"
|
|
1524
|
+
rice_header = "#{basename}.hpp"
|
|
1525
|
+
rice_cpp = "#{basename}.cpp"
|
|
1526
|
+
init_function = "Init_#{project}"
|
|
1527
|
+
|
|
1528
|
+
content = render_template("project.hpp",
|
|
1529
|
+
:init_name => init_function, :init_names => @init_names)
|
|
1530
|
+
self.outputter.write(rice_header, content)
|
|
1531
|
+
|
|
1532
|
+
content = render_template("project.cpp",
|
|
1533
|
+
:project_header => rice_header, :init_name => init_function, :init_names => @init_names)
|
|
1534
|
+
self.outputter.write(rice_cpp, content)
|
|
1535
|
+
end
|
|
1536
|
+
|
|
1537
|
+
# Map a cursor kind such as `:cursor_class_decl` to the corresponding
|
|
1538
|
+
# visitor method symbol, for example `:visit_class_decl`.
|
|
1539
|
+
def figure_method(cursor)
|
|
1540
|
+
name = cursor.kind.to_s.delete_prefix("cursor_")
|
|
1541
|
+
"visit_#{name.underscore}".to_sym
|
|
1542
|
+
end
|
|
1543
|
+
|
|
1544
|
+
# Add left padding to non-blank lines while preserving existing blank lines.
|
|
1545
|
+
def add_indentation(content, indentation)
|
|
1546
|
+
content.lines.map do |line|
|
|
1547
|
+
# Don't add indentation to blank lines
|
|
1548
|
+
line.strip.empty? ? line : " " * indentation + line
|
|
1549
|
+
end.join
|
|
1550
|
+
end
|
|
1551
|
+
|
|
1552
|
+
# Render an ERB template with the current cursor injected into the locals.
|
|
1553
|
+
def render_cursor(cursor, template, local_variables = {})
|
|
1554
|
+
render_template(template, local_variables.merge(:cursor => cursor))
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
def template_cursor_definition(cursor)
|
|
1558
|
+
return nil unless cursor
|
|
1559
|
+
|
|
1560
|
+
definition = cursor.definition
|
|
1561
|
+
return cursor if definition.kind == :cursor_invalid_file || definition.kind == :cursor_no_decl_found
|
|
1562
|
+
|
|
1563
|
+
definition
|
|
1564
|
+
end
|
|
1565
|
+
|
|
1566
|
+
def nested_template_builder_requires_outer_context?(cursor)
|
|
1567
|
+
parent = cursor.semantic_parent
|
|
1568
|
+
while parent
|
|
1569
|
+
break if [:cursor_invalid_file, :cursor_no_decl_found, :cursor_translation_unit].include?(parent.kind)
|
|
1570
|
+
return true if [:cursor_class_template,
|
|
1571
|
+
:cursor_class_template_partial_specialization,
|
|
1572
|
+
:cursor_function_template].include?(parent.kind)
|
|
1573
|
+
parent = parent.semantic_parent
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
false
|
|
1577
|
+
end
|
|
1578
|
+
|
|
1579
|
+
# Returns [content, has_builders] where has_builders indicates if any builder templates were generated
|
|
1580
|
+
def render_class_templates(cursor, indentation: 0, strip: false)
|
|
1581
|
+
results = Array.new
|
|
1582
|
+
cursor.find_by_kind(true, :cursor_class_template) do |class_template_cursor|
|
|
1583
|
+
if class_template_cursor.private? || class_template_cursor.protected?
|
|
1584
|
+
next :continue
|
|
1585
|
+
end
|
|
1586
|
+
|
|
1587
|
+
# Nested class templates inside class templates need the outer
|
|
1588
|
+
# template parameters and dependent parent qualification to form a
|
|
1589
|
+
# valid builder signature. Until that context is threaded through the
|
|
1590
|
+
# builder templates, skip emitting them rather than generating
|
|
1591
|
+
# invalid C++ such as Allocator::rebind<U>.
|
|
1592
|
+
if nested_template_builder_requires_outer_context?(class_template_cursor)
|
|
1593
|
+
next :continue
|
|
1594
|
+
end
|
|
1595
|
+
|
|
1596
|
+
# Skip forward declarations
|
|
1597
|
+
if class_template_cursor.declaration? && !class_template_cursor.definition?
|
|
1598
|
+
next :continue
|
|
1599
|
+
end
|
|
1600
|
+
if class_template_cursor.location.in_system_header?
|
|
1601
|
+
next :continue
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
# Check if class template is from the main file.
|
|
1605
|
+
# Note: from_main_file? doesn't work when -include is used, so manually check.
|
|
1606
|
+
unless translation_unit_file?(class_template_cursor)
|
|
1607
|
+
next :continue
|
|
1608
|
+
end
|
|
1609
|
+
|
|
1610
|
+
# Skip if explicitly listed in symbols
|
|
1611
|
+
if skip_symbol?(class_template_cursor)
|
|
1612
|
+
next :continue
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
builder = visit_class_template_builder(class_template_cursor)
|
|
1616
|
+
results << builder if builder
|
|
1617
|
+
end
|
|
1618
|
+
content = merge_children({ nil => results }, indentation: indentation, strip: strip)
|
|
1619
|
+
has_builders = !results.empty? && !content.strip.empty?
|
|
1620
|
+
[content, has_builders]
|
|
1621
|
+
end
|
|
1622
|
+
|
|
1623
|
+
# Visit eligible child cursors and bucket their rendered output by version
|
|
1624
|
+
# guard so later merging can emit `#if VERSION >= ...` blocks cleanly.
|
|
1625
|
+
def visit_children(cursor, exclude_kinds: Set.new, only_kinds: nil)
|
|
1626
|
+
versions = Hash.new { |h, k| h[k] = [] }
|
|
1627
|
+
cursor.each(false) do |child_cursor, parent_cursor|
|
|
1628
|
+
if child_cursor.location.in_system_header?
|
|
1629
|
+
next :continue
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
# This sometimes does not work - for example OpenCV defines the macros
|
|
1633
|
+
# CV__DNN_INLINE_NS_BEGIN/CV__DNN_INLINE_NS_END in a separate header file
|
|
1634
|
+
# which causes from_main_file? to be false. So manually check.
|
|
1635
|
+
# unless child_cursor.location.from_main_file?
|
|
1636
|
+
unless translation_unit_file?(child_cursor)
|
|
1637
|
+
next :continue
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
# For some reason child.cursor.public? filters out way too much
|
|
1641
|
+
if child_cursor.private? || child_cursor.protected?
|
|
1642
|
+
next :continue
|
|
1643
|
+
end
|
|
1644
|
+
|
|
1645
|
+
if child_cursor.deleted?
|
|
1646
|
+
next :continue
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
child_kind = child_cursor.kind
|
|
1650
|
+
|
|
1651
|
+
unless child_cursor.declaration? || child_kind == :cursor_macro_definition
|
|
1652
|
+
next :continue
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
if child_cursor.forward_declaration?
|
|
1656
|
+
next :continue
|
|
1657
|
+
end
|
|
1658
|
+
|
|
1659
|
+
if exclude_kinds.include?(child_kind)
|
|
1660
|
+
next :continue
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1663
|
+
if only_kinds && !only_kinds.include?(child_kind)
|
|
1664
|
+
next :continue
|
|
1665
|
+
end
|
|
1666
|
+
|
|
1667
|
+
visit_method = "visit_#{child_kind.to_s.delete_prefix("cursor_").underscore}".to_sym
|
|
1668
|
+
if self.respond_to?(visit_method)
|
|
1669
|
+
content = self.send(visit_method, child_cursor)
|
|
1670
|
+
version = @symbols.version(child_cursor)
|
|
1671
|
+
case content
|
|
1672
|
+
when Array
|
|
1673
|
+
versions[version] += content
|
|
1674
|
+
when String
|
|
1675
|
+
versions[version] << content
|
|
1676
|
+
end
|
|
1677
|
+
end
|
|
1678
|
+
next :continue
|
|
1679
|
+
end
|
|
1680
|
+
versions
|
|
1681
|
+
end
|
|
1682
|
+
|
|
1683
|
+
# Merge previously rendered child content into final output text, with
|
|
1684
|
+
# optional method chaining, termination, indentation, and version guards.
|
|
1685
|
+
def merge_children(versions, indentation: 0, chain: false, terminate: false, strip: false)
|
|
1686
|
+
lines = versions.keys.sort_by { |key| key.to_s }.each_with_object([]) do |version, result|
|
|
1687
|
+
next unless versions[version]&.any?
|
|
1688
|
+
result << "#if #{@version_check} >= #{version}" if version
|
|
1689
|
+
versions[version].each do |line|
|
|
1690
|
+
line = line.rstrip if strip
|
|
1691
|
+
line = ".#{line}" if chain
|
|
1692
|
+
result << line
|
|
1693
|
+
end
|
|
1694
|
+
result << "#endif\n" if version
|
|
1695
|
+
end
|
|
1696
|
+
|
|
1697
|
+
if lines.empty?
|
|
1698
|
+
return terminate ? ";" : ""
|
|
1699
|
+
end
|
|
1700
|
+
|
|
1701
|
+
result = if chain
|
|
1702
|
+
lines.join("\n")
|
|
1703
|
+
else
|
|
1704
|
+
lines.map { |l| l.chomp }.reject(&:empty?).join("\n\n")
|
|
1705
|
+
end
|
|
1706
|
+
if terminate
|
|
1707
|
+
result += ";"
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
result = add_indentation(result, indentation) if indentation > 0
|
|
1711
|
+
result = "\n" + result if chain || terminate
|
|
1712
|
+
result
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
# Convenience wrapper around `visit_children` and `merge_children`.
|
|
1716
|
+
def render_children(cursor, indentation: 0, chain: false, terminate: false, strip: false,
|
|
1717
|
+
exclude_kinds: Set.new, only_kinds: nil)
|
|
1718
|
+
versions = visit_children(cursor, exclude_kinds: exclude_kinds, only_kinds: only_kinds)
|
|
1719
|
+
merge_children(versions, indentation: indentation, chain: chain, terminate: terminate, strip: strip)
|
|
1720
|
+
end
|
|
1721
|
+
|
|
1722
|
+
end
|
|
1723
|
+
end
|
|
1724
|
+
end
|