関西闇Ruby会議とFFI::Ruby

投稿者 nanki 2011-11-15 14:12:00 GMT

関西闇Ruby会議で発表しました。

内容は最近使う機会の増えたruby-ffi(フィッフィ)の紹介。 その時のスライドです。

ちょっとまとめきれなくて最後のほうにごちゃごちゃと書いてしまっていますが、C/Ruby間で独自の型変換を作れるとあったので試しにVALUE型をRubyのオブジェクトに変換するffi-rubyというのを作ってみました。 実用的なかおりがするけどあんまり使い途ない。

VALUE型の変換これであってるのかなぁ。簡単な恒等性のテストにはパスしてるみたいだけど。


Mac OS X でFrama-Cを使ってRubyを静的解析するメモ

投稿者 nanki 2011-11-06 18:11:00 GMT

@ikegami__さんのFrama-Cを使った実験が面白そうなので試してみた。

試験環境はMacBookAir/Mac OS X Lion/ruby192

インストール

$ brew install ocaml
$ wget http://frama-c.com/download/frama-c-Nitrogen-20111001.tar.gz
$ tar zxf frama-c-Nitrogen-20111001.tar.gz
$ cd frama-c-Nitrogen-20111001
$ ./configure --prefix=...
$ make
$ make install

使ってみる

Rubyのソースツリーに移動する。

$ cd ruby
$ ./configure

記事を参考に、.ext/include/ARCH_NAME/ruby/config.h に以下の行を追加。

#ifndef TRUE
  #define TRUE 1
#endif
#ifndef FALSE
  #define FALSE 0
#endif

解析対象ファイルの一覧を作っておく。

$ vi mainobj
array.c bignum.c class.c compar.c complex.c dir.c dln_find.c enum.c enumerator.c error.c eval.c load.c proc.c file.c gc.c hash.c inits.c io.c marshal.c math.c node.c numeric.c object.c pack.c parse.c process.c random.c range.c rational.c re.c regcomp.c regenc.c regerror.c regexec.c regparse.c regsyntax.c ruby.c safe.c signal.c sprintf.c st.c strftime.c string.c struct.c time.c transcode.c util.c variable.c compile.c debug.c iseq.c vm.c vm_dump.c thread.c cont.c

一覧は、common.mk中のCOMMONOBJSを参考にしている。

Frama-C Nitrogen 20111001 で ruby-1.9.2-p290 を検査するときにどうにもならなさそうな事であげられているファイルのいくつかは、直接コンパイルされることを想定して書かれていないので失敗するようだ。

解析開始

$ frama-c \
  -cpp-extra-args="-U__BLOCKS__ -DRUBY_EXPORT -I./.ext/include/ARCH_NAME -Iinclude" \
  -machdep x86_64 \
  -save ruby.session \
  `cat mainobj` 

-U__BLOCKS__はSnowLeopardから導入されたBlocks(Cの文法を拡張している)を無効にするため。これを無効にしないとparseが失敗する。

-machdep x86_64はARCH_NAMEにあわせたものを$ frama-c -machdep helpの結果から選ぶ。

$ frame-c -load ruby.session -metrics
...(ry)...
[metrics] Syntactic metrics
          -----------------
          ** Defined functions (6297):
          ...(ry)...

          ** Undefined functions (297):
          ...(ry)...

          ** Potential entry points (1694):
          ...(ry)...

          SLOC: 144947
          Number of if statements: 25635
          Number of assignments: 40691
          Number of loops: 3651
          Number of calls: 31176
          Number of gotos: 7496
          Number of pointer access: 50736

$ frame-c -load ruby.session -users -main rb_ary_new
...(ry)...
[users] ====== DISPLAYING USERS ======
        ary_alloc: rb_newobj
        ary_new: rb_newobj ary_alloc
        rb_ary_new2: rb_newobj ary_alloc ary_new
        rb_ary_new: rb_newobj rb_ary_new2 ary_alloc ary_new
        ====== END OF USERS ==========
参考:

FizzBuzzの世界

投稿者 nanki 2011-08-25 18:20:00 GMT

ちょっと興味深いfizzbuzzを発見したので。


高度すぎるコードジェネレータ WLang

投稿者 nanki 2010-10-10 00:14:00 GMT

みなさんはコードジェネレータを使った事があるでしょうか。私はあります。

SQLにHTML, JavaScript, はてはPHPまで、Rubyでコードジェネレータを使う機会は増すばかりです。

さて、Rubyでコードジェネレーションと言えばERbを使うことが多いですが、それで充分なのでしょうか。 他の選択肢はどうでしょうか。

今日は、wlangというコードジェネレータを紹介します。

template engine?

次のサンプルコードを見てください。 とてもシンプルなHTMLテンプレートエンジンのように見えます。

require 'wlang'

puts WLang::instantiate(<<EOS, {:name => "O'Reilly & Animals", :animals => %w(Koala Rhino Giraffe)}, "wlang/xhtml")
<h1>${name}</h1>
<div id="{name}">
  <img alt='{name}' src="http://....."/>
  <ul>
    *{animals as animal}{${animal}}{, }
  </ul>
</div>
EOS

このプログラムの出力結果はこうなります。

<h1>O'Reilly &amp; Animals</h1>
<div id="O'Reilly & Animals">
  <img alt='O\'Reilly & Animals' src="http://....."/>
  Koala, Rhino, Giraffe
</div>

コンテキストに合わせて正しく、'&がエスケープされているのがわかります。 また、記法にはあまり馴染みがありませんが、配列の展開もうまく動いています。

wlang >> template engine

次にwlangの仕組みを見ていきます。 wlangでは、wlangタグを使って、テンプレートを記述します。 wlangタグは次のような形式です。

記号{.....}{.....}{.....}

後ろの{}はblockと呼ばれ、任意の個数持つ事ができます。 記号はたとえば $, *などです。

そう、実は先のテンプレートで出現した、${...}, "{...}, '{...}, *{...}{...}{...} は全て、wlangタグだったのでした。

(ちなみに、”{name}” の末尾の”はwlangタグとは関係なく、ただの文字列です。 これは <div id="{name}'>が文法エラーを出さずに動作する事で確認できます。このセンス嫌いじゃない)

なにやらあやしいかおりがしてきました。

wlang = programming language?

次に、WLang::instantiateの第三引数は”wlang/xhtml”となっていますが、WLangではこれをdialect(方言、なまりの意味)と呼んでおり、当然変えられます。

require 'wlang'

sql = "SELECT * FROM books WHERE name='{name}';"
values = {:name => "O'Reilly"}

puts WLang::instantiate(sql, values, "wlang/sql")
puts WLang::instantiate(sql, values, "wlang/sql/sybase")

# => SELECT * FROM books WHERE name = 'O\'Reilly';
# => SELECT * FROM books WHERE name = 'O''Reilly';

このように、dialect毎に'{...}タグの動作が変わるわけです。

dialectは複数のRuleSetとEncoderSetで定義されています。 RuleSetはタグの機能をオーバライドしたり新たなタグを定義します。 EncoderSetはdialect固有のencoding、つまり文字列の変換方法を定義していて、 ^{..encoding..}{...}というタグで呼び出す事ができます。

以下は大文字に変換するencodingの例です。

# instantiateとか省略します
^{plain-text/upcase}{wlang}
=> WLANG

これが例えばwlang/xhtml dialect であれば wlang/xhtml/main-encoding, wlang/xhtml/single-quoting, wlang/xhtml/double-quoting などが定義され、それぞれ違うエスケープがされるようになっています。

先のwlang/sqlwlang/sql/sybaseの結果の違いも、ここで決まっています。

勘のいい方ならお気づきだと思いますが、${...}, '{...}, "{...}はそれぞれdialectのmain-encoding, single-quoting, double-quotingを呼び出すためのショートカットになっています。

polyglot!

この様な拡張可能かつ再利用可能な変換規則を用意することで、Rubyの文字列を埋め込んだJavaScript、を埋め込んだxhtmlを埋め込んだRubyスクリプト、などといった複雑なコード生成を行う場合でも、 エスケープに悩まされることなく気楽にコード生成を行う事ができるwlangかっこいい!

でも、encodingの実装にバグがあるっぽくて、まだそれは無理っぽい。

参考:

Rubyでfinalizerの順番を制御する

投稿者 nanki 2010-09-21 00:55:00 GMT

SWIGでLLVMのRuby bindingを書いていて、やっぱりGC周りではまってしまった。

具体的には、オブジェクトの開放の順番を指定しないといけないのだが、 Rubyではfinalizerの実行順は不定だし、先に消えてほしいオブジェクトからリファレンスを持ったりしてみたのだが、 rootからの参照を一度に消されるとどうしようもない。

そこで試しに以下のようなコードを書いてみたところうまくいった。

module OrderedFinalizer
  @@queue = []

  def self.add(*v)
    @@queue << weaken_reference(v)
  end

  def self.weaken_reference(v)
    v.last.map!(&:object_id)
    v
  end

  def self.step
    @@queue.each do |group|
      unless group.last.any?{|e|ObjectSpace._id2ref(e) rescue nil}
        group.pop
        weaken_reference(group) unless group.empty?
      end
    end
    @@queue.reject!(&:empty?)
    @@queue.empty?
  end

  at_exit do
    GC.start until self.step
  end
end


# obj2, obj3 ☞ obj1 という順番で解放したい
OrderedFinalizer.add [obj1], [obj2, obj3]
OrderedFinalizer.add [obj1], [obj4, obj5]

以下軽く解説。

  1. OrderedFinalizerに解放順序を指定したいオブジェクトへのリファレンスを保持させる。
  2. 一番先に解放してほしいオブジェクトだけはweak reference(つまりobject_id)で持つ。
  3. weak referenceなオブジェクトが解放されていたら、次のオブジェクトの参照をweakにする。

OrderedFinalizerはat_exitで参照されているので、Ruby界では最後まで生き残る。

stepを各GC毎に呼ぶのが理想なのだが、そんなイベントはないよなぁ。 今回の用途には足るんだけど、今のままだと、最後までいくらかのオブジェクトを保持してしまう。

当然、obj1からobj2へのリファレンスがあったりすると無限ループになるし、[true]とか渡してやるとかも困る。

あるいは別の解法求む。


WebSDL - Ruby/SDL × WebSocket/Canvas

投稿者 nanki 2010-06-23 02:42:00 GMT

Ruby勉強会@関西44の懇親会にて @cyross, @KazkiMatz さんらとSDLをWebSocketと組み合わせて…という話をしていておもしろそうだったので実装してみた。

websdl
青は@nanki, 赤は@ujm

github - WebSDL

  1. Ruby/SDLの画面描画を全て文字列としてシリアライズし、WebSocketで送信、Canvasに描画する。
  2. ブラウザ上で発生したイベントをWebSocketで送信、SDLのイベントモデルの中に還元してやる。

というのが基本的な仕組み。

# samples/sample1.rb
require 'rubygems'
gem 'rubysdl'
require 'sdl'
require 'websdl'

class TestFrame < Frame
  attr_reader :mx, :my, :color

  def initialize
    @color = 0xff000000 | (1..3).inject(0){|r,i|r << 8 | rand(0xff)}
    @mx = -100
    @my = -100
  end

  def open_screen
    SDL::Screen.open(640, 480, 32, SDL::HWSURFACE || SDL::DOUBLEBUF)
  end

  def mainloop(screen)
    screen.fill_rect(0, 0, screen.w, screen.h, 0)

    while event = SDL::Event.poll
      @mx, @my = event.x, event.y if SDL::Event::MouseMotion === event
    end

    frames.each do |f|
      screen.fill_rect(f.mx - 15, f.my - 15, 30, 30, f.color)
    end
    
    screen.flip
  end
end


SDL = WebSDL if ARGV.first == 'web'

SDL.init(SDL::INIT_VIDEO)
SDL.run(TestFrame, :host => '0.0.0.0', :port => 3000)

上記のようなコードで、

$ rsdl -Ilib samples/sample1.rb

ではSDLのウィンドウが起動し、

$ ruby -Ilib samples/sample1.rb web

でWebSDL版が起動するようになっている。 WebSDL版はsamples/test.html をWebSocket, Canvasに対応したブラウザで開く。

ただCanvasを使うだけでは芸が無いので、マルチユーザに対応、ユーザ間の情報共有にも対応してみた。

言うまでもなくProof of Conceptの段階であり、SDLの機能もdraw_rect, MouseMotion くらいしか実装していない。

また、0.1秒毎に更新しているのがかっこわるいが、適切なイベントに基づいた処理をすれば、パズルゲームやアドベンチャーゲームなど頻繁に画面更新のないものなら実用的なものができる可能性がある。 ウェブへぇボタンとかね。

問題点

一方、SDLのAPIは同期的なものもあり、SDL::Surface#get_pixel (画面上のピクセルの色を返す) などは現在の方法では実現が困難である。

参考:

RubyでIMAPを使ってGMailにアクセスする

投稿者 nanki 2010-05-20 19:29:00 GMT

だいたい以下のような感じになる。

password_from_keychainはOSXのkeychainからパスワードを取ってくるだけの関数。

GMailのTwitterフォルダ(follow通知メールが溜まる)の中から未読(UNSEEN)のメールを探してきて、ユーザ名とおぼしき部分を抜き出して出力、既読にする。

require 'net/imap'

# for Mac
def password_from_keychain(server, user)
  require 'open3'
  _, _, err = Open3.popen3("/usr/bin/security find-internet-password -s #{server} -a #{user} -r imap -g")

  if /^password: "(.*)"$/ === err.gets
    $1
  else
    nil
  end
end

SERVER = 'imap.gmail.com'
USER = 'nanki@example.com'
imap = Net::IMAP.new(SERVER, 993, true)

imap.login(USER, password_from_keychain(SERVER, USER))
imap.select("Twitter")

imap.search(%w(UNSEEN)).each do |msg|
  body = imap.fetch(msg, "BODY[TEXT]")[0].attr["BODY[TEXT]"]

  if /twitter.com\/([^?\/]+)/ === body
    puts "f #{$1}"
    imap.store(msg, "+FLAGS", [:Seen])
  end
end

Ruby1.8, Ruby1.9のIOの挙動の違い

投稿者 nanki 2010-04-28 04:47:00 GMT

表にまとめてみた。 主な違いは、getc/readcharがbyteを返すだけみたいだ。

getchar/getlineがあってほしい((# getcは数値を返して欲しい))

get - nil at EOFread - EOFError at EOFenumeach
(char) 1.8 N/A N/A chars each_char
1.9 getc readchar chars each_char
(byte) 1.8 getbyte, getc readbyte, readchar bytes each_byte
1.9 getbyte readbyte bytes each_byte
(line) 1.8 gets readline lines each_line, each
1.9 gets readline lines each_line, each

Try maglev!

投稿者 nanki 2009-11-25 21:42:00 GMT

噂のMagLevを試してみた。以下 Mac OS X 10.5.8 での話。

⍟ git clone git://github.com/MagLev/maglev.git # clone repository
⍟ cd maglev
⍟ ./instsall.sh # download and install GemStone

⍟ vim .zshrc
export MAGLEV_HOME=/PATH_TO/maglev
export PATH=$MAGLEV_HOME/bin:$PATH

⍟ rake maglev:start
⍟ maglev-ruby -e "p 1 + 1"
2
⍟ maglev-irb
error , Expected nil to be a Boolean.,
          during /..../maglev/bin/maglev-irb
ERROR 2085, Expected nil to be a Boolean. (RuntimeError)

maglev-irb は動かない><

参考:

やはりRubyでは 〜Scalaの無名関数に憧れて〜

投稿者 nanki 2009-10-29 06:18:00 GMT

憧れシリーズ第二弾。

Scalaの勉強でもしようかな、と思ってScalaの本を開いたら、Scalaでは無名関数を_(アンダースコア)を使って定義できるらしい。 Rubyにおけるそれの実現可能性に考えが行ってしまって、Scalaの本を読むのはそれでおしまいになったのだ…

Rubyでも、

[1, 2, 3].map(_ * 2) # => [2, 4, 6]

とか書きたいよね。

Rubyにはもともと、mapなどのブロックを取るメソッドに無名関数(Proc)を引数として渡すことができる

[1, 2, 3].map(&:to_s)

この例では、:to_s は Symbol だが、Proc を返すSymbol#to_proc を定義しておくと、それが呼ばれてよしなにしてくれる。Symbol#to_proc はActiveSupport などで定義されているので、Rails使いにはおなじみだろう。

この仕組みを利用して、 _ に対して送られるメッセージを保存しておいて、to_proc される時に適当なProcを返すような何かを作ればよい。

module Scala
  class LazyCall
    instance_methods.each do |v|
      undef_method(v) unless %w(__id__ __send__).include?(v)
    end

    def initialize(v = nil)
      @v = v
      @msgs = []
    end

    def method_missing(*args, &block)
      @msgs << [args, block] unless args == [:respond_to?, :to_proc]
      self
    end

    def to_proc
      proc do |v|
        @msgs.inject(@v||v) do |r, m|
          r.__send__(*m[0].map{|a|LazyCall === a ? a.to_proc[v] : a}, &m[1])
        end
      end
    end
  end

  def _(v = nil)
    LazyCall.new v
  end

  # _0, _1, ...
  (0...3).each do |i|
    define_method("_#{i}"){LazyCall.new[i]}
  end
end

include Scala

そうしてできたのがこれ。

これを使うと…

%w(a b c d).map(&_ * _.to_i(16))
# => ["aaaaaaaaaa", "bbbbbbbbbbb", "cccccccccccc", "ddddddddddddd"]

[[1, 2], [2, 3]].map(&_0 + _1)
# => [3, 5]

_(Math).sqrt(_0 ** 2 + _1 ** 2).to_proc[[3, 4]]
# => 5.0

& の後に括弧が要ると思っていたがそんなことはなかったぜ。

メッセージをフックする関係上、直接 Math.sqrt(_0 ** 2 + _1 ** 2) とは書けないけど、そう書きたい人はブロック使ってください。

以下簡単な説明

  • _ はLazyCallのインスタンスを返し、受け取ったメッセージを引数と共に保存。
  • [:respond_to?, :to_proc] をはじいているのは、引数として渡される時にこの問い合わせが発生するので、それを無視するため。(副作用として、無名関数では.respond_to? :to_proc が使えなくなると思う)
  • to_proc内で、保存したメッセージを、procの引数に送り直している。
  • 保存された引数の中に、LazyCallのインスタンスがある場合は、call してその結果と置き換えている。これによって、_ * _ のような表記が可能になる。
  • _0は、_.[0]のエイリアス

二夜にわたるProxyObject特集、いかがでしたでしょうか。さて、来週からは…

追記:2009/10/30

Scalaの _ (プレースホルダ構文と呼ぶらしい) は _ + _と書いたとき、二つの _ が同じものを意味しないそうです。

と、閉じた本の続きに書いてあった。

あと、_0 などを使うと、20倍くらい遅い。どうしたものか。