ffidb 0.12.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/AUTHORS +1 -0
- data/CHANGES.md +7 -0
- data/CREDITS.md +2 -0
- data/README.md +201 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/bin/ffidb +387 -0
- data/etc/mappings/dart.yaml +35 -0
- data/etc/mappings/java.yaml +36 -0
- data/etc/mappings/lisp.yaml +35 -0
- data/etc/mappings/python.yaml +35 -0
- data/etc/mappings/ruby.yaml +35 -0
- data/etc/templates/c.erb +46 -0
- data/etc/templates/cpp.erb +45 -0
- data/etc/templates/dart.erb +64 -0
- data/etc/templates/go.erb +50 -0
- data/etc/templates/java.erb +56 -0
- data/etc/templates/lisp.erb +49 -0
- data/etc/templates/python.erb +59 -0
- data/etc/templates/ruby.erb +48 -0
- data/lib/ffidb.rb +34 -0
- data/lib/ffidb/enum.rb +37 -0
- data/lib/ffidb/errors.rb +64 -0
- data/lib/ffidb/exporter.rb +141 -0
- data/lib/ffidb/exporters.rb +28 -0
- data/lib/ffidb/exporters/c.rb +52 -0
- data/lib/ffidb/exporters/cpp.rb +13 -0
- data/lib/ffidb/exporters/csharp.rb +6 -0
- data/lib/ffidb/exporters/csv.rb +24 -0
- data/lib/ffidb/exporters/dart.rb +60 -0
- data/lib/ffidb/exporters/go.rb +16 -0
- data/lib/ffidb/exporters/haskell.rb +3 -0
- data/lib/ffidb/exporters/java.rb +39 -0
- data/lib/ffidb/exporters/json.rb +38 -0
- data/lib/ffidb/exporters/julia.rb +3 -0
- data/lib/ffidb/exporters/lisp.rb +41 -0
- data/lib/ffidb/exporters/luajit.rb +3 -0
- data/lib/ffidb/exporters/nim.rb +4 -0
- data/lib/ffidb/exporters/nodejs.rb +4 -0
- data/lib/ffidb/exporters/ocaml.rb +4 -0
- data/lib/ffidb/exporters/php.rb +4 -0
- data/lib/ffidb/exporters/python.rb +35 -0
- data/lib/ffidb/exporters/racket.rb +3 -0
- data/lib/ffidb/exporters/ruby.rb +33 -0
- data/lib/ffidb/exporters/rust.rb +5 -0
- data/lib/ffidb/exporters/yaml.rb +31 -0
- data/lib/ffidb/exporters/zig.rb +3 -0
- data/lib/ffidb/function.rb +70 -0
- data/lib/ffidb/glob.rb +28 -0
- data/lib/ffidb/header.rb +19 -0
- data/lib/ffidb/header_parser.rb +339 -0
- data/lib/ffidb/library.rb +120 -0
- data/lib/ffidb/library_parser.rb +132 -0
- data/lib/ffidb/location.rb +17 -0
- data/lib/ffidb/parameter.rb +35 -0
- data/lib/ffidb/registry.rb +87 -0
- data/lib/ffidb/release.rb +14 -0
- data/lib/ffidb/struct.rb +41 -0
- data/lib/ffidb/symbol_table.rb +90 -0
- data/lib/ffidb/symbolic.rb +67 -0
- data/lib/ffidb/sysexits.rb +21 -0
- data/lib/ffidb/type.rb +214 -0
- data/lib/ffidb/typedef.rb +38 -0
- data/lib/ffidb/union.rb +37 -0
- data/lib/ffidb/version.rb +21 -0
- metadata +197 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
require_relative 'location'
|
4
|
+
require_relative 'symbolic'
|
5
|
+
|
6
|
+
require 'pathname'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module FFIDB
|
10
|
+
class Function < ::Struct.new(:name, :type, :parameters, :definition, :comment, keyword_init: true)
|
11
|
+
include Symbolic
|
12
|
+
|
13
|
+
alias_method :result_type, :type
|
14
|
+
alias_method :return_type, :type
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [Boolean]
|
18
|
+
def function?() return true end
|
19
|
+
|
20
|
+
##
|
21
|
+
# @return [Boolean]
|
22
|
+
def public?() self.name[0] != '_' end
|
23
|
+
|
24
|
+
##
|
25
|
+
# @return [Boolean]
|
26
|
+
def nonpublic?() !(self.public?) end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @return [Boolean]
|
30
|
+
def nullary?() self.arity.zero? end
|
31
|
+
|
32
|
+
##
|
33
|
+
# @return [Boolean]
|
34
|
+
def unary?() self.arity.equal?(1) end
|
35
|
+
|
36
|
+
##
|
37
|
+
# @return [Boolean]
|
38
|
+
def binary?() self.arity.equal?(2) end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @return [Boolean]
|
42
|
+
def ternary?() self.arity.equal?(3) end
|
43
|
+
|
44
|
+
##
|
45
|
+
# @return [Integer]
|
46
|
+
def arity() self.parameters.size end
|
47
|
+
|
48
|
+
##
|
49
|
+
# @return [Hash<Symbol, Object>]
|
50
|
+
def to_h
|
51
|
+
{
|
52
|
+
name: self.name.to_s,
|
53
|
+
type: self.type.to_s,
|
54
|
+
parameters: self.parameters&.transform_values { |v| v.type.to_s },
|
55
|
+
definition: self.definition&.to_h,
|
56
|
+
comment: self.comment,
|
57
|
+
}.delete_if { |k, v| v.nil? }
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# @return [String]
|
62
|
+
def to_yaml
|
63
|
+
h = self.to_h
|
64
|
+
h.delete(:parameters) if h[:parameters].empty?
|
65
|
+
h.transform_keys!(&:to_s)
|
66
|
+
h.transform_values! { |v| v.is_a?(Hash) ? v.transform_keys!(&:to_s) : v }
|
67
|
+
YAML.dump(h).gsub!("---\n", "--- !#{self.kind}\n")
|
68
|
+
end
|
69
|
+
end # Function
|
70
|
+
end # FFIDB
|
data/lib/ffidb/glob.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
module FFIDB
|
4
|
+
class Glob
|
5
|
+
attr_reader :pattern
|
6
|
+
attr_reader :compiled
|
7
|
+
|
8
|
+
def initialize(pattern, ignore_case: nil, match_substring: nil)
|
9
|
+
@pattern = pattern.to_s
|
10
|
+
regexp_pattern = Regexp.escape(@pattern).gsub('\*', '.*').gsub('\?', '.')
|
11
|
+
regexp_pattern = "^#{regexp_pattern}$" unless match_substring
|
12
|
+
regexp_options = ignore_case ? Regexp::IGNORECASE : nil
|
13
|
+
@compiled = Regexp.new(regexp_pattern, regexp_options)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [String]
|
18
|
+
def to_s
|
19
|
+
self.pattern
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# @return [Boolean]
|
24
|
+
def ===(string)
|
25
|
+
self.compiled === string
|
26
|
+
end
|
27
|
+
end # Glob
|
28
|
+
end # FFIDB
|
data/lib/ffidb/header.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
require_relative 'symbol_table'
|
4
|
+
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module FFIDB
|
8
|
+
class Header < ::Struct.new(:name, :comment, :typedefs, :enums, :structs, :unions, :functions, keyword_init: true)
|
9
|
+
include SymbolTable
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
##
|
13
|
+
# @param [Header] other
|
14
|
+
# @return [Integer]
|
15
|
+
def <=>(other)
|
16
|
+
self.name <=> other.name
|
17
|
+
end
|
18
|
+
end # Header
|
19
|
+
end # FFIDB
|
@@ -0,0 +1,339 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
require_relative 'enum'
|
4
|
+
require_relative 'function'
|
5
|
+
require_relative 'header'
|
6
|
+
require_relative 'struct'
|
7
|
+
require_relative 'typedef'
|
8
|
+
require_relative 'union'
|
9
|
+
|
10
|
+
require 'pathname'
|
11
|
+
|
12
|
+
module FFIDB
|
13
|
+
class HeaderParser
|
14
|
+
attr_reader :base_directory
|
15
|
+
attr_reader :debug
|
16
|
+
attr_reader :defines
|
17
|
+
attr_reader :include_paths
|
18
|
+
attr_reader :include_symbols
|
19
|
+
attr_reader :exclude_symbols
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param [Pathname, #to_s] base_directory
|
23
|
+
def initialize(base_directory: nil, debug: nil)
|
24
|
+
require 'ffi/clang' # https://rubygems.org/gems/ffi-clang
|
25
|
+
|
26
|
+
@base_directory = base_directory
|
27
|
+
@debug = debug
|
28
|
+
@defines = {}
|
29
|
+
@include_paths = []
|
30
|
+
@include_symbols = {}
|
31
|
+
@exclude_symbols = {}
|
32
|
+
@clang_index = FFI::Clang::Index.new
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# @param [String, #to_s] var_and_val
|
37
|
+
# @return [void]
|
38
|
+
def parse_macro!(var_and_val)
|
39
|
+
var, val = var_and_val.to_s.split('=', 2)
|
40
|
+
val = 1 if val.nil?
|
41
|
+
self.define_macro! var, val
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# @param [Symbol, #to_sym] var
|
46
|
+
# @param [String, #to_s] val
|
47
|
+
# @return [void]
|
48
|
+
def define_macro!(var, val = 1)
|
49
|
+
self.defines[var.to_sym] = val.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# @param [Pathname, #to_s] path
|
54
|
+
# @return [void]
|
55
|
+
def add_include_path!(path)
|
56
|
+
self.include_paths << Pathname(path)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# @param [Pathname, #to_s] path
|
61
|
+
# @yield [exception]
|
62
|
+
# @raise [ParsePanic] if parsing encounters a fatal error
|
63
|
+
# @return [Header]
|
64
|
+
def parse_header(path)
|
65
|
+
path = Pathname(path.to_s) unless path.is_a?(Pathname)
|
66
|
+
name = (self.base_directory ? path.relative_path_from(self.base_directory) : path).to_s
|
67
|
+
args = self.defines.inject([]) { |r, (k, v)| r << "-D#{k}=#{v}" }
|
68
|
+
args += self.include_paths.map { |p| "-I#{p}" }
|
69
|
+
|
70
|
+
translation_unit = nil
|
71
|
+
begin
|
72
|
+
translation_unit = @clang_index.parse_translation_unit(path.to_s, args)
|
73
|
+
rescue FFI::Clang::Error => error
|
74
|
+
raise ParsePanic.new(error.to_s)
|
75
|
+
end
|
76
|
+
|
77
|
+
translation_unit.diagnostics.each do |diagnostic|
|
78
|
+
exception_class = case diagnostic.severity.to_sym
|
79
|
+
when :fatal then raise ParsePanic.new(diagnostic.format)
|
80
|
+
when :error then ParseError
|
81
|
+
when :warning then ParseWarning
|
82
|
+
else ParseWarning
|
83
|
+
end
|
84
|
+
yield exception_class.new(diagnostic.format)
|
85
|
+
end
|
86
|
+
|
87
|
+
okayed_files = {}
|
88
|
+
FFIDB::Header.new(name: name, typedefs: [], enums: [], structs: [], unions: [], functions: []).tap do |header|
|
89
|
+
root_cursor = translation_unit.cursor
|
90
|
+
root_cursor.visit_children do |declaration, _|
|
91
|
+
location = declaration.location
|
92
|
+
location_file = location.file
|
93
|
+
if (okayed_files[location_file] ||= self.consider_path?(location_file))
|
94
|
+
case declaration.kind
|
95
|
+
when :cursor_typedef_decl
|
96
|
+
typedef = self.parse_typedef(declaration) do |symbol|
|
97
|
+
case
|
98
|
+
when symbol.enum? then header.enums << symbol
|
99
|
+
when symbol.struct? then header.structs << symbol
|
100
|
+
when symbol.union? then header.unions << symbol
|
101
|
+
end
|
102
|
+
end
|
103
|
+
header.typedefs << typedef if typedef
|
104
|
+
when :cursor_enum_decl
|
105
|
+
enum_name = declaration.spelling
|
106
|
+
if enum_name && !enum_name.empty?
|
107
|
+
header.enums << self.parse_enum(declaration)
|
108
|
+
end
|
109
|
+
when :cursor_struct
|
110
|
+
struct_name = declaration.spelling
|
111
|
+
if struct_name && !struct_name.empty?
|
112
|
+
if (struct = self.parse_struct(declaration))
|
113
|
+
header.structs << struct
|
114
|
+
end
|
115
|
+
end
|
116
|
+
when :cursor_union
|
117
|
+
union_name = declaration.spelling
|
118
|
+
if union_name && !union_name.empty?
|
119
|
+
if (union = self.parse_union(declaration))
|
120
|
+
header.unions << union
|
121
|
+
end
|
122
|
+
end
|
123
|
+
when :cursor_function
|
124
|
+
function_name = declaration.spelling
|
125
|
+
if self.consider_function?(function_name)
|
126
|
+
function = self.parse_function(declaration)
|
127
|
+
function.definition = self.parse_location(location)
|
128
|
+
header.functions << function
|
129
|
+
end
|
130
|
+
else # TODO: other declarations of interest?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
:continue # visit the next sibling
|
134
|
+
end
|
135
|
+
header.comment = root_cursor.comment&.text
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# @param [FFI::Clang::Cursor] declaration
|
141
|
+
# @return [Typedef]
|
142
|
+
def parse_typedef(declaration, &block)
|
143
|
+
typedef_name = declaration.spelling
|
144
|
+
typedef_type = nil
|
145
|
+
declaration.visit_children do |node, _|
|
146
|
+
node_name = node.spelling
|
147
|
+
case node.kind
|
148
|
+
when :cursor_type_ref
|
149
|
+
typedef_type = node_name
|
150
|
+
when :cursor_enum_decl
|
151
|
+
typedef_type = "enum #{node_name}".rstrip
|
152
|
+
yield self.parse_enum(node, typedef_name: typedef_name)
|
153
|
+
when :cursor_struct
|
154
|
+
typedef_type = "struct #{node_name}".rstrip
|
155
|
+
yield self.parse_struct(node, typedef_name: typedef_name)
|
156
|
+
when :cursor_union
|
157
|
+
typedef_type = "union #{node_name}".rstrip
|
158
|
+
#yield self.parse_union(node, typedef_name: typedef_name) # TODO
|
159
|
+
end
|
160
|
+
:continue # visit the next sibling
|
161
|
+
end
|
162
|
+
FFIDB::Typedef.new(typedef_name, typedef_type) if typedef_type
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# @param [FFI::Clang::Cursor] declaration
|
167
|
+
# @param [String] typedef_name
|
168
|
+
# @return [Enum]
|
169
|
+
def parse_enum(declaration, typedef_name: nil)
|
170
|
+
enum_name = declaration.spelling
|
171
|
+
enum_name = typedef_name if enum_name.empty?
|
172
|
+
FFIDB::Enum.new(enum_name).tap do |enum|
|
173
|
+
declaration.visit_children do |node, _|
|
174
|
+
case node.kind
|
175
|
+
when :cursor_enum_constant_decl
|
176
|
+
k = node.spelling
|
177
|
+
v = node.enum_value
|
178
|
+
enum.values[k] = v
|
179
|
+
end
|
180
|
+
:continue # visit the next sibling
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# @param [FFI::Clang::Cursor] declaration
|
187
|
+
# @param [String] typedef_name
|
188
|
+
# @return [Struct]
|
189
|
+
def parse_struct(declaration, typedef_name: nil)
|
190
|
+
struct_name = declaration.spelling
|
191
|
+
struct_name = typedef_name if struct_name.empty?
|
192
|
+
FFIDB::Struct.new(struct_name).tap do |struct|
|
193
|
+
declaration.visit_children do |node, _|
|
194
|
+
case node.kind
|
195
|
+
when :cursor_field_decl
|
196
|
+
field_name = node.spelling
|
197
|
+
field_type = nil
|
198
|
+
node.visit_children do |node, _|
|
199
|
+
case node.kind
|
200
|
+
when :cursor_type_ref
|
201
|
+
field_type = node.spelling
|
202
|
+
:break
|
203
|
+
else :continue
|
204
|
+
end
|
205
|
+
end
|
206
|
+
struct.fields[field_name.to_sym] = Type.for(field_type)
|
207
|
+
end
|
208
|
+
:continue # visit the next sibling
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# @param [FFI::Clang::Cursor] declaration
|
215
|
+
# @param [String] typedef_name
|
216
|
+
# @return [Union]
|
217
|
+
def parse_union(declaration, typedef_name: nil)
|
218
|
+
# TODO: parse union declarations
|
219
|
+
end
|
220
|
+
|
221
|
+
##
|
222
|
+
# @param [FFI::Clang::Cursor] declaration
|
223
|
+
# @return [Function]
|
224
|
+
def parse_function(declaration)
|
225
|
+
name = declaration.spelling
|
226
|
+
comment = declaration.comment&.text
|
227
|
+
function = FFIDB::Function.new(
|
228
|
+
name: name,
|
229
|
+
type: self.parse_type(declaration.type.result_type),
|
230
|
+
parameters: {},
|
231
|
+
definition: nil, # set in #parse_header()
|
232
|
+
comment: comment && !(comment.empty?) ? comment : nil,
|
233
|
+
)
|
234
|
+
declaration.visit_children do |node, _|
|
235
|
+
case node.kind
|
236
|
+
when :cursor_parm_decl
|
237
|
+
default_name = "_#{function.parameters.size + 1}"
|
238
|
+
parameter = self.parse_parameter(node, default_name: default_name)
|
239
|
+
function.parameters[parameter.name.to_sym] = parameter
|
240
|
+
end
|
241
|
+
:continue # visit the next sibling
|
242
|
+
end
|
243
|
+
function.parameters.freeze
|
244
|
+
function.instance_variable_set(:@debug, declaration.type.spelling.sub(/\s*\(/, " #{name}(")) if self.debug # TODO: __attribute__((noreturn))
|
245
|
+
function
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# @param [FFI::Clang::Cursor] declaration
|
250
|
+
# @param [String, #to_s] default_name
|
251
|
+
# @return [Parameter]
|
252
|
+
def parse_parameter(declaration, default_name: '_')
|
253
|
+
name = declaration.spelling
|
254
|
+
type = self.parse_type(declaration.type)
|
255
|
+
FFIDB::Parameter.new(
|
256
|
+
((name.nil? || name.empty?) ? default_name.to_s : name).to_sym, type)
|
257
|
+
end
|
258
|
+
|
259
|
+
##
|
260
|
+
# @param [FFI::Clang::Type] type
|
261
|
+
# @return [Type]
|
262
|
+
def parse_type(type)
|
263
|
+
ostensible_type = type.spelling
|
264
|
+
ostensible_type.sub!(/\*const$/, '*') # remove private const qualifiers
|
265
|
+
pointer_suffix = case ostensible_type
|
266
|
+
when /(\s\*+)$/
|
267
|
+
ostensible_type.delete_suffix!($1)
|
268
|
+
$1
|
269
|
+
else nil
|
270
|
+
end
|
271
|
+
resolved_type = if self.preserve_type?(ostensible_type)
|
272
|
+
ostensible_type << pointer_suffix if pointer_suffix
|
273
|
+
ostensible_type
|
274
|
+
else
|
275
|
+
type.canonical.spelling
|
276
|
+
end
|
277
|
+
resolved_type.sub!(/\*const$/, '*') # remove private const qualifiers
|
278
|
+
Type.for(resolved_type)
|
279
|
+
end
|
280
|
+
|
281
|
+
##
|
282
|
+
# @param [String, #to_s] type_name
|
283
|
+
# @return [Boolean]
|
284
|
+
def preserve_type?(type_name)
|
285
|
+
case type_name.to_s
|
286
|
+
when 'va_list' then true # <stdarg.h>
|
287
|
+
when '_Bool' then true # <stdbool.h>
|
288
|
+
when 'size_t', 'wchar_t' then true # <stddef.h>
|
289
|
+
when 'const size_t', 'const wchar_t' then true # <stddef.h> # FIXME: need a better solution
|
290
|
+
when /^u?int\d+_t$/, /^u?int\d+_t \*$/ then true # <stdint.h>
|
291
|
+
when /^u?intptr_t$/ then true # <stdint.h>
|
292
|
+
when 'FILE' then true # <stdio.h>
|
293
|
+
when 'ssize_t', 'off_t', 'off64_t' then true # <sys/types.h>
|
294
|
+
else false
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
##
|
299
|
+
# @param [FFI::Clang::ExpansionLocation] location
|
300
|
+
# @return [Location]
|
301
|
+
def parse_location(location)
|
302
|
+
return nil if location.nil?
|
303
|
+
FFIDB::Location.new(
|
304
|
+
file: location.file ? self.make_relative_path(location.file).to_s : nil,
|
305
|
+
line: location.line,
|
306
|
+
)
|
307
|
+
end
|
308
|
+
|
309
|
+
protected
|
310
|
+
|
311
|
+
##
|
312
|
+
# @param [String, #to_s] function_name
|
313
|
+
# @return [Boolean]
|
314
|
+
def consider_function?(function_name)
|
315
|
+
function_name = function_name.to_s
|
316
|
+
if not self.include_symbols.empty?
|
317
|
+
self.include_symbols[function_name]
|
318
|
+
else
|
319
|
+
!self.exclude_symbols[function_name]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
##
|
324
|
+
# @param [Pathname, #to_s] path
|
325
|
+
# @return [Boolean]
|
326
|
+
def consider_path?(path)
|
327
|
+
path = Pathname(path) unless path.is_a?(Pathname)
|
328
|
+
path.expand_path.to_s.start_with?(base_directory.expand_path.to_s << '/')
|
329
|
+
end
|
330
|
+
|
331
|
+
##
|
332
|
+
# @param [Pathname, #to_s] path
|
333
|
+
# @return [Pathname]
|
334
|
+
def make_relative_path(path)
|
335
|
+
path = Pathname(path) unless path.is_a?(Pathname)
|
336
|
+
self.base_directory ? path.relative_path_from(self.base_directory) : path
|
337
|
+
end
|
338
|
+
end # HeaderParser
|
339
|
+
end # FFIDB
|