Updating tags for StringIO.
[rbx.git] / lib / ffi / const_generator.rb
blob495301cfe0a86322682d73422006b4ec87a076c0
1 require 'tempfile'
2 require 'open3'
4 module FFI
6   ##
7   # ConstGenerator turns C constants into ruby values.
9   class ConstGenerator
11     attr_reader :constants
13     ##
14     # Creates a new constant generator that uses +prefix+ as a name, and an
15     # options hash.
16     #
17     # The only option is :required, which if set to true raises an error if a
18     # constant you have requested was not found.
19     #
20     # When passed a block, #calculate is automatically called at the end of
21     # the block, otherwise you must call it yourself.
23     def initialize(prefix = nil, options = {})
24       @includes = []
25       @constants = {}
26       @prefix = prefix
28       @required = options[:required]
30       if block_given? then
31         yield self
32         calculate
33       end
34     end
36     def [](name)
37       @constants[name].value
38     end
40     ##
41     # Request the value for C constant +name+.  +format+ is a printf format
42     # string to print the value out, and +cast+ is a C cast for the value.
43     # +ruby_name+ allows you to give the constant an alternate ruby name for
44     # #to_ruby.  +converter+ or +converter_proc+ allow you to convert the
45     # value from a string to the appropriate type for #to_ruby.
47     def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
48               &converter_proc)
49       format ||= '%d'
50       cast ||= ''
52       if converter_proc and converter then
53         raise ArgumentError, "Supply only converter or converter block"
54       end
56       converter = converter_proc if converter.nil?
58       const = Constant.new name, format, cast, ruby_name, converter
59       @constants[name.to_s] = const
60       return const
61     end
63     def calculate
64       binary = File.join Dir.tmpdir, "rb_const_gen_bin_#{Process.pid}"
66       Tempfile.open("#{@prefix}.const_generator") do |f|
67         f.puts "#include <stdio.h>"
69         @includes.each do |inc|
70           f.puts "#include <#{inc}>"
71         end
73         f.puts "#include <stddef.h>\n\n"
74         f.puts "int main(int argc, char **argv)\n{"
76         @constants.each_value do |const|
77           f.puts <<-EOF
78   #ifdef #{const.name}
79   printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
80   #endif
81           EOF
82         end
84         f.puts "\n\treturn 0;\n}"
85         f.flush
87         output = `gcc -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
89         unless $?.success? then
90           output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
91           raise "Compilation error generating constants #{@prefix}:\n#{output}"
92         end
93       end
95       output = `#{binary}`
96       File.unlink binary
98       output.each_line do |line|
99         line =~ /^(\S+)\s(.*)$/
100         const = @constants[$1]
101         const.value = $2
102       end
104       missing_constants = @constants.select do |name, constant|
105         constant.value.nil?
106       end.map { |name,| name }
108       if @required and not missing_constants.empty? then
109         raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
110       end
111     end
113     def dump_constants(io)
114       @constants.each do |name, constant|
115         name = [@prefix, name].join '.'
116         io.puts "#{name} = #{constant.converted_value}"
117       end
118     end
120     ##
121     # Outputs values for discovered constants.  If the constant's value was
122     # not discovered it is not omitted.
124     def to_ruby
125       @constants.sort_by { |name,| name }.map do |name, constant|
126         if constant.value.nil? then
127           "# #{name} not available"
128         else
129           constant.to_ruby
130         end
131       end.join "\n"
132     end
134     def include(i)
135       @includes << i
136     end
138   end
140   class ConstGenerator::Constant
142     attr_reader :name, :format, :cast
143     attr_accessor :value
145     def initialize(name, format, cast, ruby_name = nil, converter=nil)
146       @name = name
147       @format = format
148       @cast = cast
149       @ruby_name = ruby_name
150       @converter = converter
151       @value = nil
152     end
154     def converted_value
155       if @converter
156         @converter.call(@value)
157       else
158         @value
159       end
160     end
162     def ruby_name
163       @ruby_name || @name
164     end
166     def to_ruby
167       "#{ruby_name} = #{converted_value}"
168     end
170   end