LoginSignup
2
1

More than 5 years have passed since last update.

mrubyとNimを連携させてみる

Last updated at Posted at 2016-10-05

何らかの理由で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側

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側

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

ソースコード

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

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のライブラリを利用することもできます。

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だと用途は違うので、あまり意味はないですが。。。)

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)

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
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1