ffi_gen 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -4
- data/lib/ffi_gen.rb +254 -378
- data/lib/ffi_gen/clang.rb +40 -38
- data/lib/ffi_gen/java_output.rb +242 -0
- data/lib/ffi_gen/ruby_output.rb +251 -0
- metadata +12 -5
data/README.md
CHANGED
@@ -8,13 +8,14 @@ ffi_gen - A generator for Ruby FFI bindings
|
|
8
8
|
|
9
9
|
Features
|
10
10
|
--------
|
11
|
-
* Generation of FFI methods, structures,
|
11
|
+
* Generation of FFI methods, structures, unions, enumerations and callbacks
|
12
12
|
* Generation of YARD documentation comments
|
13
13
|
* Tested with headers of the following libraries:
|
14
14
|
* Clang
|
15
15
|
* LLVM
|
16
16
|
* OpenGL
|
17
17
|
* SQLite3
|
18
|
+
* Cairo
|
18
19
|
|
19
20
|
|
20
21
|
Requirements
|
@@ -39,10 +40,12 @@ Use the following interface in a script or Rake task:
|
|
39
40
|
cflags: `llvm-config --cflags`.split(" "),
|
40
41
|
prefixes: ["clang_", "CX"],
|
41
42
|
blacklist: ["clang_getExpansionLocation"],
|
42
|
-
output: "
|
43
|
+
output: "Clang/index.rb"
|
43
44
|
)
|
44
45
|
|
45
|
-
Output: [
|
46
|
+
Output: [Clang/index.rb](https://github.com/neelance/ffi_gen/blob/master/test/gen/Clang/index.rb)
|
47
|
+
|
48
|
+
Other generated files can be found in the [test/gen](https://github.com/neelance/ffi_gen/tree/master/test/gen) directory.
|
46
49
|
|
47
50
|
|
48
51
|
Hints
|
@@ -67,7 +70,6 @@ Roadmap
|
|
67
70
|
-------
|
68
71
|
|
69
72
|
* Support for more libraries:
|
70
|
-
* Cairo
|
71
73
|
* (Write me if you have a whish)
|
72
74
|
* Automatic generation of object oriented wrappers
|
73
75
|
* Polish YARD documentation comments some more
|
data/lib/ffi_gen.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class FFIGen
|
2
|
-
RUBY_KEYWORDS = %w{alias allocate and begin break case class def defined do else elsif end ensure false for if in initialize module next nil not or redo rescue retry return self super then true undef unless until when while yield}
|
3
|
-
|
4
2
|
require "ffi_gen/clang"
|
5
|
-
|
3
|
+
require "ffi_gen/ruby_output"
|
4
|
+
require "ffi_gen/java_output"
|
5
|
+
|
6
6
|
class << Clang
|
7
7
|
def get_children(declaration)
|
8
8
|
children = []
|
@@ -21,6 +21,15 @@ class FFIGen
|
|
21
21
|
get_spelling_location location, file_ptr, line_ptr, column_ptr, offset_ptr
|
22
22
|
{ file: file_ptr.read_pointer, line: line_ptr.read_uint, column: column_ptr.read_uint, offset: offset_ptr.read_uint }
|
23
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.times.map { |i| Clang::Token.new tokens_ptr[i] }
|
32
|
+
end
|
24
33
|
end
|
25
34
|
|
26
35
|
class Clang::String
|
@@ -35,7 +44,36 @@ class FFIGen
|
|
35
44
|
end
|
36
45
|
end
|
37
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
|
+
|
38
75
|
class Enum
|
76
|
+
attr_accessor :name
|
39
77
|
attr_reader :constants, :comment
|
40
78
|
|
41
79
|
def initialize(generator, name, comment)
|
@@ -45,161 +83,41 @@ class FFIGen
|
|
45
83
|
@constants = []
|
46
84
|
end
|
47
85
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
search_pattern = @constants.all? { |constant| constant[:name].include? "_" } ? /(?<=_)/ : /[A-Z]/
|
54
|
-
first_name = @constants.first[:name]
|
55
|
-
|
56
|
-
loop do
|
57
|
-
position = first_name.index(search_pattern, prefix_length + 1) or break
|
58
|
-
prefix = first_name[0...position]
|
59
|
-
break if not @constants.all? { |constant| constant[:name].start_with? prefix }
|
60
|
-
prefix_length = position
|
61
|
-
end
|
62
|
-
|
63
|
-
loop do
|
64
|
-
position = first_name.rindex(search_pattern, first_name.size - suffix_length - 1) or break
|
65
|
-
prefix = first_name[position..-1]
|
66
|
-
break if not @constants.all? { |constant| constant[:name].end_with? prefix }
|
67
|
-
suffix_length = first_name.size - position
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
@constants.each do |constant|
|
72
|
-
constant[:symbol] = ":#{@generator.to_ruby_lowercase constant[:name][prefix_length..(-1 - suffix_length)]}"
|
73
|
-
end
|
74
|
-
|
75
|
-
writer.comment do
|
76
|
-
writer.write_description @comment
|
77
|
-
writer.puts "", "<em>This entry is only for documentation and no real method. The FFI::Enum can be accessed via #enum_type(:#{ruby_name}).</em>"
|
78
|
-
writer.puts "", "=== Options:"
|
79
|
-
@constants.each do |constant|
|
80
|
-
writer.puts "#{constant[:symbol]} ::"
|
81
|
-
writer.write_description constant[:comment], false, " ", " "
|
82
|
-
end
|
83
|
-
writer.puts "", "@method _enum_#{ruby_name}_", "@return [Symbol]", "@scope class"
|
84
|
-
end
|
85
|
-
|
86
|
-
writer.puts "enum :#{ruby_name}, ["
|
87
|
-
writer.indent do
|
88
|
-
writer.write_array @constants, "," do |constant|
|
89
|
-
"#{constant[:symbol]}#{constant[:value] ? ", #{constant[:value]}" : ''}"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
writer.puts "]", ""
|
93
|
-
end
|
94
|
-
|
95
|
-
def ruby_name
|
96
|
-
@ruby_name ||= @generator.to_ruby_lowercase @name
|
86
|
+
def shorten_names
|
87
|
+
return if @constants.size < 2
|
88
|
+
names = @constants.map { |constant| constant[:name].parts }
|
89
|
+
names.each(&:shift) while names.map(&:first).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.first.downcase
|
90
|
+
names.each(&:pop) while names.map(&:last).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.last.downcase
|
97
91
|
end
|
98
92
|
end
|
99
93
|
|
100
|
-
class
|
101
|
-
|
94
|
+
class StructOrUnion
|
95
|
+
attr_accessor :name, :comment
|
96
|
+
attr_reader :fields, :oo_functions, :written
|
102
97
|
|
103
|
-
def initialize(generator, name,
|
98
|
+
def initialize(generator, name, is_union)
|
104
99
|
@generator = generator
|
105
100
|
@name = name
|
106
|
-
@
|
101
|
+
@is_union = is_union
|
102
|
+
@comment = ""
|
107
103
|
@fields = []
|
104
|
+
@oo_functions = []
|
108
105
|
@written = false
|
109
106
|
end
|
110
|
-
|
111
|
-
def write(writer)
|
112
|
-
@fields.each do |field|
|
113
|
-
field[:symbol] = ":#{@generator.to_ruby_lowercase field[:name]}"
|
114
|
-
field[:type_data] = @generator.map_type field[:type]
|
115
|
-
end
|
116
|
-
|
117
|
-
writer.comment do
|
118
|
-
writer.write_description @comment
|
119
|
-
unless @fields.empty?
|
120
|
-
writer.puts "", "= Fields:"
|
121
|
-
@fields.each do |field|
|
122
|
-
writer.puts "#{field[:symbol]} ::"
|
123
|
-
writer.write_description field[:comment], false, " (#{field[:type_data][:description]}) ", " "
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
writer.puts "class #{ruby_name} < FFI::Struct"
|
129
|
-
writer.indent do
|
130
|
-
writer.write_array @fields, ",", "layout ", " " do |field|
|
131
|
-
"#{field[:symbol]}, #{field[:type_data][:ffi_type]}"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
writer.puts "end", ""
|
135
|
-
|
136
|
-
@written = true
|
137
|
-
end
|
138
|
-
|
139
|
-
def ruby_name
|
140
|
-
@ruby_name ||= @generator.to_ruby_camelcase @name
|
141
|
-
end
|
142
107
|
end
|
143
108
|
|
144
|
-
class
|
109
|
+
class FunctionOrCallback
|
145
110
|
attr_reader :name, :parameters, :comment
|
146
111
|
attr_accessor :return_type
|
147
112
|
|
148
|
-
def initialize(generator, name, is_callback, comment)
|
113
|
+
def initialize(generator, name, is_callback, blocking, comment)
|
149
114
|
@generator = generator
|
150
115
|
@name = name
|
151
116
|
@parameters = []
|
152
117
|
@is_callback = is_callback
|
118
|
+
@blocking = blocking
|
153
119
|
@comment = comment
|
154
120
|
end
|
155
|
-
|
156
|
-
def write(writer)
|
157
|
-
@parameters.each do |parameter|
|
158
|
-
parameter[:type_data] = @generator.map_type parameter[:type]
|
159
|
-
parameter[:ruby_name] = !parameter[:name].empty? ? @generator.to_ruby_lowercase(parameter[:name]) : parameter[:type_data][:parameter_name]
|
160
|
-
parameter[:description] = []
|
161
|
-
end
|
162
|
-
return_type_data = @generator.map_type @return_type
|
163
|
-
|
164
|
-
function_description = []
|
165
|
-
return_value_description = []
|
166
|
-
current_description = function_description
|
167
|
-
@comment.split("\n").map do |line|
|
168
|
-
line = writer.prepare_comment_line line
|
169
|
-
if line.gsub! /\\param (.*?) /, ''
|
170
|
-
parameter = @parameters.find { |parameter| parameter[:name] == $1 }
|
171
|
-
if parameter
|
172
|
-
current_description = parameter[:description]
|
173
|
-
else
|
174
|
-
current_description << "#{$1}: "
|
175
|
-
end
|
176
|
-
end
|
177
|
-
current_description = return_value_description if line.gsub! '\\returns ', ''
|
178
|
-
current_description << line
|
179
|
-
end
|
180
|
-
|
181
|
-
writer.comment do
|
182
|
-
writer.write_description function_description
|
183
|
-
writer.puts "", "<em>This entry is only for documentation and no real method.</em>" if @is_callback
|
184
|
-
writer.puts "", "@method #{@is_callback ? "_callback_#{ruby_name}_" : ruby_name}(#{@parameters.map{ |parameter| parameter[:ruby_name] }.join(', ')})"
|
185
|
-
@parameters.each do |parameter|
|
186
|
-
writer.write_description parameter[:description], false, "@param [#{parameter[:type_data][:description]}] #{parameter[:ruby_name]} ", " "
|
187
|
-
end
|
188
|
-
writer.write_description return_value_description, false, "@return [#{return_type_data[:description]}] ", " "
|
189
|
-
writer.puts "@scope class"
|
190
|
-
end
|
191
|
-
|
192
|
-
ffi_signature = "[#{@parameters.map{ |parameter| parameter[:type_data][:ffi_type] }.join(', ')}], #{return_type_data[:ffi_type]}"
|
193
|
-
if @is_callback
|
194
|
-
writer.puts "callback :#{ruby_name}, #{ffi_signature}", ""
|
195
|
-
else
|
196
|
-
writer.puts "attach_function :#{ruby_name}, :#{@name}, #{ffi_signature}", ""
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def ruby_name
|
201
|
-
@ruby_name ||= @generator.to_ruby_lowercase @name, true
|
202
|
-
end
|
203
121
|
end
|
204
122
|
|
205
123
|
class Constant
|
@@ -208,34 +126,36 @@ class FFIGen
|
|
208
126
|
@name = name
|
209
127
|
@value = value
|
210
128
|
end
|
211
|
-
|
212
|
-
def write(writer)
|
213
|
-
writer.puts "#{@generator.to_ruby_lowercase(@name, true).upcase} = #{@value}", ""
|
214
|
-
end
|
215
129
|
end
|
216
130
|
|
217
131
|
class Writer
|
218
132
|
attr_reader :output
|
219
133
|
|
220
|
-
def initialize
|
221
|
-
@
|
134
|
+
def initialize(indentation_prefix, comment_prefix, comment_start = nil, comment_end = nil)
|
135
|
+
@indentation_prefix = indentation_prefix
|
136
|
+
@comment_prefix = comment_prefix
|
137
|
+
@comment_start = comment_start
|
138
|
+
@comment_end = comment_end
|
139
|
+
@current_indentation = ""
|
222
140
|
@output = ""
|
223
141
|
end
|
224
142
|
|
225
|
-
def indent(prefix =
|
226
|
-
previous_indentation = @
|
227
|
-
@
|
143
|
+
def indent(prefix = @indentation_prefix)
|
144
|
+
previous_indentation = @current_indentation
|
145
|
+
@current_indentation += prefix
|
228
146
|
yield
|
229
|
-
@
|
147
|
+
@current_indentation = previous_indentation
|
230
148
|
end
|
231
149
|
|
232
150
|
def comment(&block)
|
233
|
-
|
151
|
+
self.puts @comment_start unless @comment_start.nil?
|
152
|
+
self.indent @comment_prefix, &block
|
153
|
+
self.puts @comment_end unless @comment_end.nil?
|
234
154
|
end
|
235
155
|
|
236
156
|
def puts(*lines)
|
237
157
|
lines.each do |line|
|
238
|
-
@output << "#{@
|
158
|
+
@output << "#{@current_indentation}#{line}\n"
|
239
159
|
end
|
240
160
|
end
|
241
161
|
|
@@ -248,11 +168,11 @@ class FFIGen
|
|
248
168
|
|
249
169
|
def prepare_comment_line(line)
|
250
170
|
line = line.dup
|
251
|
-
line.sub!
|
252
|
-
line.sub!
|
253
|
-
line.gsub!
|
254
|
-
line.gsub!
|
255
|
-
line.gsub!
|
171
|
+
line.sub!(/\ ?\*+\/\s*$/, '')
|
172
|
+
line.sub!(/^\s*\/?\*+ ?/, '')
|
173
|
+
line.gsub!(/\\(brief|determine) /, '')
|
174
|
+
line.gsub!('[', '(')
|
175
|
+
line.gsub!(']', ')')
|
256
176
|
line
|
257
177
|
end
|
258
178
|
|
@@ -260,48 +180,67 @@ class FFIGen
|
|
260
180
|
if description.is_a? String
|
261
181
|
description = description.split("\n").map { |line| prepare_comment_line(line) }
|
262
182
|
end
|
263
|
-
|
183
|
+
|
264
184
|
description.shift while not description.empty? and description.first.strip.empty?
|
265
185
|
description.pop while not description.empty? and description.last.strip.empty?
|
186
|
+
description.map! { |line| line.gsub "\t", " " }
|
187
|
+
space_prefix_length = description.map{ |line| line.index(/\S/) }.compact.min
|
188
|
+
description.map! { |line| line[space_prefix_length..-1] }
|
266
189
|
description << (not_documented_message ? "(Not documented)" : "") if description.empty?
|
267
190
|
|
268
191
|
write_array description, "", first_line_prefix, other_lines_prefix
|
269
192
|
end
|
270
193
|
end
|
271
194
|
|
272
|
-
|
195
|
+
class Name
|
196
|
+
attr_reader :raw, :parts
|
197
|
+
|
198
|
+
def initialize(generator, raw)
|
199
|
+
@generator = generator
|
200
|
+
@raw = raw
|
201
|
+
@parts = @raw.is_a?(Array) ? raw : @raw.sub(/^(#{generator.prefixes.join('|')})/, '').split(/_|(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).reject(&:empty?)
|
202
|
+
end
|
203
|
+
|
204
|
+
def format(*modes, keyword_blacklist)
|
205
|
+
parts = @parts.dup
|
206
|
+
parts.map!(&:downcase) if modes.include? :downcase
|
207
|
+
parts.map!(&:upcase) if modes.include? :upcase
|
208
|
+
parts.map! { |s| s[0].upcase + s[1..-1] } if modes.include? :camelcase
|
209
|
+
parts[0] = parts[0][0].downcase + parts[0][1..-1] if modes.include? :initial_downcase
|
210
|
+
str = parts.join(modes.include?(:underscores) ? "_" : "")
|
211
|
+
str.sub!(/^\d/, '_\0') # fix illegal beginnings
|
212
|
+
str = "#{str}_" if keyword_blacklist.include? str
|
213
|
+
str
|
214
|
+
end
|
215
|
+
|
216
|
+
def empty?
|
217
|
+
@parts.empty?
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
attr_reader :module_name, :ffi_lib, :headers, :prefixes, :output, :cflags
|
273
222
|
|
274
223
|
def initialize(options = {})
|
275
|
-
@
|
276
|
-
@ffi_lib
|
277
|
-
@headers
|
278
|
-
@cflags
|
279
|
-
@prefixes
|
280
|
-
@
|
281
|
-
@
|
282
|
-
|
283
|
-
blacklist = @blacklist
|
284
|
-
@blacklist = lambda { |name| blacklist.include? name } if @blacklist.is_a? Array
|
224
|
+
@module_name = options[:module_name] or fail "No module name given."
|
225
|
+
@ffi_lib = options[:ffi_lib] or fail "No FFI library given."
|
226
|
+
@headers = options[:headers] or fail "No headers given."
|
227
|
+
@cflags = options.fetch :cflags, []
|
228
|
+
@prefixes = options.fetch :prefixes, []
|
229
|
+
@blocking = options.fetch :blocking, []
|
230
|
+
@ffi_lib_flags = options.fetch :ffi_lib_flags, nil
|
231
|
+
@output = options.fetch :output, $stdout
|
285
232
|
|
286
233
|
@translation_unit = nil
|
287
234
|
@declarations = nil
|
288
235
|
end
|
289
236
|
|
290
237
|
def generate
|
291
|
-
|
292
|
-
writer.puts "# Generated by ffi_gen. Please do not change this file by hand.", "", "require 'ffi'", "", "module #{@ruby_module}"
|
293
|
-
writer.indent do
|
294
|
-
writer.puts "extend FFI::Library", "ffi_lib '#{@ffi_lib}'", ""
|
295
|
-
declarations.each do |name, declaration|
|
296
|
-
declaration.write writer
|
297
|
-
end
|
298
|
-
end
|
299
|
-
writer.puts "end"
|
238
|
+
code = send "generate_#{File.extname(@output)[1..-1]}"
|
300
239
|
if @output.is_a? String
|
301
|
-
File.open(@output, "w") { |file| file.write
|
240
|
+
File.open(@output, "w") { |file| file.write code }
|
302
241
|
puts "ffi_gen: #{@output}"
|
303
242
|
else
|
304
|
-
@output.write
|
243
|
+
@output.write code
|
305
244
|
end
|
306
245
|
end
|
307
246
|
|
@@ -310,7 +249,7 @@ class FFIGen
|
|
310
249
|
|
311
250
|
args = []
|
312
251
|
@headers.each do |header|
|
313
|
-
args.push "-include", header
|
252
|
+
args.push "-include", header unless header.is_a? Regexp
|
314
253
|
end
|
315
254
|
args.concat @cflags
|
316
255
|
args_ptr = FFI::MemoryPointer.new :pointer, args.size
|
@@ -334,7 +273,7 @@ class FFIGen
|
|
334
273
|
header_files = []
|
335
274
|
Clang.get_inclusions translation_unit, proc { |included_file, inclusion_stack, include_length, client_data|
|
336
275
|
filename = Clang.get_file_name(included_file).to_s_and_dispose
|
337
|
-
header_files << included_file if @headers.any? { |header| filename.end_with?
|
276
|
+
header_files << included_file if @headers.any? { |header| header.is_a?(Regexp) ? header =~ filename : filename.end_with?(header) }
|
338
277
|
}, nil
|
339
278
|
|
340
279
|
@declarations = {}
|
@@ -345,253 +284,191 @@ class FFIGen
|
|
345
284
|
|
346
285
|
extent = Clang.get_cursor_extent declaration
|
347
286
|
comment_range = Clang.get_range previous_declaration_end, Clang.get_range_start(extent)
|
348
|
-
unless
|
287
|
+
unless [:enum_decl, :struct_decl, :union_decl].include? declaration[:kind] # keep comment for typedef_decl
|
349
288
|
previous_declaration_end = Clang.get_range_end extent
|
350
289
|
end
|
351
290
|
|
352
291
|
next if not header_files.include? file
|
353
292
|
|
354
|
-
name = Clang.get_cursor_spelling(declaration).to_s_and_dispose
|
355
|
-
next if @blacklist[name]
|
356
|
-
|
357
293
|
comment = extract_comment translation_unit, comment_range
|
358
294
|
|
359
|
-
|
360
|
-
when :enum_decl, :struct_decl
|
361
|
-
read_named_declaration declaration, name, comment unless name.empty?
|
362
|
-
|
363
|
-
when :function_decl
|
364
|
-
function = Function.new self, name, false, comment
|
365
|
-
function.return_type = Clang.get_cursor_result_type declaration
|
366
|
-
@declarations[name] = function
|
367
|
-
|
368
|
-
Clang.get_children(declaration).each do |function_child|
|
369
|
-
next if function_child[:kind] != :parm_decl
|
370
|
-
param_name = Clang.get_cursor_spelling(function_child).to_s_and_dispose
|
371
|
-
param_type = Clang.get_cursor_type function_child
|
372
|
-
function.parameters << { name: param_name, type: param_type}
|
373
|
-
end
|
374
|
-
|
375
|
-
when :typedef_decl
|
376
|
-
typedef_children = Clang.get_children declaration
|
377
|
-
if typedef_children.size == 1
|
378
|
-
read_named_declaration typedef_children.first, name, comment unless @declarations.has_key? name
|
379
|
-
|
380
|
-
elsif typedef_children.size > 1
|
381
|
-
callback = Function.new self, name, true, comment
|
382
|
-
callback.return_type = Clang.get_cursor_type typedef_children.first
|
383
|
-
@declarations[name] = callback
|
384
|
-
|
385
|
-
typedef_children[1..-1].each do |param_decl|
|
386
|
-
param_name = Clang.get_cursor_spelling(param_decl).to_s_and_dispose
|
387
|
-
param_type = Clang.get_cursor_type param_decl
|
388
|
-
callback.parameters << { name: param_name, type: param_type }
|
389
|
-
end
|
390
|
-
end
|
391
|
-
|
392
|
-
when :macro_definition
|
393
|
-
tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
|
394
|
-
num_tokens_ptr = FFI::MemoryPointer.new :uint
|
395
|
-
Clang.tokenize translation_unit, extent, tokens_ptr_ptr, num_tokens_ptr
|
396
|
-
num_tokens = num_tokens_ptr.read_uint
|
397
|
-
tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer
|
398
|
-
|
399
|
-
if num_tokens == 3
|
400
|
-
token = Clang::Token.new tokens_ptr[1]
|
401
|
-
if Clang.get_token_kind(token) == :literal
|
402
|
-
value = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
|
403
|
-
@declarations[name] = Constant.new self, name, value
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
end
|
295
|
+
read_named_declaration declaration, comment
|
408
296
|
end
|
409
297
|
|
410
298
|
@declarations
|
411
299
|
end
|
412
300
|
|
413
|
-
def read_named_declaration(declaration,
|
414
|
-
|
415
|
-
comment = "#{old_declaration.comment}\n#{comment}" if old_declaration
|
301
|
+
def read_named_declaration(declaration, comment)
|
302
|
+
name = Name.new self, Clang.get_cursor_spelling(declaration).to_s_and_dispose
|
416
303
|
|
417
304
|
case declaration[:kind]
|
418
305
|
when :enum_decl
|
419
306
|
enum = Enum.new self, name, comment
|
420
|
-
@declarations[
|
307
|
+
@declarations[Clang.get_cursor_type(declaration)] = enum
|
421
308
|
|
422
309
|
previous_constant_location = Clang.get_cursor_location declaration
|
310
|
+
next_constant_value = 0
|
423
311
|
Clang.get_children(declaration).each do |enum_constant|
|
424
|
-
constant_name = Clang.get_cursor_spelling(enum_constant).to_s_and_dispose
|
425
|
-
|
426
|
-
constant_value = nil
|
427
|
-
value_cursor = Clang.get_children(enum_constant).first
|
428
|
-
constant_value = value_cursor && case value_cursor[:kind]
|
429
|
-
when :integer_literal
|
430
|
-
tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
|
431
|
-
num_tokens_ptr = FFI::MemoryPointer.new :uint
|
432
|
-
Clang.tokenize translation_unit, Clang.get_cursor_extent(value_cursor), tokens_ptr_ptr, num_tokens_ptr
|
433
|
-
token = Clang::Token.new tokens_ptr_ptr.read_pointer
|
434
|
-
literal = Clang.get_token_spelling translation_unit, token
|
435
|
-
Clang.dispose_tokens translation_unit, tokens_ptr_ptr.read_pointer, num_tokens_ptr.read_uint
|
436
|
-
literal
|
437
|
-
else
|
438
|
-
next # skip those entries for now
|
439
|
-
end
|
312
|
+
constant_name = Name.new self, Clang.get_cursor_spelling(enum_constant).to_s_and_dispose
|
440
313
|
|
441
314
|
constant_location = Clang.get_cursor_location enum_constant
|
442
315
|
constant_comment_range = Clang.get_range previous_constant_location, constant_location
|
443
316
|
constant_comment = extract_comment translation_unit, constant_comment_range
|
444
317
|
previous_constant_location = constant_location
|
445
318
|
|
446
|
-
|
319
|
+
catch :unsupported_value do
|
320
|
+
value_cursor = Clang.get_children(enum_constant).first
|
321
|
+
constant_value = if value_cursor
|
322
|
+
read_value value_cursor
|
323
|
+
else
|
324
|
+
next_constant_value
|
325
|
+
end
|
326
|
+
|
327
|
+
enum.constants << { name: constant_name, value: constant_value, comment: constant_comment }
|
328
|
+
next_constant_value = constant_value + 1
|
329
|
+
end
|
447
330
|
end
|
448
331
|
|
449
|
-
when :struct_decl
|
450
|
-
struct =
|
332
|
+
when :struct_decl, :union_decl
|
333
|
+
struct = @declarations.delete(Clang.get_cursor_type(declaration)) || StructOrUnion.new(self, name, (declaration[:kind] == :union_decl))
|
334
|
+
raise if not struct.fields.empty?
|
335
|
+
struct.comment << "\n#{comment}"
|
451
336
|
|
452
337
|
struct_children = Clang.get_children declaration
|
453
|
-
|
454
|
-
struct_children.
|
455
|
-
|
456
|
-
|
338
|
+
previous_field_end = Clang.get_cursor_location declaration
|
339
|
+
until struct_children.empty?
|
340
|
+
nested_declaration = [:struct_decl, :union_decl].include?(struct_children.first[:kind]) ? struct_children.shift : nil
|
341
|
+
field = struct_children.shift
|
342
|
+
raise if field[:kind] != :field_decl
|
457
343
|
|
458
|
-
|
459
|
-
|
344
|
+
field_name = Name.new self, Clang.get_cursor_spelling(field).to_s_and_dispose
|
345
|
+
field_extent = Clang.get_cursor_extent field
|
346
|
+
|
347
|
+
field_comment_range = Clang.get_range previous_field_end, Clang.get_range_start(field_extent)
|
348
|
+
field_comment = extract_comment translation_unit, field_comment_range
|
460
349
|
|
461
350
|
# check for comment starting on same line
|
462
|
-
|
463
|
-
following_comment_range = Clang.get_range Clang.get_range_end(
|
351
|
+
next_field_start = struct_children.first ? Clang.get_cursor_location(struct_children.first) : Clang.get_range_end(Clang.get_cursor_extent(declaration))
|
352
|
+
following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start
|
464
353
|
following_comment_token = extract_comment translation_unit, following_comment_range, false, false
|
465
|
-
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(
|
466
|
-
|
467
|
-
|
354
|
+
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]
|
355
|
+
field_comment = Clang.get_token_spelling(translation_unit, following_comment_token).to_s_and_dispose
|
356
|
+
previous_field_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
|
468
357
|
else
|
469
|
-
|
358
|
+
previous_field_end = Clang.get_range_end field_extent
|
470
359
|
end
|
471
360
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
struct.fields << { name: child_name, type: field_type, comment: child_comment }
|
477
|
-
when :struct_decl
|
478
|
-
read_named_declaration struct_child, child_name, child_comment
|
361
|
+
if nested_declaration
|
362
|
+
read_named_declaration nested_declaration, ""
|
363
|
+
decl = @declarations[Clang.get_cursor_type(nested_declaration)]
|
364
|
+
decl.name = Name.new(self, name.parts + field_name.parts) if decl and decl.name.empty?
|
479
365
|
end
|
366
|
+
|
367
|
+
field_type = Clang.get_cursor_type field
|
368
|
+
struct.fields << { name: field_name, type: field_type, comment: field_comment }
|
480
369
|
end
|
481
370
|
|
482
|
-
@declarations[
|
371
|
+
@declarations[Clang.get_cursor_type(declaration)] = struct
|
372
|
+
|
373
|
+
when :function_decl
|
374
|
+
function = FunctionOrCallback.new self, name, false, @blocking.include?(name.raw), comment
|
375
|
+
function.return_type = Clang.get_cursor_result_type declaration
|
376
|
+
@declarations[declaration] = function
|
377
|
+
|
378
|
+
Clang.get_children(declaration).each do |function_child|
|
379
|
+
next if function_child[:kind] != :parm_decl
|
380
|
+
param_name = Name.new self, Clang.get_cursor_spelling(function_child).to_s_and_dispose
|
381
|
+
param_type = Clang.get_cursor_type function_child
|
382
|
+
tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
|
383
|
+
is_array = tokens.any? { |t| Clang.get_token_spelling(translation_unit, t).to_s_and_dispose == "[" }
|
384
|
+
function.parameters << { name: param_name, type: param_type, is_array: is_array }
|
385
|
+
end
|
483
386
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
token = Clang::Token.new tokens_ptr[i]
|
496
|
-
if Clang.get_token_kind(token) == :comment
|
497
|
-
return return_spelling ? Clang.get_token_spelling(translation_unit, token).to_s_and_dispose : token
|
387
|
+
pointee_declaration = function.parameters.first && get_pointee_declaration(function.parameters.first[:type])
|
388
|
+
if pointee_declaration
|
389
|
+
type_prefix = pointee_declaration.name.parts.join.downcase
|
390
|
+
function_name_parts = name.parts.dup
|
391
|
+
while type_prefix.start_with? function_name_parts.first.downcase
|
392
|
+
type_prefix = type_prefix[function_name_parts.first.size..-1]
|
393
|
+
function_name_parts.shift
|
394
|
+
end
|
395
|
+
if type_prefix.empty?
|
396
|
+
pointee_declaration.oo_functions << [Name.new(self, function_name_parts), function, get_pointee_declaration(function.return_type)]
|
397
|
+
end
|
498
398
|
end
|
499
|
-
end
|
500
|
-
""
|
501
|
-
end
|
502
|
-
|
503
|
-
def map_type(full_type)
|
504
|
-
name = Clang.get_cursor_spelling(Clang.get_type_declaration(full_type)).to_s_and_dispose
|
505
|
-
declaration = !name.empty? && @declarations[name]
|
506
399
|
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
when :function_proto
|
534
|
-
result = [":#{declaration.ruby_name}", "Proc(_callback_#{declaration.ruby_name}_)"] if declaration
|
400
|
+
when :typedef_decl
|
401
|
+
typedef_children = Clang.get_children declaration
|
402
|
+
if typedef_children.size == 1
|
403
|
+
child_declaration = @declarations[Clang.get_cursor_type(typedef_children.first)]
|
404
|
+
child_declaration.name = name if child_declaration and child_declaration.name.empty?
|
405
|
+
|
406
|
+
elsif typedef_children.size > 1
|
407
|
+
callback = FunctionOrCallback.new self, name, true, false, comment
|
408
|
+
callback.return_type = Clang.get_cursor_type typedef_children.first
|
409
|
+
@declarations[Clang.get_cursor_type(declaration)] = callback
|
410
|
+
|
411
|
+
typedef_children[1..-1].each do |param_decl|
|
412
|
+
param_name = Name.new self, Clang.get_cursor_spelling(param_decl).to_s_and_dispose
|
413
|
+
param_type = Clang.get_cursor_type param_decl
|
414
|
+
callback.parameters << { name:param_name, type: param_type }
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
when :macro_definition
|
419
|
+
tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(declaration)
|
420
|
+
if tokens.size == 3
|
421
|
+
if Clang.get_token_kind(tokens[1]) == :literal
|
422
|
+
value = Clang.get_token_spelling(translation_unit, tokens[1]).to_s_and_dispose
|
423
|
+
value.sub!(/[A-Za-z]+$/, '') unless value.start_with? '0x' # remove number suffixes
|
424
|
+
@declarations[name] ||= Constant.new self, name, value
|
425
|
+
end
|
535
426
|
end
|
536
427
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def read_value(cursor)
|
432
|
+
parts = []
|
433
|
+
tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(cursor)
|
434
|
+
tokens.each do |token|
|
435
|
+
spelling = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
|
436
|
+
case Clang.get_token_kind(token)
|
437
|
+
when :literal
|
438
|
+
parts << spelling
|
439
|
+
when :punctuation
|
440
|
+
case spelling
|
441
|
+
when ",", "}"
|
442
|
+
# ignored
|
443
|
+
when "+", "-", "<<", ">>"
|
444
|
+
parts << spelling
|
445
|
+
else
|
446
|
+
throw :unsupported_value
|
556
447
|
end
|
557
|
-
|
448
|
+
else
|
449
|
+
throw :unsupported_value
|
558
450
|
end
|
559
|
-
|
560
|
-
result
|
561
|
-
when :record
|
562
|
-
["#{declaration.ruby_name}.by_value", declaration.ruby_name]
|
563
|
-
when :enum
|
564
|
-
[":#{declaration.ruby_name}", "Symbol from _enum_#{declaration.ruby_name}_", declaration.ruby_name]
|
565
|
-
when :constant_array
|
566
|
-
element_type_data = map_type Clang.get_array_element_type(canonical_type)
|
567
|
-
size = Clang.get_array_size canonical_type
|
568
|
-
["[#{element_type_data[:ffi_type]}, #{size}]", "Array<#{element_type_data[:description]}>"]
|
569
|
-
else
|
570
|
-
raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}"
|
571
451
|
end
|
572
|
-
|
573
|
-
{ ffi_type: data_array[0], description: data_array[1], parameter_name: to_ruby_lowercase(data_array[2] || data_array[1]) }
|
452
|
+
eval parts.join
|
574
453
|
end
|
575
454
|
|
576
|
-
def
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
str.gsub! /__+/, '_' # replace multiple underscores by only one
|
583
|
-
str.downcase!
|
584
|
-
str.sub! /^\d/, '_\0' # fix illegal beginnings
|
585
|
-
str = "_#{str}" if avoid_keywords and RUBY_KEYWORDS.include? str
|
586
|
-
str
|
455
|
+
def get_pointee_declaration(type)
|
456
|
+
canonical_type = Clang.get_canonical_type type
|
457
|
+
return nil if canonical_type[:kind] != :pointer
|
458
|
+
pointee_type = Clang.get_pointee_type canonical_type
|
459
|
+
return nil if pointee_type[:kind] != :record
|
460
|
+
@declarations[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
|
587
461
|
end
|
588
462
|
|
589
|
-
def
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
463
|
+
def extract_comment(translation_unit, range, search_backwards = true, return_spelling = true)
|
464
|
+
tokens = Clang.get_tokens translation_unit, range
|
465
|
+
iterator = search_backwards ? tokens.reverse_each : tokens.each
|
466
|
+
iterator.each do |token|
|
467
|
+
if Clang.get_token_kind(token) == :comment
|
468
|
+
return return_spelling ? Clang.get_token_spelling(translation_unit, token).to_s_and_dispose : token
|
469
|
+
end
|
470
|
+
end
|
471
|
+
""
|
595
472
|
end
|
596
473
|
|
597
474
|
def self.generate(options = {})
|
@@ -602,12 +479,11 @@ end
|
|
602
479
|
|
603
480
|
if __FILE__ == $0
|
604
481
|
FFIGen.generate(
|
605
|
-
|
482
|
+
module_name: "FFIGen::Clang",
|
606
483
|
ffi_lib: "clang",
|
607
484
|
headers: ["clang-c/Index.h"],
|
608
485
|
cflags: `llvm-config --cflags`.split(" "),
|
609
486
|
prefixes: ["clang_", "CX"],
|
610
|
-
blacklist: ["clang_getExpansionLocation"],
|
611
487
|
output: File.join(File.dirname(__FILE__), "ffi_gen/clang.rb")
|
612
488
|
)
|
613
489
|
end
|