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)


390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/inline.rb', line 390

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.



382
383
384
# File 'lib/inline.rb', line 382

def flags
  @flags
end

#init_extraObject

Returns the value of attribute init_extra.



382
383
384
# File 'lib/inline.rb', line 382

def init_extra
  @init_extra
end

#libsObject

Returns the value of attribute libs.



382
383
384
# File 'lib/inline.rb', line 382

def libs
  @libs
end

#modObject

Returns the value of attribute mod.



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

def mod
  @mod
end

#rb_fileObject (readonly)

Returns the value of attribute rb_file.



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

def rb_file
  @rb_file
end

#sigObject

Returns the value of attribute sig.



382
383
384
# File 'lib/inline.rb', line 382

def sig
  @sig
end

#srcObject

Returns the value of attribute src.



382
383
384
# File 'lib/inline.rb', line 382

def src
  @src
end

#struct_nameObject

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



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

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.



428
429
430
431
# File 'lib/inline.rb', line 428

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.



639
640
641
# File 'lib/inline.rb', line 639

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.



647
648
649
# File 'lib/inline.rb', line 647

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.



654
655
656
657
# File 'lib/inline.rb', line 654

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.



662
663
664
# File 'lib/inline.rb', line 662

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.



669
670
671
672
# File 'lib/inline.rb', line 669

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



677
678
679
680
681
682
# File 'lib/inline.rb', line 677

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.



519
520
521
522
523
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
# File 'lib/inline.rb', line 519

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
      Dir.mkdir Inline.directory, 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|
        Config::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 = [ Config::CONFIG['LDSHARED'],
              flags,
              Config::CONFIG['DLDFLAGS'],
              Config::CONFIG['CCDLFLAGS'],
              Config::CONFIG['CFLAGS'],
              '-I', hdrdir,
              config_hdrdir,
              '-I', Config::CONFIG['includedir'],
              "-L#{Config::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 Config::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.



747
748
749
750
751
752
# File 'lib/inline.rb', line 747

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)


488
489
490
491
# File 'lib/inline.rb', line 488

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.



771
772
773
# File 'lib/inline.rb', line 771

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.



778
779
780
781
782
783
# File 'lib/inline.rb', line 778

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.



757
758
759
760
761
762
763
# File 'lib/inline.rb', line 757

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.



620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/inline.rb', line 620

def crap_for_windoze
  # gawd windoze land sucks
  case RUBY_PLATFORM
  when /mswin32/ then
    " -link /LIBPATH:\"#{Config::CONFIG['libdir']}\" /DEFAULTLIB:\"#{Config::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
  when /mingw32/ then
    " -Wl,--enable-auto-import -L#{Config::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)


238
239
240
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
# File 'lib/inline.rb', line 238

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
            warn "WAR\NING: Can't find signature in #{result.inspect}\n" 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.



305
306
307
308
309
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
# File 'lib/inline.rb', line 305

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.



722
723
724
# File 'lib/inline.rb', line 722

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.



730
731
732
# File 'lib/inline.rb', line 730

def include_ruby_last
  @include_ruby_first = false
end

#loadObject

Loads the generated code back into ruby



512
513
514
# File 'lib/inline.rb', line 512

def load
  Kernel.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.



496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/inline.rb', line 496

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]


711
712
713
714
715
716
# File 'lib/inline.rb', line 711

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)



694
695
696
697
698
699
# File 'lib/inline.rb', line 694

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



363
364
365
366
367
368
369
370
371
# File 'lib/inline.rb', line 363

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.to_s[0,4]}"
  end
  @module_name
end

#parse_signature(src, raw = false) ⇒ Object

Raises:

  • (SyntaxError)


195
196
197
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
# File 'lib/inline.rb', line 195

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



737
738
739
# File 'lib/inline.rb', line 737

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.



439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/inline.rb', line 439

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.



687
688
689
# File 'lib/inline.rb', line 687

def remove_type_converter(type)
  @type_map.delete type
end

#ruby2c(type) ⇒ Object

Converts ruby type type to a C type

Raises:

  • (ArgumentError)


480
481
482
483
# File 'lib/inline.rb', line 480

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

#so_nameObject



373
374
375
376
377
378
# File 'lib/inline.rb', line 373

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

#strip_comments(src) ⇒ Object



186
187
188
189
190
191
192
193
# File 'lib/inline.rb', line 186

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.



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/inline.rb', line 460

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