14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

mrubyAdvent Calendar 2013

Day 21

mruby-ioのつくりかた

Last updated at Posted at 2013-12-21

mruby-io のつくりかた

本稿では、C言語とRubyの両方を使ってmrbgemを実装した例として、 iij/mruby-io の実装を紹介します。

12/17の mruby で C 言語の構造体をラップしたオブジェクトを作る正しい方法 の中で、 FILE構造をmrubyで扱う上での注意すべき点が説明されているので、合わせてお読みください。

before iij/mruby-io

iij/mruby-io の実装に着手したのは 2013年3月頃です。
それまでは、CRubyのIO実装をmrubyへ移植したものを利用していました (当時はmrbgemの機構が無かったので、 src/ext などのディレクトリに .c ファイルを配置していました)。

当時の面影が iij/mrubys1ブランチ に残っています。
IO や File のコード量をまとめてみると以下のようになりました。

Filename lines sloc
src/ext/io/file.c 338 284
src/ext/io/io.c 1896 1635
mrblib/ext/file.rb 63 56
mrblib/ext/io.rb 104 85
(.c -- total) 2234 1919
(.rb -- total) 167 141

コードの大部分をC言語で記述していることが見て取れます。
また、CRubyのIO実装を移植しているため、各種環境への移植のための多数の ifdef が混じったコードとなっていたり、mrubyの想定利用場面では実装されていなくとも不自由しない機能までコードとして存在するために見通しが悪い、といった課題がある状況でした。

Note: io.c などを見て頂ければ分かりますが、CRubyの実装では FILE 構造体の中にアクセスする必要があるため、OSやライブラリ、コンパイラに合わせた拡張が必要になっています。
mruby本体は C99 の範囲内で移植性を維持することを目指しています。その拡張ライブラリであるmruby-io も、可能な範囲で移植性を保つべきでしょう。

iij/mruby-io の実装コンセプト

mrubyのIO実装を行った時期には、mrubyのモジュール機構であるmrbgemは未だ登場していませんでした。
そして、CRuby実装を参考にmrubyのIOを作ったために、本家mrubyでは無効化されている MRB_TT_FILE をデータ構造として使用していました。

その後、mrbgemの仕組みが作られ、IO実装のモジュール化を実現するために、iij/mruby-ioの実装に着手しました (2013年3月頃)。
iij/mruby-ioは、以下のようなコンセプトで実装を行いました。

  • データ構造として MRB_TT_FILE の使用をやめて、 MRB_TT_DATA を使う
  • libc依存を前提とする (Unix, BSD系の環境で動作すること)
  • FILE構造体の内部状態を利用しないコードにする
  • メソッドの記述は、出来る限りRubyで記述する

この方針で再実装した iij/mruby-io のコード量は以下のようになりました。

Filename lines sloc
src/file.c 318 277
src/file_test.c 324 262
src/io.c 767 655
mrblib/file.rb 166 141
mrblib/file_constants.rb 32 26
mrblib/io.rb 312 260
mrblib/kernel.rb 16 13
(.c -- total) 1409 1194
(.rb -- total) 526 440

slocベースではC実装の行数が半分近くに減り、Ruby行数が3倍に増えています。
実装されているメソッドの数は、厳密に数えていませんが1.5倍ほどに増えていることと合わせても、Rubyの記述性の高さを再認識することになりました。

iij/mruby-io の実装

ここでは、mruby-ioの実装ポリシーを踏まえて、どのように実装を進めていったのか、簡単に紹介したいと思います。

Output編

Ruby 1.9.3 リファレンスマニュアル には、syswriteを除く全ての出力メソッドは、最終的にwriteが呼び出されるとの記述があります。

このことから、 writeとsyswriteを用意しておけば、IOの他の出力メソッドをRubyで実装することが出来そう です。

mruby-ioでは、 write メソッドは syswrite を使ってRubyで実装しているので (mrblib/io.rb#L44-L55)、実質的には syswrite が最もプリミティブな出力メソッドということになります。
現在のmruby-ioでは、write bufferを実装していませんが、必要になった場合には write メソッドの実装を変更することで、バッファリング処理をRubyで記述することが出来るでしょう。

class IO
  def write(string)
    str = string.is_a?(String) ? string : string.to_s
    return str.size unless str.size > 0

    len = syswrite(str)
    if str.size == len
      @pos += len
      return len
    end

    raise IOError
  end
end

Input編

IOから読み込むメソッドについては、書き込むメソッドと比べて若干複雑です。
以下に、IOの読み込みメソッドの特徴を整理しました。

メソッド 動作 EOFに達していた時
IO#gets 区切り文字が出現するまで読み込む nil
IO#readline 区切り文字が出現するまで読み込む EOFError
IO#getc 1文字読み込む nil
IO#readchar 1文字読み込む EOFError
IO#read(length = nil) EOFに達するまでファイルを読み込む "" (空文字列を返す)
IO#read(length = size) sizeに達するまで待つ(ブロックする) nil

ここで、getsやreadlineのような、 区切り文字まで読むメソッドを実装する方法を考える必要があります。
単純には以下の2つのアプローチがあるかと思いますが、mruby-ioでは後者のアプローチを選択しました。

  1. 1バイトずつファイルを読み、区切り文字が出現した時点までに読んだ文字列を返す
  2. 一定の長さずつファイルを読み、その中に区切り文字が含まれていれば、その区切り文字までの部分文字列を返す
    • 残りの部分文字列は、次回のファイル読み込みメソッドの実行時に処理される

この、 一定の長さずつファイルを読み込む部分は、IO#_read_bufとして実装しています。

class IO
  def _read_buf
    return @buf if @buf && @buf.size > 0
    @buf = sysread(BUF_SIZE)
  end
end

@buf は IOクラスのインスタンス変数で、read bufferの実体となります。
mruby-ioでは、@bufの状態とIO#sysreadの実行を組み合わせることで、CRubyのIOに準拠した機能を実現しています。

まとめにならないまとめ

本稿では、mruby-ioの実装について紹介しました。
mrbgemの実装を行う際に、Cとrubyを組み合わせて機能を実現する歳の参考になれば幸いです。

14
12
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
14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?