c2ffi4rb 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +13 -6
- data/exe/c2ffi4rb +5 -5
- data/lib/c2ffi4rb/bind_gen.rb +230 -0
- data/lib/c2ffi4rb/version.rb +1 -1
- data/lib/c2ffi4rb.rb +1 -1
- metadata +4 -4
- data/lib/c2ffi4rb/parser.rb +0 -187
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9471bfff4cabf71daa4a888c294218eb8b74200168b35584cb166771a062fe4a
|
4
|
+
data.tar.gz: f84570b4abbae4fd1eba8e1ee76554da81aa621a92be31014748ac5e5aa5e20e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2443c99c551de4a8c42a977604c4654e75a0f9f9c754395fb894bdcf488d388e1523aac2a0862126baf17b7cda86966e902f71bbf0ecd2db54f8a68df1e2da8
|
7
|
+
data.tar.gz: ce7450668d888f8fc575e56a8a9be650ea2862044feac2e49e0a94f5f50834576cb3a3a46c4f6ac05ea056942e6b9924401342b87876941ebfe97dddd45c5e02
|
data/README.md
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
[](https://badge.fury.io/rb/c2ffi4rb)
|
4
4
|
[](https://github.com/kojix2/c2ffi4rb/actions/workflows/ci.yml)
|
5
5
|
|
6
|
-
|
7
6
|
[c2ffi](https://github.com/rpav/c2ffi) - FFI binding generator - for Ruby
|
8
7
|
|
9
8
|
## installation
|
@@ -14,21 +13,29 @@ gem install c2ffi4rb
|
|
14
13
|
|
15
14
|
## Usage
|
16
15
|
|
17
|
-
First, produce a `spec` file using `c2ffi
|
16
|
+
First, produce a `spec` file using `c2ffi`.
|
18
17
|
|
19
18
|
```sh
|
20
19
|
c2ffi -M macros.h -o example.json example.h
|
21
|
-
c2ffi -o macros.json macros.h
|
20
|
+
c2ffi -o macros.json example.h macros.h
|
22
21
|
```
|
23
22
|
|
24
|
-
|
25
|
-
`c2ffi-ruby`, as follows:
|
23
|
+
Next, use c2ffi4rb to generate ruby code.
|
26
24
|
|
27
25
|
```sh
|
28
26
|
c2ffi4rb example.json macro.json > simple.rb
|
29
27
|
```
|
30
28
|
|
31
|
-
|
29
|
+
Finally, improve simple.rb manually to complete the binding.
|
30
|
+
|
31
|
+
## Development
|
32
|
+
|
33
|
+
To run tests, install [c2ffi][c2ffi], [Cairo][cairo], and [pkg-config][pkg-config] in advance.
|
34
|
+
Then execute `rake test`.
|
35
|
+
|
36
|
+
[cairo]: https://www.cairographics.org/ "Cairo"
|
37
|
+
[pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/ "freedesktop.org"
|
38
|
+
[c2ffi]: https://github.com/rpav/c2ffi "GitHub"
|
32
39
|
|
33
40
|
## Licence
|
34
41
|
|
data/exe/c2ffi4rb
CHANGED
@@ -8,16 +8,16 @@
|
|
8
8
|
require 'optparse'
|
9
9
|
require 'c2ffi4rb'
|
10
10
|
|
11
|
-
options = {}
|
12
|
-
|
13
|
-
Version = C2FFI4RB::VERSION # Fixme
|
14
|
-
|
15
11
|
opts = OptionParser.new
|
16
12
|
opts.banner = 'Usage: c2ffi4rb [options] [file1, file2, ...]'
|
17
13
|
opts.on('-h', '--help', 'Show this message') do
|
18
14
|
puts opts
|
19
15
|
exit
|
20
16
|
end
|
17
|
+
opts.on('-v', '--version', 'Show version') do
|
18
|
+
puts C2FFI4RB::VERSION
|
19
|
+
exit
|
20
|
+
end
|
21
21
|
opts.parse!
|
22
22
|
|
23
23
|
if ARGV.empty?
|
@@ -32,4 +32,4 @@ ARGV.each do |file|
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
C2FFI4RB::
|
35
|
+
C2FFI4RB::BindGen.generate_bindings(spec)
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module C2FFI4RB
|
6
|
+
class BindGen
|
7
|
+
TYPE_TABLE = {
|
8
|
+
':unsigned-int' => ':uint',
|
9
|
+
':unsigned-char' => ':uchar',
|
10
|
+
':unsigned-short' => ':ushort',
|
11
|
+
':long-long' => ':long_long',
|
12
|
+
':unsigned-long' => ':ulong',
|
13
|
+
':unsigned-long-long' => ':ulong_long',
|
14
|
+
':function-pointer' => ':pointer'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def self.generate_bindings(arr)
|
18
|
+
new.generate_bindings(arr)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@struct_type = []
|
23
|
+
@toplevels = []
|
24
|
+
@anon_counter = 0
|
25
|
+
@anon_enum_ids = []
|
26
|
+
@typedefs = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate_bindings(arr)
|
30
|
+
arr.each { |form| process_toplevel(form) }
|
31
|
+
puts @toplevels.join("\n\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def process_toplevel(form)
|
37
|
+
lines = parse_form(form)
|
38
|
+
@toplevels << lines
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_form(form)
|
42
|
+
case form[:tag]
|
43
|
+
when 'typedef' then generate_typedef(form)
|
44
|
+
when 'const' then generate_const(form)
|
45
|
+
when 'extern' then generate_extern(form)
|
46
|
+
when 'function' then generate_function(form)
|
47
|
+
when 'struct', 'union' then generate_struct_or_union(form)
|
48
|
+
when 'enum' then generate_enum(form)
|
49
|
+
else
|
50
|
+
raise "Unknown form: #{form[:tag]}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_typedef(form)
|
55
|
+
type = resolve_type(form[:type])
|
56
|
+
if @struct_type.include?(type)
|
57
|
+
name = define_struct(form[:name])
|
58
|
+
"#{name} = #{type}"
|
59
|
+
elsif type == ":{#{form[:name]}}"
|
60
|
+
warn "Ignoring self-referential typedef #{form[:name]}"
|
61
|
+
else
|
62
|
+
if @typedefs.has_key?(form[:name])
|
63
|
+
return "# typedef already defined? #{form[:name]}" if @typedefs[form[:name]] == type
|
64
|
+
|
65
|
+
warn "# Redefinition of #{form[:name]} from #{@typedefs[form[:name]]} to #{type}"
|
66
|
+
|
67
|
+
end
|
68
|
+
@typedefs[form[:name]] = type
|
69
|
+
"typedef #{type}, :#{form[:name]}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate_const(form)
|
74
|
+
type = resolve_type(form[:type])
|
75
|
+
value = type == ':string' ? "\"#{form[:value]}\"" : form[:value]
|
76
|
+
"#{form[:name].upcase} = #{value}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_extern(form)
|
80
|
+
"attach_variable :#{form[:name]}, :#{form[:name]}, #{resolve_type(form[:type])}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_function(form)
|
84
|
+
params = form[:parameters].map { |f| " #{resolve_type(f[:type])}," }.join("\n")
|
85
|
+
<<~FUNCTION
|
86
|
+
attach_function '#{form[:name]}', [
|
87
|
+
#{params}
|
88
|
+
], #{resolve_type(form[:'return-type'])}
|
89
|
+
FUNCTION
|
90
|
+
end
|
91
|
+
|
92
|
+
def generate_struct_or_union(form)
|
93
|
+
create_struct_definition(form)
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_enum(form)
|
97
|
+
id = form[:id]
|
98
|
+
if form[:name].empty?
|
99
|
+
return "# Already defined? anon_type_#{id}" if @anon_enum_ids.include?(id)
|
100
|
+
|
101
|
+
@anon_enum_ids << id
|
102
|
+
end
|
103
|
+
name = normalize_enum_name(form[:name])
|
104
|
+
fields = form[:fields].map { |f| " :#{f[:name]}, #{f[:value]}," }.join("\n")
|
105
|
+
<<~ENUM
|
106
|
+
enum #{name}, [
|
107
|
+
#{fields}
|
108
|
+
]
|
109
|
+
ENUM
|
110
|
+
end
|
111
|
+
|
112
|
+
def normalize_struct_name(name)
|
113
|
+
# Anonymous structs are given a name
|
114
|
+
if name.empty?
|
115
|
+
@anon_counter += 1
|
116
|
+
name = "Anon_Type_#{@anon_counter}"
|
117
|
+
return name
|
118
|
+
end
|
119
|
+
|
120
|
+
# Do not allow names that start with an underscore
|
121
|
+
name = 'C' + name if name.start_with?('_')
|
122
|
+
|
123
|
+
# Convert snake_case to CamelCase
|
124
|
+
name = name[0].upcase + name[1..-1]
|
125
|
+
|
126
|
+
name.gsub!(/_([a-z])/) { |m| "#{m[1].upcase}" }
|
127
|
+
|
128
|
+
name
|
129
|
+
end
|
130
|
+
|
131
|
+
def register_struct(name)
|
132
|
+
if @struct_type.include? name
|
133
|
+
false
|
134
|
+
else
|
135
|
+
@struct_type << name
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def normalize_enum_name(name)
|
140
|
+
# Anonymous enums are given a name
|
141
|
+
if name.empty?
|
142
|
+
@anon_counter += 1
|
143
|
+
name = "anon_type_#{@anon_counter}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# All enums are prefixed with a colon
|
147
|
+
name = ":#{name}" unless name.start_with?(':')
|
148
|
+
name
|
149
|
+
end
|
150
|
+
|
151
|
+
def define_struct(name)
|
152
|
+
name = normalize_struct_name(name)
|
153
|
+
register_struct(name)
|
154
|
+
name
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_struct_definition(form)
|
158
|
+
name = normalize_struct_name(form[:name])
|
159
|
+
# FIXME: This is a hack to avoid redefining structs
|
160
|
+
return "# Already defined? #{name}" if @struct_type.include?(name)
|
161
|
+
|
162
|
+
register_struct(name)
|
163
|
+
|
164
|
+
type = form[:tag] == 'struct' ? 'FFI::Struct' : 'FFI::Union'
|
165
|
+
|
166
|
+
lines = ["class #{name} < #{type}"]
|
167
|
+
|
168
|
+
if form[:fields].any?
|
169
|
+
lines << ' layout \\'
|
170
|
+
anon_field_counter = 0
|
171
|
+
|
172
|
+
form[:fields].each_with_index do |f, i|
|
173
|
+
field_name = f[:name].empty? ? "anon_field_#{anon_field_counter}" : f[:name]
|
174
|
+
anon_field_counter += 1 if f[:name].empty?
|
175
|
+
|
176
|
+
sep = i == form[:fields].size - 1 ? '' : ','
|
177
|
+
lines << " :#{field_name}, #{resolve_type(f[:type])}#{sep}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
lines << 'end'
|
182
|
+
lines.join("\n")
|
183
|
+
end
|
184
|
+
|
185
|
+
def resolve_type(form)
|
186
|
+
TYPE_TABLE.fetch(form[:tag]) do
|
187
|
+
case form[:tag]
|
188
|
+
when ':pointer' then resolve_pointer_type(form)
|
189
|
+
when ':array' then resolve_array_type(form)
|
190
|
+
when ':struct', ':union' then define_struct(form[:name])
|
191
|
+
when ':enum' then normalize_enum_name(form[:name])
|
192
|
+
when 'struct', 'union' then define_struct_or_union_type(form)
|
193
|
+
when 'enum' then normalize_enum_name_type(form)
|
194
|
+
else resolve_default_type(form)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def resolve_pointer_type(form)
|
200
|
+
pointee = resolve_type(form[:type])
|
201
|
+
if [':char', ':uchar'].include?(pointee)
|
202
|
+
':string'
|
203
|
+
elsif @struct_type.include?(pointee)
|
204
|
+
"#{pointee}.ptr"
|
205
|
+
else
|
206
|
+
':pointer'
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def resolve_array_type(form)
|
211
|
+
"[#{resolve_type(form[:type])}, #{form[:size]}]"
|
212
|
+
end
|
213
|
+
|
214
|
+
def normalize_enum_name_type(form)
|
215
|
+
form[:name] = normalize_enum_name(form[:name])
|
216
|
+
process_toplevel(form)
|
217
|
+
form[:name]
|
218
|
+
end
|
219
|
+
|
220
|
+
def define_struct_or_union_type(form)
|
221
|
+
form[:name] = normalize_struct_name(form[:name])
|
222
|
+
process_toplevel(form)
|
223
|
+
form[:name]
|
224
|
+
end
|
225
|
+
|
226
|
+
def resolve_default_type(form)
|
227
|
+
form[:tag].start_with?(':') ? form[:tag] : ":#{form[:tag]}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
data/lib/c2ffi4rb/version.rb
CHANGED
data/lib/c2ffi4rb.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: c2ffi4rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kojix2
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-09-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -65,7 +65,7 @@ files:
|
|
65
65
|
- README.md
|
66
66
|
- exe/c2ffi4rb
|
67
67
|
- lib/c2ffi4rb.rb
|
68
|
-
- lib/c2ffi4rb/
|
68
|
+
- lib/c2ffi4rb/bind_gen.rb
|
69
69
|
- lib/c2ffi4rb/version.rb
|
70
70
|
homepage: https://github.com/kojix2/c2ffi4rb
|
71
71
|
licenses:
|
@@ -86,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
86
|
- !ruby/object:Gem::Version
|
87
87
|
version: '0'
|
88
88
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
89
|
+
rubygems_version: 3.5.11
|
90
90
|
signing_key:
|
91
91
|
specification_version: 4
|
92
92
|
summary: C2FFI for Ruby-FFI
|
data/lib/c2ffi4rb/parser.rb
DELETED
@@ -1,187 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module C2FFI4RB
|
6
|
-
class Parser
|
7
|
-
TYPE_TABLE = {
|
8
|
-
':unsigned-int' => ':uint',
|
9
|
-
':unsigned-char' => ':uchar',
|
10
|
-
':unsigned-long' => ':ulong',
|
11
|
-
':function-pointer' => ':pointer'
|
12
|
-
}
|
13
|
-
|
14
|
-
def self.parse(arr)
|
15
|
-
Parser.new.parse(arr)
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize
|
19
|
-
@struct_type = []
|
20
|
-
@toplevels = []
|
21
|
-
@anon_counter = 0
|
22
|
-
end
|
23
|
-
|
24
|
-
def parse(arr)
|
25
|
-
arr.each do |form|
|
26
|
-
parse_toplevel(form)
|
27
|
-
end
|
28
|
-
|
29
|
-
puts @toplevels.join("\n\n")
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def parse_toplevel(form)
|
35
|
-
lines = \
|
36
|
-
case form[:tag]
|
37
|
-
when 'typedef'
|
38
|
-
type = parse_type(form[:type])
|
39
|
-
|
40
|
-
# I don't think typedef works right with structs, so assign
|
41
|
-
if @struct_type.include?(type)
|
42
|
-
name = add_struct(form[:name])
|
43
|
-
"#{name} = #{type}"
|
44
|
-
else
|
45
|
-
"typedef #{type}, :#{form[:name]}"
|
46
|
-
end
|
47
|
-
|
48
|
-
when 'const'
|
49
|
-
type = parse_type(form[:type])
|
50
|
-
if type == ':string'
|
51
|
-
"#{form[:name].upcase} = \"#{form[:value]}\""
|
52
|
-
else
|
53
|
-
"#{form[:name].upcase} = #{form[:value]}"
|
54
|
-
end
|
55
|
-
|
56
|
-
when 'extern'
|
57
|
-
'attach_variable ' \
|
58
|
-
":#{form[:name]}, :#{form[:name]}, #{parse_type(form[:type])}"
|
59
|
-
|
60
|
-
when 'function'
|
61
|
-
l = []
|
62
|
-
l << "attach_function '#{form[:name]}', ["
|
63
|
-
form[:parameters].each do |f|
|
64
|
-
l << " #{parse_type(f[:type])},"
|
65
|
-
end
|
66
|
-
l << "], #{parse_type(form['return-type'.intern])}"
|
67
|
-
# emacs doesn't like :"foo" ---^
|
68
|
-
l.join("\n")
|
69
|
-
|
70
|
-
when 'struct', 'union'
|
71
|
-
make_struct(form)
|
72
|
-
|
73
|
-
when 'enum'
|
74
|
-
name = add_enum(form[:name])
|
75
|
-
l = []
|
76
|
-
l << "enum #{name}, ["
|
77
|
-
form[:fields].each do |f|
|
78
|
-
l << " :#{f[:name]}, #{f[:value]},"
|
79
|
-
end
|
80
|
-
l << ']'
|
81
|
-
l.join("\n")
|
82
|
-
end
|
83
|
-
|
84
|
-
@toplevels << lines
|
85
|
-
nil
|
86
|
-
end
|
87
|
-
|
88
|
-
def add_struct(name)
|
89
|
-
# Anonymous structs are given a name
|
90
|
-
if name == ''
|
91
|
-
@anon_counter += 1
|
92
|
-
name = "Anon_Type_#{@anon_counter}"
|
93
|
-
return name
|
94
|
-
end
|
95
|
-
|
96
|
-
# Do not allow names that start with an underscore
|
97
|
-
name = 'C' + name if name.start_with? '_'
|
98
|
-
|
99
|
-
# Convert snake_case to CamelCase
|
100
|
-
name = name.capitalize.gsub!(/_([a-z])/) { |m| "_#{m[1].upcase}" }
|
101
|
-
|
102
|
-
@struct_type << name unless @struct_type.include? name
|
103
|
-
name
|
104
|
-
end
|
105
|
-
|
106
|
-
def add_enum(name)
|
107
|
-
# Anonymous enums are given a name
|
108
|
-
if name == ''
|
109
|
-
@anon_counter += 1
|
110
|
-
name = ':anon_type_' + @anon_counter.to_s
|
111
|
-
return name
|
112
|
-
end
|
113
|
-
|
114
|
-
# All enums are prefixed with a colon
|
115
|
-
name = ":#{name}" unless name.start_with? ':'
|
116
|
-
name
|
117
|
-
end
|
118
|
-
|
119
|
-
def make_struct(form)
|
120
|
-
name = add_struct(form[:name])
|
121
|
-
|
122
|
-
type = if form[:tag] == ':struct'
|
123
|
-
'FFI::Struct'
|
124
|
-
else
|
125
|
-
'FFI::Union'
|
126
|
-
end
|
127
|
-
|
128
|
-
l = []
|
129
|
-
l << "class #{name} < #{type}"
|
130
|
-
|
131
|
-
if form[:fields].length.positive?
|
132
|
-
l << ' layout \\'
|
133
|
-
size = form[:fields].length
|
134
|
-
form[:fields].each_with_index do |f, i|
|
135
|
-
sep = i >= (size - 1) ? '' : ','
|
136
|
-
l << " :#{f[:name]}, #{parse_type(f[:type])}#{sep}"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
l << 'end'
|
140
|
-
l.join("\n")
|
141
|
-
end
|
142
|
-
|
143
|
-
def parse_type(form)
|
144
|
-
tt = TYPE_TABLE[form[:tag]]
|
145
|
-
return tt if tt
|
146
|
-
|
147
|
-
case form[:tag]
|
148
|
-
when ':pointer'
|
149
|
-
pointee = parse_type(form[:type])
|
150
|
-
if [':char', ':uchar'].include?(pointee)
|
151
|
-
':string'
|
152
|
-
elsif @struct_type.include?(pointee)
|
153
|
-
"#{pointee}.ptr"
|
154
|
-
else
|
155
|
-
':pointer'
|
156
|
-
end
|
157
|
-
|
158
|
-
when ':array'
|
159
|
-
"[#{parse_type(form[:type])}, #{form[:size]}]"
|
160
|
-
|
161
|
-
when ':struct', ':union'
|
162
|
-
add_struct(form[:name])
|
163
|
-
|
164
|
-
when ':enum'
|
165
|
-
add_enum(form[:name])
|
166
|
-
|
167
|
-
when 'enum'
|
168
|
-
form[:name] = add_enum(form[:name])
|
169
|
-
parse_toplevel(form)
|
170
|
-
form[:name]
|
171
|
-
|
172
|
-
when 'struct', 'union'
|
173
|
-
form[:name] = add_struct(form[:name])
|
174
|
-
parse_toplevel(form)
|
175
|
-
form[:name]
|
176
|
-
|
177
|
-
else
|
178
|
-
# All non-Classy types are :-prefixed?
|
179
|
-
if form[:tag][0] != ':'
|
180
|
-
":#{form[:tag]}"
|
181
|
-
else
|
182
|
-
form[:tag]
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|