c2ffi4rb 0.0.1 → 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: 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