クラウド・コンピューティング時代のプログラミング言語
時代はクラウド・コンピューティングらしい。というわけで『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本スタックを用意すれば良さそうにも思えるけど、どうなんだろうか。