c2ffi4rb 0.0.0 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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