c2ffi4rb 0.0.1 → 0.0.2

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: 3a7511b48117f836d65991de99d5aeaf1a4dc5f4c9a9b99e4a9577fe415c941a
4
- data.tar.gz: 3ee454048f080abae9a07ab3a11293d1af7a8f57518d6ae1ae12eaac6fd7d2ab
3
+ metadata.gz: 9471bfff4cabf71daa4a888c294218eb8b74200168b35584cb166771a062fe4a
4
+ data.tar.gz: f84570b4abbae4fd1eba8e1ee76554da81aa621a92be31014748ac5e5aa5e20e
5
5
  SHA512:
6
- metadata.gz: b75a019a77528b7ceac118bc3108991c0cae5aba439ef330a1480fe6dd7390ce04e5780f7263dfab9dd626f98bf754166a9780272277267098e5e1871625d9fe
7
- data.tar.gz: 3aa7cf6cf49094f75d9ff220c90e66edda3b80092fe96d087c1471b22f71d9d9c5b56a3f5bab914ec0db908222f90c7d0be26f2c9e6eac0a406b806b82f56936
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
- Now you can generate a file manually with the included tool,
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
- This produces the `simple.rb` file, as included.
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::Parser.parse(spec)
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
@@ -1,3 +1,3 @@
1
1
  module C2FFI4RB
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/c2ffi4rb.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'c2ffi4rb/version'
4
- require_relative 'c2ffi4rb/parser'
4
+ require_relative 'c2ffi4rb/bind_gen'
5
5
 
6
6
  module C2FFI4RB
7
7
  end
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.1
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: 2023-09-18 00:00:00.000000000 Z
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/parser.rb
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.4.14
89
+ rubygems_version: 3.5.11
90
90
  signing_key:
91
91
  specification_version: 4
92
92
  summary: C2FFI for Ruby-FFI
@@ -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