あ伊藤です.

In article <199812141327.WAA13108 / picachu.netlab.co.jp>,
	Yukihiro Matsumoto <ruby-ext / netlab.co.jp> writes:

> In message "[ruby-ext:00033] module 生成支援スクリプト"
>     on 98/12/14, Noritsugu Nakamura <nnakamur / mxq.mesh.ne.jp> writes:
> 
> |関数リストを作成しておくと、それから、
> |拡張モジュールを作成するのを手助けしてくれるような
> |スクリプトってありますでしょうか?
> 
> 例の仕事の一貫でそういうの作ろうとしてます.
> が,だれか作ってくれるなら喜んで支援します.

たわむれに,簡単なのを書いてみました.

begin_ccode
#include <math.h>
end_ccode

% this is comment line

% function  return type argument type
%--------------------------------------------------------
module ExtMath
  lgamma        double  double
  asin          double  double
  acos          double  double
  atan          double  double
  sinh          double  double
  cosh          double  double
  tanh          double  double
  jn            double  int double
end

% global
  atoi          char*   int
  atof          char*   double

module Random
  drand48       double
  lrand48       long
  srand48       void    long
end
  
という感じのファイル extension_name.tbl から,extension_name.c を
生成します.SWIG コンパチの入力形式にしてもよかったのですが,
面倒なので簡単な形になってます.グローバル変数はサポートしていません.
引数と返値に使える型は int, long, double, char* のみです.

クラスとメソッドも作れるように書こうと思ったのですが,そうすると 
new をtblファイル内でどう記述するかとか,struct と T_DATA の関係を
どう書くかとか悩ましい問題が発生しますね.C レベルの関数を Ruby から
使うのが目的なら,グローバル関数とモジュール関数だけでもいいのかなあ.

--
aito

-------------------------------------------- tbl2ext.rb
#!/usr/local/bin/ruby
#

def usage
  STDERR.print "usage: $0 file.tbl\n"
  exit 1
end

usage if ARGV.size == 0

i_name = ARGV.shift
usage unless i_name =~ /\.tbl$/
ext_module_name = i_name.sub(/\.tbl/,'')
o_name = ext_module_name+".c"
i_f = open(i_name,"r")
o_f = open(o_name,"w")

o_f.print "/* generated from #{i_name} using #$0 */
#include <ruby.h>

VALUE float_new(double);
"

ccode_mode = false
current_class = nil
current_module = nil

functions = {'global'=>[]}
classes = {}
modules = {}

while i_f.gets
  if ccode_mode then
    if $_ =~ / *end_ccode *$/ then
      ccode_mode = false
    else
      o_f.print $_
    end
    next
  end
  next if $_ =~ /^ *%/   # comment
  next if $_ =~ /^ *$/   # blank line
  if $_ =~ /^ *begin_ccode *$/ then
    ccode_mode = true
    next
  end
  arg = chop.split
  arg1 = arg.shift
  case arg1 
  when 'module'
    if current_class != nil || current_module != nil then
      fail "nested module/class definition at #$."
    end
    current_module = arg.shift
    o_f.print "static VALUE m#{current_module};\n";
    unless functions.key?(current_module)
      functions[current_module] = []
    end
    unless modules.key?(current_module)
      modules[current_module] = 1
    end
  when 'class'
    if current_class != nil || current_module != nil then
      fail "nested module/class definition at #$."
    end
    current_class = arg.shift
    o_f.print "static VALUE c#{current_module}\n";
    unless functions.key?(current_class)
      functions[current_class] = []
    end
    unless classes.key?(current_class)
      classes[current_class] = arg
    end
  when 'end'
    current_class = current_module = nil
  else
    if arg.size == 0 then
      fail "too few argument at #$."
    end
    arg.unshift(arg1)
    if current_class.nil? and current_module.nil? then
      functions['global'].push(arg)
    elsif current_class then
      functions[current_class].push(arg)
    else
      functions[current_module].push(arg)
    end
  end
end

# define ruby function
for mod,funcs in functions
  for f in funcs
    name,ret_type,*arg_type = f
    c_name = "rb_ext_#{mod}_#{name}"
    n_arg = arg_type.size
    o_f.print "VALUE #{c_name}("
    c_args = ["VALUE self"]
    for i in 0..n_arg-1
      c_args.push("VALUE arg#{i}")
    end
    o_f.print c_args.join(','),")\n{\n"
    if ret_type != 'void' then
      o_f.print "  #{ret_type} ret_val = \n"
    end
    # call
    o_f.print "  #{name}("
    c_args = []
    for i in 0..n_arg-1
      case arg_type[i]
      when 'int','long'
        c_args.push("FIX2INT(arg#{i})")
      when 'double'
        c_args.push("NUM2DBL(arg#{i})")
      when 'char*'
        c_args.push("STR2CSTR(arg#{i})")
      else
        fail "Can't handle C type #{arg_type[i]}"
      end
    end
    o_f.print c_args.join(','),");\n"
    case ret_type
    when 'int'
      o_f.print "  return INT2FIX(ret_val);\n"
    when 'long'
      o_f.print "  return INT2NUM(ret_val);\n"
    when 'double'
      o_f.print "  return float_new(ret_val);\n"
    when 'char*'
      o_f.print "  return str_new2(ret_val);\n"
    else # void
      o_f.print "  return Qnil;\n"
    end
    o_f.print "}\n"
  end
end

# init routine
o_f.print "Init_#{ext_module_name}(){\n"
for mod,funcs in functions
  current = :global
  if classes.key?(mod) then
    s = classes[mod]
    if s.size > 0 then
      sup_class = s[0]
    else
      sup_class = 'cObject'
    end
    o_f.print "  c#{mod} = rb_define_class(\"#{mod}\",#{sup_class});\n"
    current = :class
  elsif modules.key?(mod) then
    o_f.print "  m#{mod} = rb_define_module(\"#{mod}\");\n"
    current = :module
  else
    current = :global
  end

  for f in funcs
    name,ret_type,*arg_type = f
    c_name = "rb_ext_#{mod}_#{name}"
    case current
    when :global
      o_f.print "  rb_define_global_function("
    when :class
      o_f.print "  rb_define_method(c#{mod},"
    when :module
      o_f.print "  rb_define_module_function(m#{mod},"
    end
    o_f.print "\"#{name}\",#{c_name},#{arg_type.size});\n"
  end
end
o_f.print "}\n"