Class: Inline::C

Inherits:
Object
  • Object
show all
Includes:
ZenTestMapping
Defined in:
lib/inline.rb

Overview

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.

Constant Summary collapse

MAGIC_ARITY_THRESHOLD =
15
MAGIC_ARITY =
-1
TYPE_MAP =

Default C to ruby and ruby to C type map

{
  'char'               => [ 'NUM2CHR',        'CHR2FIX'      ],

  'char *'             => [ 'StringValuePtr', 'rb_str_new2'  ],

  'double'             => [ 'NUM2DBL',        'rb_float_new' ],

  'int'                => [ "FI\X2INT",       'INT2FIX'      ],
  'unsigned int'       => [ 'NUM2UINT',       'UINT2NUM'     ],
  'unsigned'           => [ 'NUM2UINT',       'UINT2NUM'     ],

  'long'               => [ 'NUM2LONG',       'LONG2NUM'     ],
  'unsigned long'      => [ 'NUM2ULONG',      'ULONG2NUM'    ],

  'long long'          => [ 'NUM2LL',         'LL2NUM'       ],
  'unsigned long long' => [ 'NUM2ULL',        'ULL2NUM'      ],

  'off_t'              => [ 'NUM2OFFT',       'OFFT2NUM'     ],

  'VALUE'              => [ '',               ''             ],
  # Can't do these converters because they conflict with the above:
  # ID2SYM(x), SYM2ID(x), F\IX2UINT(x)
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mod) ⇒ C

Returns a new instance of C.

Raises:

  • (ArgumentError)


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/inline.rb', line 395

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
  @src = []
  @inc = []
  @sig = {}
  @flags = []
  @libs = []
  @init_extra = []
  @include_ruby_first = true
  @inherited_methods = {}
  @struct_name = nil

  @type_map = TYPE_MAP.dup
end

Instance Attribute Details

#flagsObject

Returns the value of attribute flags.



387
388
389
# File 'lib/inline.rb', line 387

def flags
  @flags
end

#init_extraObject

Returns the value of attribute init_extra.



387
388
389
# File 'lib/inline.rb', line 387

def init_extra
  @init_extra
end

#libsObject

Returns the value of attribute libs.



387
388
389
# File 'lib/inline.rb', line 387

def libs
  @libs
end

#modObject

Returns the value of attribute mod.



385
386
387
# File 'lib/inline.rb', line 385

def mod
  @mod
end

#rb_fileObject (readonly)

Returns the value of attribute rb_file.



385
386
387
# File 'lib/inline.rb', line 385

def rb_file
  @rb_file
end

#sigObject

Returns the value of attribute sig.



387
388
389
# File 'lib/inline.rb', line 387

def sig
  @sig
end

#srcObject

Returns the value of attribute src.



387
388
389
# File 'lib/inline.rb', line 387

def src
  @src
end

#struct_nameObject

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



393
394
395
# File 'lib/inline.rb', line 393

def struct_name
  @struct_name
end

Instance Method Details

#accessor(method, type, member = method) ⇒ Object

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.



433
434
435
436
# File 'lib/inline.rb', line 433

def accessor(method, type, member = method)
  reader method, type, member
  writer method, type, member
end

#add_compile_flags(*flags) ⇒ Object

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



644
645
646
# File 'lib/inline.rb', line 644

def add_compile_flags(*flags)
  @flags.push(*flags)
end

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



652
653
654
# File 'lib/inline.rb', line 652

def add_link_flags(*flags)
  @libs.push(*flags)
end

#add_static(name, init, type = "VALUE") ⇒ Object

Create a static variable and initialize it to a value.



659
660
661
662
# File 'lib/inline.rb', line 659

def add_static name, init, type = "VALUE"
  prefix      "static #{type} #{name};"
  add_to_init "#{name} = #{init};"
end

#add_to_init(*src) ⇒ Object

Adds custom content to the end of the init function.



667
668
669
# File 'lib/inline.rb', line 667

def add_to_init(*src)
  @init_extra.push(*src)
end

#add_type_converter(type, r2c, c2r) ⇒ Object

Registers C type-casts r2c and c2r for type.



674
675
676
677
# File 'lib/inline.rb', line 674

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) ⇒ Object

Registers C type alias_type as an alias of existing_type



682
683
684
685
686
687
# File 'lib/inline.rb', line 682

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

#buildObject

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



524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/inline.rb', line 524

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 = "#{src_name}.old"
    should_compare = File.write_with_backup(src_name) do |io|
      io.puts generate_ext
    end

    # recompile only if the files are different
    recompile = true
    if so_exists and should_compare 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

      hdrdir = %w(srcdir 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  = @libs.join(' ')

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

      cmd = [ RbConfig::CONFIG['LDSHARED'],
              flags,
              RbConfig::CONFIG['DLDFLAGS'],
              RbConfig::CONFIG['CCDLFLAGS'],
              RbConfig::CONFIG['CFLAGS'],
              '-I', hdrdir,
              config_hdrdir,
              '-I', RbConfig::CONFIG['includedir'],
              "-L#{RbConfig::CONFIG['libdir']}",
              '-o', so_name.inspect,
              File.expand_path(src_name).inspect,
              libs,
              crap_for_windoze ].join(' ')

      # TODO: remove after osx 10.5.2
      cmd += ' -flat_namespace -undefined suppress' if
        RUBY_PLATFORM =~ /darwin9\.[01]/
      cmd += " 2> #{DEV_NULL}" if $TESTING and not $DEBUG

      warn "Building #{so_name} with '#{cmd}'" if $DEBUG
      result = `#{cmd}`
      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 = {}) ⇒ Object

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.



752
753
754
755
756
757
# File 'lib/inline.rb', line 752

def c src, options = {}
  options = {
    :expand_types => true,
  }.merge options
  self.generate src, options
end

#c2ruby(type) ⇒ Object

Converts C type type to a ruby type

Raises:

  • (ArgumentError)


493
494
495
496
# File 'lib/inline.rb', line 493

def c2ruby(type)
  raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type
  @type_map[type].last
end

#c_raw(src, options = {}) ⇒ Object

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.



776
777
778
# File 'lib/inline.rb', line 776

def c_raw src, options = {}
  self.generate src, options
end

#c_raw_singleton(src, options = {}) ⇒ Object

Same as c_raw, but adds a class function.



783
784
785
786
787
788
# File 'lib/inline.rb', line 783

def c_raw_singleton src, options = {}
  options = {
    :singleton => true,
  }.merge options
  self.generate src, options
end

#c_singleton(src, options = {}) ⇒ Object

Same as c, but adds a class function.



762
763
764
765
766
767
768
# File 'lib/inline.rb', line 762

def c_singleton src, options = {}
  options = {
    :expand_types => true,
    :singleton    => true,
  }.merge options
  self.generate src, options
end

#crap_for_windozeObject

Returns extra compilation flags for windoze platforms. Ugh.



625
626
627
628
629
630
631
632
633
634
635
636
637
# File 'lib/inline.rb', line 625

def crap_for_windoze
  # gawd windoze land sucks
  case RUBY_PLATFORM
  when /mswin32/ then
    " -link /LIBPATH:\"#{RbConfig::CONFIG['libdir']}\" /DEFAULTLIB:\"#{RbConfig::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
  when /mingw32/ then
    " -Wl,--enable-auto-import -L#{RbConfig::CONFIG['libdir']} -lmsvcrt-ruby18"
  when /i386-cygwin/ then
    ' -L/usr/local/lib -lruby.dll'
  else
    ''
  end
end

#generate(src, options = {}) ⇒ Object

def parse_signature

Raises:

  • (ArgumentError)


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/inline.rb', line 241

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 = caller[1].split(/:/)
  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_extObject

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



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/inline.rb', line 310

def generate_ext
  ext = []

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

  ext << @inc
  ext << nil
  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}() {"
  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) ⇒ Object

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



727
728
729
# File 'lib/inline.rb', line 727

def include(header)
  @inc << "#include #{header}"
end

#include_ruby_lastObject

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



735
736
737
# File 'lib/inline.rb', line 735

def include_ruby_last
  @include_ruby_first = false
end

#loadObject

Loads the generated code back into ruby



517
518
519
# File 'lib/inline.rb', line 517

def load
  require "#{so_name}" or raise LoadError, "require on #{so_name} failed"
end

#load_cacheObject

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



501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/inline.rb', line 501

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) ⇒ Object

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]


716
717
718
719
720
721
# File 'lib/inline.rb', line 716

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) ⇒ Object

Maps a ruby constant to C (with the same name)



699
700
701
702
703
704
# File 'lib/inline.rb', line 699

def map_ruby_const(*names)
  names.each do |name|
    self.prefix "static VALUE #{name};"
    self.add_to_init "    #{name} = rb_const_get(c, rb_intern(#{name.to_s.inspect}));"
  end
end

#module_nameObject



368
369
370
371
372
373
374
375
376
# File 'lib/inline.rb', line 368

def module_name
  unless defined? @module_name then
    module_name = @mod.name.gsub('::','__')
    md5 = Digest::MD5.new
    @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) ⇒ Object

Raises:

  • (SyntaxError)


198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/inline.rb', line 198

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) ⇒ Object

Adds any amount of text/code to the source



742
743
744
# File 'lib/inline.rb', line 742

def prefix(code)
  @src << code
end

#reader(method, type, member = method) ⇒ Object

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.



444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/inline.rb', line 444

def reader(method, type, member = method)
  raise "struct name not set for reader #{method} #{type}" unless
    @struct_name

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

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

  return #{c2ruby type}(pointer->#{member});
}
  C
end

#remove_type_converter(type) ⇒ Object

Unregisters C type-casts for type.



692
693
694
# File 'lib/inline.rb', line 692

def remove_type_converter(type)
  @type_map.delete type
end

#ruby2c(type) ⇒ Object

Converts ruby type type to a C type

Raises:

  • (ArgumentError)


485
486
487
488
# File 'lib/inline.rb', line 485

def ruby2c(type)
  raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type
  @type_map[type].first
end

#so_nameObject



378
379
380
381
382
383
# File 'lib/inline.rb', line 378

def so_name
  unless defined? @so_name then
    @so_name = "#{Inline.directory}/#{module_name}.#{RbConfig::CONFIG["DLEXT"]}"
  end
  @so_name
end

#strip_comments(src) ⇒ Object



189
190
191
192
193
194
195
196
# File 'lib/inline.rb', line 189

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) ⇒ Object

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.



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/inline.rb', line 465

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