c2ffi4rb 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/c2ffi4rb.svg)](https://badge.fury.io/rb/c2ffi4rb)
|
4
4
|
[![test](https://github.com/kojix2/c2ffi4rb/actions/workflows/ci.yml/badge.svg)](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
|