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,687 @@
|
|
|
1
|
+
module RubyBindgen
|
|
2
|
+
module Generators
|
|
3
|
+
class FFI < Generator
|
|
4
|
+
attr_reader :library_names, :library_versions
|
|
5
|
+
|
|
6
|
+
def self.template_dir
|
|
7
|
+
__dir__
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(inputter, outputter, config)
|
|
11
|
+
super(inputter, outputter, config)
|
|
12
|
+
raise ArgumentError, "FFI format requires the 'project' option" unless @project
|
|
13
|
+
@version_check = config[:version_check]
|
|
14
|
+
@library_names = config[:library_names] || []
|
|
15
|
+
@library_versions = config[:library_versions] || []
|
|
16
|
+
@symbols = RubyBindgen::Symbols.new(config[:symbols] || {})
|
|
17
|
+
raise ArgumentError, "version_check is required when symbols.versions is non-empty" if @symbols.has_versions? && !@version_check
|
|
18
|
+
@library_search_path = config[:library_search_path]
|
|
19
|
+
@export_macros = config[:export_macros] || []
|
|
20
|
+
@module_name = config[:module]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def generate
|
|
24
|
+
clang_args = @config[:clang_args] || []
|
|
25
|
+
parser = RubyBindgen::Parser.new(@inputter, clang_args, libclang: @config[:libclang])
|
|
26
|
+
symbols_config = @config[:symbols] || {}
|
|
27
|
+
rename_types = RubyBindgen::NameMapper.from_config(symbols_config[:rename_types] || [])
|
|
28
|
+
rename_methods = RubyBindgen::NameMapper.from_config(symbols_config[:rename_methods] || [])
|
|
29
|
+
@namer = RubyBindgen::Namer.new(rename_types, rename_methods)
|
|
30
|
+
::FFI::Clang::Cursor.namer = @namer
|
|
31
|
+
parser.generate(self)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Check if cursor has one of the required export macros in its source text.
|
|
35
|
+
# When export_macros is empty, all symbols pass (no filtering).
|
|
36
|
+
def has_export_macro?(cursor)
|
|
37
|
+
return true if @export_macros.empty?
|
|
38
|
+
|
|
39
|
+
source_text = cursor.extent.text
|
|
40
|
+
return false if source_text.nil?
|
|
41
|
+
@export_macros.any? { |macro| source_text.include?(macro) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def visit_start
|
|
45
|
+
@generated_files = []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def visit_translation_unit(translation_unit, path, relative_path)
|
|
49
|
+
basename = File.basename(relative_path, ".*").underscore
|
|
50
|
+
relative_path_2 = File.join(File.dirname(relative_path), "#{basename}.rb")
|
|
51
|
+
|
|
52
|
+
cursor = translation_unit.cursor
|
|
53
|
+
module_name = @module_name || cursor.ruby_name
|
|
54
|
+
module_parts = module_name.split("::")
|
|
55
|
+
|
|
56
|
+
content = render_children(cursor, indentation: module_parts.length * 2)
|
|
57
|
+
|
|
58
|
+
result = render_template("translation_unit",
|
|
59
|
+
:module_parts => module_parts,
|
|
60
|
+
:content => content.rstrip)
|
|
61
|
+
|
|
62
|
+
result.gsub!(/\n\n\n/, "\n\n")
|
|
63
|
+
@generated_files << File.join(File.dirname(relative_path), basename)
|
|
64
|
+
self.outputter.write(relative_path_2, result)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def visit_end
|
|
68
|
+
create_project_file
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def create_project_file
|
|
72
|
+
return if @generated_files.empty?
|
|
73
|
+
|
|
74
|
+
module_name = @module_name || @project.camelize
|
|
75
|
+
module_parts = module_name.split("::")
|
|
76
|
+
depth = module_parts.length
|
|
77
|
+
library = add_indentation(render_template("library"), depth * 2)
|
|
78
|
+
|
|
79
|
+
has_versions = @symbols.has_versions?
|
|
80
|
+
version_file = has_versions ? "#{@project}_version" : nil
|
|
81
|
+
|
|
82
|
+
content = render_template("project",
|
|
83
|
+
:module_parts => module_parts,
|
|
84
|
+
:library => library.rstrip,
|
|
85
|
+
:version_file => version_file,
|
|
86
|
+
:files => @generated_files)
|
|
87
|
+
|
|
88
|
+
self.outputter.write("#{@project}_ffi.rb", content)
|
|
89
|
+
|
|
90
|
+
create_version_file(module_parts) if version_file
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def create_version_file(module_parts)
|
|
94
|
+
relative_path = "#{@project}_version.rb"
|
|
95
|
+
full_path = self.outputter.output_path(relative_path)
|
|
96
|
+
return if File.exist?(full_path)
|
|
97
|
+
|
|
98
|
+
method_name = @version_check
|
|
99
|
+
depth = module_parts.length
|
|
100
|
+
method_body = add_indentation(render_template("version_method", :method_name => method_name), depth * 2)
|
|
101
|
+
|
|
102
|
+
content = render_template("version",
|
|
103
|
+
:module_parts => module_parts,
|
|
104
|
+
:method_body => method_body.rstrip)
|
|
105
|
+
self.outputter.write(relative_path, content)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def visit_children(cursor, exclude_kinds: Set.new)
|
|
109
|
+
versions = Hash.new { |h, k| h[k] = [] }
|
|
110
|
+
cursor.each(false) do |child_cursor, parent_cursor|
|
|
111
|
+
if child_cursor.location.in_system_header?
|
|
112
|
+
next :continue
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Note: from_main_file? doesn't work when -include is used, so manually check.
|
|
116
|
+
unless translation_unit_file?(child_cursor)
|
|
117
|
+
next :continue
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# For some reason child.cursor.public? filters out way too much
|
|
121
|
+
if child_cursor.private? || child_cursor.protected?
|
|
122
|
+
next :continue
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if child_cursor.deleted?
|
|
126
|
+
next :continue
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
unless child_cursor.declaration? || child_cursor.kind == :cursor_linkage_spec
|
|
130
|
+
next :continue
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if child_cursor.forward_declaration?
|
|
134
|
+
next :continue
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if exclude_kinds.include?(child_cursor.kind)
|
|
138
|
+
next :continue
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
visit_method = self.figure_method(child_cursor)
|
|
142
|
+
if self.respond_to?(visit_method)
|
|
143
|
+
content = self.send(visit_method, child_cursor)
|
|
144
|
+
version = @symbols.version(child_cursor)
|
|
145
|
+
case content
|
|
146
|
+
when Array
|
|
147
|
+
versions[version] += content
|
|
148
|
+
when String
|
|
149
|
+
versions[version] << content
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
next :continue
|
|
153
|
+
end
|
|
154
|
+
versions
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def merge_children(versions, indentation: 0, comma: false, strip: false)
|
|
158
|
+
lines = versions.keys.sort_by { |key| key.to_s }.each_with_object([]) do |version, result|
|
|
159
|
+
next unless versions[version]&.any?
|
|
160
|
+
result << "if #{@version_check} >= #{version}" if version
|
|
161
|
+
versions[version].each do |line|
|
|
162
|
+
line = line.rstrip if strip
|
|
163
|
+
line = add_indentation(line, 2) if version
|
|
164
|
+
result << line
|
|
165
|
+
end
|
|
166
|
+
result << "end" if version
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
return "" if lines.empty?
|
|
170
|
+
|
|
171
|
+
separator = comma ? ",\n" : "\n"
|
|
172
|
+
result = lines.join(separator)
|
|
173
|
+
result = add_indentation(result, indentation) if indentation > 0
|
|
174
|
+
result
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def render_children(cursor, indentation: 0, comma: false, strip: false, exclude_kinds: Set.new)
|
|
178
|
+
versions = visit_children(cursor, exclude_kinds: exclude_kinds)
|
|
179
|
+
merge_children(versions, indentation: indentation, comma: comma, strip: strip)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def visit_callback(name, parameters, type)
|
|
183
|
+
parameter_types = parameters.map do |parameter|
|
|
184
|
+
if parameter.find_first_by_kind(false, :cursor_type_ref) && parameter.type.is_a?(::FFI::Clang::Types::Pointer)
|
|
185
|
+
":pointer"
|
|
186
|
+
else
|
|
187
|
+
figure_ffi_type(parameter.type, :callback)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
self.render_callback(name, parameter_types, type.result_type)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def visit_enum_decl(cursor)
|
|
194
|
+
return if @symbols.skip?(cursor)
|
|
195
|
+
|
|
196
|
+
versions = visit_children(cursor)
|
|
197
|
+
has_versioned_constants = versions.keys.any? { |k| !k.nil? }
|
|
198
|
+
|
|
199
|
+
if has_versioned_constants
|
|
200
|
+
render_versioned_enum(cursor, versions)
|
|
201
|
+
else
|
|
202
|
+
children = merge_children(versions, indentation: 2, comma: true, strip: true)
|
|
203
|
+
template = cursor.anonymous? ? "enum_decl_anonymous" : "enum_decl"
|
|
204
|
+
self.render_cursor(cursor, template, :children => children)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def visit_enum_constant_decl(cursor)
|
|
209
|
+
self.render_cursor(cursor,"enum_constant_decl")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# extern "C" {} — transparent wrapper, recurse into children
|
|
213
|
+
def visit_linkage_spec(cursor)
|
|
214
|
+
versions = visit_children(cursor)
|
|
215
|
+
merge_children(versions)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def visit_function(cursor)
|
|
219
|
+
return if cursor.availability == :deprecated
|
|
220
|
+
return if @symbols.skip?(cursor)
|
|
221
|
+
return if references_skipped_type?(cursor.type.result_type)
|
|
222
|
+
return if has_skipped_param_type?(cursor)
|
|
223
|
+
return if has_va_list_param?(cursor)
|
|
224
|
+
return unless has_export_macro?(cursor)
|
|
225
|
+
|
|
226
|
+
signature = @symbols.override(cursor)
|
|
227
|
+
if signature
|
|
228
|
+
return self.render_cursor(cursor, "function", :parameter_types => nil, :signature => signature)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
result = Array.new
|
|
232
|
+
parameter_types = cursor.find_by_kind(false, :cursor_parm_decl).map do |parameter|
|
|
233
|
+
callback_name = "#{cursor.spelling.underscore}_#{parameter.spelling.underscore}_callback"
|
|
234
|
+
if parameter.type.is_a?(::FFI::Clang::Types::Pointer) && parameter.type.function?
|
|
235
|
+
parameters = parameter.find_by_kind(false, :cursor_parm_decl)
|
|
236
|
+
result << self.visit_callback(callback_name, parameters, parameter.type.pointee)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
if parameter.type.is_a?(::FFI::Clang::Types::Pointer) && parameter.type.function?
|
|
240
|
+
":#{callback_name}"
|
|
241
|
+
else
|
|
242
|
+
figure_ffi_type(parameter.type, :function)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
parameter_types << ":varargs" if cursor.type.variadic?
|
|
246
|
+
result << self.render_cursor(cursor, "function", :parameter_types => parameter_types, :signature => nil)
|
|
247
|
+
result.join("\n")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def visit_struct(cursor)
|
|
251
|
+
return if cursor.forward_declaration?
|
|
252
|
+
return if cursor.opaque_declaration?
|
|
253
|
+
return if @symbols.skip?(cursor)
|
|
254
|
+
return if cursor.anonymous? && !cursor.anonymous_definer
|
|
255
|
+
|
|
256
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
257
|
+
|
|
258
|
+
# Define any embedded structures
|
|
259
|
+
cursor.find_by_kind(false, :cursor_struct).each do |struct|
|
|
260
|
+
content = visit_struct(struct)
|
|
261
|
+
next unless content
|
|
262
|
+
version = @symbols.version(struct)
|
|
263
|
+
result[version] << content
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Define any embedded unions
|
|
267
|
+
cursor.find_by_kind(false, :cursor_union).each do |union|
|
|
268
|
+
content = visit_union(union)
|
|
269
|
+
next unless content
|
|
270
|
+
version = @symbols.version(union)
|
|
271
|
+
result[version] << content
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Define any embedded callbacks
|
|
275
|
+
cursor.find_by_kind(false, :cursor_field_decl).each do |field|
|
|
276
|
+
if field.type.is_a?(::FFI::Clang::Types::Pointer) && field.type.function?
|
|
277
|
+
callback_name = "#{cursor.spelling.underscore}_#{field.spelling.underscore}_callback"
|
|
278
|
+
parameters = field.find_by_kind(false, :cursor_parm_decl)
|
|
279
|
+
result[nil] << self.visit_callback(callback_name, parameters, field.type.pointee)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
versions = visit_children(cursor, exclude_kinds: [:cursor_struct, :cursor_union])
|
|
284
|
+
has_versioned_fields = versions.keys.any? { |k| !k.nil? }
|
|
285
|
+
|
|
286
|
+
if has_versioned_fields
|
|
287
|
+
result[nil] << render_versioned_layout(cursor, versions, "struct")
|
|
288
|
+
else
|
|
289
|
+
children = merge_children(versions, indentation: 9, comma: true, strip: true)
|
|
290
|
+
result[nil] << self.render_cursor(cursor, "struct", :children => children.lstrip)
|
|
291
|
+
end
|
|
292
|
+
merge_children(result)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def visit_field_decl(cursor)
|
|
296
|
+
ffi_type = if cursor.type.is_a?(::FFI::Clang::Types::Pointer)
|
|
297
|
+
if cursor.type.function?
|
|
298
|
+
":#{cursor.semantic_parent.spelling.underscore}_#{cursor.spelling.underscore}_callback"
|
|
299
|
+
elsif pointer_to_forward_declaration?(cursor.type)
|
|
300
|
+
":pointer"
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
ffi_type ||= figure_ffi_type(cursor.type, :structure)
|
|
305
|
+
self.render_cursor(cursor, "field_decl", ffi_type: ffi_type)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def visit_typedef_decl(cursor)
|
|
309
|
+
return if @symbols.skip?(cursor)
|
|
310
|
+
|
|
311
|
+
underlying_type = cursor.underlying_type
|
|
312
|
+
canonical_type = underlying_type.canonical
|
|
313
|
+
|
|
314
|
+
if [:type_record, :type_enum].include?(canonical_type.kind)
|
|
315
|
+
# Opaque struct/union typedef - emit typedef :pointer
|
|
316
|
+
if canonical_type.declaration.opaque_declaration?
|
|
317
|
+
return render_cursor(cursor, "typedef_decl")
|
|
318
|
+
end
|
|
319
|
+
# Otherwise it's a struct/union/enum we have already rendered - skip
|
|
320
|
+
return
|
|
321
|
+
elsif underlying_type.kind == :type_pointer && underlying_type.function?
|
|
322
|
+
func_type = underlying_type.pointee
|
|
323
|
+
parameter_types = (0...func_type.args_size).map { |i| figure_ffi_type(func_type.arg_type(i), :callback) }
|
|
324
|
+
return render_callback(cursor.ruby_name, parameter_types, func_type.result_type)
|
|
325
|
+
end
|
|
326
|
+
render_cursor(cursor, "typedef_decl")
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def visit_union(cursor)
|
|
330
|
+
return if cursor.forward_declaration?
|
|
331
|
+
return if cursor.opaque_declaration?
|
|
332
|
+
return if @symbols.skip?(cursor)
|
|
333
|
+
return if cursor.anonymous? && !cursor.anonymous_definer
|
|
334
|
+
|
|
335
|
+
result = Hash.new { |h, k| h[k] = [] }
|
|
336
|
+
|
|
337
|
+
# Define any embedded unions
|
|
338
|
+
cursor.find_by_kind(false, :cursor_union).each do |union|
|
|
339
|
+
content = visit_union(union)
|
|
340
|
+
next unless content
|
|
341
|
+
version = @symbols.version(union)
|
|
342
|
+
result[version] << content
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Define any embedded structures
|
|
346
|
+
cursor.find_by_kind(false, :cursor_struct).each do |struct|
|
|
347
|
+
content = visit_struct(struct)
|
|
348
|
+
next unless content
|
|
349
|
+
version = @symbols.version(struct)
|
|
350
|
+
result[version] << content
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Define any embedded callbacks
|
|
354
|
+
cursor.find_by_kind(false, :cursor_field_decl).each do |field|
|
|
355
|
+
if field.type.is_a?(::FFI::Clang::Types::Pointer) && field.type.function?
|
|
356
|
+
callback_name = "#{cursor.spelling.underscore}_#{field.spelling.underscore}_callback"
|
|
357
|
+
parameters = field.find_by_kind(false, :cursor_parm_decl)
|
|
358
|
+
result[nil] << self.visit_callback(callback_name, parameters, field.type.pointee)
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
versions = visit_children(cursor, exclude_kinds: [:cursor_struct, :cursor_union])
|
|
363
|
+
has_versioned_fields = versions.keys.any? { |k| !k.nil? }
|
|
364
|
+
|
|
365
|
+
if has_versioned_fields
|
|
366
|
+
result[nil] << render_versioned_layout(cursor, versions, "union")
|
|
367
|
+
else
|
|
368
|
+
children = merge_children(versions, indentation: 9, comma: true, strip: true)
|
|
369
|
+
result[nil] << self.render_cursor(cursor, "union", :children => children.lstrip)
|
|
370
|
+
end
|
|
371
|
+
merge_children(result)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def visit_variable(cursor)
|
|
375
|
+
return if @symbols.skip?(cursor)
|
|
376
|
+
|
|
377
|
+
if cursor.type.const_qualified?
|
|
378
|
+
tokens = cursor.translation_unit.tokenize(cursor.extent)
|
|
379
|
+
eq_index = tokens.tokens.index { |t| t.spelling == "=" }
|
|
380
|
+
if eq_index && tokens.tokens[eq_index + 1]&.kind == :literal
|
|
381
|
+
return render_cursor(cursor, "constant",
|
|
382
|
+
name: cursor.ruby_name,
|
|
383
|
+
value: literal_to_ruby(tokens.tokens[eq_index + 1].spelling))
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
self.render_cursor(cursor, "variable")
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Convert a C literal token spelling into something that parses as Ruby.
|
|
391
|
+
# libclang gives us the verbatim source text, so C numeric suffixes
|
|
392
|
+
# (2.5f, 100000L, 42U, 1ULL, 0xABCDuLL) flow through unchanged and break
|
|
393
|
+
# the generated module. Char ('A') and string ("hello") literals are
|
|
394
|
+
# returned as-is — they're already valid Ruby strings.
|
|
395
|
+
#
|
|
396
|
+
# Hex literals need different treatment: f/F are valid hex digits, so
|
|
397
|
+
# 0xff must NOT have its trailing f stripped (would yield 0xf = 15
|
|
398
|
+
# instead of 255). Hex strips integer suffixes only.
|
|
399
|
+
C_INT_SUFFIX = /(?:[uU][lL]{0,2}|[lL]{1,2}[uU]?)\z/.freeze
|
|
400
|
+
C_FLOAT_SUFFIX = /(?:[uU][lL]{0,2}|[lL]{1,2}[uU]?|[fFlL])\z/.freeze
|
|
401
|
+
def literal_to_ruby(spelling)
|
|
402
|
+
return spelling if spelling.start_with?("'", '"')
|
|
403
|
+
suffix = spelling.match?(/\A[+-]?0[xX]/) ? C_INT_SUFFIX : C_FLOAT_SUFFIX
|
|
404
|
+
spelling.sub(suffix, '')
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def figure_method(cursor)
|
|
408
|
+
name = cursor.kind.to_s.delete_prefix("cursor_")
|
|
409
|
+
"visit_#{name.underscore}".to_sym
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Check if any parameter is a va_list type, which cannot be constructed from Ruby.
|
|
413
|
+
# va_list representation varies by platform:
|
|
414
|
+
# Linux x86_64: elaborated(va_list) -> typedef(__builtin_va_list) -> struct __va_list_tag[1]
|
|
415
|
+
# Other targets: may use __builtin_va_list, __gnuc_va_list, or other forms
|
|
416
|
+
# The typedef declaration spelling is the most reliable check.
|
|
417
|
+
VA_LIST_NAMES = Set.new(%w[va_list __builtin_va_list __gnuc_va_list]).freeze
|
|
418
|
+
|
|
419
|
+
def has_va_list_param?(cursor)
|
|
420
|
+
cursor.find_by_kind(false, :cursor_parm_decl).any? do |param|
|
|
421
|
+
type = param.type
|
|
422
|
+
# Check declaration spelling (works for elaborated and typedef types)
|
|
423
|
+
decl = type.declaration
|
|
424
|
+
next true if decl.kind == :cursor_typedef_decl && VA_LIST_NAMES.include?(decl.spelling)
|
|
425
|
+
|
|
426
|
+
# Fallback: check canonical type for __va_list_tag (Linux x86_64 array form)
|
|
427
|
+
canonical = type.canonical
|
|
428
|
+
canonical.kind == :type_constant_array &&
|
|
429
|
+
canonical.element_type.kind == :type_record &&
|
|
430
|
+
canonical.element_type.declaration.spelling == "__va_list_tag"
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Check if any parameter type of a function references a skipped symbol.
|
|
435
|
+
def has_skipped_param_type?(cursor)
|
|
436
|
+
(0...cursor.type.args_size).any? do |i|
|
|
437
|
+
references_skipped_type?(cursor.type.arg_type(i))
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Check if a type references a skipped symbol (unwrapping pointers).
|
|
442
|
+
def references_skipped_type?(type)
|
|
443
|
+
type = type.intrinsic_type
|
|
444
|
+
decl = type.declaration
|
|
445
|
+
return false if decl.kind == :cursor_no_decl_found
|
|
446
|
+
@symbols.skip?(decl)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def pointer_to_forward_declaration?(type)
|
|
450
|
+
return false unless type.kind == :type_pointer
|
|
451
|
+
|
|
452
|
+
decl = type.pointee.canonical.declaration
|
|
453
|
+
return false if [:cursor_invalid_file, :cursor_no_decl_found].include?(decl.kind)
|
|
454
|
+
|
|
455
|
+
decl.opaque_declaration?
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def figure_ffi_type(type, context = nil)
|
|
459
|
+
case type.kind
|
|
460
|
+
when :type_bool
|
|
461
|
+
":bool"
|
|
462
|
+
when :type_float
|
|
463
|
+
":float"
|
|
464
|
+
when :type_double
|
|
465
|
+
":double"
|
|
466
|
+
when :type_int
|
|
467
|
+
":int"
|
|
468
|
+
when :type_long
|
|
469
|
+
":long"
|
|
470
|
+
when :type_longlong
|
|
471
|
+
":long_long"
|
|
472
|
+
when :type_ulong
|
|
473
|
+
":ulong"
|
|
474
|
+
when :type_ulonglong
|
|
475
|
+
":ulong_long"
|
|
476
|
+
when :type_uint
|
|
477
|
+
":uint"
|
|
478
|
+
when :type_short
|
|
479
|
+
":short"
|
|
480
|
+
when :type_ushort
|
|
481
|
+
":ushort"
|
|
482
|
+
when :type_char_s
|
|
483
|
+
":char"
|
|
484
|
+
when :type_uchar, :type_char_u
|
|
485
|
+
":uchar"
|
|
486
|
+
when :type_schar
|
|
487
|
+
":int8"
|
|
488
|
+
when :type_wchar
|
|
489
|
+
figure_ffi_type(type.canonical, context)
|
|
490
|
+
when :type_char16
|
|
491
|
+
":uint16"
|
|
492
|
+
when :type_char32
|
|
493
|
+
":uint32"
|
|
494
|
+
when :type_longdouble
|
|
495
|
+
":long_double"
|
|
496
|
+
when :type_int128, :type_uint128
|
|
497
|
+
raise("Unsupported 128-bit integer type: #{type.kind}")
|
|
498
|
+
when :type_void
|
|
499
|
+
":void"
|
|
500
|
+
when :type_elaborated
|
|
501
|
+
figure_ffi_declared_type(type, context)
|
|
502
|
+
when :type_record
|
|
503
|
+
figure_ffi_record_type(type, context)
|
|
504
|
+
when :type_typedef
|
|
505
|
+
figure_ffi_declared_type(type, context)
|
|
506
|
+
when :type_pointer
|
|
507
|
+
figure_ffi_pointer_type(type, context)
|
|
508
|
+
when :type_enum
|
|
509
|
+
type.declaration.ruby_name
|
|
510
|
+
when :type_constant_array
|
|
511
|
+
case context
|
|
512
|
+
when :structure
|
|
513
|
+
"[#{figure_ffi_type(type.element_type)}, #{type.size}]"
|
|
514
|
+
else
|
|
515
|
+
":pointer"
|
|
516
|
+
end
|
|
517
|
+
when :type_incomplete_array
|
|
518
|
+
case type.element_type.kind
|
|
519
|
+
when :type_char_s
|
|
520
|
+
":string"
|
|
521
|
+
else
|
|
522
|
+
raise("Unsupported incomplete array type: #{type.element_type.kind}")
|
|
523
|
+
end
|
|
524
|
+
when :type_block_pointer
|
|
525
|
+
# Objective-C block pointers (macOS) — treat as opaque pointer
|
|
526
|
+
":pointer"
|
|
527
|
+
else
|
|
528
|
+
raise("Unsupported type: #{type.kind}")
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def figure_ffi_declared_type(type, context = nil)
|
|
533
|
+
if type.declaration.spelling == "va_list"
|
|
534
|
+
# va_list cannot be constructed from Ruby — functions with va_list
|
|
535
|
+
# params are skipped in visit_function. Map to :pointer as fallback.
|
|
536
|
+
":pointer"
|
|
537
|
+
elsif type.canonical.kind == :type_pointer && type.canonical.function?
|
|
538
|
+
# Typedef'd function pointer (callback) — use the callback name
|
|
539
|
+
":#{type.declaration.ruby_name}"
|
|
540
|
+
elsif type.canonical.kind == :type_function_proto
|
|
541
|
+
":pointer"
|
|
542
|
+
elsif type.canonical.kind == :type_record
|
|
543
|
+
figure_ffi_record_type(type, context)
|
|
544
|
+
elsif type.canonical.kind == :type_enum
|
|
545
|
+
figure_ffi_type(type.canonical, context)
|
|
546
|
+
else
|
|
547
|
+
spelling = type.declaration.spelling
|
|
548
|
+
if ::FFI::TypeDefs.key?(spelling.to_sym)
|
|
549
|
+
":#{spelling}"
|
|
550
|
+
else
|
|
551
|
+
self.figure_ffi_type(type.canonical, context)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def figure_ffi_record_type(type, context = nil)
|
|
557
|
+
if type.canonical.declaration.opaque_declaration?
|
|
558
|
+
return ":pointer"
|
|
559
|
+
end
|
|
560
|
+
if type.anonymous?
|
|
561
|
+
definer = type.declaration.anonymous_definer
|
|
562
|
+
return definer ? definer.spelling.camelize : ":pointer"
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
case
|
|
566
|
+
when context == :function
|
|
567
|
+
"#{type.declaration.ruby_name}.by_value"
|
|
568
|
+
when context == :callback
|
|
569
|
+
"#{type.declaration.ruby_name}.by_value"
|
|
570
|
+
else
|
|
571
|
+
type.declaration.ruby_name
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def figure_ffi_pointer_type(type, context)
|
|
576
|
+
case type.pointee.kind
|
|
577
|
+
when :type_char_s
|
|
578
|
+
case context
|
|
579
|
+
when :callback_return
|
|
580
|
+
":pointer"
|
|
581
|
+
else
|
|
582
|
+
type.pointee.const_qualified? ? ":string" : ":pointer"
|
|
583
|
+
end
|
|
584
|
+
else
|
|
585
|
+
figure_ffi_record_pointer_type(type.pointee, context) || ":pointer"
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def figure_ffi_record_pointer_type(type, context)
|
|
590
|
+
return nil unless type.canonical.kind == :type_record
|
|
591
|
+
return ":pointer" if type.canonical.declaration.opaque_declaration?
|
|
592
|
+
|
|
593
|
+
case context
|
|
594
|
+
when :union, :structure, :typedef
|
|
595
|
+
"#{type.canonical.declaration.ruby_name}.ptr"
|
|
596
|
+
when :function, :callback, :callback_return
|
|
597
|
+
"#{type.canonical.declaration.ruby_name}.by_ref"
|
|
598
|
+
else
|
|
599
|
+
type.canonical.declaration.ruby_name
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def add_indentation(content, indentation)
|
|
605
|
+
content.lines.map do |line|
|
|
606
|
+
if line.strip.empty?
|
|
607
|
+
line
|
|
608
|
+
else
|
|
609
|
+
" " * indentation + line
|
|
610
|
+
end
|
|
611
|
+
end.join
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Render a struct/union with versioned fields as separate definitions per version threshold.
|
|
615
|
+
# Each version gets a complete definition with cumulative fields up to that version.
|
|
616
|
+
# Output is wrapped in if/elsif/else guards.
|
|
617
|
+
def render_versioned_layout(cursor, versions, template)
|
|
618
|
+
# Build sorted version thresholds (nil = unversioned, always included)
|
|
619
|
+
thresholds = versions.keys.compact.sort.reverse
|
|
620
|
+
|
|
621
|
+
# Build cumulative field lists: each threshold includes all fields from lower versions
|
|
622
|
+
lines = []
|
|
623
|
+
first = true
|
|
624
|
+
thresholds.each do |threshold|
|
|
625
|
+
guard = first ? "if" : "elsif"
|
|
626
|
+
first = false
|
|
627
|
+
lines << "#{guard} #{@version_check} >= #{threshold}"
|
|
628
|
+
|
|
629
|
+
# Collect all fields: unversioned + all versions <= threshold
|
|
630
|
+
cumulative_fields = (versions[nil] || []).dup
|
|
631
|
+
versions.keys.compact.sort.each do |v|
|
|
632
|
+
cumulative_fields.concat(versions[v]) if v <= threshold
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
children = cumulative_fields.map(&:rstrip).join(",\n" + " " * 9)
|
|
636
|
+
lines << add_indentation(render_cursor(cursor, template, :children => children).strip, 2)
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# else branch: unversioned fields only (may be empty, but type must still be defined)
|
|
640
|
+
base_fields = versions[nil] || []
|
|
641
|
+
lines << "else"
|
|
642
|
+
children = base_fields.map(&:rstrip).join(",\n" + " " * 9)
|
|
643
|
+
lines << add_indentation(render_cursor(cursor, template, :children => children).strip, 2)
|
|
644
|
+
lines << "end"
|
|
645
|
+
lines.join("\n")
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
# Render an enum with versioned constants as separate definitions per version threshold.
|
|
649
|
+
def render_versioned_enum(cursor, versions)
|
|
650
|
+
thresholds = versions.keys.compact.sort.reverse
|
|
651
|
+
|
|
652
|
+
lines = []
|
|
653
|
+
first = true
|
|
654
|
+
thresholds.each do |threshold|
|
|
655
|
+
guard = first ? "if" : "elsif"
|
|
656
|
+
first = false
|
|
657
|
+
lines << "#{guard} #{@version_check} >= #{threshold}"
|
|
658
|
+
|
|
659
|
+
cumulative = (versions[nil] || []).dup
|
|
660
|
+
versions.keys.compact.sort.each do |v|
|
|
661
|
+
cumulative.concat(versions[v]) if v <= threshold
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
children = add_indentation(cumulative.map(&:rstrip).join(",\n"), 2)
|
|
665
|
+
lines << add_indentation(render_cursor(cursor, "enum_decl", :children => children).strip, 2)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# else branch: unversioned constants only (may be empty, but enum must still be defined)
|
|
669
|
+
base = versions[nil] || []
|
|
670
|
+
lines << "else"
|
|
671
|
+
children = add_indentation(base.map(&:rstrip).join(",\n"), 2)
|
|
672
|
+
lines << add_indentation(render_cursor(cursor, "enum_decl", :children => children).strip, 2)
|
|
673
|
+
lines << "end"
|
|
674
|
+
lines.join("\n")
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def render_cursor(cursor, template, local_variables = {})
|
|
678
|
+
render_template(template, local_variables.merge(:cursor => cursor))
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def render_callback(name, parameter_types, result_type)
|
|
682
|
+
render_template("callback", :ruby => name, :parameter_types => parameter_types,
|
|
683
|
+
:result_type => result_type)
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:<%= cursor.ruby_name.empty? ? "unnamed_#{cursor.semantic_parent.map {|child, parent| child}.index(cursor)}" : cursor.ruby_name %>, <%= ffi_type %>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<%- if signature -%>
|
|
2
|
+
attach_function :<%= cursor.ruby_name %>, :<%= cursor.spelling %>, <%= signature -%>
|
|
3
|
+
<%- else -%>
|
|
4
|
+
attach_function :<%= cursor.ruby_name %>, :<%= cursor.spelling %>, [<%= parameter_types.join(", ") %>], <%= figure_ffi_type(cursor.result_type, :function) -%>
|
|
5
|
+
<%- end -%>
|