mrubyとNimを連携させてみる

何らかの理由でC言語を書けない状況で、mrubyを使いたい場合があると思います。
そんな時のためにNimを使ってmrubyを組み込んでみました

環境
1. OSX Yosemite
2. Nim 0.15
3. mruby
4. ruby

# Nimとmrubyの設定

## 準備

mrubyをgithubからcloneしましょう。
以下、コンソール。

```
$ mkdir nim-mruby && cd nim-mruby

$ git clone --depth 1 https://github.com/mruby/mruby.git

$ mkdir -p nimruby/tools/nimruby

$ mkdir -p nimruby/mrblib

$ touch nimruby/mrblib/nimruby.rb nimruby/tools/nimruby/nimruby.nim nimruby/mrbgem.rake
```

こんな感じ

```
mruby/ <- githubから落としてきたmruby
nimruby/
  mrbgem.rake
  mrblib/
    nimruby.rb
  tools/
    nimruby/
      nimruby.nim
```
## プログラムのビルドの設定

### mruby側

```ruby:mruby/build_config.rb
MRuby::Build.new do |conf|
  toolchain :gcc

  conf.gem '../nimruby' do |nim|
  	# nimbase.hなどが入っているところ
    nim.cc.include_paths << "/<nimのパス>/lib"
    nim.cc.option_include_path = '-I%s'
  end
  
  conf.gembox 'default'
end
```

### nim側

```ruby:nimruby/mrbgem.rake
MRuby::Gem::Specification.new('nimruby') do |spec|
  spec.license = 'MIT'
  spec.author = 'hogehoge'
  spec.bins = %w(nimruby)

  nimcache = File.join(File.dirname(__FILE__), %w(tools nimruby))
  nim = File.join(nimcache, 'nimruby.nim')

  # .nimから.cのファイルを生成するだけにする（プログラムは作らない）
  `nim c --nimcache:#{nimcache} --compileOnly #{nim}`
end
```

## ソースコード

フィボナッチ数を生成するプログラム。

```nim:nimruby/tools/nimruby/nimruby.nim
{.emit: """
  #include "mruby.h"
""".}

type
  mrb_int = cint
  mrb_aspec = uint
  mrb_value {.importc, header: "mruby.h"} = object
  mrb_state {.importc, header: "mruby.h"} = object
    object_class: ref RClass
  RClass {.importc, header: "mruby.h"} = object
  mrb_func_t {.importc, header: "mruby.h"} = 
    proc (mrb: ref mrb_state; val:mrb_value): mrb_value {.cdecl.}

proc mrb_get_args(mrb: ref mrb_state; format: cstring): cint 
  {.importc, varargs, header: "mruby.h"}
proc mrb_fixnum_value(v: cint): mrb_value 
  {.importc, header: "mruby.h"}
proc mrb_fixnum(v: mrb_value): cint 
  {.importc, header: "mruby.h"}

proc mrb_open(): ref mrb_state 
  {.importc, header: "mruby.h"}
proc mrb_close(mrb: ref mrb_state) 
  {.importc, header: "mruby.h"}

proc MRB_ARGS_REQ(n: cint): mrb_aspec 
  {.importc: "MRB_ARGS_REQ", header: "mruby.h"}
proc mrb_define_method(mrb: ref mrb_state; c: ref RClass; name: cstring;
  f: mrb_func_t; aspec: mrb_aspec) 
    {.importc, header: "mruby.h"}

proc mrb_funcall(mrb: ref mrb_state; self: mrb_value; 
  name: cstring; argc: cint): mrb_value {.importc, varargs, header: "mruby.h"}
proc mrb_top_self(mrb: ref mrb_state): mrb_value 
  {.importc, header: "mruby.h"}

proc fib(a: cint): cint =
  if a <= 2:
    result = 1
  else:
    result = fib(a-1) + fib(a-2)

proc nim_fib(mrb: ref mrb_state, self: mrb_value): mrb_value {.exportc, cdecl.} =
  var x: cint
  let _ = mrb_get_args(mrb, "i", addr(x))
  mrb_fixnum_value(fib(x))

proc nimruby(n: cint): cint {.exportc.} =
  var mrb = mrb_open()
  mrb_define_method(mrb, mrb.object_class, "nim_fib", nim_fib, MRB_ARGS_REQ(1))
  let r = mrb_funcall(mrb, mrb_top_self(mrb), "__main__", 1, mrb_fixnum_value(n));
  result = mrb_fixnum(r)
  mrb_close(mrb)

echo nimruby(10)
```

nimbleが使えるようであれば、Nimのライブラリを利用することもできます。

```ruby:nimruby/mrblib/nimruby.rb
def __main__(n)
  nim_fib(n)
end
```

## ビルドと実行

.cがあれば、コンパイル+ビルドを設定に従ってやってくれます。

```
$ cd mruby

$ ruby minirake

$ bin/nimruby
55
```

# 感想

Nimは.emitでC言語・C++をかけたり、マクロの機能や.c,.cpp,.jsを生成できたり、とても面白い言語だと思います。
Nimはまだ、0.15というバージョンですが個人的に注目していきたい言語です。

# おまけ

軽くベンチマークしてみました。
（まあ、mrubyとnimだと用途は違うので、あまり意味はないですが。。。）

```nim:nimruby/tools/nimruby/nimruby.nim
（中略）

import strutils
import times
import os
{.experimental.}
import threadpool

proc fib2x(a: cint): cint =
  if a <= 2:
    result = 1
  else:
    var ch = newSeq[cint](2)
    parallel:
      if 1 <= len(ch) - 1: # bounds check
        ch[0] = spawn fib(a-1)
        ch[1] = spawn fib(a-2)

    result = ch[0] + ch[1]

template bm(title: string, code: typed) =
  let s = epochTime()
  echo code
  let e = epochTime() - s
  echo title, " ", e.formatFloat(format = ffDecimal, precision = 10), "s"

proc nimruby(n: cint) =
  var mrb = mrb_open()

  bm "mruby版 fib":
    let r = mrb_funcall(mrb, mrb_top_self(mrb), "fib", 1, mrb_fixnum_value(n));
    mrb_fixnum(r)

  mrb_close(mrb)
     
  bm "nim版 fib":
    fib(n)

  bm "nim版 fib2x":
    fib2x(n)

let n = parse_int(os.paramStr(1))
nimruby(n.cint)

```

```ruby:nimruby/mrblib/nimruby.rb
def fib(n)
  return 1 if n <= 2
  fib(n-1) + fib(n-2)
end
```

```
$ ruby minirake && bin/nimruby 35

9227465                                                                                                                                                                                                                                       
mruby版 fib 3.4108259678s                                                                                                                                                                                                                     
9227465                                                                                                                                                                                                                                       
nim版 fib 0.0541160107s                                                                                                                                                                                                                       
9227465                                                                                                                                                                                                                                       
nim版 fib2x 0.0345828533s
```