class Inline::C

Inline::C is the default builder used and the only one provided by Inline. It can be used as a template to write builders for other languages. It understands type-conversions for the basic types and can be extended as needed using add_type_converter, alias_type_converter and remove_type_converter.

Constants

MAGIC_ARITY
MAGIC_ARITY_THRESHOLD
TYPE_MAP

Default C to ruby and ruby to C type map

Attributes

flags[RW]
init_extra[RW]
libs[RW]
mod[RW]
pre[RW]
rb_file[R]
sig[RW]
src[RW]
struct_name[RW]

Sets the name of the C struct for generating accessors. Used with accessor, reader, writer.

Public Class Methods

new(mod) click to toggle source
# File lib/inline.rb, line 397
def initialize(mod)
  raise ArgumentError, "Class/Module arg is required" unless Module === mod
  # new (but not on some 1.8s) -> inline -> real_caller|eval
  stack = caller
  meth = stack.shift until meth =~ /in .(inline|test_|setup)/ or stack.empty?
  raise "Couldn't discover caller" if stack.empty?
  real_caller = stack.first
  real_caller = stack[3] if real_caller =~ /\(eval\)/
  real_caller =~ /(.*):(\d+)/
  real_caller = $1
  @rb_file = File.expand_path real_caller

  @mod = mod
  @pre = []
  @src = []
  @inc = []
  @sig = {}
  @flags = []
  @libs = []
  @init_extra = []
  @include_ruby_first = true
  @inherited_methods = {}
  @struct_name = nil

  @type_map = TYPE_MAP.dup
end

Public Instance Methods

accessor(method, type, member = method) click to toggle source

Adds a reader and writer for a C struct member wrapped via Data_Wrap_Struct. method is the ruby name to give the accessor, type is the C type. Unless the C member name is overridden with member, the method name is used as the struct member.

builder.struct_name = 'MyStruct'
builder.accessor :title,        'char *'
builder.accessor :stream_index, 'int',   :index

The latter accesses MyStruct->index via the stream_index method.

# File lib/inline.rb, line 436
def accessor(method, type, member = method)
  reader method, type, member
  writer method, type, member
end
add_compile_flags(*flags) click to toggle source

Adds compiler options to the compiler command line. No preprocessing is done, so you must have all your dashes and everything.

# File lib/inline.rb, line 669
def add_compile_flags(*flags)
  @flags.push(*flags)
end
add_id(name) click to toggle source

Registers a static id_name for the symbol :name.

# File lib/inline.rb, line 676
def add_id name
  self.add_static "id_#{name}", "rb_intern(\"#{name}\")"
end
add_static(name, init, type = "VALUE") click to toggle source

Create a static variable and initialize it to a value.

# File lib/inline.rb, line 691
def add_static name, init, type = "VALUE"
  prefix      "static #{type} #{name};"
  add_to_init "#{name} = #{init};"
end
add_to_init(*src) click to toggle source

Adds custom content to the end of the init function.

# File lib/inline.rb, line 699
def add_to_init(*src)
  @init_extra.push(*src)
end
add_type_converter(type, r2c, c2r) click to toggle source

Registers C type-casts r2c and c2r for type.

# File lib/inline.rb, line 706
def add_type_converter(type, r2c, c2r)
  warn "WAR\NING: overridding #{type} on #{caller[0]}" if @type_map.has_key? type
  @type_map[type] = [r2c, c2r]
end
alias_type_converter(existing_type, alias_type) click to toggle source

Registers C type alias_type as an alias of existing_type

# File lib/inline.rb, line 714
def alias_type_converter(existing_type, alias_type)
  warn "WAR\NING: overridding #{type} on #{caller[0]}" if
    @type_map.has_key? alias_type

  @type_map[alias_type] = @type_map[existing_type]
end
build() click to toggle source

Builds the source file, if needed, and attempts to compile it.

# File lib/inline.rb, line 527
def build
  so_name = self.so_name
  so_exists = File.file? so_name
  unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then

    unless File.directory? Inline.directory then
      warn "NOTE: creating #{Inline.directory} for RubyInline" if $DEBUG
      FileUtils.mkdir_p Inline.directory, :mode => 0700
    end

    src_name = "#{Inline.directory}/#{module_name}.c"
    old_src_name = File.write_with_backup src_name, generate_ext

    # recompile only if the files are different
    recompile = true
    if so_exists and old_src_name and
        FileUtils.compare_file(old_src_name, src_name) then
      recompile = false

      # Updates the timestamps on all the generated/compiled files.
      # Prevents us from entering this conditional unless the source
      # file changes again.
      t = Time.now
      File.utime(t, t, src_name, old_src_name, so_name)
    end

    if recompile then
      unless RbConfig::CONFIG["ENABLE_SHARED"] == "yes" then
        raise "this ruby isn't configured for dynamic linking"
      end

      hdrdir = %w(srcdir includedir archdir rubyhdrdir).map { |name|
        RbConfig::CONFIG[name]
      }.find { |dir|
        dir and File.exist? File.join(dir, "ruby.h")
      } or abort "ERROR: Can't find header dir for ruby. Exiting..."

      flags = @flags.join(' ')

      @libs << RbConfig::CONFIG['LIBRUBYARG_SHARED']

      libs  = @libs.join(' ')

      config_hdrdir = if RbConfig::CONFIG['rubyarchhdrdir'] then
                        "-I #{RbConfig::CONFIG['rubyarchhdrdir']}"
                      elsif RUBY_VERSION > '1.9' then
                        "-I #{File.join hdrdir, RbConfig::CONFIG['arch']}"
                      else
                        nil
                      end

      windoze = WINDOZE and RUBY_PLATFORM =~ /mswin/
      sane = ! windoze
      cmd = [ RbConfig::CONFIG['LDSHARED'],
              flags,
              (RbConfig::CONFIG['DLDFLAGS']         if sane),
              (RbConfig::CONFIG['CCDLFLAGS']        if sane),
              RbConfig::CONFIG['CFLAGS'],
              (RbConfig::CONFIG['LDFLAGS']          if sane),
              '-I', hdrdir,
              config_hdrdir,
              '-I', RbConfig::CONFIG['includedir'],
              ("-L#{RbConfig::CONFIG['libdir']}"    if sane),
              (['-o', so_name.inspect]              if sane),
              File.expand_path(src_name).inspect,
              libs,
              crap_for_windoze,
              (RbConfig::CONFIG['LDFLAGS']          if windoze),
              (RbConfig::CONFIG['CCDLFLAGS']        if windoze),
            ].compact.join(' ')

      # odd compilation error on clang + freebsd 10. Ruby built w/ rbenv.
      cmd = cmd.gsub(/-Wl,-soname,\$@/, "-Wl,-soname,#{File.basename so_name}")

      # strip off some makefile macros for mingw 1.9
      cmd = cmd.gsub(/\$\(.*\)/, '') if RUBY_PLATFORM =~ /mingw/

      cmd += " 2> #{DEV_NULL}" if $TESTING and not $DEBUG

      warn "Building #{so_name} with '#{cmd}'" if $DEBUG

      result = if WINDOZE
                 Dir.chdir(Inline.directory) { `#{cmd}` }
               else
                 `#{cmd}`
               end

      warn "Output:\n#{result}" if $DEBUG

      if $? != 0 then
        bad_src_name = src_name + ".bad"
        File.rename src_name, bad_src_name
        raise CompilationError, "error executing #{cmd.inspect}: #{$?}\nRenamed #{src_name} to #{bad_src_name}"
      end

      # NOTE: manifest embedding is only required when using VC8 ruby
      # build or compiler.
      # Errors from this point should be ignored if RbConfig::CONFIG['arch']
      # (RUBY_PLATFORM) matches 'i386-mswin32_80'
      if WINDOZE and RUBY_PLATFORM =~ /_80$/ then
        Dir.chdir Inline.directory do
          cmd = "mt /manifest lib.so.manifest /outputresource:so.dll;#2"
          warn "Embedding manifest with '#{cmd}'" if $DEBUG
          result = `#{cmd}`
          warn "Output:\n#{result}" if $DEBUG
          if $? != 0 then
            raise CompilationError, "error executing #{cmd}: #{$?}"
          end
        end
      end

      warn "Built successfully" if $DEBUG
    end

  else
    warn "#{so_name} is up to date" if $DEBUG
  end # unless (file is out of date)
end
c(src, options = {}) click to toggle source

Adds a C function to the source, including performing automatic type conversion to arguments and the return value. The Ruby method name can be overridden by providing method_name. Unknown type conversions can be extended by using add_type_converter.

# File lib/inline.rb, line 784
def c src, options = {}
  options = {
    :expand_types => true,
  }.merge options
  self.generate src, options
end
c2ruby(type) click to toggle source

Converts C type type to a ruby type

# File lib/inline.rb, line 496
def c2ruby(type)
  raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type
  @type_map[type].last
end
c_raw(src, options = {}) click to toggle source

Adds a raw C function to the source. This version does not perform any type conversion and must conform to the ruby/C coding conventions. The Ruby method name can be overridden by providing method_name.

# File lib/inline.rb, line 808
def c_raw src, options = {}
  self.generate src, options
end
c_raw_singleton(src, options = {}) click to toggle source

Same as c_raw, but adds a class function.

# File lib/inline.rb, line 815
def c_raw_singleton src, options = {}
  options = {
    :singleton => true,
  }.merge options
  self.generate src, options
end
c_singleton(src, options = {}) click to toggle source

Same as c, but adds a class function.

# File lib/inline.rb, line 794
def c_singleton src, options = {}
  options = {
    :expand_types => true,
    :singleton    => true,
  }.merge options
  self.generate src, options
end
crap_for_windoze() click to toggle source

Returns extra compilation flags for windoze platforms. Ugh.

# File lib/inline.rb, line 649
def crap_for_windoze
  # gawd windoze land sucks
  case RUBY_PLATFORM
  when /mswin32/ then
    " -link /OUT:\"#{self.so_name}\" /LIBPATH:\"#{RbConfig::CONFIG['libdir']}\" /DEFAULTLIB:\"#{RbConfig::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
  when /mingw32/ then
    c = RbConfig::CONFIG
    " -Wl,--enable-auto-import -L#{c['libdir']} -l#{c['RUBY_SO_NAME']} -o #{so_name.inspect}"
  when /i386-cygwin/ then
    ' -L/usr/local/lib -lruby.dll'
  else
    ''
  end
end
generate(src, options={}) click to toggle source
# File lib/inline.rb, line 237
def generate(src, options={})
  options = {:expand_types=>options} unless Hash === options

  expand_types = options[:expand_types]
  singleton = options[:singleton]
  result = self.strip_comments(src)

  signature = parse_signature(src, !expand_types)
  function_name = signature['name']
  method_name = options[:method_name]
  method_name ||= test_to_normal function_name
  return_type = signature['return']
  arity = options[:arity] || signature['arity']

  raise ArgumentError, "too many arguments" if arity > MAGIC_ARITY_THRESHOLD

  if expand_types then
    prefix = "static VALUE #{function_name}("
    if arity <= MAGIC_ARITY then
      prefix += "int argc, VALUE *argv, VALUE self"
    else
      prefix += "VALUE self"
      prefix += signature['args'].map { |arg, type| ", VALUE _#{arg}"}.join
    end
    prefix += ") {\n"
    prefix += signature['args'].map { |arg, type|
      "  #{type} #{arg} = #{ruby2c(type)}(_#{arg});\n"
    }.join

    # replace the function signature (hopefully) with new sig (prefix)
    result.sub!(/[^;\/\"\>]+#{function_name}\s*\([^\{]+\{/, "\n" + prefix)
    result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
    unless return_type == "void" then
      raise SyntaxError, "Couldn't find return statement for #{function_name}" unless
        result =~ /return/
      result.gsub!(/return\s+([^\;\}]+)/) do
        "return #{c2ruby(return_type)}(#{$1})"
      end
    else
      result.sub!(/\s*\}\s*\Z/, "\nreturn Qnil;\n}")
    end
  else
    prefix = "static #{return_type} #{function_name}("
    result.sub!(/[^;\/\"\>]+#{function_name}\s*\(/, prefix)
    result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
  end

  delta = if result =~ /\A(static.*?\{)/m then
            $1.split(/\n/).size
          else
            msg = "WAR\NING: Can't find signature in #{result.inspect}\n"
            warn msg unless $TESTING
            0
          end

  file, line = $1, $2 if caller[1] =~ /(.*?):(\d+)/

  result = "# line #{line.to_i + delta} \"#{file}\"\n" + result unless
    $DEBUG and not $TESTING

  @src << result
  @sig[function_name] = [arity,singleton,method_name]

  return result if $TESTING
end
generate_ext() click to toggle source

Builds a complete C extension suitable for writing to a file and compiling.

# File lib/inline.rb, line 307
def generate_ext
  ext = []

  if @include_ruby_first
    @inc.unshift "#include \"ruby.h\""
  else
    @inc.push "#include \"ruby.h\""
  end

  ext << @inc
  ext << nil
  unless @pre.empty? then
    ext << @pre.join("\n\n")
    ext << nil
  end
  ext << @src.join("\n\n")
  ext << nil
  ext << nil
  ext << "#ifdef __cplusplus"
  ext << "extern \"C\" {"
  ext << "#endif"
  ext << "  __declspec(dllexport)" if WINDOZE
  ext << "  void Init_#{module_name}(void) {"
  ext << "    VALUE c = rb_cObject;"

  # TODO: use rb_class2path
  # ext << "    VALUE c = rb_path2class(#{@mod.name.inspect});"
  ext << @mod.name.split("::").map { |n|
    "    c = rb_const_get(c, rb_intern(\"#{n}\"));"
  }.join("\n")

  ext << nil

  @sig.keys.sort.each do |name|
    method = ''
    arity, singleton, method_name = @sig[name]
    if singleton then
      if method_name == 'allocate' then
        raise "#{@mod}::allocate must have an arity of zero" if arity > 0
        ext << "    rb_define_alloc_func(c, (VALUE(*)(VALUE))#{name});"
        next
      end
      method << "    rb_define_singleton_method(c, \"#{method_name}\", "
    else
      method << "    rb_define_method(c, \"#{method_name}\", "
    end
    method << "(VALUE(*)(ANYARGS))#{name}, #{arity});"
    ext << method
  end

  ext << @init_extra.join("\n") unless @init_extra.empty?

  ext << nil
  ext << "  }"
  ext << "#ifdef __cplusplus"
  ext << "}"
  ext << "#endif"
  ext << nil

  ext.join "\n"
end
include(header) click to toggle source

Adds an include to the top of the file. Don’t forget to use quotes or angle brackets.

# File lib/inline.rb, line 759
def include(header)
  @inc << "#include #{header}"
end
include_ruby_last() click to toggle source

Specifies that the the ruby.h header should be included after custom header(s) instead of before them.

# File lib/inline.rb, line 767
def include_ruby_last
  @include_ruby_first = false
end
load() click to toggle source

Loads the generated code back into ruby

# File lib/inline.rb, line 520
def load
  require "#{so_name}" or raise LoadError, "require on #{so_name} failed"
end
load_cache() click to toggle source

Attempts to load pre-generated code returning true if it succeeds.

# File lib/inline.rb, line 504
def load_cache
  begin
    file = File.join("inline", File.basename(so_name))
    if require file then
      dir = Inline.directory
      warn "WAR\NING: #{dir} exists but is not being used" if test ?d, dir and $VERBOSE
      return true
    end
  rescue LoadError
  end
  return false
end
map_c_const(names_and_types) click to toggle source

Maps a C constant to ruby. names_and_types is a hash that maps the name of the constant to its C type.

builder.map_c_const :C_NAME => :int

If you wish to give the constant a different ruby name:

builder.map_c_const :C_NAME => [:int, :RUBY_NAME]
# File lib/inline.rb, line 748
def map_c_const(names_and_types)
  names_and_types.each do |name, typ|
    typ, ruby_name = Array === typ ? typ : [typ, name]
    self.add_to_init "    rb_define_const(c, #{ruby_name.to_s.inspect}, #{c2ruby(typ.to_s)}(#{name}));"
  end
end
map_ruby_const(*names) click to toggle source

Maps RubyConstants to cRubyConstants.

# File lib/inline.rb, line 731
def map_ruby_const(*names)
  names.each do |name|
    self.prefix "static VALUE c#{name};"
    self.add_to_init "    c#{name} = rb_const_get(c, rb_intern(#{name.to_s.inspect}));"
  end
end
module_name() click to toggle source
# File lib/inline.rb, line 369
def module_name
  unless defined? @module_name then
    module_name = @mod.name.gsub('::','__')
    md5 = Digest::MD5.new
    @pre.each { |m| md5 << m.to_s }
    @sig.keys.sort_by { |x| x.to_s }.each { |m| md5 << m.to_s }
    @module_name = "Inline_#{module_name}_#{md5}"
  end
  @module_name
end
parse_signature(src, raw=false) click to toggle source
# File lib/inline.rb, line 194
def parse_signature(src, raw=false)

  sig = self.strip_comments(src)
  # strip preprocessor directives
  sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
  # strip {}s
  sig.gsub!(/\{[^\}]*\}/, '{ }')
  # clean and collapse whitespace
  sig.gsub!(/\s+/, ' ')

  unless defined? @types then
    @types = 'void|' + @type_map.keys.map{|x| Regexp.escape(x)}.join('|')
  end

  if /(#{@types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
    return_type, function_name, arg_string = $1, $2, $3
    args = []
    arg_string.split(',').each do |arg|

      # helps normalize into 'char * varname' form
      arg = arg.gsub(/\s*\*\s*/, ' * ').strip

      if /(((#{@types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
        args.push([$4, $1])
      elsif arg != "void" then
        warn "WAR\NING: '#{arg}' not understood"
      end
    end

    arity = args.size
    arity = MAGIC_ARITY if raw

    return {
      'return' => return_type,
      'name'   => function_name,
      'args'   => args,
      'arity'  => arity
    }
  end

  raise SyntaxError, "Can't parse signature: #{sig}"
end
prefix(code) click to toggle source

Adds any amount of text/code to the source

# File lib/inline.rb, line 774
def prefix(code)
  @pre << code
end
reader(method, type, member = method) click to toggle source

Adds a reader for a C struct member wrapped via Data_Wrap_Struct. method is the ruby name to give the reader, type is the C type. Unless the C member name is overridden with member, the method name is used as the struct member. See accessor for an example.

# File lib/inline.rb, line 447
    def reader(method, type, member = method)
      raise "struct name not set for reader #{method} #{type}" unless
        @struct_name

      c <<-C
VALUE #{method}(void) {
  #{@struct_name} *pointer;

  Data_Get_Struct(self, #{@struct_name}, pointer);

  return #{c2ruby type}(pointer->#{member});
}
      C
    end
remove_type_converter(type) click to toggle source

Unregisters C type-casts for type.

# File lib/inline.rb, line 724
def remove_type_converter(type)
  @type_map.delete type
end
ruby2c(type) click to toggle source

Converts ruby type type to a C type

# File lib/inline.rb, line 488
def ruby2c(type)
  raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type
  @type_map[type].first
end
so_name() click to toggle source
# File lib/inline.rb, line 380
def so_name
  unless defined? @so_name then
    @so_name = "#{Inline.directory}/#{module_name}.#{RbConfig::CONFIG["DLEXT"]}"
  end
  @so_name
end
strip_comments(src) click to toggle source
# File lib/inline.rb, line 185
def strip_comments(src)
  # strip c-comments
  src = src.gsub(%r%\s*/\*.*?\*/%m, '')
  # strip cpp-comments
  src = src.gsub(%r%^\s*//.*?\n%, '')
  src = src.gsub(%r%[ \t]*//[^\n]*%, '')
  src
end
writer(method, type, member = method) click to toggle source

Adds a writer for a C struct member wrapped via Data_Get_Struct. method is the ruby name to give the writer, type is the C type. Unless the C member name is overridden with member, the method name is used as the struct member. See accessor for an example.

# File lib/inline.rb, line 468
    def writer(method, type, member = method)
      raise "struct name not set for writer #{method} #{type}" unless
        @struct_name

      c <<-C
VALUE #{method}_equals(VALUE value) {
  #{@struct_name} *pointer;

  Data_Get_Struct(self, #{@struct_name}, pointer);

  pointer->#{member} = #{ruby2c type}(value);

  return value;
}
      C
    end