ffi2-generators 0.1.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/ffi2-generators.gemspec +21 -0
- data/lib/ffi2/generators.rb +164 -0
- data/lib/ffi2/generators/constants.rb +178 -0
- data/lib/ffi2/generators/file_processor.rb +85 -0
- data/lib/ffi2/generators/structures.rb +198 -0
- data/lib/ffi2/generators/types.rb +142 -0
- data/lib/ffi2/generators/version.rb +5 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8f4b27c539662e9b2d469ed8e8345bb8ed5f0244
|
4
|
+
data.tar.gz: 3a6992fb5b224faf1ec1f85aacaa4b6bbbab7a4c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b31668fd36d8fdeb444659bc7a78d7682c648a8b98e99b37f86c23eb199939944c0d6a93ee57b447b92e370dff6241bd1daa860bd5e820c86f5d066c7378dd93
|
7
|
+
data.tar.gz: 611e20ede0e0de89462f2142923154c637f0d9574550ac348220a7d82ddffc3b5a618f360c18324affe164ceb911fba2723c635cfeecbc9c489b057485497465
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2013, Brian Shirai
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
3. Neither the name of the library nor the names of its contributors may be
|
13
|
+
used to endorse or promote products derived from this software without
|
14
|
+
specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
|
20
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
22
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
23
|
+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
24
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
25
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Ffi2::Generators
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ffi2-generators'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ffi2-generators
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require './lib/ffi2/generators/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "ffi2-generators"
|
6
|
+
spec.version = FFI::Generators::VERSION
|
7
|
+
spec.authors = ["Brian Shirai"]
|
8
|
+
spec.email = ["brixen@gmail.com"]
|
9
|
+
spec.description = %q{Utilities for generating constants, types, and structs for FFI.}
|
10
|
+
spec.summary = %q{Utilities for generating constants, types, and structs for FFI.}
|
11
|
+
spec.homepage = "https://github.com/rubinius/ffi2-generators"
|
12
|
+
spec.license = "BSD"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
20
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
21
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require "ffi2/generators/version"
|
2
|
+
require "ffi2/generators/constants"
|
3
|
+
require "ffi2/generators/structures"
|
4
|
+
require "ffi2/generators/types"
|
5
|
+
require "ffi2/generators/file_processor"
|
6
|
+
|
7
|
+
require "rbconfig"
|
8
|
+
|
9
|
+
module FFI
|
10
|
+
module Generators
|
11
|
+
|
12
|
+
##
|
13
|
+
# BodyGuard does your file system dirty work and cleans up after any
|
14
|
+
# fallout.
|
15
|
+
|
16
|
+
class BodyGuardError < Exception; end
|
17
|
+
|
18
|
+
class BodyGuard
|
19
|
+
def initialize(subject, label, platform)
|
20
|
+
@subject = subject
|
21
|
+
@label = label.gsub(/\W/, '-')
|
22
|
+
@platform = platform
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Orchestrates a workflow by querying the subject at each stage and cleans
|
27
|
+
# up if problems arise before raising an exception.
|
28
|
+
#
|
29
|
+
# Expects the subject to respond to the following methods:
|
30
|
+
#
|
31
|
+
# #source io
|
32
|
+
# Where io is an IO instance used to create the source for later
|
33
|
+
# stages.
|
34
|
+
#
|
35
|
+
# #prepare name, target
|
36
|
+
# Where name is the source file name and target is the file that would
|
37
|
+
# be created by the prepare process. The method should return the
|
38
|
+
# command to run.
|
39
|
+
#
|
40
|
+
# #prepare_failed
|
41
|
+
# The method should return the error message for #raise to which will
|
42
|
+
# be appended the output of running the command returned by #prepare.
|
43
|
+
#
|
44
|
+
# #process target
|
45
|
+
# Where target is the same as passed to #prepare. The method should
|
46
|
+
# return the command to run. If no further options or changes are
|
47
|
+
# needed, #process should just return target.
|
48
|
+
#
|
49
|
+
# #process_failed
|
50
|
+
# The method should return the error message for #raise to which will
|
51
|
+
# be appended the output of running the command returned by #process.
|
52
|
+
#
|
53
|
+
# The #perform method returns the result of running the command returned
|
54
|
+
# by the #process method.
|
55
|
+
|
56
|
+
def perform
|
57
|
+
begin
|
58
|
+
name = "rbx-ffi-generators-#{@label}"
|
59
|
+
source = File.expand_path name + @platform.source_ext
|
60
|
+
target = File.expand_path name + @platform.executable_ext
|
61
|
+
|
62
|
+
File.open source, "wb" do |f|
|
63
|
+
@subject.source f
|
64
|
+
end
|
65
|
+
|
66
|
+
if preparer = @subject.prepare(source, target)
|
67
|
+
handle preparer, :prepare_failed
|
68
|
+
else
|
69
|
+
target = source
|
70
|
+
end
|
71
|
+
|
72
|
+
processor = @subject.process target
|
73
|
+
return handle(processor, :process_failed)
|
74
|
+
ensure
|
75
|
+
remove source, target
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle(command, failure)
|
80
|
+
result = `#{command}`
|
81
|
+
Process.waitpid $?.pid rescue nil
|
82
|
+
|
83
|
+
unless $?.success?
|
84
|
+
result = result.split("\n").map { |l| "\t#{l}" }.join "\n"
|
85
|
+
msg = "#{@subject.send failure}:\n#{result}"
|
86
|
+
raise BodyGuardError, msg
|
87
|
+
end
|
88
|
+
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def remove(*names)
|
93
|
+
names.each do |name|
|
94
|
+
File.delete name if File.exists? name
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Platform
|
100
|
+
# TODO: Make these configurable to enable cross-compiling
|
101
|
+
|
102
|
+
def initialize(kind=:c)
|
103
|
+
@kind = kind
|
104
|
+
end
|
105
|
+
|
106
|
+
def source_ext
|
107
|
+
case @kind
|
108
|
+
when :c
|
109
|
+
".c"
|
110
|
+
when :cpp
|
111
|
+
".cpp"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def executable_ext
|
116
|
+
windows? ? ".exe" : ""
|
117
|
+
end
|
118
|
+
|
119
|
+
def defines
|
120
|
+
case @kind
|
121
|
+
when :c
|
122
|
+
RbConfig::CONFIG["CFLAGS"]
|
123
|
+
when :cpp, :cxx
|
124
|
+
RbConfig::CONFIG["CPPFLAGS"] || RbConfig["CXXFLAGS"]
|
125
|
+
else
|
126
|
+
RbConfig::CONFIG["CFLAGS"]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def windows?
|
131
|
+
RUBY_PLATFORM =~ /mswin|mingw/
|
132
|
+
end
|
133
|
+
|
134
|
+
def compiler
|
135
|
+
case @kind
|
136
|
+
when :c
|
137
|
+
RbConfig::CONFIG["CC"]
|
138
|
+
when :cpp, :cxx
|
139
|
+
RbConfig::CONFIG["CXX"] || RbConfig::CONFIG["CC"]
|
140
|
+
else
|
141
|
+
RbConfig::CONFIG["CC"]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def language
|
146
|
+
case @kind
|
147
|
+
when :c
|
148
|
+
"c"
|
149
|
+
when :cpp, :cxx
|
150
|
+
"c++"
|
151
|
+
else
|
152
|
+
"c"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def compile(include_dirs, source, target)
|
157
|
+
includes = include_dirs.map { |i| "-I#{i}" }.join(" ")
|
158
|
+
compile_options = "#{defines} -x #{language} #{includes} -Wall -Werror"
|
159
|
+
|
160
|
+
"#{compiler} #{compile_options} #{source} -o #{target} 2>&1"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module FFI
|
2
|
+
module Generators
|
3
|
+
##
|
4
|
+
# Constants turns C constants into ruby values.
|
5
|
+
|
6
|
+
class Constants
|
7
|
+
|
8
|
+
class Constant
|
9
|
+
attr_reader :name, :format, :cast
|
10
|
+
attr_accessor :value
|
11
|
+
|
12
|
+
def initialize(name, format, cast, ruby_name=nil, converter=nil)
|
13
|
+
@name = name
|
14
|
+
@format = format
|
15
|
+
@cast = cast
|
16
|
+
@ruby_name = ruby_name
|
17
|
+
@converter = converter
|
18
|
+
@value = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def value?
|
22
|
+
@value != nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def converted_value
|
26
|
+
@converter ? @converter.call(@value) : @value
|
27
|
+
end
|
28
|
+
|
29
|
+
def ruby_name
|
30
|
+
@ruby_name || @name
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_ruby
|
34
|
+
"#{ruby_name} = #{converted_value}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
attr_reader :constants
|
40
|
+
|
41
|
+
##
|
42
|
+
# Creates a new constant generator that uses +prefix+ as a name, and an
|
43
|
+
# options hash.
|
44
|
+
#
|
45
|
+
# The only option is :required, which if set to true raises an error if a
|
46
|
+
# constant you have requested was not found.
|
47
|
+
#
|
48
|
+
# When passed a block, #calculate is automatically called at the end of
|
49
|
+
# the block, otherwise you must call it yourself.
|
50
|
+
|
51
|
+
def initialize(prefix=nil, options={})
|
52
|
+
@includes = []
|
53
|
+
@include_dirs = []
|
54
|
+
@constants = {}
|
55
|
+
@prefix = prefix
|
56
|
+
@platform = Platform.new
|
57
|
+
|
58
|
+
@required = options[:required]
|
59
|
+
|
60
|
+
if block_given?
|
61
|
+
yield self
|
62
|
+
calculate
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def [](name)
|
67
|
+
@constants[name].value
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Request the value for C constant +name+. +format+ is a printf format
|
72
|
+
# string to print the value out, and +cast+ is a C cast for the value.
|
73
|
+
# +ruby_name+ allows you to give the constant an alternate ruby name for
|
74
|
+
# #to_ruby. +converter+ or +converter_proc+ allow you to convert the
|
75
|
+
# value from a string to the appropriate type for #to_ruby.
|
76
|
+
|
77
|
+
def const(name, format=nil, cast=nil, ruby_name=nil, converter=nil, &block)
|
78
|
+
format ||= '%ld'
|
79
|
+
cast ||= '(long)'
|
80
|
+
|
81
|
+
if block and converter
|
82
|
+
raise ArgumentError, "Supply only converter or converter block"
|
83
|
+
end
|
84
|
+
|
85
|
+
converter = block if converter.nil?
|
86
|
+
|
87
|
+
const = Constant.new name, format, cast, ruby_name, converter
|
88
|
+
@constants[name.to_s] = const
|
89
|
+
return const
|
90
|
+
end
|
91
|
+
|
92
|
+
def source(io)
|
93
|
+
io.puts "#include <stdio.h>"
|
94
|
+
|
95
|
+
@includes.each do |inc|
|
96
|
+
io.puts "#include <#{inc}>"
|
97
|
+
end
|
98
|
+
|
99
|
+
io.puts "#include <stddef.h>\n\n"
|
100
|
+
io.puts "int main(int argc, char **argv)\n{"
|
101
|
+
|
102
|
+
@constants.each_value do |const|
|
103
|
+
io.puts <<-EOF
|
104
|
+
#ifdef #{const.name}
|
105
|
+
printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
|
106
|
+
#endif
|
107
|
+
EOF
|
108
|
+
end
|
109
|
+
|
110
|
+
io.puts "\n\treturn 0;\n}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def prepare(name, target)
|
114
|
+
@platform.compile(@include_dirs, name, target)
|
115
|
+
end
|
116
|
+
|
117
|
+
def prepare_failed
|
118
|
+
"Compilation error generating constants #{@prefix}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def process(target)
|
122
|
+
target
|
123
|
+
end
|
124
|
+
|
125
|
+
def process_failed
|
126
|
+
"Error generating constants #{@prefix}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def calculate
|
130
|
+
output = BodyGuard.new(self, @prefix, @platform).perform
|
131
|
+
|
132
|
+
output.each_line do |line|
|
133
|
+
line =~ /^(\S+)\s(.*)$/
|
134
|
+
const = @constants[$1]
|
135
|
+
const.value = $2
|
136
|
+
end
|
137
|
+
|
138
|
+
missing_constants = @constants.reject { |_, c| c.value? }.keys
|
139
|
+
|
140
|
+
if @required and not missing_constants.empty?
|
141
|
+
raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def write_constants(io)
|
146
|
+
@constants.each do |name, constant|
|
147
|
+
io.print @prefix, "."
|
148
|
+
io.puts constant.to_ruby
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Outputs values for discovered constants. If the constant's value was
|
154
|
+
# not discovered it is not omitted.
|
155
|
+
|
156
|
+
def to_ruby
|
157
|
+
@constants.sort_by { |name,| name }.map do |name, constant|
|
158
|
+
if constant.value?
|
159
|
+
constant.to_ruby
|
160
|
+
else
|
161
|
+
"# #{name} not defined"
|
162
|
+
end
|
163
|
+
end.join "\n"
|
164
|
+
end
|
165
|
+
|
166
|
+
def include(i)
|
167
|
+
@includes << i
|
168
|
+
end
|
169
|
+
|
170
|
+
def include_dir(i)
|
171
|
+
@include_dirs << i
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
ConstGenerator = Generators::Constants
|
178
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module FFI
|
2
|
+
|
3
|
+
# Processes a file containing Ruby code with blocks of FFI definitions
|
4
|
+
# delimited by @@@. The blocks are replaced with Ruby code produced by
|
5
|
+
# running the FFI generators contained in the blocks. For example:
|
6
|
+
#
|
7
|
+
# module Something
|
8
|
+
# @@@
|
9
|
+
# constants do |c|
|
10
|
+
# c.include 'somefile.h'
|
11
|
+
#
|
12
|
+
# c.const 'MAX'
|
13
|
+
# c.const 'MIN'
|
14
|
+
# end
|
15
|
+
# @@@
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# would be converted to:
|
19
|
+
#
|
20
|
+
# module Something
|
21
|
+
# MAX = 1
|
22
|
+
# MIN = 2
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# assuming that
|
26
|
+
#
|
27
|
+
# #define MAX 1
|
28
|
+
# #define MIN 2
|
29
|
+
#
|
30
|
+
# was contained in the file 'something.h'.
|
31
|
+
|
32
|
+
class FileProcessor
|
33
|
+
|
34
|
+
def initialize(ffi_name, rb_name)
|
35
|
+
@name = File.basename rb_name, '.rb'
|
36
|
+
|
37
|
+
definitions = File.read ffi_name
|
38
|
+
|
39
|
+
replacement = definitions.gsub(/^( *)@@@(.*?)@@@/m) do
|
40
|
+
@constants = []
|
41
|
+
@structs = []
|
42
|
+
|
43
|
+
indent = $1
|
44
|
+
line_count = $2.count("\n") + 1
|
45
|
+
|
46
|
+
instance_eval $2
|
47
|
+
|
48
|
+
lines = []
|
49
|
+
@constants.each { |c| lines << c.to_ruby }
|
50
|
+
@structs.each { |s| lines << s.generate_layout }
|
51
|
+
|
52
|
+
# expand multiline blocks
|
53
|
+
lines = lines.join("\n").split "\n"
|
54
|
+
lines = lines.map { |line| indent + line }
|
55
|
+
|
56
|
+
# preserve source line numbers in output
|
57
|
+
padding = line_count - lines.length
|
58
|
+
lines += [nil] * padding if padding >= 0
|
59
|
+
|
60
|
+
lines.join "\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
File.open rb_name, 'wb' do |f|
|
64
|
+
f.puts "# This file is generated #{self.class} from #{ffi_name}."
|
65
|
+
f.puts
|
66
|
+
f.puts replacement
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def constants(options={}, &block)
|
71
|
+
@constants << FFI::ConstGenerator.new(@name, options, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def struct(&block)
|
75
|
+
@structs << FFI::StructGenerator.new(@name, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Utility converter for constants
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
proc { |obj| obj.to_s.inspect }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
module FFI
|
2
|
+
module Generators
|
3
|
+
##
|
4
|
+
# Generates an FFI Struct layout.
|
5
|
+
#
|
6
|
+
# Given the @@@ portion in:
|
7
|
+
#
|
8
|
+
# module Zlib::ZStream < FFI::Struct
|
9
|
+
# @@@
|
10
|
+
# name "struct z_stream_s"
|
11
|
+
# include "zlib.h"
|
12
|
+
#
|
13
|
+
# field :next_in, :pointer
|
14
|
+
# field :avail_in, :uint
|
15
|
+
# field :total_in, :ulong
|
16
|
+
#
|
17
|
+
# # ...
|
18
|
+
# @@@
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Structures will create the layout:
|
22
|
+
#
|
23
|
+
# layout :next_in, :pointer, 0,
|
24
|
+
# :avail_in, :uint, 4,
|
25
|
+
# :total_in, :ulong, 8,
|
26
|
+
# # ...
|
27
|
+
#
|
28
|
+
# StructGenerator does its best to pad the layout it produces to preserve
|
29
|
+
# line numbers. Place the struct definition as close to the top of the file
|
30
|
+
# for best results.
|
31
|
+
|
32
|
+
class Structures
|
33
|
+
|
34
|
+
##
|
35
|
+
# A field in a Struct.
|
36
|
+
|
37
|
+
class Field
|
38
|
+
|
39
|
+
attr_reader :name
|
40
|
+
attr_reader :type
|
41
|
+
attr_reader :offset
|
42
|
+
attr_accessor :size
|
43
|
+
|
44
|
+
def initialize(name, type)
|
45
|
+
@name = name
|
46
|
+
@type = type
|
47
|
+
@offset = nil
|
48
|
+
@size = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def offset=(o)
|
52
|
+
@offset = o
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_config(name)
|
56
|
+
buf = []
|
57
|
+
buf << "rbx.platform.#{name}.#{@name}.offset = #{@offset}"
|
58
|
+
buf << "rbx.platform.#{name}.#{@name}.size = #{@size}"
|
59
|
+
buf << "rbx.platform.#{name}.#{@name}.type = #{@type}" if @type
|
60
|
+
buf
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
attr_accessor :size
|
66
|
+
attr_reader :fields
|
67
|
+
|
68
|
+
def initialize(name)
|
69
|
+
@name = name
|
70
|
+
@struct_name = nil
|
71
|
+
@includes = []
|
72
|
+
@include_dirs = []
|
73
|
+
@fields = []
|
74
|
+
@found = false
|
75
|
+
@size = nil
|
76
|
+
@platform = Platform.new
|
77
|
+
|
78
|
+
if block_given? then
|
79
|
+
yield self
|
80
|
+
calculate
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def source(io)
|
85
|
+
io.puts "#include <stdio.h>"
|
86
|
+
|
87
|
+
@includes.each do |inc|
|
88
|
+
io.puts "#include <#{inc}>"
|
89
|
+
end
|
90
|
+
|
91
|
+
io.puts "#include <stddef.h>\n\n"
|
92
|
+
io.puts "int main(int argc, char **argv)\n{"
|
93
|
+
io.puts " #{@struct_name} s;"
|
94
|
+
io.puts %[ printf("sizeof(#{@struct_name}) %u\\n", (unsigned int) sizeof(#{@struct_name}));]
|
95
|
+
|
96
|
+
@fields.each do |field|
|
97
|
+
io.puts <<-EOF
|
98
|
+
printf("#{field.name} %u %u\\n", (unsigned int) offsetof(#{@struct_name}, #{field.name}),
|
99
|
+
(unsigned int) sizeof(s.#{field.name}));
|
100
|
+
EOF
|
101
|
+
end
|
102
|
+
|
103
|
+
io.puts "\n return 0;\n}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def prepare(name, target)
|
107
|
+
@platform.compile(@include_dirs, name, target)
|
108
|
+
end
|
109
|
+
|
110
|
+
def prepare_failed
|
111
|
+
"Compilation error generating struct #{@name} (#{@struct_name})"
|
112
|
+
end
|
113
|
+
|
114
|
+
def process(target)
|
115
|
+
target
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_failed
|
119
|
+
"Error generating struct #{@name} (#{@struct_name})"
|
120
|
+
end
|
121
|
+
|
122
|
+
def calculate
|
123
|
+
raise "struct name not set" if @struct_name.nil?
|
124
|
+
|
125
|
+
output = BodyGuard.new(self, @struct_name, @platform).perform.split "\n"
|
126
|
+
|
127
|
+
sizeof = output.shift
|
128
|
+
unless @size
|
129
|
+
m = /\s*sizeof\([^)]+\) (\d+)/.match sizeof
|
130
|
+
@size = m[1]
|
131
|
+
end
|
132
|
+
|
133
|
+
line_no = 0
|
134
|
+
output.each do |line|
|
135
|
+
md = line.match(/.+ (\d+) (\d+)/)
|
136
|
+
@fields[line_no].offset = md[1].to_i
|
137
|
+
@fields[line_no].size = md[2].to_i
|
138
|
+
|
139
|
+
line_no += 1
|
140
|
+
end
|
141
|
+
|
142
|
+
@found = true
|
143
|
+
end
|
144
|
+
|
145
|
+
def field(name, type=nil)
|
146
|
+
field = Field.new(name, type)
|
147
|
+
@fields << field
|
148
|
+
return field
|
149
|
+
end
|
150
|
+
|
151
|
+
def found?
|
152
|
+
@found
|
153
|
+
end
|
154
|
+
|
155
|
+
def write_config(io)
|
156
|
+
io.puts "rbx.platform.#{@name}.sizeof = #{@size}"
|
157
|
+
|
158
|
+
@fields.each { |field| io.puts field.to_config(@name) }
|
159
|
+
end
|
160
|
+
|
161
|
+
def generate_layout
|
162
|
+
buf = ""
|
163
|
+
|
164
|
+
@fields.each_with_index do |field, i|
|
165
|
+
if buf.empty?
|
166
|
+
buf << "layout :#{field.name}, :#{field.type}, #{field.offset}"
|
167
|
+
else
|
168
|
+
buf << " :#{field.name}, :#{field.type}, #{field.offset}"
|
169
|
+
end
|
170
|
+
|
171
|
+
if i < @fields.length - 1
|
172
|
+
buf << ",\n"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
buf
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_field(name)
|
180
|
+
@fields.find { |f| name == f.name }
|
181
|
+
end
|
182
|
+
|
183
|
+
def include(i)
|
184
|
+
@includes << i
|
185
|
+
end
|
186
|
+
|
187
|
+
def include_dir(i)
|
188
|
+
@include_dirs << i
|
189
|
+
end
|
190
|
+
|
191
|
+
def name(n)
|
192
|
+
@struct_name = n
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
StructGenerator = Generators::Structures
|
198
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module FFI
|
2
|
+
module Generators
|
3
|
+
class Types
|
4
|
+
|
5
|
+
##
|
6
|
+
# Maps different C types to the C type representations we use
|
7
|
+
|
8
|
+
TYPE_MAP = {
|
9
|
+
"char" => :char,
|
10
|
+
"signed char" => :char,
|
11
|
+
"__signed char" => :char,
|
12
|
+
"unsigned char" => :uchar,
|
13
|
+
|
14
|
+
"short" => :short,
|
15
|
+
"signed short" => :short,
|
16
|
+
"signed short int" => :short,
|
17
|
+
"unsigned short" => :ushort,
|
18
|
+
"unsigned short int" => :ushort,
|
19
|
+
|
20
|
+
"int" => :int,
|
21
|
+
"signed int" => :int,
|
22
|
+
"unsigned int" => :uint,
|
23
|
+
|
24
|
+
"long" => :long,
|
25
|
+
"long int" => :long,
|
26
|
+
"signed long" => :long,
|
27
|
+
"signed long int" => :long,
|
28
|
+
"unsigned long" => :ulong,
|
29
|
+
"unsigned long int" => :ulong,
|
30
|
+
"long unsigned int" => :ulong,
|
31
|
+
|
32
|
+
"long long" => :long_long,
|
33
|
+
"long long int" => :long_long,
|
34
|
+
"signed long long" => :long_long,
|
35
|
+
"signed long long int" => :long_long,
|
36
|
+
"unsigned long long" => :ulong_long,
|
37
|
+
"unsigned long long int" => :ulong_long,
|
38
|
+
|
39
|
+
"char *" => :string,
|
40
|
+
"void *" => :pointer,
|
41
|
+
}
|
42
|
+
|
43
|
+
def self.generate
|
44
|
+
new.generate
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@platform = Platform.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def source(io)
|
52
|
+
io.puts "#include <stdint.h>"
|
53
|
+
io.puts "#include <sys/types.h>"
|
54
|
+
unless @platform.windows?
|
55
|
+
io.puts "#include <sys/socket.h>"
|
56
|
+
io.puts "#include <sys/resource.h>"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def prepare(name, target)
|
61
|
+
# we have nothing to do in this stage
|
62
|
+
end
|
63
|
+
|
64
|
+
def process(target)
|
65
|
+
"#{@platform.compiler} -E #{@platform.defines} #{target}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_failed
|
69
|
+
"Error generating C types"
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate
|
73
|
+
typedefs = BodyGuard.new(self, "ffi_types_generator", @platform).perform
|
74
|
+
|
75
|
+
code = ""
|
76
|
+
|
77
|
+
typedefs.split(/\n/).each do |type|
|
78
|
+
# We only care about single line typedef
|
79
|
+
next unless type =~ /typedef/
|
80
|
+
# Ignore unions or structs
|
81
|
+
next if type =~ /union|struct/
|
82
|
+
|
83
|
+
# strip off the starting typedef and ending ;
|
84
|
+
type.gsub!(/^(.*typedef\s*)/, "")
|
85
|
+
type.gsub!(/\s*;\s*$/, "")
|
86
|
+
|
87
|
+
parts = type.split(/\s+/)
|
88
|
+
def_type = parts.join(" ")
|
89
|
+
|
90
|
+
# GCC does mapping with __attribute__ stuf, also see
|
91
|
+
# http://hal.cs.berkeley.edu/cil/cil016.html section 16.2.7. Problem
|
92
|
+
# with this is that the __attribute__ stuff can either occur before or
|
93
|
+
# after the new type that is defined...
|
94
|
+
if type =~ /__attribute__/
|
95
|
+
if parts.last =~ /__QI__|__HI__|__SI__|__DI__|__word__/
|
96
|
+
|
97
|
+
# In this case, the new type is BEFORE __attribute__ we need to
|
98
|
+
# find the final_type as the type before the part that starts with
|
99
|
+
# __attribute__
|
100
|
+
final_type = ""
|
101
|
+
parts.each do |p|
|
102
|
+
break if p =~ /__attribute__/
|
103
|
+
final_type = p
|
104
|
+
end
|
105
|
+
else
|
106
|
+
final_type = parts.pop
|
107
|
+
end
|
108
|
+
|
109
|
+
def_type = case type
|
110
|
+
when /__QI__/ then "char"
|
111
|
+
when /__HI__/ then "short"
|
112
|
+
when /__SI__/ then "int"
|
113
|
+
when /__DI__/ then "long long"
|
114
|
+
when /__word__/ then "long"
|
115
|
+
else "int"
|
116
|
+
end
|
117
|
+
|
118
|
+
def_type = "unsigned #{def_type}" if type =~ /unsigned/
|
119
|
+
else
|
120
|
+
final_type = parts.pop
|
121
|
+
def_type = parts.join(" ")
|
122
|
+
end
|
123
|
+
|
124
|
+
if type = TYPE_MAP[def_type]
|
125
|
+
code << "rbx.platform.typedef.#{final_type} = #{type}\n"
|
126
|
+
TYPE_MAP[final_type] = TYPE_MAP[def_type]
|
127
|
+
else
|
128
|
+
# Fallback to an ordinary pointer if we don't know the type
|
129
|
+
if def_type =~ /\*/
|
130
|
+
code << "rbx.platform.typedef.#{final_type} = pointer\n"
|
131
|
+
TYPE_MAP[final_type] = :pointer
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
code
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
TypesGenerator = Generators::Types
|
142
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi2-generators
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Shirai
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Utilities for generating constants, types, and structs for FFI.
|
42
|
+
email:
|
43
|
+
- brixen@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- ffi2-generators.gemspec
|
54
|
+
- lib/ffi2/generators.rb
|
55
|
+
- lib/ffi2/generators/constants.rb
|
56
|
+
- lib/ffi2/generators/file_processor.rb
|
57
|
+
- lib/ffi2/generators/structures.rb
|
58
|
+
- lib/ffi2/generators/types.rb
|
59
|
+
- lib/ffi2/generators/version.rb
|
60
|
+
homepage: https://github.com/rubinius/ffi2-generators
|
61
|
+
licenses:
|
62
|
+
- BSD
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.0.7
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Utilities for generating constants, types, and structs for FFI.
|
84
|
+
test_files: []
|