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