c2ffi4rb 0.0.0 → 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: 86a593339fb44c53b753ecbae5120b0d3cd67363f8899b8122fa100f800d1fcc
4
- data.tar.gz: c2a2c0ab90ce7e1307d706b268778267237691a56ce71491bfe2503b27b3f8c8
3
+ metadata.gz: 9471bfff4cabf71daa4a888c294218eb8b74200168b35584cb166771a062fe4a
4
+ data.tar.gz: f84570b4abbae4fd1eba8e1ee76554da81aa621a92be31014748ac5e5aa5e20e
5
5
  SHA512:
6
- metadata.gz: 42260432b9a7267183c23e4ab79303257e25445c5ea5be46bcf61d7d27b43e8283144741bcf6eb95a9fd66ac7e3094067e68165648fa6fe13594459b588670f4
7
- data.tar.gz: '08281db0ccbaedba8fb39908f343c69dbe348da5e8e49071ded01c382468962b84b308b59c6a57c707586a94dba6b5fb86937d48e0c3a1f37486ae1b52b29bdd'
6
+ metadata.gz: a2443c99c551de4a8c42a977604c4654e75a0f9f9c754395fb894bdcf488d388e1523aac2a0862126baf17b7cda86966e902f71bbf0ecd2db54f8a68df1e2da8
7
+ data.tar.gz: ce7450668d888f8fc575e56a8a9be650ea2862044feac2e49e0a94f5f50834576cb3a3a46c4f6ac05ea056942e6b9924401342b87876941ebfe97dddd45c5e02
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # c2ffi4rb
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/c2ffi4rb.svg)](https://badge.fury.io/rb/c2ffi4rb)
4
+ [![test](https://github.com/kojix2/c2ffi4rb/actions/workflows/ci.yml/badge.svg)](https://github.com/kojix2/c2ffi4rb/actions/workflows/ci.yml)
5
+
3
6
  [c2ffi](https://github.com/rpav/c2ffi) - FFI binding generator - for Ruby
4
7
 
5
- # installation
8
+ ## installation
6
9
 
7
10
  ```sh
8
11
  gem install c2ffi4rb
@@ -10,21 +13,29 @@ gem install c2ffi4rb
10
13
 
11
14
  ## Usage
12
15
 
13
- First, produce a `spec` file using `c2ffi`:
16
+ First, produce a `spec` file using `c2ffi`.
14
17
 
15
18
  ```sh
16
19
  c2ffi -M macros.h -o example.json example.h
17
- c2ffi -o macros.json macros.h
20
+ c2ffi -o macros.json example.h macros.h
18
21
  ```
19
22
 
20
- Now you can generate a file manually with the included tool,
21
- `c2ffi-ruby`, as follows:
23
+ Next, use c2ffi4rb to generate ruby code.
22
24
 
23
25
  ```sh
24
26
  c2ffi4rb example.json macro.json > simple.rb
25
27
  ```
26
28
 
27
- 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"
28
39
 
29
40
  ## Licence
30
41
 
data/exe/c2ffi4rb CHANGED
@@ -6,19 +6,18 @@
6
6
  ## your build system. But this is how you can use it.
7
7
 
8
8
  require 'optparse'
9
- require 'json'
10
9
  require 'c2ffi4rb'
11
10
 
12
- options = {}
13
-
14
- Version = C2FFI4RB::VERSION # Fixme
15
-
16
11
  opts = OptionParser.new
17
12
  opts.banner = 'Usage: c2ffi4rb [options] [file1, file2, ...]'
18
13
  opts.on('-h', '--help', 'Show this message') do
19
14
  puts opts
20
15
  exit
21
16
  end
17
+ opts.on('-v', '--version', 'Show version') do
18
+ puts C2FFI4RB::VERSION
19
+ exit
20
+ end
22
21
  opts.parse!
23
22
 
24
23
  if ARGV.empty?
@@ -33,4 +32,4 @@ ARGV.each do |file|
33
32
  end
34
33
  end
35
34
 
36
- 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.0'
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,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: c2ffi4rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - kojix2
8
8
  - Ryan Pavlik
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-09-14 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,13 +65,13 @@ 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:
72
72
  - LGPL-2
73
73
  metadata: {}
74
- post_install_message:
74
+ post_install_message:
75
75
  rdoc_options: []
76
76
  require_paths:
77
77
  - lib
@@ -86,8 +86,8 @@ 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.10
90
- signing_key:
89
+ rubygems_version: 3.5.11
90
+ signing_key:
91
91
  specification_version: 4
92
92
  summary: C2FFI for Ruby-FFI
93
93
  test_files: []
@@ -1,188 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module C2FFI4RB
4
- class Parser
5
- TYPE_TABLE = {
6
- "unsigned-int": :uint,
7
- "unsigned-char": :uchar,
8
- "unsigned-long": :ulong,
9
- "function-pointer": :pointer
10
- }.freeze
11
-
12
- def self.parse(arr)
13
- Parser.new.parse(arr)
14
- end
15
-
16
- def initialize
17
- @struct_type = {}
18
- @toplevels = []
19
- @anon_counter = 0
20
- end
21
-
22
- def parse(arr)
23
- arr.each do |form|
24
- s = parse_toplevel(form)
25
- @toplevels << s if s
26
- end
27
-
28
- @toplevels.each do |t|
29
- puts
30
- case t
31
- when String
32
- puts "#{t}"
33
- when Array
34
- t.each do |l|
35
- puts "#{l}"
36
- end
37
- end
38
- end
39
- end
40
-
41
- private
42
-
43
- def parse_toplevel(form)
44
- case form[:tag]
45
- when 'typedef'
46
- type = parse_type(form[:type])
47
-
48
- # I don't think typedef works right with structs, so assign
49
- if @struct_type[type]
50
- name = add_struct(form[:name])
51
- "#{name} = #{type}"
52
- else
53
- "typedef #{type}, :#{form[:name]}"
54
- end
55
-
56
- when 'const'
57
- type = parse_type(form[:type])
58
- if type == ':string'
59
- "#{form[:name].upcase} = \"#{form[:value]}\""
60
- else
61
- "#{form[:name].upcase} = #{form[:value]}"
62
- end
63
-
64
- when 'extern'
65
- 'attach_variable ' \
66
- ":#{form[:name]}, :#{form[:name]}, #{parse_type(form[:type])}"
67
-
68
- when 'function'
69
- s = []
70
- s << "attach_function '#{form[:name]}', ["
71
- form[:parameters].each do |f|
72
- s << " #{parse_type(f[:type])},"
73
- end
74
- s << "], #{parse_type(form['return-type'.intern])}"
75
- # emacs doesn't like :"foo" ---^
76
-
77
- when 'struct', 'union'
78
- name = add_struct(form[:name])
79
- make_struct(form)
80
-
81
- when 'enum'
82
- name = add_enum(form[:name])
83
- s = []
84
- s << "enum #{name}, ["
85
- form[:fields].each do |f|
86
- s << " :#{f[:name]}, #{f[:value]},"
87
- end
88
- s << ']'
89
- end
90
- end
91
-
92
- def add_struct(name)
93
- if name == ''
94
- @anon_counter += 1
95
- name = 'Anon_Type_' + @anon_counter.to_s
96
- return name
97
- end
98
-
99
- name = 'C' + name if name.start_with? '_'
100
-
101
- name = name.capitalize.gsub!(/_([a-z])/) { |m| "_#{m[1].upcase}" }
102
- @struct_type[name] = true
103
- name
104
- end
105
-
106
- def add_enum(name)
107
- if name == ''
108
- @anon_counter += 1
109
- name = ':anon_type_' + @anon_counter.to_s
110
- return name
111
- end
112
-
113
- if name[0] != ':'
114
- ":#{name}"
115
- else
116
- name
117
- end
118
- end
119
-
120
- def make_struct(form)
121
- name = add_struct(form[:name])
122
-
123
- type = if form[:tag] == ':struct'
124
- 'FFI::Struct'
125
- else
126
- 'FFI::Union'
127
- end
128
-
129
- s = []
130
- s << "class #{name} < #{type}"
131
-
132
- if form[:fields].length.positive?
133
- s << ' layout \\'
134
- size = form[:fields].length
135
- sep = ','
136
- form[:fields].each_with_index do |f, i|
137
- sep = '' if i >= (size - 1)
138
- s << " :#{f[:name]}, #{parse_type(f[:type])}#{sep}"
139
- end
140
- end
141
- s << 'end'
142
- end
143
-
144
- def parse_type(form)
145
- tt = TYPE_TABLE[form[:tag]]
146
- return tt if tt
147
-
148
- case form[:tag]
149
- when ':pointer'
150
- pointee = parse_type(form[:type])
151
- if [':char', ':uchar'].include?(pointee)
152
- ':string'
153
- elsif @struct_type[pointee]
154
- "#{@struct_type[pointee]}.ptr"
155
- else
156
- ':pointer'
157
- end
158
-
159
- when ':array'
160
- "[#{parse_type(form[:type])}, #{form[:size]}]"
161
-
162
- when ':struct', ':union'
163
- add_struct(form[:name])
164
-
165
- when ':enum'
166
- add_enum(form[:name])
167
-
168
- when 'enum'
169
- form[:name] = add_enum(form[:name])
170
- parse_toplevel(form)
171
- form[:name]
172
-
173
- when 'struct', 'union'
174
- form[:name] = add_struct(form[:name])
175
- parse_toplevel(form)
176
- form[:name]
177
-
178
- else
179
- # All non-Classy types are :-prefixed?
180
- if form[:tag][0] != ':'
181
- ":#{form[:tag]}"
182
- else
183
- form[:tag]
184
- end
185
- end
186
- end
187
- end
188
- end