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