クラウド・コンピューティング時代のプログラミング言語

時代はクラウド・コンピューティングらしい。というわけで『Rubyで作る奇妙なプログラミング言語』の演習として、以下のようなスタック型マシンのプログラミング言語「Croud」を作ってみたいと思った。

ソースコードの記述に使うのは、以下の4つのモコモコ。

()
(_)

 ()
(_)

(_)
(__)

 (_)
(__)

4つもあれば、

1 stack manupulation
  :push n
  :drop
  :dup
  :swap

2 arithmetic
  :add
  :sub
  :mul
  :div

3 flow control
  :setl n
  :jmpl n
  :zjmpl n
  :nzjmpl n

4 i/o
  :puts
  :gets
  :putnum
  :getnum

ぐらいの命令セットを表現するには充分っぽい。

数値は、

( )  (_)  ( )  (_)  ( )  (_)  ( )  (_)
(  ) (  ) (_ ) (_ ) ( _) ( _) (__) (__)
 0    1    2    3    4    5    6    7

 ( )  (_)  ( )  (_)  ( )  (_)  ( )  (_)
(  ) (  ) (_ ) (_ ) ( _) ( _) (__) (__)
 8    9    a    b    c    d    e    f

で表す。例えばAを表示するプログラムは、

push 0x48
puts

()  ()  ( )   ( )  (_) (_)
(_) (_) ( _) (  ) (__) (  )

というようにモコモコする。見た目がすべて。雲が並べばそれでいい。

ホントは各系統で4命令ずつだと、どんな命令でも必ずモコモコが2個で済んでいいのだけど、どうもフィボナッチ数列を計算しようと思ったら、スタック操作にrollのような命令がないとできない気がして、後から追加した。rollというのは、スタックの上から3つめを引っ張り出してもう1度スタックに積む操作(というのが一般的かどうかは知らないけど)。これがないと、ぜんぜん使いものにならない。

スタックだけじゃなくてヒープを作ればいいのだろうけど、ヒープなんかあったら、そりゃ何だってできるでしょうにと思ったりした。楽しくなさそう。rollのような命令を使うか、スタックを2本にして、スタックをスイッチしながら使うようなことをすれば楽しそうじゃないかと思った。そんなのアリか分からないけど。

モコモコは2行にまたがるので、パーズが面倒くさそう。とりあえず、命令を1行ずつ書いたファイルを読んで実行するような処理系を書いてみた。テキストファイルから、シンボルと数値からなる配列を作って、その配列を読みながらスタック操作とI/OをするVM方式。

module Croud

  class Compiler
    def initialize(src)
      @src = src
      @token = []
    end
  end

  class Asm
    def initialize(asm)
      @asm = asm
      @inst = []
    end

    def asm
      @asm.split(/\n/).each do |line|
        op,num = line.split(/,/)
        case op
        when "push"   then @inst << :push << num.to_i
        when "drop"   then @inst << :drop
        when "puts"   then @inst << :puts
        when "putnum" then @inst << :putnum
        when "dup"    then @inst << :dup
        when "swap"   then @inst << :swap
        when "roll"   then @inst << :roll
        when "add"    then @inst << :add
        when "sub"    then @inst << :sub
        when "mul"    then @inst << :mul
        when "div"    then @inst << :div
        when "mod"    then @inst << :mod
        when "setl"   then @inst << :setl << num.to_i
        when "jmpl"   then @inst << :jmpl << num.to_i
        when "nzjmpl" then @inst << :nzjmpl << num.to_i
        end
      end
      @inst
    end
  end

  class VM
    def initialize(inst)
      @inst = inst
      @label = {}
    end

    def mk_jump_table
      @inst.each_with_index do |op, i|
        @label[@inst[i + 1]] = i if op == :setl
      end
    end

    def run
      self.mk_jump_table
      pc = 0
      stc = []
      while pc < @inst.size
        op = @inst[pc]

        case op
        when :push
          pc += 1
          num = @inst[pc]
          stc << num
        when :drop
          stc.pop
        when :puts
          print stc.last.chr
        when :putnum
          print stc.last
          puts
        when :dup
          stc << stc.last
        when :swap
          x, y = stc.pop, stc.pop
          stc << x << y
        when :roll
          stc << stc.delete_at(-3)
        when :add
          stc << stc.pop + stc.pop
        when :sub
          y, x = stc.pop, stc.pop
          stc << x - y
        when :mul
          stc << stc.pop * stc.pop
        when :div
          y, x = stc.pop, stc.pop
          stc << x / y
        when :mod
          y, x = stc.pop, stc.pop
          stc << x % y
        when :jmpl
          pc = @label[@inst[pc + 1]]
        when :nzjmpl
          pc = @label[@inst[pc + 1]] if stc.last != 0
        end
        pc += 1
      end
    end
  end
end

c = Croud::Asm.new(ARGF.read)
i = c.asm
i = Croud::VM.new(i)
i.run
puts

AからZまでを表示するプログラムは、

push,65
push,26
setl,1
swap
puts
push,1
add
swap
push,1
sub
nzjmpl,1

という感じで、

$ ruby croud.rb abc.cr 
ABCDEFGHIJKLMNOPQRSTUVWXYZ
$

とうまくいった。フィボナッチはrollを使って、

push,1
push,2
setl,1
dup
roll
add
putnum
jmpl,1

と書ける。結果は、

$ ruby croud.rb fib.cr 
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
  :
  :
781774079430987230203437
1264937032042997393488322
2046711111473984623691759
3311648143516982017180081
5358359254990966640871840
  :
  :

とうまくいってる。と、淡々と書いてるけど、ABC……が表示されたり、フィボナッチ数列が出てきたときには、けっこう感動した。たぶんこれで動くはずと、なんとなく命令を並べただけで、ほんとに動いて驚いた。

しかし、フィボナッチ数列の計算を停止することができないような気がする。もうちょい柔軟性のあるスタック操作を加えるか、もう1本スタックを用意すれば良さそうにも思えるけど、どうなんだろうか。