c2ffi4rb 0.0.1 → 0.0.3

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: 3a7511b48117f836d65991de99d5aeaf1a4dc5f4c9a9b99e4a9577fe415c941a
4
- data.tar.gz: 3ee454048f080abae9a07ab3a11293d1af7a8f57518d6ae1ae12eaac6fd7d2ab
3
+ metadata.gz: 06ea739a36f2f4a6485ba3e65b6e91724eafed7f7440df1d26e7dfb5d5290897
4
+ data.tar.gz: 2eaf74991d02fd8b6e5beae79150cc1b40f5178c4bb27cf1277bf1bae0c744f2
5
5
  SHA512:
6
- metadata.gz: b75a019a77528b7ceac118bc3108991c0cae5aba439ef330a1480fe6dd7390ce04e5780f7263dfab9dd626f98bf754166a9780272277267098e5e1871625d9fe
7
- data.tar.gz: 3aa7cf6cf49094f75d9ff220c90e66edda3b80092fe96d087c1471b22f71d9d9c5b56a3f5bab914ec0db908222f90c7d0be26f2c9e6eac0a406b806b82f56936
6
+ metadata.gz: cdc2f8439c2722a83b3a82aac50821d4bf4f9be35efa52e30202e44417ab29b3a203896ddc955e0046637667d50751a354902955df92560c22a4b74a9939fc61
7
+ data.tar.gz: 5bb0f1ba5929c3ba8dbc4ca176701fddc05a3fb836ac218a1606b935335af035972354cfb4ba0950f5a19daf04457486801fca9ea868685070c1134b90e6938b
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,28 @@
8
8
  require 'optparse'
9
9
  require 'c2ffi4rb'
10
10
 
11
- options = {}
12
-
13
- Version = C2FFI4RB::VERSION # Fixme
11
+ type_table = {}
14
12
 
15
13
  opts = OptionParser.new
16
14
  opts.banner = 'Usage: c2ffi4rb [options] [file1, file2, ...]'
15
+ opts.on('-t FILE', '--table FILE', 'Type conversion table TSV') do |file|
16
+ File.open(file) do |io|
17
+ io.each_line do |line|
18
+ next if line =~ /^#/
19
+
20
+ from, to = line.chomp.split("\t")
21
+ type_table[from] = to
22
+ end
23
+ end
24
+ end
17
25
  opts.on('-h', '--help', 'Show this message') do
18
26
  puts opts
19
27
  exit
20
28
  end
29
+ opts.on('-v', '--version', 'Show version') do
30
+ puts C2FFI4RB::VERSION
31
+ exit
32
+ end
21
33
  opts.parse!
22
34
 
23
35
  if ARGV.empty?
@@ -32,4 +44,4 @@ ARGV.each do |file|
32
44
  end
33
45
  end
34
46
 
35
- C2FFI4RB::Parser.parse(spec)
47
+ C2FFI4RB::BindGen.new(type_table).generate_bindings(spec)
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module C2FFI4RB
6
+ class BindGen
7
+ DEFAULT_TYPE_TABLE = {
8
+ ':signed-int' => ':int',
9
+ ':unsigned-int' => ':uint',
10
+ ':signed-char' => ':char',
11
+ ':unsigned-char' => ':uchar',
12
+ ':signed-short' => ':short',
13
+ ':unsigned-short' => ':ushort',
14
+ ':long-long' => ':long_long',
15
+ ':ulong-long' => ':ulong_long',
16
+ ':long-double' => ':long_double',
17
+ ':signed-long' => ':long',
18
+ ':unsigned-long' => ':ulong',
19
+ ':unsigned-long-long' => ':ulong_long',
20
+ ':function-pointer' => ':pointer'
21
+ }
22
+
23
+ def self.generate_bindings(arr)
24
+ new.generate_bindings(arr)
25
+ end
26
+
27
+ def initialize(type_table = {})
28
+ @type_table = DEFAULT_TYPE_TABLE.merge(type_table)
29
+ @struct_type = []
30
+ @toplevels = []
31
+ @anon_counter = 0
32
+ @anon_enum_ids = []
33
+ @typedefs = {}
34
+ end
35
+
36
+ def generate_bindings(arr)
37
+ arr.each { |form| process_toplevel(form) }
38
+ puts @toplevels.join("\n\n")
39
+ end
40
+
41
+ private
42
+
43
+ def process_toplevel(form)
44
+ lines = parse_form(form)
45
+ @toplevels << lines
46
+ end
47
+
48
+ def parse_form(form)
49
+ case form[:tag]
50
+ when 'typedef' then generate_typedef(form)
51
+ when 'const' then generate_const(form)
52
+ when 'extern' then generate_extern(form)
53
+ when 'function' then generate_function(form)
54
+ when 'struct', 'union' then generate_struct_or_union(form)
55
+ when 'enum' then generate_enum(form)
56
+ else
57
+ raise "Unknown form: #{form[:tag]}"
58
+ end
59
+ end
60
+
61
+ def generate_typedef(form)
62
+ 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
+
76
+ warn "# Redefinition of #{form[:name]} from #{@typedefs[form[:name]]} to #{type}"
77
+
78
+ end
79
+ "typedef :pointer, :#{form[:name]} # #{type}" if form[:type][:tag] == ':array'
80
+ @typedefs[form[:name]] = type
81
+ "typedef #{type}, :#{form[:name]}"
82
+ end
83
+ end
84
+
85
+ def generate_const(form)
86
+ type = resolve_type(form[:type])
87
+ value = type == ':string' ? "\"#{form[:value]}\"" : form[:value]
88
+ "#{form[:name].upcase} = #{value}"
89
+ end
90
+
91
+ def generate_extern(form)
92
+ "attach_variable :#{form[:name]}, :#{form[:name]}, #{resolve_type(form[:type])}"
93
+ end
94
+
95
+ def generate_function(form)
96
+ params = form[:parameters].map do |f|
97
+ f[:type][:tag] == ':array' ? " :pointer, # #{resolve_type(f[:type])}" : " #{resolve_type(f[:type])},"
98
+ end.join("\n")
99
+ <<~FUNCTION
100
+ attach_function '#{form[:name]}', [
101
+ #{params}
102
+ ], #{resolve_type(form[:'return-type'])}
103
+ FUNCTION
104
+ end
105
+
106
+ def generate_struct_or_union(form)
107
+ create_struct_definition(form)
108
+ end
109
+
110
+ def generate_enum(form)
111
+ id = form[:id]
112
+ if form[:name].empty?
113
+ return "# Already defined? anon_type_#{id}" if @anon_enum_ids.include?(id)
114
+
115
+ @anon_enum_ids << id
116
+ end
117
+ name = normalize_enum_name(form[:name], form[:id])
118
+ fields = form[:fields].map { |f| " :#{f[:name]}, #{f[:value]}," }.join("\n")
119
+ <<~ENUM
120
+ enum #{name}, [
121
+ #{fields}
122
+ ]
123
+ ENUM
124
+ end
125
+
126
+ def normalize_struct_name(name)
127
+ # Anonymous structs are given a name
128
+ if name.empty?
129
+ @anon_counter += 1
130
+ name = "Anon_Type_#{@anon_counter}"
131
+ return name
132
+ end
133
+
134
+ # Do not allow names that start with an underscore
135
+ name = 'C' + name if name.start_with?('_')
136
+
137
+ # Convert snake_case to CamelCase
138
+ name = name[0].upcase + name[1..-1]
139
+
140
+ name.gsub!(/_([a-z])/) { |m| "#{m[1].upcase}" }
141
+
142
+ name
143
+ end
144
+
145
+ def register_struct(name)
146
+ if @struct_type.include? name
147
+ false
148
+ else
149
+ @struct_type << name
150
+ end
151
+ end
152
+
153
+ def normalize_enum_name(name, id)
154
+ # Anonymous enums are given a name
155
+ name = "anon_enum_#{id}" if name.empty?
156
+
157
+ # All enums are prefixed with a colon
158
+ name = ":#{name}" unless name.start_with?(':')
159
+ name
160
+ end
161
+
162
+ def define_struct(name)
163
+ name = normalize_struct_name(name)
164
+ register_struct(name)
165
+ name
166
+ end
167
+
168
+ def create_struct_definition(form)
169
+ 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)
172
+
173
+ register_struct(name)
174
+
175
+ # FIXME: :FFI::Struct or FFI::Struct
176
+ type = form[:tag] == 'struct' ? '::FFI::Struct' : '::FFI::Union'
177
+
178
+ lines = ["class #{name} < #{type}"]
179
+
180
+ if form[:fields].any?
181
+ lines << ' layout \\'
182
+ anon_field_counter = 0
183
+
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?
187
+
188
+ sep = i == form[:fields].size - 1 ? '' : ','
189
+ lines << " :#{field_name}, #{resolve_type(f[:type])}#{sep}"
190
+ end
191
+ end
192
+
193
+ lines << 'end'
194
+ lines.join("\n")
195
+ end
196
+
197
+ def resolve_type(form)
198
+ @type_table.fetch(form[:tag]) do
199
+ case form[:tag]
200
+ when ':pointer' then resolve_pointer_type(form)
201
+ when ':array' then resolve_array_type(form)
202
+ when ':struct', ':union' then define_struct(form[:name])
203
+ when ':enum' then normalize_enum_name(form[:name], form[:id])
204
+ when 'struct', 'union' then define_struct_or_union_type(form)
205
+ when 'enum' then normalize_enum_name_type(form)
206
+ else resolve_default_type(form)
207
+ end
208
+ end
209
+ end
210
+
211
+ def resolve_pointer_type(form)
212
+ pointee = resolve_type(form[:type])
213
+ if [':char', ':uchar'].include?(pointee)
214
+ ':string'
215
+ elsif @struct_type.include?(pointee)
216
+ "#{pointee}.ptr"
217
+ else
218
+ ':pointer'
219
+ end
220
+ end
221
+
222
+ def resolve_array_type(form)
223
+ "[#{resolve_type(form[:type])}, #{form[:size]}]"
224
+ end
225
+
226
+ def normalize_enum_name_type(form)
227
+ form[:name] = normalize_enum_name(form[:name], form[:id])
228
+ process_toplevel(form)
229
+ form[:name]
230
+ end
231
+
232
+ def define_struct_or_union_type(form)
233
+ form[:name] = normalize_struct_name(form[:name])
234
+ process_toplevel(form)
235
+ form[:name]
236
+ end
237
+
238
+ def resolve_default_type(form)
239
+ ## FIXME
240
+ st_name = normalize_struct_name(form[:tag])
241
+ return st_name if @struct_type.include?(st_name)
242
+
243
+ form[:tag].start_with?(':') ? form[:tag] : ":#{form[:tag]}"
244
+ end
245
+ end
246
+ end
@@ -1,3 +1,3 @@
1
1
  module C2FFI4RB
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.3'
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.3
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-06 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