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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f84bbae856ac0af2ff42f9f1da68092cd9f901d434583c832b697a9cb8f96a2
4
- data.tar.gz: 2f3740e34e698dc85498622f17045170d6779f6f9252dbea4404b33b7cf9baf5
3
+ metadata.gz: 4cfce78845fc55372ded99151cccc949d8759e76e4e1a5bc081ca1175f8fde2e
4
+ data.tar.gz: 0bd91921a5f4a53cf7f9bdc5c4ac13390fd1d2797150b574ff6b84a092921f8e
5
5
  SHA512:
6
- metadata.gz: '08d5bcec48ccd732ee87790d6eb1776321dc6023ba89896cc11dfa1ad85183c4a40d6213d8ed0eea6b5af417d8a419521133c6df9ac61ed99d52d67371d2791e'
7
- data.tar.gz: a50a704ee9897de887565fe00fec384767ed100be5b400dea390c5c51b3023e8a0bcdd699b40a2cdd183155d11a044648ba72e9c5fd708d315a28b6824a8a0ce
6
+ metadata.gz: 3c93c4a27c799cd764a9be55c3cba58d54279fd4d56c0324ff92c6e155e394e94a9553f7f1f3b052436b8cbadeb96a4c513b7e009f5852b89c7c8a82147bb926
7
+ data.tar.gz: a2ddb54eed4af09b13df2a792f82e90c6d0ff6d798f4d48df19b889d0e14c43b66ef9deec3db9e50d2ea3b634b29346647b891067526e3dd02ab2e3385df5eb5
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
@@ -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
- 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, 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)
@@ -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 "Unknown form: #{form[:tag]}"
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
- warn "# Redefinition of #{form[:name]} from #{@typedefs[form[:name]]} to #{type}"
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
- end
79
- "typedef :pointer, :#{form[:name]} # #{type}" if form[:type][:tag] == ':array'
80
- @typedefs[form[:name]] = type
81
- "typedef #{type}, :#{form[:name]}"
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
- # FIXME: This is a hack to avoid redefining structs
171
- return "# Already defined? #{name}" if @struct_type.include?(name)
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
- # FIXME: :FFI::Struct or FFI::Struct
176
- type = form[:tag] == 'struct' ? '::FFI::Struct' : '::FFI::Union'
248
+ lines = build_struct_class_definition(form, name)
249
+ lines.join("\n")
250
+ end
177
251
 
178
- lines = ["class #{name} < #{type}"]
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
- 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?
259
+ lines << 'end'
260
+ lines
261
+ end
187
262
 
188
- sep = i == form[:fields].size - 1 ? '' : ','
189
- lines << " :#{field_name}, #{resolve_type(f[:type])}#{sep}"
190
- end
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 << 'end'
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
- ## FIXME
240
- st_name = normalize_struct_name(form[:tag])
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
- form[:tag].start_with?(':') ? form[:tag] : ":#{form[:tag]}"
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
@@ -1,3 +1,3 @@
1
1
  module C2FFI4RB
2
- VERSION = '0.0.4'
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
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: 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: 3.6.9
91
88
  specification_version: 4
92
89
  summary: C2FFI for Ruby-FFI
93
90
  test_files: []