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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS +1 -0
  3. data/CHANGES.md +7 -0
  4. data/CREDITS.md +2 -0
  5. data/README.md +201 -0
  6. data/UNLICENSE +24 -0
  7. data/VERSION +1 -0
  8. data/bin/ffidb +387 -0
  9. data/etc/mappings/dart.yaml +35 -0
  10. data/etc/mappings/java.yaml +36 -0
  11. data/etc/mappings/lisp.yaml +35 -0
  12. data/etc/mappings/python.yaml +35 -0
  13. data/etc/mappings/ruby.yaml +35 -0
  14. data/etc/templates/c.erb +46 -0
  15. data/etc/templates/cpp.erb +45 -0
  16. data/etc/templates/dart.erb +64 -0
  17. data/etc/templates/go.erb +50 -0
  18. data/etc/templates/java.erb +56 -0
  19. data/etc/templates/lisp.erb +49 -0
  20. data/etc/templates/python.erb +59 -0
  21. data/etc/templates/ruby.erb +48 -0
  22. data/lib/ffidb.rb +34 -0
  23. data/lib/ffidb/enum.rb +37 -0
  24. data/lib/ffidb/errors.rb +64 -0
  25. data/lib/ffidb/exporter.rb +141 -0
  26. data/lib/ffidb/exporters.rb +28 -0
  27. data/lib/ffidb/exporters/c.rb +52 -0
  28. data/lib/ffidb/exporters/cpp.rb +13 -0
  29. data/lib/ffidb/exporters/csharp.rb +6 -0
  30. data/lib/ffidb/exporters/csv.rb +24 -0
  31. data/lib/ffidb/exporters/dart.rb +60 -0
  32. data/lib/ffidb/exporters/go.rb +16 -0
  33. data/lib/ffidb/exporters/haskell.rb +3 -0
  34. data/lib/ffidb/exporters/java.rb +39 -0
  35. data/lib/ffidb/exporters/json.rb +38 -0
  36. data/lib/ffidb/exporters/julia.rb +3 -0
  37. data/lib/ffidb/exporters/lisp.rb +41 -0
  38. data/lib/ffidb/exporters/luajit.rb +3 -0
  39. data/lib/ffidb/exporters/nim.rb +4 -0
  40. data/lib/ffidb/exporters/nodejs.rb +4 -0
  41. data/lib/ffidb/exporters/ocaml.rb +4 -0
  42. data/lib/ffidb/exporters/php.rb +4 -0
  43. data/lib/ffidb/exporters/python.rb +35 -0
  44. data/lib/ffidb/exporters/racket.rb +3 -0
  45. data/lib/ffidb/exporters/ruby.rb +33 -0
  46. data/lib/ffidb/exporters/rust.rb +5 -0
  47. data/lib/ffidb/exporters/yaml.rb +31 -0
  48. data/lib/ffidb/exporters/zig.rb +3 -0
  49. data/lib/ffidb/function.rb +70 -0
  50. data/lib/ffidb/glob.rb +28 -0
  51. data/lib/ffidb/header.rb +19 -0
  52. data/lib/ffidb/header_parser.rb +339 -0
  53. data/lib/ffidb/library.rb +120 -0
  54. data/lib/ffidb/library_parser.rb +132 -0
  55. data/lib/ffidb/location.rb +17 -0
  56. data/lib/ffidb/parameter.rb +35 -0
  57. data/lib/ffidb/registry.rb +87 -0
  58. data/lib/ffidb/release.rb +14 -0
  59. data/lib/ffidb/struct.rb +41 -0
  60. data/lib/ffidb/symbol_table.rb +90 -0
  61. data/lib/ffidb/symbolic.rb +67 -0
  62. data/lib/ffidb/sysexits.rb +21 -0
  63. data/lib/ffidb/type.rb +214 -0
  64. data/lib/ffidb/typedef.rb +38 -0
  65. data/lib/ffidb/union.rb +37 -0
  66. data/lib/ffidb/version.rb +21 -0
  67. metadata +197 -0
@@ -0,0 +1,49 @@
1
+ <% if header? %>
2
+ ; <%= FFIDB.header %>
3
+
4
+ <% end %>
5
+ (asdf:load-system :cffi)
6
+ <% for library in @libraries %>
7
+ <% if library && @functions[library] %>
8
+
9
+ (cffi:define-foreign-library <%= library&.name || :lib %>
10
+ (t (:default "<%= dlopen_paths_for(library).first %>")))
11
+
12
+ (cffi:use-foreign-library <%= library&.name || :lib %>)
13
+ <% end %>
14
+ <% for enum in @enums[library] || [] %>
15
+
16
+ <% if enum.comment %>
17
+ ;; <%= enum.comment %>
18
+ <% end %>
19
+ (cffi:defcenum <%= enum.name %>
20
+ <% for name, value in enum.values || {} %>
21
+ (:<%= name %> <%= value %>)
22
+ <% end %>
23
+ )
24
+ <% end %>
25
+ <% for struct in @structs[library] || [] %>
26
+
27
+ <% if struct.comment %>
28
+ ;; <%= struct.comment %>
29
+ <% end %>
30
+ <% if struct.opaque? %>
31
+ (cffi:defctype <%= struct.name %> :pointer)
32
+ <% else %>
33
+ (cffi:defcstruct <%= struct.name %>
34
+ <% for name, type in struct.fields || {} %>
35
+ (<%= name %> <%= struct_type(type).map(&:inspect).join(' ') %>)
36
+ <% end %>
37
+ )
38
+ <% end %>
39
+ <% end %>
40
+ <% for function in @functions[library] || [] %>
41
+
42
+ <% if function.comment %>
43
+ ;; <%= function.comment %>
44
+ <% end %>
45
+ (cffi:defcfun "<%= function.name %>" <%= param_type(function.type).inspect %> <%=
46
+ function.parameters.each_value.map { |p| "(#{p.name} #{param_type(p.type).inspect})" }.join(' ')
47
+ %>)
48
+ <% end %>
49
+ <% end %>
@@ -0,0 +1,59 @@
1
+ <% if header? %>
2
+ # <%= FFIDB.header %>
3
+
4
+ <% end %>
5
+ import ctypes, ctypes.util
6
+ <% for library in @libraries %>
7
+ <% if library && @functions[library] %>
8
+
9
+ <%= library&.name || :lib %> = ctypes.CDLL(
10
+ <% self.dlopen_paths_for(library).each_with_index do |library_path, i| %>
11
+ <%= i.nonzero? ? 'or ' : '' %>ctypes.util.find_library("<%= library_path %>")
12
+ <% end %>
13
+ <% if !options[:library_path] && library&.dlopen %>
14
+ or "<%= library.dlopen.first %>"
15
+ <% end %>
16
+ )
17
+ <% end %>
18
+ <% for enum in @enums[library] || [] %>
19
+
20
+ <% if enum.comment %>
21
+ # <%= enum.comment %>
22
+ <% end %>
23
+ <%= enum.name %> = ctypes.c_int
24
+ <% for name, value in enum.values || {} %>
25
+ <%= name %> = <%= value %>
26
+ <% end %>
27
+ <% end %>
28
+ <% for struct in @structs[library] || [] %>
29
+
30
+ <% if struct.comment %>
31
+ # <%= struct.comment %>
32
+ <% end %>
33
+ <% if struct.opaque? %>
34
+ <%= struct.name %> = ctypes.POINTER(ctypes.c_void)
35
+ <% else %>
36
+ class <%= struct.name %>(Structure):
37
+ <% if struct.fields.nil? || struct.fields.empty? %>
38
+ _fields_ = []
39
+ <% else %>
40
+ _fields_ = [
41
+ <% for name, type in struct.fields || {} %>
42
+ (<%= name.to_s.inspect %>, <%= struct_type(type) %>), # <%= type %>
43
+ <% end %>
44
+ ]
45
+ <% end %>
46
+ <% end %>
47
+ <% end %>
48
+ <% for function in @functions[library] || [] %>
49
+
50
+ <% if function.comment %>
51
+ # <%= function.comment %>
52
+ <% end %>
53
+ <%= function.name %> = <%= library&.name || :lib %>.<%= function.name %>
54
+ <%= function.name %>.restype = <%= param_type(function.type) %>
55
+ <%= function.name %>.argtypes = [<%=
56
+ function.parameters.each_value.map { |p| param_type(p.type) }.join(', ')
57
+ %>]
58
+ <% end %>
59
+ <% end %>
@@ -0,0 +1,48 @@
1
+ <% if header? %>
2
+ # <%= FFIDB.header %>
3
+
4
+ <% end %>
5
+ require 'ffi'
6
+ <% for library in @libraries %>
7
+
8
+ module <%= options[:module] || library&.name&.capitalize || :FFI %>
9
+ <% if library && @functions[library] %>
10
+ extend FFI::Library
11
+ ffi_lib [<%= dlopen_paths_for(library).map(&:inspect).join(', ') %>]
12
+ <% end %>
13
+ <% for enum in @enums[library] || [] %>
14
+
15
+ <% if enum.comment %>
16
+ # <%= enum.comment %>
17
+ <% end %>
18
+ <%= enum.name %> = :int
19
+ <% for name, value in enum.values || {} %>
20
+ <%= name %> = <%= value %>
21
+ <% end %>
22
+ <% end %>
23
+ <% for struct in @structs[library] || [] %>
24
+
25
+ <% if struct.comment %>
26
+ # <%= struct.comment %>
27
+ <% end %>
28
+ <% if struct.opaque? %>
29
+ <%= struct.name %> = FFI::Pointer
30
+ <% else %>
31
+ class <%= struct.name %> < FFI::Struct
32
+ <% for (name, type), i in (struct.fields || {}).each_with_index %>
33
+ <%= i.zero? ? 'layout' : ' '*6 %> :<%= name %>, <%= struct_type(type).inspect %><%= (i == (struct.fields || {}).size-1) ? '' : ',' %> # <%= type %>
34
+ <% end %>
35
+ end
36
+ <% end %>
37
+ <% end %>
38
+ <% for function in @functions[library] || [] %>
39
+
40
+ <% if function.comment %>
41
+ # <%= function.comment %>
42
+ <% end %>
43
+ attach_function :<%= function.name %>, [<%=
44
+ function.parameters.each_value.map { |p| param_type(p.type).inspect }.join(', ')
45
+ %>], :<%= param_type(function.type) %>
46
+ <% end %>
47
+ end # <%= options[:module] || library&.name&.capitalize || :FFI %>
48
+ <% end %>
@@ -0,0 +1,34 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require_relative 'ffidb/enum'
4
+ require_relative 'ffidb/errors'
5
+ require_relative 'ffidb/exporter'
6
+ require_relative 'ffidb/exporters'
7
+ require_relative 'ffidb/function'
8
+ require_relative 'ffidb/glob'
9
+ require_relative 'ffidb/header'
10
+ require_relative 'ffidb/header_parser'
11
+ require_relative 'ffidb/library'
12
+ require_relative 'ffidb/library_parser'
13
+ require_relative 'ffidb/location'
14
+ require_relative 'ffidb/parameter'
15
+ require_relative 'ffidb/registry'
16
+ require_relative 'ffidb/release'
17
+ require_relative 'ffidb/struct'
18
+ require_relative 'ffidb/symbolic'
19
+ require_relative 'ffidb/symbol_table'
20
+ require_relative 'ffidb/sysexits'
21
+ require_relative 'ffidb/type'
22
+ require_relative 'ffidb/typedef'
23
+ require_relative 'ffidb/union'
24
+ require_relative 'ffidb/version'
25
+
26
+ module FFIDB
27
+ HEADER = "This is free and unencumbered software released into the public domain.".freeze
28
+
29
+ ##
30
+ # @return [String]
31
+ def self.header
32
+ HEADER
33
+ end
34
+ end # FFIDB
@@ -0,0 +1,37 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require_relative 'symbolic'
4
+
5
+ module FFIDB
6
+ class Enum < ::Struct.new(:name, :values, :comment)
7
+ include Symbolic
8
+
9
+ ##
10
+ # @param [Symbol, #to_sym] name
11
+ # @param [Map<String, Integer>] values
12
+ # @param [String, #to_s] comment
13
+ def initialize(name, values = {}, comment = nil)
14
+ super(name.to_sym, values || {}, comment&.to_s)
15
+ end
16
+
17
+ ##
18
+ # @return [Boolean]
19
+ def enum?() return true end
20
+
21
+ ##
22
+ # @return [String]
23
+ def to_s
24
+ "enum #{self.name}"
25
+ end
26
+
27
+ ##
28
+ # @return [Hash<Symbol, Type>]
29
+ def to_h
30
+ {
31
+ name: self.name.to_s,
32
+ comment: self.comment,
33
+ values: self.values,
34
+ }.delete_if { |k, v| v.nil? }
35
+ end
36
+ end # Enum
37
+ end # FFIDB
@@ -0,0 +1,64 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ require_relative 'sysexits'
4
+
5
+ module FFIDB
6
+ ##
7
+ # Base class for FFIDB errors.
8
+ class Error < StandardError
9
+ EXIT_CODE = Sysexits::EX_SOFTWARE
10
+
11
+ ##
12
+ # @return [Integer]
13
+ def exit_code
14
+ self.class.const_get(:EXIT_CODE)
15
+ end
16
+ end
17
+
18
+ ##
19
+ # An error indicating that a problem with the registry.
20
+ class RegistryError < Error
21
+ EXIT_CODE = Sysexits::EX_CONFIG
22
+ end
23
+
24
+ ##
25
+ # An error indicating that opening a registry directory requires a newer
26
+ # version of FFIDB.rb than the current one.
27
+ class RegistryVersionMismatch < RegistryError; end
28
+
29
+ ##
30
+ # An error indicating that an FFI symbol was not found.
31
+ class SymbolNotFound < Error
32
+ EXIT_CODE = Sysexits::EX_NOINPUT
33
+
34
+ def initialize(symbol_kind, symbol_name)
35
+ super("#{symbol_kind.to_s.capitalize} not found: #{symbol_name}")
36
+ end
37
+ end
38
+
39
+ ##
40
+ # An error indicating that an FFI function was not found.
41
+ class FunctionNotFound < SymbolNotFound
42
+ def initialize(function_name)
43
+ super(:function, function_name)
44
+ end
45
+ end
46
+
47
+ ##
48
+ # A warning raised during header file parsing.
49
+ class ParseWarning < Error
50
+ EXIT_CODE = Sysexits::EX_DATAERR
51
+ end
52
+
53
+ ##
54
+ # An error raised during header file parsing.
55
+ class ParseError < Error
56
+ EXIT_CODE = Sysexits::EX_DATAERR
57
+ end
58
+
59
+ ##
60
+ # A fatal error raised during header file parsing.
61
+ class ParsePanic < Error
62
+ EXIT_CODE = Sysexits::EX_DATAERR
63
+ end
64
+ end # FFIDB
@@ -0,0 +1,141 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ #require 'erb'
4
+ require 'tilt' # https://rubygems.org/gems/tilt
5
+ require 'yaml'
6
+
7
+ module FFIDB
8
+ class Exporter
9
+ def self.for(format) # TODO
10
+ require_relative 'exporters'
11
+ case format&.to_sym
12
+ when :c, :c99, :c11, :c18 then Exporters::C
13
+ when :'c++', :'c++11', :'c++14', :'c++17', :'c++20', :cpp, :cxx then Exporters::Cpp
14
+ when :csv then Exporters::CSV
15
+ when :dart, :flutter then Exporters::Dart
16
+ when :go, :cgo then Exporters::Go
17
+ when :java, :jna then Exporters::Java
18
+ when :json then Exporters::JSON
19
+ when :lisp, :'common-lisp' then Exporters::Lisp
20
+ when :python, :py then Exporters::Python
21
+ when :ruby, :rb then Exporters::Ruby
22
+ # TODO: csharp, haskell, julia, luajit, nim, nodejs, ocaml, php, racket, rust, zig
23
+ when :yaml then Exporters::YAML
24
+ else raise "unknown output format: #{format}"
25
+ end
26
+ end
27
+
28
+ attr_reader :options
29
+
30
+ def initialize(stream = $stdout, **kwargs)
31
+ @stream = stream
32
+ @options = kwargs.transform_keys(&:to_sym).freeze
33
+ end
34
+
35
+ def debug?
36
+ self.options[:debug]
37
+ end
38
+
39
+ def verbose?
40
+ self.options[:verbose] || self.debug?
41
+ end
42
+
43
+ def header?
44
+ self.options[:header]
45
+ end
46
+
47
+ def emit(&block)
48
+ begin
49
+ self.begin
50
+ yield self
51
+ self.finish
52
+ ensure
53
+ self.close
54
+ end
55
+ end
56
+
57
+ def dlopen_paths_for(library)
58
+ if library_path = self.options[:library_path]
59
+ library.objects.map { |lib| library_path.delete_suffix('/') << "/" << lib }
60
+ else
61
+ library.objects + library.dlopen
62
+ end
63
+ end
64
+
65
+ def begin() end
66
+
67
+ def begin_library(library)
68
+ @library = library
69
+ @libraries ||= []
70
+ @libraries << library
71
+ @typedefs ||= {}
72
+ @enums ||= {}
73
+ @structs ||= {}
74
+ @unions ||= {}
75
+ @functions ||= {}
76
+ end
77
+
78
+ def export_header(header)
79
+ header.typedefs.sort.each { |typedef| self.export_typedef(typedef) }
80
+ header.enums.sort.each { |enum| self.export_enum(enum) }
81
+ header.structs.sort.each { |struct| self.export_struct(struct) }
82
+ header.unions.sort.each { |union| self.export_union(union) }
83
+ header.functions.sort.each { |function| self.export_function(function) }
84
+ end
85
+
86
+ def export_symbol(symbol, disabled: nil)
87
+ self.__send__("export_#{symbol.kind}", symbol, disabled: disabled)
88
+ end
89
+
90
+ def export_typedef(typedef, disabled: nil)
91
+ (@typedefs[@library] ||= []) << typedef
92
+ end
93
+
94
+ def export_enum(enum, disabled: nil)
95
+ (@enums[@library] ||= []) << enum
96
+ end
97
+
98
+ def export_struct(struct, disabled: nil)
99
+ (@structs[@library] ||= []) << struct
100
+ end
101
+
102
+ def export_union(union, disabled: nil)
103
+ (@unions[@library] ||= []) << union
104
+ end
105
+
106
+ def export_function(function, disabled: nil)
107
+ (@functions[@library] ||= []) << function
108
+ end
109
+
110
+ def finish_library
111
+ @library = nil
112
+ end
113
+
114
+ def finish() end
115
+
116
+ def close() end
117
+
118
+ protected
119
+
120
+ def puts(*args)
121
+ @stream.puts *args
122
+ end
123
+
124
+ def print(*args)
125
+ @stream.print *args
126
+ end
127
+
128
+ def render_template(template_name)
129
+ #ERB.new(self.load_template(template_name)).result(binding)
130
+ Tilt.new(self.path_to_template(template_name)).render(self)
131
+ end
132
+
133
+ def load_template(template_name)
134
+ File.read(self.path_to_template(template_name))
135
+ end
136
+
137
+ def path_to_template(template_name)
138
+ File.expand_path("../../etc/templates/#{template_name}", __dir__)
139
+ end
140
+ end # Exporter
141
+ end # FFIDB
@@ -0,0 +1,28 @@
1
+ # This is free and unencumbered software released into the public domain.
2
+
3
+ module FFIDB
4
+ module Exporters; end
5
+ end
6
+
7
+ require_relative 'exporters/c'
8
+ require_relative 'exporters/cpp'
9
+ require_relative 'exporters/csharp'
10
+ require_relative 'exporters/csv'
11
+ require_relative 'exporters/dart'
12
+ require_relative 'exporters/go'
13
+ require_relative 'exporters/haskell'
14
+ require_relative 'exporters/java'
15
+ require_relative 'exporters/julia'
16
+ require_relative 'exporters/json'
17
+ require_relative 'exporters/lisp'
18
+ require_relative 'exporters/luajit'
19
+ require_relative 'exporters/nim'
20
+ require_relative 'exporters/nodejs'
21
+ require_relative 'exporters/ocaml'
22
+ require_relative 'exporters/php'
23
+ require_relative 'exporters/python'
24
+ require_relative 'exporters/racket'
25
+ require_relative 'exporters/ruby'
26
+ require_relative 'exporters/rust'
27
+ require_relative 'exporters/yaml'
28
+ require_relative 'exporters/zig'