Croudのスタックを2本に拡張

クラウド・コンピューティング時代のオレ様プログラミング言語「Croud」にはスタックが1本しかなかった。これでは制限がきつすぎるし、かといってヒープとかじゃつまらないと思い、もう1本、裏スタックを加えようと思って、次のようにStackクラスを作ることを考えた。

何となくArrayを継承して、外側からはArrayに見えて、内部的には2本の配列を切り替えるようなものを作ればいいんだろうと思って、あれこれがんばってみた。

class Stack < Array

  def initialize
    @stc1 = []
    @stc2 = []
    @current = 1
  end
  
  def switch
    if @current == 1
      @stc = @stc2
      @current = 2
    elsif @current == 2
      @stc = @stc1
      @current = 1
    end
  end

  super()  ??

end

なにか、決定的にアホな間違いをしている気がする……。ArrayとStackでは内部的なデータ表現が配列1本と2本で全然違う。だからこれって継承とか、そういう問題じゃないのか。継承したいのはメソッド群だけなんだけど。

もっと素直に次のように書いてみた。

class Stack
  
  attr_accessor :stc
  
  def initialize
    @stc1 = []
    @stc2 = []
    @current = 1
    @stc = @stc1
  end
  
  def switch
    if @current == 1
      @stc = @stc2
      @current = 2
    elsif @current == 2
      @stc = @stc1
      @current = 1
    end
  end

end

これで動作自体は期待通り。

irb(main):001:0> require "stack.rb"
=> true
irb(main):002:0> s = Stack.new
=> []
irb(main):003:0> (1..10).each do |i| s.stc << i end
=> 1..10
irb(main):004:0> s.stc
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):005:0> s.switch
=> 2
irb(main):006:0> ("a".."z").each do |i| s.stc << i end
=> "a".."z"
irb(main):007:0> s.stc
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
irb(main):008:0> s.switch
=> 1
irb(main):009:0> s.stc
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
=end

switという命令を追加して、この命令が来たら2本のスタックを入れ替えるようにした。

s = Stack.new

  :
  :

  when :drop
   s.stc.pop

  :
  :

  when :swit
   s.switch

  :

フィボナッチ数列の、最初のほうの20個だけ表示するCroud(の中間言語)で書いたコードは次の通り。2本目の裏スタックに置いたカウンターをループの停止条件に使うようにしている。

push,1
push,2
swit
push,20
setl,1
swit
dup
roll
add
putnum
swit
push,1
sub
nzjmpl,1

うまくいった!

$ ./crd fib2.cr 
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657

$

s.stc.popという表現が山のようにでてきて汚いコードになってしまったけど、とりあえずできたし、ちゃんとフィボナッチが停止して感激した。

しかし、最初にfib2.crを書いたときには、ジャンプの戻り先を間違えて一段階スタック切り替えを忘れてしまった。そういうバグがあると、Rubyのエラーになりがちで、一瞬処理系のほうが壊れたかと思ってしまう。やっぱりエラー対策とか、例外処理みたいなことって必要なものなのかなと思ったりした。

スタックを入れ替えるだけじゃなくて、スタックの一番上同士を入れ替えるような命令とか、「X」の形に入れ替えるような命令があると、なんだか楽しそうな気もする。

裏スタックに「Hello, world」とゼロターミネートした文字列を置いて、それを表示させるサブルーチンを呼ぶというようなことも、ひょっとしてできるかも。とか考え出すと、なるほど単純なジャンプだけじゃなくて戻り先を何とかうまく処理する仕組みもほしいよな、とかなってくる。

そういえば、やる気がなかったgets、getnum命令も実装してみた。

  when :gets
    s.stc << $stdin.gets.chomp!
  when :getnum
    s.stc << $stdin.gets.chomp!.to_i

これで、入力された数字の回数だけフィボナッチを繰り返すコードは次のように書ける。

getnum
swit
push,1
push,2
swit
setl,1
swit
dup
roll
add
putnum
swit
push,1
sub
nzjmpl,1

実行結果は、

$ ./crd fib3.cr 
10
3
5
8
13
21
34
55
89
144
233

$ 

となる。