MRI 1.8.6のirbの中の小さな奇妙な場所(IronRuby rev 183のbugだと思っていた)


元旦前後にDLR関連の項目が長く更新されていません.この2日間、IronRubyはrev 181からrev 183(rubyforge上のSVN倉庫のバージョン番号)に更新された.
このrevisionは私に小さなサプライズをくれました:ir.exe(IronRubyのconsole)はついにインタラクティブモードでグローバル役割ドメインで局所変数をサポートした.前にIronRubyのインタラクティブな解釈器を持って遊びに来た時、方法外の変数はすべて$記号でグローバル変数に修飾しなければならなくて、なんだか違和感を感じました.今は何とかなりました~いつかIronRubyでirbを直接使えるのを期待しています...
しかし、今日遊んでいたとき、手が間違っていたので奇妙な現象を発見しました.インタラクティブインタラクションで遊んで、挿入演算子("<<")を書いた後、スペースを書くのを忘れて後ろの数字を書いて、車に戻りました.
まずRuby 1.8.6 p 287を用いて実験を行った.
E:\ironruby\build\debug>irb
irb(main):001:0> a, b = [], 'xyz'
=> []
irb(main):002:0> a <<2 <<3
irb(main):003:0" puts b.succ
irb(main):004:0" 2
xza
=> 2
irb(main):005:0> a
=> [2, 3]

Ruby 1.8.6のirbは<<2をheredocの開始フラグと見なしているようだが、動作はheredocが文字列を返すのではなく、<<2から2の間のコードを実行している.それだけでなく、<<2と<<3は2と3をaに挿入してしまいました・・・
これがどんな現象なのか知っている人に説明してください...私は混乱しました.
ちなみにJRuby 1.1.5のjirbの挙動はRuby 1.8.6のirbと同じである.
次にIronRuby rev 183を用いてこの現象をテストする.
E:\ironruby\build\debug>ir
IronRuby 1.0.0.0 on .NET 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.

>>> a = []
=> []
>>> a <<2 <<3
=> [2, 3]

IronRubyのirが見えます.exeはこれが通常の連続2つの挿入操作であると考えている.おかしいな......
(しかし、テストした後、この動作はRuby言語では正しい.インタラクティブ解釈器ir.exeとMRIのirbの動作が異なるだけだ.以下を参照)
その後、JRubyでこのようなコードをどのように理解しているかを見てみましょう.JRuby 1.1.5のjirbを開く:
irb(main):001:0> src = <<SRCEND
irb(main):002:0" a = []
irb(main):003:0" b = 'xyz'
irb(main):004:0" a <<2 <<3
irb(main):005:0" puts b.succ
irb(main):006:0" 2
irb(main):007:0" a
irb(main):008:0" SRCEND
=> "a = []
b = 'xyz'
a <<2 <<3
puts b.succ
2
a
" irb(main):009:0> ast = JRuby.parse src => RootNode BlockNode NewlineNode LocalAsgnNode |a| &0 >0 ZArrayNode NewlineNode LocalAsgnNode |b| &1 >0 StrNode =="xyz" NewlineNode CallOneArgNode |<<| CallOneArgNode |<<| LocalVarNode |a| &0 >0 ArrayNode FixnumNode ==2 ArrayNode FixnumNode ==3 NewlineNode FCallOneArgNode |puts| ArrayNode CallNoArgNode |succ| LocalVarNode |b| &1 >0 NewlineNode FixnumNode ==2 NewlineNode LocalVarNode |a| &0 >0

JRubyはこのコードにはheredocがなく,普通の文だと考えていることがわかる.つまり、<<後ろにスペースがある場合の結果と同じです.
irb(main):001:0> src = <<SRCEND
irb(main):002:0" a = []
irb(main):003:0" b = 'xyz'
irb(main):004:0" a << 2 << 3
irb(main):005:0" puts b.succ
irb(main):006:0" 2
irb(main):007:0" a
irb(main):008:0" SRCEND
=> "a = []
b = 'xyz'
a << 2 << 3
puts b.succ
2
a
" irb(main):009:0> ast = JRuby.parse src => RootNode BlockNode NewlineNode LocalAsgnNode |a| &0 >0 ZArrayNode NewlineNode LocalAsgnNode |b| &1 >0 StrNode =="xyz" NewlineNode CallOneArgNode |<<| CallOneArgNode |<<| LocalVarNode |a| &0 >0 ArrayNode FixnumNode ==2 ArrayNode FixnumNode ==3 NewlineNode FCallOneArgNode |puts| ArrayNode CallNoArgNode |succ| LocalVarNode |b| &1 >0 NewlineNode FixnumNode ==2 NewlineNode LocalVarNode |a| &0 >0

しかし、これはaが配列であることを前提条件としている.もしaが配列でなかったら?JRuby 1.1.5の反応を見てみましょう
irb(main):001:0> JRuby.parse "a <<2 <<3
puts 'ok'
2" => RootNode NewlineNode FCallOneArgNode |a| ArrayNode CallOneArgNode |<<| StrNode =="puts 'ok'
" ArrayNode FixnumNode ==3

あまりにかわいくて、それはaが1つの方法だと思って、それでは
a <<2 <<3
puts 'ok'
2

に等しい
a("puts 'ok'" << 3)

Ruby 1.8.6 p 287で見てみましょう.
E:\>irb
irb(main):001:0> def a(str)
irb(main):002:1>   str
irb(main):003:1> end
=> nil
irb(main):004:0> a <<2 <<3
irb(main):005:0" puts 'ok'
irb(main):006:0" 2
=> "puts 'ok'
\003"

ほら、やっぱり数字3を文字列「puts'ok」に挿入したのか・・・
しかし、この状況はIronRuby rev 183でも同じです.
E:\ironruby\build\debug>ir
IronRuby 1.0.0.0 on .NET 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.

>>> def a(str)
...   str
... end
=> nil
>>> a <<2 <<3
... puts 'ok'
... 2
=> "puts 'ok'
\003"

うーん、全体的に、最初に出会ったのはirbのバグのようです.IronRubyは「Ruby言語に忠実」に言えばますますよくなってきたが、ここはIronRubyのせいではないはずだ.
しかし、うっかりコードにheredocを出してしまったのは本当に恥ずかしいです.これからはどうしても演算子の両側にスペースを入れることに注意しなければなりませんが...
==========================================================================
IronRuby rev 183といえば、今回SVNに入れたコードはbuildでテストしたことがないのではないでしょうか・・・VS 2008で直接開くのはbuildではありません.MicrosoftでScripting.csprojはSpecSharpに関する内容をすべて削除し、Microsoft.Scripting.Extensionattribute.csprojのバージョン番号が間違っているSystem.dllの引用を削除し、各プロジェクトのReferenceをリセットしてからbuildを得ることができます.こんな状况になったのは初めてではないのですが、多少は気分が悪くなりますが…