c2ffi4rb 0.0.5 → 0.0.7
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 +10 -1
- data/exe/c2ffi4rb +5 -2
- data/lib/c2ffi4rb/bind_gen.rb +136 -38
- data/lib/c2ffi4rb/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4573994a71db281f73abc92ea251ef6e9eb9a5911fbce28a41d75f6eeb73ecb6
|
|
4
|
+
data.tar.gz: 0eadc89a07d1d35cc9bb80a9f512146e30a5ce14586f7604ded6b5b18bbd15e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72adc5c1ee2742339d5009b0af8053993194125414e1d1c9a26b64d326e074a8db2fbc6f7801ef16795a5119d9fa093b9be4ba9c43f4584bdc5f177930c892fa
|
|
7
|
+
data.tar.gz: 5a3ecb8bb976ffcb7427eed61c4285edaec9a8d9c4514be0bfc5f82578d9eb6c68984a1c22e92299f97cb7a9cae5c277488f29281b58a20b99b9af4724e832d3
|
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/c2ffi4rb)
|
|
4
4
|
[](https://github.com/kojix2/c2ffi4rb/actions/workflows/ci.yml)
|
|
5
|
+
[](https://tokei.kojix2.net/github/kojix2/c2ffi4rb)
|
|
5
6
|
|
|
6
7
|
[c2ffi](https://github.com/rpav/c2ffi) - FFI binding generator - for Ruby
|
|
7
8
|
|
|
@@ -23,11 +24,19 @@ c2ffi -o macros.json example.h macros.h
|
|
|
23
24
|
Next, use c2ffi4rb to generate ruby code.
|
|
24
25
|
|
|
25
26
|
```sh
|
|
26
|
-
c2ffi4rb example.json
|
|
27
|
+
c2ffi4rb example.json macros.json > simple.rb
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
Finally, improve simple.rb manually to complete the binding.
|
|
30
31
|
|
|
32
|
+
### Options
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
c2ffi4rb -h # Show help
|
|
36
|
+
c2ffi4rb -v # Show version
|
|
37
|
+
c2ffi4rb -t table.tsv file.json macros.json > output.rb # Use type conversion table
|
|
38
|
+
```
|
|
39
|
+
|
|
31
40
|
## Development
|
|
32
41
|
|
|
33
42
|
To run tests, install [c2ffi][c2ffi], [Cairo][cairo], and [pkg-config][pkg-config] in advance.
|
data/exe/c2ffi4rb
CHANGED
|
@@ -40,10 +40,13 @@ end
|
|
|
40
40
|
spec = []
|
|
41
41
|
ARGV.each do |file|
|
|
42
42
|
File.open(file) do |io|
|
|
43
|
-
spec += JSON.parse(io.read,
|
|
43
|
+
spec += JSON.parse(io.read, symbolize_names: true)
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
unless $stdin.tty?
|
|
48
|
+
stdin_content = $stdin.read
|
|
49
|
+
spec += JSON.parse(stdin_content, symbolize_names: true) if stdin_content && !stdin_content.strip.empty?
|
|
50
|
+
end
|
|
48
51
|
|
|
49
52
|
C2FFI4RB::BindGen.new(type_table).generate_bindings(spec)
|
data/lib/c2ffi4rb/bind_gen.rb
CHANGED
|
@@ -17,13 +17,17 @@ module C2FFI4RB
|
|
|
17
17
|
':signed-long' => ':long',
|
|
18
18
|
':unsigned-long' => ':ulong',
|
|
19
19
|
':unsigned-long-long' => ':ulong_long',
|
|
20
|
-
':function-pointer' => ':pointer'
|
|
20
|
+
':function-pointer' => ':pointer',
|
|
21
|
+
':function' => ':pointer',
|
|
22
|
+
':_Bool' => ':bool'
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
# Class method to create instance and generate bindings
|
|
23
26
|
def self.generate_bindings(arr)
|
|
24
27
|
new.generate_bindings(arr)
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# Initialize with optional type conversion table
|
|
27
31
|
def initialize(type_table = {})
|
|
28
32
|
@type_table = DEFAULT_TYPE_TABLE.merge(type_table)
|
|
29
33
|
@struct_type = []
|
|
@@ -33,6 +37,7 @@ module C2FFI4RB
|
|
|
33
37
|
@typedefs = {}
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
# Main entry point: process all forms and output Ruby FFI code
|
|
36
41
|
def generate_bindings(arr)
|
|
37
42
|
arr.each { |form| process_toplevel(form) }
|
|
38
43
|
puts @toplevels.join("\n\n")
|
|
@@ -40,12 +45,16 @@ module C2FFI4RB
|
|
|
40
45
|
|
|
41
46
|
private
|
|
42
47
|
|
|
48
|
+
# Process a single top-level form and add to output
|
|
43
49
|
def process_toplevel(form)
|
|
44
50
|
lines = parse_form(form)
|
|
45
51
|
@toplevels << lines
|
|
46
52
|
end
|
|
47
53
|
|
|
54
|
+
# Parse a form based on its tag and generate appropriate Ruby code
|
|
48
55
|
def parse_form(form)
|
|
56
|
+
validate_form(form)
|
|
57
|
+
|
|
49
58
|
case form[:tag]
|
|
50
59
|
when 'typedef' then generate_typedef(form)
|
|
51
60
|
when 'const' then generate_const(form)
|
|
@@ -54,44 +63,100 @@ module C2FFI4RB
|
|
|
54
63
|
when 'struct', 'union' then generate_struct_or_union(form)
|
|
55
64
|
when 'enum' then generate_enum(form)
|
|
56
65
|
else
|
|
57
|
-
raise
|
|
66
|
+
raise ArgumentError,
|
|
67
|
+
"Unknown form tag: '#{form[:tag]}'. Expected one of: typedef, const, extern, function, struct, union, enum"
|
|
68
|
+
end
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
warn "[c2ffi4rb] Error processing form #{form.inspect}: #{e.message}"
|
|
71
|
+
"# [c2ffi4rb] Error: #{e.message}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Validate form structure before processing
|
|
75
|
+
def validate_form(form)
|
|
76
|
+
raise ArgumentError, 'Form must be a Hash' unless form.is_a?(Hash)
|
|
77
|
+
raise ArgumentError, 'Form must have a :tag key' unless form.key?(:tag)
|
|
78
|
+
raise ArgumentError, 'Form :tag cannot be nil or empty' if form[:tag].nil? || form[:tag].empty?
|
|
79
|
+
|
|
80
|
+
# Additional validation based on form type
|
|
81
|
+
case form[:tag]
|
|
82
|
+
when 'typedef', 'const', 'extern'
|
|
83
|
+
raise ArgumentError, "Form '#{form[:tag]}' must have a :name key" unless form.key?(:name)
|
|
84
|
+
raise ArgumentError, "Form '#{form[:tag]}' :name cannot be empty" if form[:name].nil? || form[:name].empty?
|
|
85
|
+
when 'function'
|
|
86
|
+
raise ArgumentError, "Form 'function' must have a :name key" unless form.key?(:name)
|
|
87
|
+
|
|
88
|
+
unless form.key?(:parameters) && form[:parameters].is_a?(Array)
|
|
89
|
+
raise ArgumentError,
|
|
90
|
+
"Form 'function' must have :parameters array"
|
|
91
|
+
end
|
|
92
|
+
when 'struct', 'union'
|
|
93
|
+
unless form.key?(:fields) && form[:fields].is_a?(Array)
|
|
94
|
+
raise ArgumentError,
|
|
95
|
+
"Form '#{form[:tag]}' must have :fields array"
|
|
96
|
+
end
|
|
97
|
+
when 'enum'
|
|
98
|
+
unless form.key?(:fields) && form[:fields].is_a?(Array)
|
|
99
|
+
raise ArgumentError,
|
|
100
|
+
"Form 'enum' must have :fields array"
|
|
101
|
+
end
|
|
102
|
+
raise ArgumentError, "Form 'enum' must have :id" unless form.key?(:id)
|
|
58
103
|
end
|
|
59
104
|
end
|
|
60
105
|
|
|
106
|
+
# Generate typedef declaration, handling different typedef types
|
|
61
107
|
def generate_typedef(form)
|
|
62
108
|
type = resolve_type(form[:type])
|
|
63
|
-
if @struct_type.include?(type)
|
|
64
|
-
name = define_struct(form[:name])
|
|
65
|
-
if name == type
|
|
66
|
-
"# #{name} = #{type}"
|
|
67
|
-
else
|
|
68
|
-
"#{name} = #{type}"
|
|
69
|
-
end
|
|
70
|
-
elsif type == ":{#{form[:name]}}"
|
|
71
|
-
warn "Ignoring self-referential typedef #{form[:name]}"
|
|
72
|
-
else
|
|
73
|
-
if @typedefs.has_key?(form[:name])
|
|
74
|
-
return "# typedef already defined? #{form[:name]}" if @typedefs[form[:name]] == type
|
|
75
109
|
|
|
76
|
-
|
|
110
|
+
return handle_struct_typedef(form, type) if @struct_type.include?(type)
|
|
111
|
+
return handle_self_referential_typedef(form[:name]) if type == ":{#{form[:name]}}"
|
|
77
112
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
handle_regular_typedef(form, type)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Handle typedef for struct types
|
|
117
|
+
def handle_struct_typedef(form, type)
|
|
118
|
+
name = define_struct(form[:name])
|
|
119
|
+
name == type ? "# #{name} = #{type}" : "#{name} = #{type}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Handle self-referential typedef (ignore with warning)
|
|
123
|
+
def handle_self_referential_typedef(name)
|
|
124
|
+
warn "[c2ffi4rb] Ignoring self-referential typedef #{name}"
|
|
125
|
+
"# [c2ffi4rb] Ignoring self-referential typedef #{name}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Handle regular typedef declarations
|
|
129
|
+
def handle_regular_typedef(form, type)
|
|
130
|
+
name = form[:name]
|
|
131
|
+
|
|
132
|
+
if @typedefs.key?(name)
|
|
133
|
+
return "# [c2ffi4rb] typedef already defined? #{name}" if @typedefs[name] == type
|
|
134
|
+
|
|
135
|
+
warn "[c2ffi4rb] Redefinition of #{name} from #{@typedefs[name]} to #{type}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
@typedefs[name] = type
|
|
139
|
+
|
|
140
|
+
if form[:type][:tag] == ':array'
|
|
141
|
+
"typedef :pointer, :#{name} # #{type}"
|
|
142
|
+
else
|
|
143
|
+
"typedef #{type}, :#{name}"
|
|
82
144
|
end
|
|
83
145
|
end
|
|
84
146
|
|
|
147
|
+
# Generate constant definition
|
|
85
148
|
def generate_const(form)
|
|
86
149
|
type = resolve_type(form[:type])
|
|
87
150
|
value = type == ':string' ? "\"#{form[:value]}\"" : form[:value]
|
|
88
151
|
"#{form[:name].upcase} = #{value}"
|
|
89
152
|
end
|
|
90
153
|
|
|
154
|
+
# Generate external variable attachment
|
|
91
155
|
def generate_extern(form)
|
|
92
156
|
"attach_variable :#{form[:name]}, :#{form[:name]}, #{resolve_type(form[:type])}"
|
|
93
157
|
end
|
|
94
158
|
|
|
159
|
+
# Generate function attachment with parameters
|
|
95
160
|
def generate_function(form)
|
|
96
161
|
params = form[:parameters].map do |f|
|
|
97
162
|
f[:type][:tag] == ':array' ? " :pointer, # #{resolve_type(f[:type])}" : " #{resolve_type(f[:type])},"
|
|
@@ -103,10 +168,12 @@ module C2FFI4RB
|
|
|
103
168
|
FUNCTION
|
|
104
169
|
end
|
|
105
170
|
|
|
171
|
+
# Generate struct or union definition
|
|
106
172
|
def generate_struct_or_union(form)
|
|
107
173
|
create_struct_definition(form)
|
|
108
174
|
end
|
|
109
175
|
|
|
176
|
+
# Generate enum definition with fields
|
|
110
177
|
def generate_enum(form)
|
|
111
178
|
id = form[:id]
|
|
112
179
|
if form[:name].empty?
|
|
@@ -123,6 +190,7 @@ module C2FFI4RB
|
|
|
123
190
|
ENUM
|
|
124
191
|
end
|
|
125
192
|
|
|
193
|
+
# Normalize struct name: handle anonymous structs and convert to CamelCase
|
|
126
194
|
def normalize_struct_name(name)
|
|
127
195
|
# Anonymous structs are given a name
|
|
128
196
|
if name.empty?
|
|
@@ -142,6 +210,7 @@ module C2FFI4RB
|
|
|
142
210
|
name
|
|
143
211
|
end
|
|
144
212
|
|
|
213
|
+
# Register struct name to avoid redefinition
|
|
145
214
|
def register_struct(name)
|
|
146
215
|
if @struct_type.include? name
|
|
147
216
|
false
|
|
@@ -150,6 +219,7 @@ module C2FFI4RB
|
|
|
150
219
|
end
|
|
151
220
|
end
|
|
152
221
|
|
|
222
|
+
# Normalize enum name: handle anonymous enums and add colon prefix
|
|
153
223
|
def normalize_enum_name(name, id)
|
|
154
224
|
# Anonymous enums are given a name
|
|
155
225
|
name = "anon_enum_#{id}" if name.empty?
|
|
@@ -159,41 +229,56 @@ module C2FFI4RB
|
|
|
159
229
|
name
|
|
160
230
|
end
|
|
161
231
|
|
|
232
|
+
# Define struct name and register it
|
|
162
233
|
def define_struct(name)
|
|
163
234
|
name = normalize_struct_name(name)
|
|
164
235
|
register_struct(name)
|
|
165
236
|
name
|
|
166
237
|
end
|
|
167
238
|
|
|
239
|
+
# Create complete struct/union definition with fields
|
|
168
240
|
def create_struct_definition(form)
|
|
169
241
|
name = normalize_struct_name(form[:name])
|
|
170
|
-
|
|
171
|
-
|
|
242
|
+
|
|
243
|
+
if @struct_type.include?(name)
|
|
244
|
+
warn "[c2ffi4rb] Struct '#{name}' already defined, skipping redefinition"
|
|
245
|
+
return "# [c2ffi4rb] Already defined: #{name} (#{form[:tag]})"
|
|
246
|
+
end
|
|
172
247
|
|
|
173
248
|
register_struct(name)
|
|
174
249
|
|
|
175
|
-
|
|
176
|
-
|
|
250
|
+
lines = build_struct_class_definition(form, name)
|
|
251
|
+
lines.join("\n")
|
|
252
|
+
end
|
|
177
253
|
|
|
178
|
-
|
|
254
|
+
# Build struct class definition with inheritance
|
|
255
|
+
def build_struct_class_definition(form, name)
|
|
256
|
+
base_class = form[:tag] == 'struct' ? '::FFI::Struct' : '::FFI::Union'
|
|
257
|
+
lines = ["class #{name} < #{base_class}"]
|
|
179
258
|
|
|
180
|
-
if form[:fields].any?
|
|
181
|
-
lines << ' layout \\'
|
|
182
|
-
anon_field_counter = 0
|
|
259
|
+
lines.concat(build_struct_layout(form[:fields])) if form[:fields].any?
|
|
183
260
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
261
|
+
lines << 'end'
|
|
262
|
+
lines
|
|
263
|
+
end
|
|
187
264
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
265
|
+
# Build layout definition for struct fields
|
|
266
|
+
def build_struct_layout(fields)
|
|
267
|
+
lines = [' layout \\']
|
|
268
|
+
anon_field_counter = 0
|
|
269
|
+
|
|
270
|
+
fields.each_with_index do |field, index|
|
|
271
|
+
field_name = field[:name].empty? ? "anon_field_#{anon_field_counter}" : field[:name]
|
|
272
|
+
anon_field_counter += 1 if field[:name].empty?
|
|
273
|
+
|
|
274
|
+
separator = index == fields.size - 1 ? '' : ','
|
|
275
|
+
lines << " :#{field_name}, #{resolve_type(field[:type])}#{separator}"
|
|
191
276
|
end
|
|
192
277
|
|
|
193
|
-
lines
|
|
194
|
-
lines.join("\n")
|
|
278
|
+
lines
|
|
195
279
|
end
|
|
196
280
|
|
|
281
|
+
# Resolve C type to Ruby FFI type
|
|
197
282
|
def resolve_type(form)
|
|
198
283
|
@type_table.fetch(form[:tag]) do
|
|
199
284
|
case form[:tag]
|
|
@@ -208,6 +293,7 @@ module C2FFI4RB
|
|
|
208
293
|
end
|
|
209
294
|
end
|
|
210
295
|
|
|
296
|
+
# Resolve pointer type: handle char* as string, struct pointers
|
|
211
297
|
def resolve_pointer_type(form)
|
|
212
298
|
pointee = resolve_type(form[:type])
|
|
213
299
|
if [':char', ':uchar'].include?(pointee)
|
|
@@ -219,28 +305,40 @@ module C2FFI4RB
|
|
|
219
305
|
end
|
|
220
306
|
end
|
|
221
307
|
|
|
308
|
+
# Resolve array type to FFI array format
|
|
222
309
|
def resolve_array_type(form)
|
|
223
310
|
"[#{resolve_type(form[:type])}, #{form[:size]}]"
|
|
224
311
|
end
|
|
225
312
|
|
|
313
|
+
# Process enum type and return normalized name
|
|
226
314
|
def normalize_enum_name_type(form)
|
|
227
315
|
form[:name] = normalize_enum_name(form[:name], form[:id])
|
|
228
316
|
process_toplevel(form)
|
|
229
317
|
form[:name]
|
|
230
318
|
end
|
|
231
319
|
|
|
320
|
+
# Process struct/union type and return normalized name
|
|
232
321
|
def define_struct_or_union_type(form)
|
|
233
322
|
form[:name] = normalize_struct_name(form[:name])
|
|
234
323
|
process_toplevel(form)
|
|
235
324
|
form[:name]
|
|
236
325
|
end
|
|
237
326
|
|
|
327
|
+
# Resolve default/unknown types
|
|
238
328
|
def resolve_default_type(form)
|
|
239
|
-
|
|
240
|
-
|
|
329
|
+
tag = form[:tag]
|
|
330
|
+
|
|
331
|
+
# Check if it's a known struct type
|
|
332
|
+
st_name = normalize_struct_name(tag)
|
|
241
333
|
return st_name if @struct_type.include?(st_name)
|
|
242
334
|
|
|
243
|
-
|
|
335
|
+
# Handle primitive types and unknown types
|
|
336
|
+
if tag.start_with?(':')
|
|
337
|
+
tag
|
|
338
|
+
else
|
|
339
|
+
warn "[c2ffi4rb] Unknown type '#{tag}', treating as primitive type"
|
|
340
|
+
":#{tag}"
|
|
341
|
+
end
|
|
244
342
|
end
|
|
245
343
|
end
|
|
246
344
|
end
|
data/lib/c2ffi4rb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
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.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kojix2
|
|
8
8
|
- Ryan Pavlik
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: exe
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: bundler
|
|
@@ -71,7 +70,6 @@ homepage: https://github.com/kojix2/c2ffi4rb
|
|
|
71
70
|
licenses:
|
|
72
71
|
- LGPL-2
|
|
73
72
|
metadata: {}
|
|
74
|
-
post_install_message:
|
|
75
73
|
rdoc_options: []
|
|
76
74
|
require_paths:
|
|
77
75
|
- lib
|
|
@@ -86,8 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
86
84
|
- !ruby/object:Gem::Version
|
|
87
85
|
version: '0'
|
|
88
86
|
requirements: []
|
|
89
|
-
rubygems_version:
|
|
90
|
-
signing_key:
|
|
87
|
+
rubygems_version: 4.0.16
|
|
91
88
|
specification_version: 4
|
|
92
89
|
summary: C2FFI for Ruby-FFI
|
|
93
90
|
test_files: []
|