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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44d6bb3e8733edaee85ce7b13ed2a564d85b4cca68feda25659c85d4c42c5e92
4
- data.tar.gz: e8d280a236be2c87a96808e18476a9d44cf3722a548bf03e52aab209e4665686
3
+ metadata.gz: 4573994a71db281f73abc92ea251ef6e9eb9a5911fbce28a41d75f6eeb73ecb6
4
+ data.tar.gz: 0eadc89a07d1d35cc9bb80a9f512146e30a5ce14586f7604ded6b5b18bbd15e7
5
5
  SHA512:
6
- metadata.gz: fa28e2eefaeab88bdbda8730cfef082f9b69b3d169397ee1485333d20f9154b7525340c8e97008ca9342d5fe3e83fd32d2cce3d983cd216573b22bb6c50c60df
7
- data.tar.gz: 8066189cba149c945267ffba5bd6608b7e863b314547a6c4f9aa0518b8292582df9669a6f600e62771a6c88f13f788f36ff2e0d6557eef5fd23d9864fe7e0671
6
+ metadata.gz: 72adc5c1ee2742339d5009b0af8053993194125414e1d1c9a26b64d326e074a8db2fbc6f7801ef16795a5119d9fa093b9be4ba9c43f4584bdc5f177930c892fa
7
+ data.tar.gz: 5a3ecb8bb976ffcb7427eed61c4285edaec9a8d9c4514be0bfc5f82578d9eb6c68984a1c22e92299f97cb7a9cae5c277488f29281b58a20b99b9af4724e832d3
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
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
+ [![Lines of Code](https://img.shields.io/endpoint?url=https%3A%2F%2Ftokei.kojix2.net%2Fbadge%2Fgithub%2Fkojix2%2Fc2ffi4rb%2Flines)](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 macro.json > simple.rb
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, Hash[:symbolize_names, true])
43
+ spec += JSON.parse(io.read, symbolize_names: true)
44
44
  end
45
45
  end
46
46
 
47
- spec += JSON.parse($stdin.read, Hash[:symbolize_names, true]) unless $stdin.tty?
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)
@@ -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 "Unknown form: #{form[:tag]}"
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
- warn "# Redefinition of #{form[:name]} from #{@typedefs[form[:name]]} to #{type}"
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
- end
79
- "typedef :pointer, :#{form[:name]} # #{type}" if form[:type][:tag] == ':array'
80
- @typedefs[form[:name]] = type
81
- "typedef #{type}, :#{form[:name]}"
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
- # FIXME: This is a hack to avoid redefining structs
171
- return "# Already defined? #{name}" if @struct_type.include?(name)
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
- # FIXME: :FFI::Struct or FFI::Struct
176
- type = form[:tag] == 'struct' ? '::FFI::Struct' : '::FFI::Union'
250
+ lines = build_struct_class_definition(form, name)
251
+ lines.join("\n")
252
+ end
177
253
 
178
- lines = ["class #{name} < #{type}"]
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
- form[:fields].each_with_index do |f, i|
185
- field_name = f[:name].empty? ? "anon_field_#{anon_field_counter}" : f[:name]
186
- anon_field_counter += 1 if f[:name].empty?
261
+ lines << 'end'
262
+ lines
263
+ end
187
264
 
188
- sep = i == form[:fields].size - 1 ? '' : ','
189
- lines << " :#{field_name}, #{resolve_type(f[:type])}#{sep}"
190
- end
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 << 'end'
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
- ## FIXME
240
- st_name = normalize_struct_name(form[:tag])
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
- form[:tag].start_with?(':') ? form[:tag] : ":#{form[:tag]}"
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
@@ -1,3 +1,3 @@
1
1
  module C2FFI4RB
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.7'
3
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.5
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: 2024-09-29 00:00:00.000000000 Z
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.5.11
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: []