mrruby-sqlite

FreeBSD upgrade

root@fb:~ # freebsd-update upgrade -r 13.1-RELEASE
 :
The following components of FreeBSD seem to be installed:
kernel/generic kernel/generic-dbg src/src world/base

The following components of FreeBSD do not seem to be installed:
world/base-dbg

Does this look reasonable (y/n)? y
Fetching metadata signature for 13.1-RELEASE from update2.freebsd.org... done.
 :
marge some file by hand
root@fb:~ # freebsd-update install
Kernel updates have been installed.  Please reboot and run
"/usr/sbin/freebsd-update install" again to finish installing updates.
shutdown -r now
root@fb:~ # freebsd-update install
Installing updates...Scanning //usr/share/certs/blacklisted for certificates...
Scanning //usr/share/certs/trusted for certificates...
Scanning //usr/local/share/certs for certificates...
 done.
root@fb:~ # freebsd-update install
No updates are available to install.
Run '/usr/sbin/freebsd-update fetch' first.
shutdown -r now

コンパイルの準備

前回Rakefileをみた時、いきなりモジュールMRubyが使われていてびっくりした。どんなタイミングでbuild.rb等のMRuby構成要素を読込みしてるのだろう?

こういう時は、トレースするに限るな。

sakae@deb:/tmp/mruby$ strace -o SLOG rakae

まずはRakefileがオープンされて、それからbuild.rbがオープンされてる。

sakae@deb:/tmp/mruby$ egrep '(Rakefile|build.rb)' SLOG | grep -v 'No such' | grep open
openat(AT_FDCWD, "/tmp/mruby/Rakefile", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5
openat(AT_FDCWD, "/tmp/mruby/Rakefile", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5
openat(AT_FDCWD, "/tmp/mruby/lib/mruby/build.rb", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5
openat(AT_FDCWD, "/tmp/mruby/lib/mruby/build.rb", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5

他の構成要素はどうだ? なるべくユニークなファイル名を指定してあげる。

sakae@deb:/tmp/mruby$ egrep '(presym.rb|build.rb)' SLOG | grep -v 'No such' | grep open
openat(AT_FDCWD, "/tmp/mruby/lib/mruby/build.rb", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5
openat(AT_FDCWD, "/tmp/mruby/lib/mruby/build.rb", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5
openat(AT_FDCWD, "/tmp/mruby/lib/mruby/presym.rb", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5
openat(AT_FDCWD, "/tmp/mruby/lib/mruby/presym.rb", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC) = 5

やっぱり、大本のRakefileを見るのが基本。

MRUBY_ROOT = File.dirname(File.expand_path(__FILE__))

$LOAD_PATH << File.join(MRUBY_ROOT, "lib")

# load build systems
require "mruby/core_ext"
require "mruby/build"

冒頭はこんな風に始まっている。そしてオイラーは冒頭で躓いた。

sakae@deb:/tmp/mruby$ irb
irb(main):001:0> File.dirname(File.expand_path(__FILE__))
=> "/tmp/mruby"
sakae@deb:/tmp/mruby$ cd src/
sakae@deb:/tmp/mruby/src$ irb
irb(main):001:0> File.dirname(File.expand_path(__FILE__))
=> "/tmp/mruby/src"

なる程、pwdのruby表現なのね。で、得られたdirにlibを結合して、それををロードパスに追加する。後は、requireしてるのね。なる程。読込みがまだまだ、浅いな。

racc

mrubyのコンパイルログをみていると、rubyのコードをコンパイルする装置を用意してる。どうやら、これがそうみたい。

vbox$ ls /tmp/mruby/mrbgems/mruby-compiler
core/        mrbgem.rake
p/mruby/mrbgems/mruby-compiler/core/                                          <
codegen.c  keywords   lex.def    node.h     parse.y    y.tab.c

binsonとかで、ごにょごにょするやつ。難しいみたいなので、rubyの同等品を当ってみる。

Racc で遊ぼう なにか、昔流行した256倍シリーズの本を彷彿させるな。 ちょいと遊んでみたよ。こういう道草も楽しいな。

mruby-sqlite

mrubyのHPにmruby用のgemの集積が案内されてる。つらつら見ていたら、面白そうなものを発見した。 mruby-sqlite

これはもう試してみるしか。buildの案内はこうなってた。

Download or clone MRuby
Add the mruby-sqlite gem declaration to MRuby's build_config.rb file.
    Use conf.gem :github => 'jbreeden/mruby-sqlite' to have MRuby checkout 
    the lasest version automatically before building.
    Use conf.gem 'PATH/TO/mruby-sqlite' if you've already downloaded this 
    repo.
Run rake in MRuby's root directory.

build_config.rb に設定をしろって亊だけど、これは古い。現代のmrubyでは、こちらに設定を行う。あらかじめ、塊をDLしておいた。

[sakae@fb /tmp/mruby]$ cat build_config/default.rb
  :
  # include the GEM box
  conf.gembox 'default'
  conf.gem '/tmp/mruby/mruby-sqlite'

そして、mrubyを最初からコンパイルする。後で設定を付け足しでもダメ。一気に組込むのがmrubyの流儀。rake clean; rake てやるのが正解。

[sakae@fb /tmp/mruby]$ rake
  :
LD    build/host/bin/mirb
ld: error: undefined symbol: pthread_create
>>> referenced by sqlite3.c:22701 (mruby-sqlite/src/sqlite3.c:22701)
>>>               sqlite3.o:(sqlite3ThreadCreate) in archive /tmp/mruby/build/host/lib/libmruby.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
rake aborted!
Command failed with status (1): [clang  -o "/tmp/mruby/build/host/bin/mirb"...]
/tmp/mruby/lib/mruby/build/command.rb:37:in `_run'
/tmp/mruby/lib/mruby/build/command.rb:217:in `run'
/tmp/mruby/tasks/bin.rake:17:in `block (4 levels) in <top (required)>'
/tmp/mruby/Rakefile:42:in `block in <top (required)>'
Tasks: TOP => build => /tmp/mruby/bin/mirb => /tmp/mruby/build/host/bin/mirb
(See full trace by running task with --trace)

んが、エラーだよ。追加したsqliteで名前解決出来無いとな。でも、よくみるとスレッドが作る方法が不明だよのエラーだ。

野生の勘を働かせて、 mruby-thread も一緒にコンパイル。これで、上手くいった。本当に使えるか実験。

sql.rb 2022-04-17(App by webrick) からコピペ。但し、require 'sqlite3' は不要。mrubyでは、既に組込まれているから。

[sakae@fb /tmp/mruby]$ cat sql.rb
def search(sw)
  ew = sw + "%"
  db = SQLite3::Database.new("/usr/share/dict/ejdict.sqlite3")
  stmt = db.prepare("SELECT word,mean FROM items WHERE word like ? limit 3")
  stmt.bind_param(1, ew)
  stmt.execute().each do |res|
    puts("<dt>#{res[0]}</dt>\n")
    puts("<dd>#{res[1]}</dd>\n")
  end
  stmt.close(); db.close()
end

puts search("ruby")
[sakae@fb /tmp/mruby]$ bin/mruby sql.rb
<dt>ruby</dt>
<dd>〈C〉紅玉,ルビー / 〈U〉ルビー色,深紅色(しんこうしょく) / ルビー色の,深紅の,
  本発のプログラミング言語</dd>
true

やったね!!

mirbでも当然requireは使えないし、頼みの綱のloadも無い。上の場合だと、関数をコピペで入力してあげよう。

尚、多数のgemが公開されてるけど、みんな5年以上前のものばかりだ。ひょっとしたら現代のmrubyで動かないものもあるかも知れない。

OpenBSDでも、同じ亊をやってみた。ええ、ちょっと確認するのを忘れていたので。

vbox$ ldd bin/mruby
bin/mruby:
        Start    End      Type  Open Ref GrpRef Name
        163ca000 363da000 exe   2    0   0      bin/mruby
        05ef5000 25ef7000 rlib  0    1   0      /usr/lib/libm.so.10.1
        0dfe1000 2dfe3000 rlib  0    1   0      /usr/lib/libpthread.so.26.1
        05bc4000 25bd5000 rlib  0    1   0      /usr/lib/libc.so.96.1
        0a6d2000 0a6d2000 ld.so 0    1   0      /usr/libexec/ld.so

sqlite3は内部に格納してるけど、pthreadはシステムのそれを借用してるのね。まあ、sqlite3は組み込みを目指しているから、組み込んでしまえって亊なんでしょうね。

それから、sql.rbは、関数なんだけど、括弧が省けるのか? これも疑問なんで確認。

vbox$ bin/mirb
mirb - Embeddable Interactive Ruby Shell

> def search(sw)
 *   ew = sw + "%"
 *   db = SQLite3::Database.new("/usr/share/dict/ejdict.sqlite3")
     :
 *   stmt.close(); db.close()
 * end
 => :search
> search 'ruby'
<dt>ruby</dt>
<dd>〈C〉紅玉,ルビー / 〈U〉ルビー色,深紅色(しんこうしょく) / ルビー色の,深紅の,
  本発のプログラミング言語</dd>
 => true

on debian

ついでなので、毛色の違う、リナ系でもやってみる。

sakae@deb:/tmp/mruby$ rake
 :
LD    build/host/bin/mirb
/usr/bin/ld: /tmp/mruby/build/host/lib/libmruby.a(sqlite3.o): in function `unixDlOpen':
/tmp/mruby/mruby-sqlite/src/sqlite3.c:31507: undefined reference to `dlopen'
  :

こいつも、問題児だな。ダイナミックロードは、ほぼ必須だと言うのに、デフォの設定に入っていない。いや、それは違うだろう。組み込み系なんで、リナだけ特別扱いはしませんと言う、大人の配慮がなされている。

tasks/toolchains/gcc.rake に、デフォを増やす設定を追加。

linker.libraries = %w(m dl)

これで、ちゃんと、辞書アプリも動くようになった。

sakae@deb:/tmp/mruby$ ldd bin/mirb
        linux-gate.so.1 (0xb7f8a000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7c93000)
        libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7c8d000)
        libreadline.so.8 => /lib/i386-linux-gnu/libreadline.so.8 (0xb7c3a000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb7c18000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7a2f000)
        /lib/ld-linux.so.2 (0xb7f8c000)
        libtinfo.so.6 => /lib/i386-linux-gnu/libtinfo.so.6 (0xb7a06000)

libdlが組込まれている。mirbの場合は、追加でlibreadlineも加わるのか。

mruby-threadを組み込みしたけど、linker.libraries の所に、pthreadを追加した方がよさげだな。

(m)ruby with gnuplot

上でraccを調べていた時、プログラムで遊ぼうというサイトに行き着いたんだ。面白いやつがあったので、ちょっとやってみる。

題材は、奥村晴彦著「C 言語による最新アルゴリズム事典」 から、風が吹けば桶屋が儲かるじゃなくて、アマゾンで蝶が羽搏けば、日本が大嵐になる(かもしれない)という有名なやつ。

なんてのは、表の説明。気象予報士の試験に出る、何故、天気予報は当らないを、素人に説明するためのやつです。

大気循環モデル。どんな風に大気が動くか、時間を追って計算。桜の花弁なり枯葉が、風に舞ってどう動くかを説明する式。花弁の位置は、3次元の座標 x,y,z で表わす、結果になる。

A,B,Cは、気温の傾斜だとか、気圧の傾きだとか、要する風の発生原因を表すパラメータだ。 花弁は、初期状態の位置、x=y=z=1の原点にある。そこから、細かい時間区切Dによって、少しづつ、系を更新してくって亊で、計算を進行させる。

やってみると分るけど、パラメータがちょっと違っただけで、とんでもなく異った結果になる。 いかにパラメータをちゃんと計測しても、何らかの誤差が内在してる。そのため、結果がバラケル亊になるよって亊だ。こればかりは、どんなスパコンをもってしても、解決は出来無い。

/***********************************************************
    lorenz.c -- Lorenz (ローレンツ) アトラクタ
***********************************************************/
#include "plotter.c"  /* ラージモデルでコンパイル */

#define A  10.0
#define B  28.0
#define C  (8.0 / 3.0)
#define D  0.01

int main()
{
    int k;
    double x, y, z, dx, dy, dz;

    gr_on();  gr_window(-30, 0, 30, 60, 1, GREEN);
    x = y = z = 1;
    for (k = 0; k < 3000; k++) {
        dx = A * (y - x);
        dy = x * (B - z) - y;
        dz = x * y - C * z;
        x += D * dx;  y += D * dy;  z += D * dz;
        if (k > 100) draw(x, z);  else move(x, z);
    }
    hitanykey();
    return EXIT_SUCCESS;
}

これをruby語に翻訳する。翻訳なんておこがましい。ちょっと変換しただけだ。知らない間に、微分方程式の数値解を求めてる。pythonでAIは、うんざりだからね。

require "plotter"

A = 10.0
B = 28.0
C = (8.0 / 3.0)
D = 0.01

def main()
  gr_on()
  x = y = z = 1
  for k in 0..3000 do
    dx = A * (y - x)
    dy = x * (B - z) - y
    dz = x * y - C * z
    x += D * dx;  y += D * dy;  z += D * dz
    if k > 100 then draw(x, z) else move(x, z) end
  end
  gr_off()
end

main()

ドライバーは、gnuplotの駆動ルーチンだ。 /usr/local/lib/ruby/site_ruby/ とかに入れておくと、寂る亊なく未来永劫で使えるぞ。

それから、翻訳の過程で、 gr_window( ... ) を省略しちゃった。これは、X、Y軸の範囲を固定するもの。gnuplotは、範囲指定がないと、自動で決定してくれる。

# coding: utf-8
#/***********************************************************
#    plotter.rb -- グラフィックス
#***********************************************************/
# プロッタのシミュレーション

$xpen = 0; $ypen = 0;  # ペンの現在位置

def gr_on
  $gplot = IO.popen("gnuplot -persist", "w")
  $gplot.puts 'plot "-" w l'
end

def gr_off
  $gplot.puts "end"
  $gplot.close
end

def gr_wline(x1, y1, x2, y2)
  $gplot.puts "#{x1} #{y1}\n#{x2} #{y2}\n\n"
end

def move(x,  y)  # ペンアップで移動
    $xpen = x;  $ypen = y
end

def move_rel( dx,  dy)  # 同上 (相対座標)
    $xpen += dx;  $ypen += dy
end

def draw(x,  y)  # ペンダウンで移動
    gr_wline($xpen, $ypen, x, y)
    $xpen = x;  $ypen = y
end

def draw_rel( dx,  dy)  # 同上 (相対座標)
    gr_wline($xpen, $ypen, $xpen + dx, $ypen + dy)
    $xpen += dx;  $ypen += dy
end

# sample usage
=begin
gr_on
gr_wline(0, 0, 10, 10)
move(5, 0)
draw_rel(0, 5)
gr_off
=end

mirbに最初、plotter.rbを貼り付けてから、次に本体を貼り付けたら、ちゃんとグラフが出てきた。素晴しい。

vbox$ bin/mirb
 :
> def draw_rel( dx,  dy)  # 同上 (相対座標)
 *     gr_wline($xpen, $ypen, $xpen + dx, $ypen + dy)
 *     $xpen += dx;  $ypen += dy
 * end
 => :draw_rel
> gr_on
 => nil
> for x in 0 .. 100 do
 * draw x, Math.sqrt(x)
 * end
 => 0..100
> gr_off
 => nil

こんな風に、貼り付けてから、手動でグラフを描画しても、勿論OK。matplotだかの使いかたを思い出すより、よっぽど楽です。

plotter 改造

上で使ったのは、プロッターをシュミレーションする技術。勿論デバイスは、gnuplotだ。

でも、今や2次元平面を描画するだけじゃなくて、3Dが大流行。そう3Dプリンターね。 宇宙へ持っていって、そこで工作しましょって時代。 宇宙空間において3Dプリンターで人工衛星アンテナを製造する技術を開発

地上にいる我々も、3Dプロッターしたい。 そんな目論見もあって、あえて基礎になるコードを展開させてもらってる。作者様には感謝の念がたえません。

デバイス本体が対応してるか? 答はYES。protってなってる所を、splotに変更するのがまずは必要。後は、x, y とかしてる所を、x, y, z に変更するだけ。おっと、$zpen = 0 も忘れないでね。機械的な変更だから、容易に出来るだろう。

2Dと3Dを同居させるなら、draw(x,y) draw3(x,y,z) ぐらいで片づけるのいいかな。

gr_on の所は、 gr3_on ね。'splot "-" w l' 3Dでプロットしてね。"-" は、データは、パイプからの奴を使ってね。w l は省略形。正式に書くと、with line 線グラフって亊。

パイプからのデータ流し込みが終ったら、end で、教えてあげる。これは、2Dも3Dも一緒だ。 共通に使える。


This year's Index

Home