長生きするirbを書きたくなったのでその素振り。複数のirbのインターフェイスが実は一つのインタプリタを共有している、ということをやってみる。
まず、クライアント(?)。
require 'drb/drb' require 'irb/input-method' STDOUT.sync = true DRb.start_service im = IRB::StdioInputMethod.new im.extend(DRbUndumped) ro = DRbObject.new_with_uri('druby://localhost:54345') th = ro.start(im, $stdout) th.join
input-methodと標準出力をサーバ(???)に渡して、irbをstartします。クライアントはわりときれい。
私の環境のOSXのreadlineはスレッドが固まってしまうので、StdioInputMethodを使いました。
出力側はちょっとよくわからなくて、output-methodではなく$stdoutを与えました。
output-methodっていうのはどういうときに使うんだろう。プロンプトの印字ははoutput-methodを経由してくれないのですよね。
つぎにサーバ側。一部は既存のメソッドを置き換えます。前述したように出力に関してはかなり苦労しています。IRB::Contextでのprintとprintfをクライアント側に回すために、@stdoutを新設しました。もし指定されてたらそっちへ委譲しちゃうのだ。
require 'irb' require 'drb/drb' module IRB class Context
- 追記。verbose?じゃなくてprompting?だったみたい。
def verbose? true end end class Irb attr_accessor :stdout def print(*opts) @stdout ? @stdout.print(*opts) : super end def printf(*opts) @stdout ? @stdout.printf(*opts) : super end end def IRB.conf; @CONF; end end class IRb def start(input_method, output) irb = IRB::Irb.new(nil, input_method) irb.stdout = output IRB.conf[:MAIN_CONTEXT] = irb.context th = Thread.new do catch(:IRB_EXIT) do irb.eval_input end end end end IRB.setup(__FILE__) DRb.start_service('druby://:54345', IRb.new) DRb.thread.join
もしも、readlineでブロックしない環境なら、最後の部分を次のようにしたらよいと思うよ。
DRb.start_service('druby://:54345', IRb.new) IRB.start(__FILE__)
そうすると、サーバ自身もirbのインターフェイスとして動くので1.5倍くらいたのしい。
使い方は、端末をたくさん用意して、端末1でサーバを実行し、端末2からnでクライアントを実行するだけ。
それぞれの端末にbindingができるのでローカル変数等は混じらないけど、グローバル変数を経由してオブジェクトを交換できます。グローバル変数にRinda::TupleSpaceを置いといてそれぞれの端末でオブジェクトを投げ合うと楽しいかもね。
[term-1] サーバ側の起動 % ruby irb_d.rb [term-2] クライアント1の起動 % ruby irb_c.rb irb(main):001:0> a = [] a = [] => [] [term-3] クライアント2の起動。[term-2]のaは[term-3]からは見えないよ。 % ruby irb_c.rb irb(main):001:0> a a NameError: undefined local variable or method `a' for main:Object from (irb):1 from :0 [term-3] クラスFooを定義する。大文字で始まるものはグローバルなので、きっとみんな見える。 irb(main):002:0> class Foo class Foo irb(main):003:1> attr :foo attr_accessor :foo irb(main):004:1> end end => nil [term-2] Fooを使ってみる。 irb(main):002:0> foo = Foo.new foo = Foo.new => #<Foo:0x45c360> irb(main):003:0> foo.foo = 'foo' foo.foo = 'foo' => "foo" [term-2] TsにTupleSpaceをいれて、[:hello, nil]でブロックしてみよう irb(main):005:0> require 'rinda/tuplespace' require 'rinda/tuplespace' => true irb(main):006:0> Ts = Rinda::TupleSpace.new(5) Ts = Rinda::TupleSpace.new(5) => #<Rinda::TupleSpace:0x42967c ...> irb(main):007:0> Ts.take([:hello, nil]) [term-3] Tsは見えるので、[:hello, :again]タプルを投入してみる irb(main):007:0> Ts.write([:hello, :again]) Ts.write([:hello, :again]) => #<Rinda::TupleEntry:0...> [term-2] ブロックがとけ、タプルが届いたよ!! Ts.take([:hello, nil]) => [:hello, :again]
とかさあ。
inf-rubyから使うにはどうしたらいいかな。