class ExcelToC

Attributes

allow_unknown_functions[RW]

If true, allows unknown Excel functions rather than aborting

create_makefile[RW]

If true, creates a Makefile, if false, doesn't (default false)

create_rakefile[RW]

If true, creates a Rakefile, if false, doesn't (default true)

write_tests_in_c[RW]

If true, writes tests in C rather than in ruby

Public Instance Methods

compile_code() click to toggle source
# File src/commands/excel_to_c.rb, line 404
def compile_code
  return unless actually_compile_code || actually_run_tests
  name = output_name.downcase
  log.info "Compiling"
  puts `gcc -fPIC -o #{File.join(output_directory, name)}.o -c #{File.join(output_directory, name)}.c`
  puts `gcc -shared -fPIC -o #{File.join(output_directory, FFI.map_library_name(name))} #{File.join(output_directory, name)}.o`
end
language() click to toggle source
# File src/commands/excel_to_c.rb, line 23
def language
  "c"
end
run_tests() click to toggle source
# File src/commands/excel_to_c.rb, line 412
def run_tests
  return unless actually_run_tests
  log.info "Running the resulting tests"
  if write_tests_in_c
    puts `cd #{File.join(output_directory)}; gcc "test_#{output_name.downcase}.c"; ./a.out`
  end
  puts `cd #{File.join(output_directory)}; ruby "test_#{output_name.downcase}.rb"`
end
set_defaults() click to toggle source
Calls superclass method ExcelToX#set_defaults
# File src/commands/excel_to_c.rb, line 16
def set_defaults
  super
  @create_rakefile = true if @create_rakefile == nil
  @create_makefile = false if @create_makefile == nil
  @write_tests_in_c = false if @write_tests_in_c == nil
end
write_build_script() click to toggle source

FIXME: Should make a Rakefile, especially in order to make sure the dynamic library name is set properly

# File src/commands/excel_to_c.rb, line 119
def write_build_script
  log.info "Writing Build script"
  write_makefile if create_makefile
  write_rakefile if create_rakefile
end
write_code() click to toggle source

These actually create the code version of the excel

# File src/commands/excel_to_c.rb, line 28
def write_code
  write_out_excel_as_code
  write_build_script
  write_fuby_ffi_interface
  if write_tests_in_c
    write_tests_as_c
  end
  write_tests_as_ruby
end
write_fuby_ffi_interface() click to toggle source
# File src/commands/excel_to_c.rb, line 191
  def write_fuby_ffi_interface
    log.info "Writing ruby FFI code"

    name = output_name.downcase
    o = output("#{name}.rb")
      
    code = <<END
require 'ffi'
require 'singleton'

class #{ruby_module_name}

  # WARNING: this is not thread safe
  def initialize
    reset
  end

  def reset
    C.reset
  end

  def method_missing(name, *arguments)
    if arguments.size == 0
      get(name)
    elsif arguments.size == 1
      set(name, arguments.first)
    else
      super
    end 
  end

  def get(name)
    return 0 unless C.respond_to?(name)
    ruby_value_from_excel_value(C.send(name))
  end

  def ruby_value_from_excel_value(excel_value)
    case excel_value[:type]
    when :ExcelNumber; excel_value[:number]
    when :ExcelString; excel_value[:string].read_string.force_encoding("utf-8")
    when :ExcelBoolean; excel_value[:number] == 1
    when :ExcelEmpty; nil
    when :ExcelRange
      r = excel_value[:rows]
      c = excel_value[:columns]
      p = excel_value[:array]
      s = C::ExcelValue.size
      a = Array.new(r) { Array.new(c) }
      (0...r).each do |row|
        (0...c).each do |column|
          a[row][column] = ruby_value_from_excel_value(C::ExcelValue.new(p + (((row*c)+column)*s)))
        end
      end 
      return a
    when :ExcelError; [:value,:name,:div0,:ref,:na,:num][excel_value[:number]]
    else
      raise Exception.new("ExcelValue type \u0023{excel_value[:type].inspect} not recognised")
    end
  end

  def set(name, ruby_value)
    name = name.to_s
    name = "set_\#{name[0..-2]}" if name.end_with?('=')
    return false unless C.respond_to?(name)
    C.send(name, excel_value_from_ruby_value(ruby_value))
  end

  def excel_value_from_ruby_value(ruby_value, excel_value = C::ExcelValue.new)
    case ruby_value
    when Numeric
      excel_value[:type] = :ExcelNumber
      excel_value[:number] = ruby_value
    when String
      excel_value[:type] = :ExcelString
      excel_value[:string] = FFI::MemoryPointer.from_string(ruby_value.encode('utf-8'))
    when TrueClass, FalseClass
      excel_value[:type] = :ExcelBoolean
      excel_value[:number] = ruby_value ? 1 : 0
    when nil
      excel_value[:type] = :ExcelEmpty
    when Array
      excel_value[:type] = :ExcelRange
      # Presumed to be a row unless specified otherwise
      if ruby_value.first.is_a?(Array)
        excel_value[:rows] = ruby_value.size
        excel_value[:columns] = ruby_value.first.size
      else
        excel_value[:rows] = 1
        excel_value[:columns] = ruby_value.size
      end
      ruby_values = ruby_value.flatten
      pointer = FFI::MemoryPointer.new(C::ExcelValue, ruby_values.size)
      excel_value[:array] = pointer
      ruby_values.each.with_index do |v,i|
        excel_value_from_ruby_value(v, C::ExcelValue.new(pointer[i]))
      end
    when Symbol
      excel_value[:type] = :ExcelError
      excel_value[:number] = [:value, :name, :div0, :ref, :na].index(ruby_value)
    else
      raise Exception.new("Ruby value \u0023{ruby_value.inspect} not translatable into excel")
    end
    excel_value
  end


  module C 
    extend FFI::Library
    ffi_lib  File.join(File.dirname(__FILE__),FFI.map_library_name('#{name}'))
    ExcelType = enum :ExcelEmpty, :ExcelNumber, :ExcelString, :ExcelBoolean, :ExcelError, :ExcelRange
                  
    class ExcelValue < FFI::Struct
      layout :type, ExcelType,
             :number, :double,
             :string, :pointer,
             :array, :pointer,
             :rows, :int,
             :columns, :int             
    end
  
END
    o.puts code
    o.puts
    o.puts "    # use this function to reset all cell values"
    o.puts "    attach_function 'reset', [], :void"


    worksheets do |name, xml_filename|
      c_name = c_name_for_worksheet_name(name)

      # Put in place the setters, if any
      settable_refs = @cells_that_can_be_set_at_runtime[name]
      if settable_refs
        settable_refs = @formulae.keys.select { |k| k.first == name }.map { |k| k.last } if settable_refs == :all
        settable_refs.each do |ref|
          o.puts "    attach_function 'set_#{c_name}_#{ref.downcase}', [ExcelValue.by_value], :void"
        end
      end

      # Put in place the getters
      if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
        getable_refs = @formulae.keys.select { |ref| ref.first == name }.map { |ref| ref.last } 
      elsif !cells_to_keep[name] && settable_refs
        getable_refs = settable_refs
      else
        getable_refs = cells_to_keep[name] || []
      end
              
      getable_refs.each do |ref|
        o.puts "    attach_function '#{c_name}_#{ref.downcase}', [], ExcelValue.by_value"
      end
        
      o.puts "    # end of #{name}"
    end

    o.puts "    # Start of named references"
    # Getters
    @named_references_to_keep.each do |name|
      o.puts "    attach_function '#{c_name_for(name)}', [], ExcelValue.by_value"
    end

    # Setters
    @named_references_that_can_be_set_at_runtime.each do |name|
      o.puts "    attach_function 'set_#{c_name_for(name)}', [ExcelValue.by_value], :void"
    end

    o.puts "    # End of named references"

    o.puts "  end # C module"  
    o.puts "end # #{ruby_module_name}"  
    close(o)
  end
write_makefile() click to toggle source
# File src/commands/excel_to_c.rb, line 125
def write_makefile
  log.info "Writing Makefile"

  name = output_name.downcase
  o = output("Makefile")

  # Target for shared library
  shared_library_name = FFI.map_library_name(name)
  o.puts "#{shared_library_name}: #{name}.o"
  o.puts "\tgcc -shared -o #{shared_library_name} #{name}.o"
  o.puts

  # Target for compiled version
  o.puts "#{name}.o:"
  o.puts "\tgcc -fPIC -c #{name}.c"
  o.puts

  # Target for cleaning
  o.puts "clean:"
  o.puts "\trm #{name}.o"
  o.puts "\trm #{shared_library_name}"

  close(o)
end
write_out_excel_as_code() click to toggle source
# File src/commands/excel_to_c.rb, line 38
def write_out_excel_as_code
  log.info "Writing C code"
      
  number_of_refs = @formulae.size + @named_references_to_keep.size
      
  # Output the workbook preamble
  o = output("#{output_name.downcase}.c")
  o.puts "// #{excel_file} approximately translated into C"
  o.puts "// definitions"
  o.puts "#define NUMBER_OF_REFS #{number_of_refs}"
  o.puts "#define EXCEL_FILENAME  #{excel_file.inspect}"
  o.puts "// end of definitions"
  o.puts

  o.puts '// First we have c versions of all the excel functions that we know'
  o.puts IO.readlines(File.join(File.dirname(__FILE__),'..','compile','c','excel_to_c_runtime.c')).join
  o.puts '// End of the generic c functions'
  o.puts 
  o.puts '// Start of the file specific functions'
  o.puts
  

  c = CompileToCHeader.new
  c.settable = settable
  c.gettable = gettable
  c.rewrite(@formulae, @worksheet_c_names, o)
  
  # Output the value constants
  o.puts "// starting the value constants"
  mapper = MapValuesToCStructs.new
  @constants.each do |ref, ast|
    begin
      calculation = mapper.map(ast)
      o.puts "static ExcelValue #{ref} = #{calculation};"
    rescue Exception => e
      puts "Exception at #{ref} #{ast}"
      raise
    end
  end          
  o.puts "// ending the value constants"
  o.puts
  
  variable_set_counter = 0
  
  c = CompileToC.new
  c.allow_unknown_functions = self.allow_unknown_functions
  c.variable_set_counter = variable_set_counter
  # Output the elements from each worksheet in turn
  c.settable = settable
  c.gettable = gettable
  c.rewrite(@formulae, @worksheet_c_names, o)

  # Output the named references

  # Getters
  o.puts "// Start of named references"
  c.gettable = lambda { |ref| true }
  c.settable = lambda { |ref| false }
  named_references_ast = {}
  @named_references_to_keep.each do |ref|
    c_name = ref.is_a?(Array) ? c_name_for(ref) : ["", c_name_for(ref)]
    named_references_ast[c_name] = @named_references[ref] || @table_areas[ref]
  end

  c.rewrite(named_references_ast, @worksheet_c_names, o)

  # Setters
  c = CompileNamedReferenceSetters.new
  c.cells_that_can_be_set_at_runtime = cells_that_can_be_set_at_runtime
  named_references_ast = {}
  @named_references_that_can_be_set_at_runtime.each do |ref|
    named_references_ast[c_name_for(ref)] = @named_references[ref] || @table_areas[ref]
  end
  c.rewrite(named_references_ast, @worksheet_c_names, o)
  o.puts "// End of named references"

  close(o)
end
write_rakefile() click to toggle source
# File src/commands/excel_to_c.rb, line 150
def write_rakefile
  log.info "Writing Rakefile"

  o = output("Rakefile")
  name = output_name.downcase
  o.puts "require 'ffi'"
  o.puts

  o.puts "this_directory = File.dirname(__FILE__)"
  o.puts
  o.puts "COMPILER = 'gcc'"
  o.puts "COMPILE_FLAGS = '-fPIC'"
  o.puts "SHARED_LIBRARY_FLAGS = '-shared -fPIC'"
  o.puts

  o.puts "OUTPUT = FFI.map_library_name '#{name}'"
  o.puts "OUTPUT_DIR = this_directory" 
  o.puts "SOURCE = '#{name}.c'"
  o.puts "OBJECT = '#{name}.o'"
  o.puts

  o.puts "task :default => [:build]"
  o.puts
  o.puts "desc 'Build the 2050 model, then install it'"
  o.puts "task :build => [OUTPUT]"
  o.puts

  o.puts "file OUTPUT => OBJECT do"
  o.puts '  puts "Turning #{OBJECT} and putting it in #{OUTPUT_DIR} as #{OUTPUT}"'
  o.puts '  puts "Note that this is a really large c file, it may take tens of minutes to compile."'
  o.puts '  sh "#{COMPILER} #{SHARED_LIBRARY_FLAGS} -o #{File.join(OUTPUT_DIR,OUTPUT)} #{OBJECT}"'
  o.puts 'end'
  o.puts

  o.puts 'file OBJECT => SOURCE do'
  o.puts '  puts "Building #{SOURCE}"'
  o.puts '  puts "Note that this is a really large c file, it may take tens of minutes to compile."'
  o.puts '  sh "#{COMPILER} #{COMPILE_FLAGS} -o #{OBJECT} -c #{SOURCE}"'
  o.puts 'end'
end
write_tests_as_c() click to toggle source
# File src/commands/excel_to_c.rb, line 387
def write_tests_as_c
  log.info "Writing tests in C" 

  name = output_name.downcase
  o = output("test_#{name}.c")    
  o.puts "#include \"#{name}.c\""
  o.puts "int main() {"
  o.puts "  printf(\"\\n\\nRunning tests on #{name}\\n\\n\");"
  CompileToCUnitTest.rewrite(Hash[@references_to_test_array], sloppy_tests, @worksheet_c_names, @constants, o)
  o.puts "  free_all_allocated_memory();"
  o.puts "  printf(\"\\n\\nFinished tests on #{name}\\n\\n\");"
  o.puts "  return 0;"
  o.puts "}"
  close(o)
end
write_tests_as_ruby() click to toggle source
# File src/commands/excel_to_c.rb, line 364
def write_tests_as_ruby
  log.info "Writing tests in ruby" 

  name = output_name.downcase
  o = output("test_#{name}.rb")    
  o.puts "# coding: utf-8"
  o.puts "# Test for #{name}"
  o.puts  "require 'minitest/autorun'"
  o.puts  "require_relative '#{output_name.downcase}'"
  o.puts
  o.puts "class Test#{ruby_module_name} < Minitest::Unit::TestCase"
  o.puts "  def self.runnable_methods"
  o.puts "    puts 'Overriding minitest to run tests in a defined order'"
  o.puts "    methods = methods_matching(/^test_/)"
  o.puts "  end" 
  o.puts "  def worksheet; @worksheet ||= init_spreadsheet; end"
  o.puts "  def init_spreadsheet; #{ruby_module_name}.new end"
  
  CompileToRubyUnitTest.rewrite(Hash[@references_to_test_array], sloppy_tests, @worksheet_c_names, @constants, o)
  o.puts "end"
  close(o)
end