Permalink
Browse files

Reworked FFI generators.

  • Loading branch information...
brixen authored and Brian Ford committed Feb 20, 2011
1 parent 11d8e4b commit 0db919c0e637fbb0c156d86f67254198768f440c
View
@@ -1,172 +0,0 @@
-require 'tempfile'
-require 'open3'
-
-module FFI
-
- ##
- # ConstGenerator turns C constants into ruby values.
-
- class ConstGenerator
-
- attr_reader :constants
-
- ##
- # Creates a new constant generator that uses +prefix+ as a name, and an
- # options hash.
- #
- # The only option is :required, which if set to true raises an error if a
- # constant you have requested was not found.
- #
- # When passed a block, #calculate is automatically called at the end of
- # the block, otherwise you must call it yourself.
-
- def initialize(prefix = nil, options = {})
- @includes = []
- @constants = {}
- @prefix = prefix
-
- @required = options[:required]
-
- if block_given? then
- yield self
- calculate
- end
- end
-
- def [](name)
- @constants[name].value
- end
-
- ##
- # Request the value for C constant +name+. +format+ is a printf format
- # string to print the value out, and +cast+ is a C cast for the value.
- # +ruby_name+ allows you to give the constant an alternate ruby name for
- # #to_ruby. +converter+ or +converter_proc+ allow you to convert the
- # value from a string to the appropriate type for #to_ruby.
-
- def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
- &converter_proc)
- format ||= '%d'
- cast ||= ''
-
- if converter_proc and converter then
- raise ArgumentError, "Supply only converter or converter block"
- end
-
- converter = converter_proc if converter.nil?
-
- const = Constant.new name, format, cast, ruby_name, converter
- @constants[name.to_s] = const
- return const
- end
-
- def calculate
- binary = File.join Dir.tmpdir, "rb_const_gen_bin_#{Process.pid}"
-
- Tempfile.open("#{@prefix}.const_generator") do |f|
- f.puts "#include <stdio.h>"
-
- @includes.each do |inc|
- f.puts "#include <#{inc}>"
- end
-
- f.puts "#include <stddef.h>\n\n"
- f.puts "int main(int argc, char **argv)\n{"
-
- @constants.each_value do |const|
- f.puts <<-EOF
- #ifdef #{const.name}
- printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
- #endif
- EOF
- end
-
- f.puts "\n\treturn 0;\n}"
- f.flush
-
- 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`
-
- unless $?.success? then
- output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
- raise "Compilation error generating constants #{@prefix}:\n#{output}"
- end
- end
-
- output = `#{binary}`
- File.unlink binary
-
- output.each_line do |line|
- line =~ /^(\S+)\s(.*)$/
- const = @constants[$1]
- const.value = $2
- end
-
- missing_constants = @constants.select do |name, constant|
- constant.value.nil?
- end.map { |name,| name }
-
- if @required and not missing_constants.empty? then
- raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
- end
- end
-
- def dump_constants(io)
- @constants.each do |name, constant|
- name = [@prefix, name].join '.'
- io.puts "#{name} = #{constant.converted_value}"
- end
- end
-
- ##
- # Outputs values for discovered constants. If the constant's value was
- # not discovered it is not omitted.
-
- def to_ruby
- @constants.sort_by { |name,| name }.map do |name, constant|
- if constant.value.nil? then
- "# #{name} not available"
- else
- constant.to_ruby
- end
- end.join "\n"
- end
-
- def include(i)
- @includes << i
- end
-
- end
-
- class ConstGenerator::Constant
-
- attr_reader :name, :format, :cast
- attr_accessor :value
-
- def initialize(name, format, cast, ruby_name = nil, converter=nil)
- @name = name
- @format = format
- @cast = cast
- @ruby_name = ruby_name
- @converter = converter
- @value = nil
- end
-
- def converted_value
- if @converter
- @converter.call(@value)
- else
- @value
- end
- end
-
- def ruby_name
- @ruby_name || @name
- end
-
- def to_ruby
- "#{ruby_name} = #{converted_value}"
- end
-
- end
-
-end
View
@@ -0,0 +1,87 @@
+require File.expand_path('../generators', __FILE__)
+
+module FFI
+
+ # Processes a file containing Ruby code with blocks of FFI definitions
+ # delimited by @@@. The blocks are replaced with Ruby code produced by
+ # running the FFI generators contained in the blocks. For example:
+ #
+ # module Something
+ # @@@
+ # constants do |c|
+ # c.include 'somefile.h'
+ #
+ # c.const 'MAX'
+ # c.const 'MIN'
+ # end
+ # @@@
+ # end
+ #
+ # would be converted to:
+ #
+ # module Something
+ # MAX = 1
+ # MIN = 2
+ # end
+ #
+ # assuming that
+ #
+ # #define MAX 1
+ # #define MIN 2
+ #
+ # was contained in the file 'something.h'.
+
+ class FileProcessor
+
+ def initialize(ffi_name, rb_name)
+ @name = File.basename rb_name, '.rb'
+
+ definitions = File.read ffi_name
+
+ replacement = definitions.gsub(/^( *)@@@(.*?)@@@/m) do
+ @constants = []
+ @structs = []
+
+ indent = $1
+ line_count = $2.count("\n") + 1
+
+ instance_eval $2
+
+ lines = []
+ @constants.each { |c| lines << c.to_ruby }
+ @structs.each { |s| lines << s.generate_layout }
+
+ # expand multiline blocks
+ lines = lines.join("\n").split "\n"
+ lines = lines.map { |line| indent + line }
+
+ # preserve source line numbers in output
+ padding = line_count - lines.length
+ lines += [nil] * padding if padding >= 0
+
+ lines.join "\n"
+ end
+
+ File.open rb_name, 'wb' do |f|
+ f.puts "# This file is generated #{self.class} from #{ffi_name}."
+ f.puts
+ f.puts replacement
+ end
+ end
+
+ def constants(options={}, &block)
+ @constants << FFI::ConstGenerator.new(@name, options, &block)
+ end
+
+ def struct(&block)
+ @structs << FFI::StructGenerator.new(@name, &block)
+ end
+
+ ##
+ # Utility converter for constants
+
+ def to_s
+ proc { |obj| obj.to_s.inspect }
+ end
+ end
+end
View
@@ -1,58 +1,12 @@
+require File.expand_path('../file_processor', __FILE__)
+
module FFI
class Generator
-
def initialize(ffi_name, rb_name)
- @ffi_name = ffi_name
- @rb_name = rb_name
-
- @name = File.basename rb_name, '.rb'
-
- file = File.read @ffi_name
-
- new_file = file.gsub(/^( *)@@@(.*?)@@@/m) do
- @constants = []
- @structs = []
-
- indent = $1
- original_lines = $2.count "\n"
-
- instance_eval $2
-
- new_lines = []
- @constants.each { |c| new_lines << c.to_ruby }
- @structs.each { |s| new_lines << s.generate_layout }
-
- new_lines = new_lines.join("\n").split "\n" # expand multiline blocks
- new_lines = new_lines.map { |line| indent + line }
-
- padding = original_lines - new_lines.length
- new_lines += [nil] * padding if padding >= 0
+ STDERR.puts "FFI::Generator is deprecated. Use FFI::FileProcessor"
+ STDERR.puts "Called from: #{caller(1).first}"
- new_lines.join "\n"
- end
-
- open @rb_name, 'w' do |f|
- f.puts "# This file is generated by rake. Do not edit."
- f.puts
- f.puts new_file
- end
- end
-
- def constants(options = {}, &block)
- @constants << FFI::ConstGenerator.new(@name, options, &block)
- end
-
- def struct(&block)
- @structs << FFI::StructGenerator.new(@name, &block)
+ FileProcessor.new ffi_name, rb_name
end
-
- ##
- # Utility converter for constants
-
- def to_s
- proc { |obj| obj.to_s.inspect }
- end
-
end
end
-
Oops, something went wrong.

0 comments on commit 0db919c

Please sign in to comment.