ghazel-ffi_gen 1.3.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/LICENSE +20 -0
- data/README.md +83 -0
- data/lib/ffi_gen/clang.rb +5932 -0
- data/lib/ffi_gen/empty.h +0 -0
- data/lib/ffi_gen/java_enum.java +3 -0
- data/lib/ffi_gen/java_output.rb +324 -0
- data/lib/ffi_gen/java_pre.java +35 -0
- data/lib/ffi_gen/ruby_output.rb +302 -0
- data/lib/ffi_gen.rb +763 -0
- metadata +67 -0
data/lib/ffi_gen.rb
ADDED
@@ -0,0 +1,763 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
class FFIGen
|
4
|
+
require "ffi_gen/clang"
|
5
|
+
|
6
|
+
class << Clang
|
7
|
+
def get_children(cursor)
|
8
|
+
children = []
|
9
|
+
visit_children cursor, lambda { |visit_result, child, child_parent, child_client_data|
|
10
|
+
children << child
|
11
|
+
:continue
|
12
|
+
}, nil
|
13
|
+
children
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_spelling_location_data(location)
|
17
|
+
file_ptr = FFI::MemoryPointer.new :pointer
|
18
|
+
line_ptr = FFI::MemoryPointer.new :uint
|
19
|
+
column_ptr = FFI::MemoryPointer.new :uint
|
20
|
+
offset_ptr = FFI::MemoryPointer.new :uint
|
21
|
+
get_spelling_location location, file_ptr, line_ptr, column_ptr, offset_ptr
|
22
|
+
{ file: file_ptr.read_pointer, line: line_ptr.read_uint, column: column_ptr.read_uint, offset: offset_ptr.read_uint }
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_tokens(translation_unit, range)
|
26
|
+
tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
|
27
|
+
num_tokens_ptr = FFI::MemoryPointer.new :uint
|
28
|
+
Clang.tokenize translation_unit, range, tokens_ptr_ptr, num_tokens_ptr
|
29
|
+
num_tokens = num_tokens_ptr.read_uint
|
30
|
+
tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer
|
31
|
+
(num_tokens - 1).times.map { |i| Clang::Token.new tokens_ptr[i] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Clang::String
|
36
|
+
def to_s
|
37
|
+
Clang.get_c_string self
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s_and_dispose
|
41
|
+
str = to_s
|
42
|
+
Clang.dispose_string self
|
43
|
+
str
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Clang::Cursor
|
48
|
+
def ==(other)
|
49
|
+
other.is_a?(Clang::Cursor) && Clang.equal_cursors(self, other) == 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def eql?(other)
|
53
|
+
self == other
|
54
|
+
end
|
55
|
+
|
56
|
+
def hash
|
57
|
+
Clang.hash_cursor self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Clang::Type
|
62
|
+
def ==(other)
|
63
|
+
other.is_a?(Clang::Type) && Clang.equal_types(self, other) == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
def eql?(other)
|
67
|
+
self == other
|
68
|
+
end
|
69
|
+
|
70
|
+
def hash
|
71
|
+
0 # no hash available
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Type
|
76
|
+
end
|
77
|
+
|
78
|
+
class Enum < Type
|
79
|
+
attr_accessor :name
|
80
|
+
|
81
|
+
def initialize(generator, name, constants, description)
|
82
|
+
@generator = generator
|
83
|
+
@name = name
|
84
|
+
@constants = constants
|
85
|
+
@description = description
|
86
|
+
end
|
87
|
+
|
88
|
+
def shorten_names
|
89
|
+
return if @constants.size < 2
|
90
|
+
names = @constants.map { |constant| constant[:name].parts }
|
91
|
+
names.each(&:shift) while names.map(&:first).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.first.downcase
|
92
|
+
names.each(&:pop) while names.map(&:last).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.last.downcase
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class StructOrUnion < Type
|
97
|
+
attr_accessor :name, :description
|
98
|
+
attr_reader :fields, :oo_functions, :written
|
99
|
+
|
100
|
+
def initialize(generator, name, is_union)
|
101
|
+
@generator = generator
|
102
|
+
@name = name
|
103
|
+
@is_union = is_union
|
104
|
+
@description = []
|
105
|
+
@fields = []
|
106
|
+
@oo_functions = []
|
107
|
+
@written = false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class FunctionOrCallback < Type
|
112
|
+
attr_reader :name, :parameters, :return_type, :function_description, :return_value_description
|
113
|
+
|
114
|
+
def initialize(generator, name, parameters, return_type, is_callback, blocking, function_description, return_value_description)
|
115
|
+
@generator = generator
|
116
|
+
@name = name
|
117
|
+
@parameters = parameters
|
118
|
+
@return_type = return_type
|
119
|
+
@is_callback = is_callback
|
120
|
+
@blocking = blocking
|
121
|
+
@function_description = function_description
|
122
|
+
@return_value_description = return_value_description
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Define
|
127
|
+
def initialize(generator, name, parameters, value)
|
128
|
+
@generator = generator
|
129
|
+
@name = name
|
130
|
+
@parameters = parameters
|
131
|
+
@value = value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Writer
|
136
|
+
attr_reader :output
|
137
|
+
|
138
|
+
def initialize(indentation_prefix, comment_prefix, comment_start = nil, comment_end = nil)
|
139
|
+
@indentation_prefix = indentation_prefix
|
140
|
+
@comment_prefix = comment_prefix
|
141
|
+
@comment_start = comment_start
|
142
|
+
@comment_end = comment_end
|
143
|
+
@current_indentation = ""
|
144
|
+
@output = ""
|
145
|
+
end
|
146
|
+
|
147
|
+
def indent(prefix = @indentation_prefix)
|
148
|
+
previous_indentation = @current_indentation
|
149
|
+
@current_indentation += prefix
|
150
|
+
yield
|
151
|
+
@current_indentation = previous_indentation
|
152
|
+
end
|
153
|
+
|
154
|
+
def comment(&block)
|
155
|
+
self.puts @comment_start unless @comment_start.nil?
|
156
|
+
self.indent @comment_prefix, &block
|
157
|
+
self.puts @comment_end unless @comment_end.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
def puts(*lines)
|
161
|
+
lines.each do |line|
|
162
|
+
@output << "#{@current_indentation}#{line}\n"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def write_array(array, separator = "", first_line_prefix = "", other_lines_prefix = "")
|
167
|
+
array.each_with_index do |entry, index|
|
168
|
+
entry = yield entry if block_given?
|
169
|
+
puts "#{index == 0 ? first_line_prefix : other_lines_prefix}#{entry}#{index < array.size - 1 ? separator : ''}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def write_description(description, not_documented_message = true, first_line_prefix = "", other_lines_prefix = "")
|
174
|
+
description.shift while not description.empty? and description.first.strip.empty?
|
175
|
+
description.pop while not description.empty? and description.last.strip.empty?
|
176
|
+
description.map! { |line| line.gsub "\t", " " }
|
177
|
+
space_prefix_length = description.map{ |line| line.index(/\S/) }.compact.min
|
178
|
+
description.map! { |line| line[space_prefix_length..-1] }
|
179
|
+
description << (not_documented_message ? "(Not documented)" : "") if description.empty?
|
180
|
+
|
181
|
+
write_array description, "", first_line_prefix, other_lines_prefix
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class Name
|
186
|
+
attr_reader :parts, :raw
|
187
|
+
|
188
|
+
def initialize(parts, raw = nil)
|
189
|
+
@parts = parts
|
190
|
+
@raw = raw
|
191
|
+
end
|
192
|
+
|
193
|
+
def format(*modes, keyword_blacklist)
|
194
|
+
parts = @parts.dup
|
195
|
+
parts.map!(&:downcase) if modes.include? :downcase
|
196
|
+
parts.map!(&:upcase) if modes.include? :upcase
|
197
|
+
parts.map! { |s| s[0].upcase + s[1..-1] } if modes.include? :camelcase
|
198
|
+
parts[0] = parts[0][0].downcase + parts[0][1..-1] if modes.include? :initial_downcase
|
199
|
+
str = parts.join(modes.include?(:underscores) ? "_" : "")
|
200
|
+
str.sub!(/^\d/, '_\0') # fix illegal beginnings
|
201
|
+
str = "#{str}_" if keyword_blacklist.include? str
|
202
|
+
str
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class PrimitiveType < Type
|
207
|
+
def initialize(clang_type)
|
208
|
+
@clang_type = clang_type
|
209
|
+
end
|
210
|
+
|
211
|
+
def name
|
212
|
+
Name.new [@clang_type.to_s]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
class StringType < Type
|
217
|
+
def name
|
218
|
+
Name.new ["string"]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class ByValueType < Type
|
223
|
+
def initialize(inner_type)
|
224
|
+
@inner_type = inner_type
|
225
|
+
end
|
226
|
+
|
227
|
+
def name
|
228
|
+
@inner_type.name
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class PointerType < Type
|
233
|
+
attr_reader :pointee_name, :depth
|
234
|
+
|
235
|
+
def initialize(pointee_name, depth)
|
236
|
+
@pointee_name = pointee_name
|
237
|
+
@depth = depth
|
238
|
+
end
|
239
|
+
|
240
|
+
def name
|
241
|
+
@pointee_name
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class ArrayType < Type
|
246
|
+
def initialize(element_type, constant_size)
|
247
|
+
@element_type = element_type
|
248
|
+
@constant_size = constant_size
|
249
|
+
end
|
250
|
+
|
251
|
+
def name
|
252
|
+
Name.new ["array"]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class UnknownType < Type
|
257
|
+
def name
|
258
|
+
Name.new ["unknown"]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
attr_reader :module_name, :ffi_lib, :headers, :prefixes, :output, :cflags
|
263
|
+
|
264
|
+
def initialize(options = {})
|
265
|
+
@module_name = options[:module_name] or fail "No module name given."
|
266
|
+
@ffi_lib = options.fetch :ffi_lib, nil
|
267
|
+
@headers = options[:headers] or fail "No headers given."
|
268
|
+
@cflags = options.fetch :cflags, []
|
269
|
+
@prefixes = options.fetch :prefixes, []
|
270
|
+
@suffixes = options.fetch :suffixes, []
|
271
|
+
@blocking = options.fetch :blocking, []
|
272
|
+
@ffi_lib_flags = options.fetch :ffi_lib_flags, nil
|
273
|
+
@output = options.fetch :output, $stdout
|
274
|
+
|
275
|
+
@translation_unit = nil
|
276
|
+
@declarations = nil
|
277
|
+
end
|
278
|
+
|
279
|
+
def generate
|
280
|
+
code = send "generate_#{File.extname(@output)[1..-1]}"
|
281
|
+
if @output.is_a? String
|
282
|
+
File.open(@output, "w") { |file| file.write code }
|
283
|
+
puts "ffi_gen: #{@output}"
|
284
|
+
else
|
285
|
+
@output.write code
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def translation_unit
|
290
|
+
return @translation_unit unless @translation_unit.nil?
|
291
|
+
|
292
|
+
args = []
|
293
|
+
@headers.each do |header|
|
294
|
+
args.push "-include", header unless header.is_a? Regexp
|
295
|
+
end
|
296
|
+
args.concat @cflags
|
297
|
+
args_ptr = FFI::MemoryPointer.new :pointer, args.size
|
298
|
+
pointers = args.map { |arg| FFI::MemoryPointer.from_string arg }
|
299
|
+
args_ptr.write_array_of_pointer pointers
|
300
|
+
|
301
|
+
index = Clang.create_index 0, 0
|
302
|
+
@translation_unit = Clang.parse_translation_unit index, File.join(File.dirname(__FILE__), "ffi_gen/empty.h"), args_ptr, args.size, nil, 0, Clang.enum_type(:translation_unit_flags)[:detailed_preprocessing_record]
|
303
|
+
|
304
|
+
Clang.get_num_diagnostics(@translation_unit).times do |i|
|
305
|
+
diag = Clang.get_diagnostic @translation_unit, i
|
306
|
+
$stderr.puts Clang.format_diagnostic(diag, Clang.default_diagnostic_display_options).to_s_and_dispose
|
307
|
+
end
|
308
|
+
|
309
|
+
@translation_unit
|
310
|
+
end
|
311
|
+
|
312
|
+
def declarations
|
313
|
+
return @declarations unless @declarations.nil?
|
314
|
+
|
315
|
+
header_files = []
|
316
|
+
Clang.get_inclusions translation_unit, proc { |included_file, inclusion_stack, include_length, client_data|
|
317
|
+
filename = Clang.get_file_name(included_file).to_s_and_dispose
|
318
|
+
header_files << included_file if @headers.any? { |header| header.is_a?(Regexp) ? header =~ filename : filename.end_with?(header) }
|
319
|
+
}, nil
|
320
|
+
|
321
|
+
unit_cursor = Clang.get_translation_unit_cursor translation_unit
|
322
|
+
declaration_cursors = Clang.get_children unit_cursor
|
323
|
+
declaration_cursors.delete_if { |cursor| [:macro_expansion, :inclusion_directive, :var_decl].include? cursor[:kind] }
|
324
|
+
declaration_cursors.delete_if { |cursor| !header_files.include?(Clang.get_spelling_location_data(Clang.get_cursor_location(cursor))[:file]) }
|
325
|
+
|
326
|
+
is_nested_declaration = []
|
327
|
+
min_offset = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration_cursors.last))[:offset]
|
328
|
+
declaration_cursors.reverse_each do |declaration_cursor|
|
329
|
+
offset = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration_cursor))[:offset]
|
330
|
+
is_nested_declaration.unshift(offset > min_offset)
|
331
|
+
min_offset = offset if offset < min_offset
|
332
|
+
end
|
333
|
+
|
334
|
+
@declarations = []
|
335
|
+
@declarations_by_name = {}
|
336
|
+
@declarations_by_type = {}
|
337
|
+
previous_declaration_end = Clang.get_cursor_location unit_cursor
|
338
|
+
declaration_cursors.each_with_index do |declaration_cursor, index|
|
339
|
+
comment = []
|
340
|
+
unless is_nested_declaration[index]
|
341
|
+
comment_range = Clang.get_range previous_declaration_end, Clang.get_cursor_location(declaration_cursor)
|
342
|
+
comment, _ = extract_comment translation_unit, comment_range
|
343
|
+
previous_declaration_end = Clang.get_range_end Clang.get_cursor_extent(declaration_cursor)
|
344
|
+
end
|
345
|
+
|
346
|
+
read_declaration declaration_cursor, comment
|
347
|
+
end
|
348
|
+
|
349
|
+
@declarations
|
350
|
+
end
|
351
|
+
|
352
|
+
def read_declaration(declaration_cursor, comment)
|
353
|
+
name = read_name declaration_cursor
|
354
|
+
|
355
|
+
declaration = case declaration_cursor[:kind]
|
356
|
+
when :enum_decl
|
357
|
+
enum_description = []
|
358
|
+
constant_descriptions = {}
|
359
|
+
current_description = enum_description
|
360
|
+
comment.each do |line|
|
361
|
+
if line.gsub!(/@(.*?): /, '')
|
362
|
+
current_description = []
|
363
|
+
constant_descriptions[$1] = current_description
|
364
|
+
end
|
365
|
+
current_description = enum_description if line.strip.empty?
|
366
|
+
current_description << line
|
367
|
+
end
|
368
|
+
|
369
|
+
constants = []
|
370
|
+
previous_constant_location = Clang.get_cursor_location declaration_cursor
|
371
|
+
next_constant_value = 0
|
372
|
+
Clang.get_children(declaration_cursor).each do |enum_constant|
|
373
|
+
constant_name = read_name enum_constant
|
374
|
+
next if constant_name.nil?
|
375
|
+
|
376
|
+
constant_location = Clang.get_cursor_location enum_constant
|
377
|
+
constant_comment_range = Clang.get_range previous_constant_location, constant_location
|
378
|
+
constant_description, _ = extract_comment translation_unit, constant_comment_range
|
379
|
+
constant_description.concat(constant_descriptions[constant_name.raw] || [])
|
380
|
+
previous_constant_location = constant_location
|
381
|
+
|
382
|
+
begin
|
383
|
+
value_cursor = Clang.get_children(enum_constant).first
|
384
|
+
constant_value = if value_cursor
|
385
|
+
parts = []
|
386
|
+
Clang.get_tokens(translation_unit, Clang.get_cursor_extent(value_cursor)).each do |token|
|
387
|
+
spelling = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
|
388
|
+
case Clang.get_token_kind(token)
|
389
|
+
when :literal
|
390
|
+
parts << spelling
|
391
|
+
when :punctuation
|
392
|
+
case spelling
|
393
|
+
when "+", "-", "<<", ">>", "(", ")"
|
394
|
+
parts << spelling
|
395
|
+
else
|
396
|
+
raise ArgumentError
|
397
|
+
end
|
398
|
+
else
|
399
|
+
raise ArgumentError
|
400
|
+
end
|
401
|
+
end
|
402
|
+
eval parts.join
|
403
|
+
else
|
404
|
+
next_constant_value
|
405
|
+
end
|
406
|
+
|
407
|
+
constants << { name: constant_name, value: constant_value, comment: constant_description }
|
408
|
+
next_constant_value = constant_value + 1
|
409
|
+
rescue ArgumentError
|
410
|
+
puts "Warning: Could not process value of enum constant \"#{constant_name.raw}\""
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
Enum.new self, name, constants, enum_description
|
415
|
+
|
416
|
+
when :struct_decl, :union_decl
|
417
|
+
struct = @declarations_by_type[Clang.get_cursor_type(declaration_cursor)] || StructOrUnion.new(self, name, (declaration_cursor[:kind] == :union_decl))
|
418
|
+
raise if not struct.fields.empty?
|
419
|
+
struct.description.concat comment
|
420
|
+
|
421
|
+
struct_children = Clang.get_children declaration_cursor
|
422
|
+
previous_field_end = Clang.get_cursor_location declaration_cursor
|
423
|
+
last_nested_declaration = nil
|
424
|
+
until struct_children.empty?
|
425
|
+
child = struct_children.shift
|
426
|
+
case child[:kind]
|
427
|
+
when :struct_decl, :union_decl
|
428
|
+
last_nested_declaration = read_declaration child, []
|
429
|
+
when :field_decl
|
430
|
+
field_name = read_name child
|
431
|
+
field_extent = Clang.get_cursor_extent child
|
432
|
+
|
433
|
+
field_comment_range = Clang.get_range previous_field_end, Clang.get_range_start(field_extent)
|
434
|
+
field_comment, _ = extract_comment translation_unit, field_comment_range
|
435
|
+
|
436
|
+
# check for comment starting on same line
|
437
|
+
next_field_start = struct_children.first ? Clang.get_cursor_location(struct_children.first) : Clang.get_range_end(Clang.get_cursor_extent(declaration_cursor))
|
438
|
+
following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start
|
439
|
+
following_comment, following_comment_token = extract_comment translation_unit, following_comment_range, false
|
440
|
+
if following_comment_token and Clang.get_spelling_location_data(Clang.get_token_location(translation_unit, following_comment_token))[:line] == Clang.get_spelling_location_data(Clang.get_range_end(field_extent))[:line]
|
441
|
+
field_comment = following_comment
|
442
|
+
previous_field_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
|
443
|
+
else
|
444
|
+
previous_field_end = Clang.get_range_end field_extent
|
445
|
+
end
|
446
|
+
|
447
|
+
field_type = resolve_type Clang.get_cursor_type(child)
|
448
|
+
last_nested_declaration.name ||= Name.new(name.parts + field_name.parts) if last_nested_declaration
|
449
|
+
last_nested_declaration = nil
|
450
|
+
struct.fields << { name: field_name, type: field_type, comment: field_comment }
|
451
|
+
when :unexposed_attr
|
452
|
+
else
|
453
|
+
raise
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
struct
|
458
|
+
|
459
|
+
when :function_decl
|
460
|
+
function_description = []
|
461
|
+
return_value_description = []
|
462
|
+
parameter_descriptions = {}
|
463
|
+
current_description = function_description
|
464
|
+
comment.each do |line|
|
465
|
+
if line.gsub!(/\\param (.*?) /, '')
|
466
|
+
current_description = []
|
467
|
+
parameter_descriptions[$1] = current_description
|
468
|
+
end
|
469
|
+
current_description = return_value_description if line.gsub! '\\returns ', ''
|
470
|
+
current_description << line
|
471
|
+
end
|
472
|
+
|
473
|
+
return_type = resolve_type Clang.get_cursor_result_type(declaration_cursor)
|
474
|
+
parameters = []
|
475
|
+
first_parameter_type = nil
|
476
|
+
Clang.get_children(declaration_cursor).each do |function_child|
|
477
|
+
next if function_child[:kind] != :parm_decl
|
478
|
+
param_name = read_name function_child
|
479
|
+
tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
|
480
|
+
is_array = tokens.any? { |t| Clang.get_token_spelling(translation_unit, t).to_s_and_dispose == "[" }
|
481
|
+
param_type = resolve_type Clang.get_cursor_type(function_child), is_array
|
482
|
+
param_name ||= param_type.name
|
483
|
+
param_name ||= Name.new []
|
484
|
+
first_parameter_type ||= Clang.get_cursor_type function_child
|
485
|
+
parameters << { name: param_name, type: param_type }
|
486
|
+
end
|
487
|
+
|
488
|
+
parameters.each_with_index do |parameter, index|
|
489
|
+
parameter[:description] = parameter[:name] && parameter_descriptions[parameter[:name].raw]
|
490
|
+
parameter[:description] ||= parameter_descriptions.values[index] if parameter_descriptions.size == parameters.size # workaround for wrong names
|
491
|
+
parameter[:description] ||= []
|
492
|
+
end
|
493
|
+
|
494
|
+
function = FunctionOrCallback.new self, name, parameters, return_type, false, @blocking.include?(name.raw), function_description, return_value_description
|
495
|
+
|
496
|
+
pointee_declaration = first_parameter_type && get_pointee_declaration(first_parameter_type)
|
497
|
+
if pointee_declaration
|
498
|
+
type_prefix = pointee_declaration.name.parts.join.downcase
|
499
|
+
function_name_parts = name.parts.dup
|
500
|
+
while type_prefix.start_with? function_name_parts.first.downcase
|
501
|
+
type_prefix = type_prefix[function_name_parts.first.size..-1]
|
502
|
+
function_name_parts.shift
|
503
|
+
break if function_name_parts.empty?
|
504
|
+
end
|
505
|
+
if type_prefix.empty?
|
506
|
+
pointee_declaration.oo_functions << [Name.new(function_name_parts), function]
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
function
|
511
|
+
|
512
|
+
when :typedef_decl
|
513
|
+
typedef_children = Clang.get_children declaration_cursor
|
514
|
+
if typedef_children.size == 1
|
515
|
+
child_declaration = @declarations_by_type[Clang.get_cursor_type(typedef_children.first)]
|
516
|
+
child_declaration.name = name if child_declaration and child_declaration.name.nil?
|
517
|
+
nil
|
518
|
+
elsif typedef_children.size > 1
|
519
|
+
return_type = resolve_type Clang.get_cursor_type(typedef_children.first)
|
520
|
+
parameters = []
|
521
|
+
typedef_children.each do |param_decl|
|
522
|
+
param_name = read_name param_decl
|
523
|
+
param_type = resolve_type Clang.get_cursor_type(param_decl)
|
524
|
+
param_name ||= param_type.name
|
525
|
+
parameters << { name:param_name, type: param_type, description: [] }
|
526
|
+
end
|
527
|
+
FunctionOrCallback.new self, name, parameters, return_type, true, false, comment, []
|
528
|
+
else
|
529
|
+
nil
|
530
|
+
end
|
531
|
+
|
532
|
+
when :macro_definition
|
533
|
+
tokens = Clang.get_tokens(translation_unit, Clang.get_cursor_extent(declaration_cursor)).map { |token|
|
534
|
+
[Clang.get_token_kind(token), Clang.get_token_spelling(translation_unit, token).to_s_and_dispose]
|
535
|
+
}
|
536
|
+
if tokens.size > 1
|
537
|
+
tokens.shift
|
538
|
+
begin
|
539
|
+
parameters = nil
|
540
|
+
if tokens.first[1] == "("
|
541
|
+
tokens_backup = tokens.dup
|
542
|
+
begin
|
543
|
+
parameters = []
|
544
|
+
tokens.shift
|
545
|
+
loop do
|
546
|
+
kind, spelling = tokens.shift
|
547
|
+
case kind
|
548
|
+
when :identifier
|
549
|
+
parameters << spelling
|
550
|
+
when :punctuation
|
551
|
+
break if spelling == ")"
|
552
|
+
raise ArgumentError unless spelling == ","
|
553
|
+
else
|
554
|
+
raise ArgumentError
|
555
|
+
end
|
556
|
+
end
|
557
|
+
rescue ArgumentError
|
558
|
+
parameters = nil
|
559
|
+
tokens = tokens_backup
|
560
|
+
end
|
561
|
+
end
|
562
|
+
value = []
|
563
|
+
until tokens.empty?
|
564
|
+
kind, spelling = tokens.shift
|
565
|
+
case kind
|
566
|
+
when :literal
|
567
|
+
value << spelling
|
568
|
+
when :punctuation
|
569
|
+
case spelling
|
570
|
+
when "+", "-", "<<", ">>", ")"
|
571
|
+
value << spelling
|
572
|
+
when ","
|
573
|
+
value << ", "
|
574
|
+
when "("
|
575
|
+
if tokens[1][1] == ")"
|
576
|
+
tokens.delete_at 1
|
577
|
+
else
|
578
|
+
value << spelling
|
579
|
+
end
|
580
|
+
else
|
581
|
+
raise ArgumentError
|
582
|
+
end
|
583
|
+
when :identifier
|
584
|
+
raise ArgumentError unless parameters
|
585
|
+
if parameters.include? spelling
|
586
|
+
value << spelling
|
587
|
+
elsif spelling == "NULL"
|
588
|
+
value << "nil"
|
589
|
+
else
|
590
|
+
if not tokens.empty? and tokens.first[1] == "("
|
591
|
+
tokens.shift
|
592
|
+
if spelling == "strlen"
|
593
|
+
argument_kind, argument_spelling = tokens.shift
|
594
|
+
second_token_kind, second_token_spelling = tokens.shift
|
595
|
+
raise ArgumentError unless argument_kind == :identifier and second_token_spelling == ")"
|
596
|
+
value << "#{argument_spelling}.length"
|
597
|
+
else
|
598
|
+
value << [:method, read_name(spelling)]
|
599
|
+
value << "("
|
600
|
+
end
|
601
|
+
else
|
602
|
+
value << [:constant, read_name(spelling)]
|
603
|
+
end
|
604
|
+
end
|
605
|
+
when :keyword
|
606
|
+
raise ArgumentError unless spelling == "sizeof" and tokens[0][1] == "(" and tokens[1][0] == :literal and tokens[2][1] == ")"
|
607
|
+
tokens.shift
|
608
|
+
argument_kind, argument_spelling = tokens.shift
|
609
|
+
value << "#{argument_spelling}.length"
|
610
|
+
tokens.shift
|
611
|
+
else
|
612
|
+
raise ArgumentError
|
613
|
+
end
|
614
|
+
end
|
615
|
+
Define.new(self, name, parameters, value)
|
616
|
+
rescue ArgumentError
|
617
|
+
puts "Warning: Could not process value of macro \"#{name.raw}\""
|
618
|
+
nil
|
619
|
+
end
|
620
|
+
else
|
621
|
+
nil
|
622
|
+
end
|
623
|
+
|
624
|
+
else
|
625
|
+
raise declaration_cursor[:kind].to_s
|
626
|
+
|
627
|
+
end
|
628
|
+
|
629
|
+
return nil if declaration.nil?
|
630
|
+
@declarations.delete declaration
|
631
|
+
@declarations << declaration
|
632
|
+
@declarations_by_name[name] = name.raw unless name.nil?
|
633
|
+
type = Clang.get_cursor_type declaration_cursor
|
634
|
+
@declarations_by_type[type] = declaration unless type.nil?
|
635
|
+
|
636
|
+
declaration
|
637
|
+
end
|
638
|
+
|
639
|
+
def resolve_type(full_type, is_array = false)
|
640
|
+
canonical_type = Clang.get_canonical_type full_type
|
641
|
+
data_array = case canonical_type[:kind]
|
642
|
+
when :void, :bool, :u_char, :u_short, :u_int, :u_long, :u_long_long, :char_s, :s_char, :short, :int, :long, :long_long, :float, :double
|
643
|
+
PrimitiveType.new canonical_type[:kind]
|
644
|
+
when :pointer
|
645
|
+
if is_array
|
646
|
+
ArrayType.new resolve_type(Clang.get_pointee_type(canonical_type)), nil
|
647
|
+
else
|
648
|
+
pointee_type = Clang.get_pointee_type canonical_type
|
649
|
+
type = case pointee_type[:kind]
|
650
|
+
when :char_s
|
651
|
+
StringType.new
|
652
|
+
when :record
|
653
|
+
@declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
|
654
|
+
when :function_proto
|
655
|
+
@declarations_by_type[full_type]
|
656
|
+
else
|
657
|
+
nil
|
658
|
+
end
|
659
|
+
|
660
|
+
if type.nil?
|
661
|
+
pointer_depth = 0
|
662
|
+
pointee_name = ""
|
663
|
+
current_type = full_type
|
664
|
+
loop do
|
665
|
+
declaration_cursor = Clang.get_type_declaration current_type
|
666
|
+
pointee_name = read_name declaration_cursor
|
667
|
+
break if pointee_name
|
668
|
+
|
669
|
+
case current_type[:kind]
|
670
|
+
when :pointer
|
671
|
+
pointer_depth += 1
|
672
|
+
current_type = Clang.get_pointee_type current_type
|
673
|
+
when :unexposed
|
674
|
+
break
|
675
|
+
else
|
676
|
+
pointee_name = Name.new Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose.split("_")
|
677
|
+
break
|
678
|
+
end
|
679
|
+
end
|
680
|
+
type = PointerType.new pointee_name, pointer_depth
|
681
|
+
end
|
682
|
+
|
683
|
+
type
|
684
|
+
end
|
685
|
+
when :record
|
686
|
+
type = @declarations_by_type[canonical_type]
|
687
|
+
type &&= ByValueType.new(type)
|
688
|
+
type || UnknownType.new # TODO
|
689
|
+
when :enum
|
690
|
+
@declarations_by_type[canonical_type] || UnknownType.new # TODO
|
691
|
+
when :constant_array
|
692
|
+
ArrayType.new resolve_type(Clang.get_array_element_type(canonical_type)), Clang.get_array_size(canonical_type)
|
693
|
+
when :unexposed, :function_proto
|
694
|
+
UnknownType.new
|
695
|
+
when :incomplete_array
|
696
|
+
PointerType.new resolve_type(Clang.get_array_element_type(canonical_type)).name, 1
|
697
|
+
else
|
698
|
+
raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}"
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
def read_name(source)
|
703
|
+
source = Clang.get_cursor_spelling(source).to_s_and_dispose if source.is_a? Clang::Cursor
|
704
|
+
return nil if source.empty?
|
705
|
+
trimmed = source.sub(/^(#{@prefixes.join('|')})/, '')
|
706
|
+
trimmed = trimmed.sub(/(#{@suffixes.join('|')})$/, '')
|
707
|
+
parts = trimmed.split(/_|(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).reject(&:empty?)
|
708
|
+
Name.new parts, source
|
709
|
+
end
|
710
|
+
|
711
|
+
def get_pointee_declaration(type)
|
712
|
+
canonical_type = Clang.get_canonical_type type
|
713
|
+
return nil if canonical_type[:kind] != :pointer
|
714
|
+
pointee_type = Clang.get_pointee_type canonical_type
|
715
|
+
return nil if pointee_type[:kind] != :record
|
716
|
+
@declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
|
717
|
+
end
|
718
|
+
|
719
|
+
def extract_comment(translation_unit, range, search_backwards = true)
|
720
|
+
tokens = Clang.get_tokens translation_unit, range
|
721
|
+
|
722
|
+
iterator = search_backwards ? tokens.reverse_each : tokens.each
|
723
|
+
comment_lines = []
|
724
|
+
comment_token = nil
|
725
|
+
comment_block = false
|
726
|
+
iterator.each do |token|
|
727
|
+
next if Clang.get_token_kind(token) != :comment
|
728
|
+
comment = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
|
729
|
+
lines = comment.split("\n").map { |line|
|
730
|
+
line.sub!(/\ ?\*+\/\s*$/, '')
|
731
|
+
line.sub!(/^\s*\/?[*\/]+ ?/, '')
|
732
|
+
line.gsub!(/\\(brief|determine) /, '')
|
733
|
+
line.gsub!('[', '(')
|
734
|
+
line.gsub!(']', ')')
|
735
|
+
line
|
736
|
+
}
|
737
|
+
comment_lines = lines + comment_lines
|
738
|
+
comment_token = token
|
739
|
+
comment_block = !comment_block if comment == "///"
|
740
|
+
break unless comment_block and search_backwards
|
741
|
+
end
|
742
|
+
|
743
|
+
return comment_lines, comment_token
|
744
|
+
end
|
745
|
+
|
746
|
+
def self.generate(options = {})
|
747
|
+
self.new(options).generate
|
748
|
+
end
|
749
|
+
|
750
|
+
require "ffi_gen/ruby_output"
|
751
|
+
require "ffi_gen/java_output"
|
752
|
+
end
|
753
|
+
|
754
|
+
if __FILE__ == $0
|
755
|
+
FFIGen.generate(
|
756
|
+
module_name: "FFIGen::Clang",
|
757
|
+
ffi_lib: ["libclang-3.5.so.1", "libclang.so.1", "clang"],
|
758
|
+
headers: ["clang-c/CXErrorCode.h", "clang-c/CXString.h", "clang-c/Index.h"],
|
759
|
+
cflags: `llvm-config --cflags`.split(" "),
|
760
|
+
prefixes: ["clang_", "CX"],
|
761
|
+
output: File.join(File.dirname(__FILE__), "ffi_gen/clang.rb")
|
762
|
+
)
|
763
|
+
end
|