0と1だけだけど、機械語じゃない言語「Whirl」

言語機能としての「グリルチキン」とはなにか、というツッコミは とりあえず置いとくとして、引き算すらないというのはどういうことか。 ちなみに命令は0と1しかない。

あはは。なんかだか可笑しい。
そこはかとなくCASLを思い出してしまいました。
で、これくらい軽量なら実装かんたんだなー、と思ってRubyで書いてみた。


工夫無しのベタ書きの長文。
なんか仕様がわかりずらくて(Sets A to B が A->B と B->A の両方で使われてるような・・・)2時間もかかってしまった。
ついでに、リンク先からサンプルとして「X+Y」と「Hello,World」を貰って追加しておく。

#
# Whirl Interpreter
# (http://www.bigzaphod.org/whirl/)
#

#
# 'Ring' base class
#
class Ring
  def initialize(hardware)
    @hardware = hardware
    self.reset
    @ring = []
  end
  
  def reset
    @index = 0
    @direction = 1
    @value = 0
  end
  
  def reverse
    @direction = -@direction
  end
  
  def rotate
    @index += @direction
    if @index == -1
      @index = @ring.length - 1
    elsif @index == @ring.length
      @index = 0
    end
  end

  def command
    @ring[@index]
  end
  
  def value
    return @value
  end
  
  def value=(val)
    @value = val
  end
end

#
# OperationRing class
#
class OperationRing < Ring
  def initialize(hardware)
    super
    @ring = [
    	{:name => "OPE:Noop", :proc => self.method("_noop")},
    	{:name => "OPE:Exit", :proc => method("_exit")},
    	{:name => "OPE:One", :proc => method("_one")},
    	{:name => "OPE:Zero", :proc => method("_zero")},
    	{:name => "OPE:Load", :proc => method("_load")},
    	{:name => "OPE:Store", :proc => method("_store")},
    	{:name => "OPE:PAdd", :proc => method("_padd")},
    	{:name => "OPE:DAdd", :proc => method("_dadd")},
    	{:name => "OPE:Logic", :proc => method("_logic")},
    	{:name => "OPE:If", :proc => method("_if")},
    	{:name => "OPE:Intl0", :proc => method("_intl0")},
    	{:name => "OPE:Ascl0", :proc => method("_ascl0")},
    ]
  end

  def _noop
  end

  def _exit
    @program_pointer_position = 9999999999
  end

  def _one
    self.value = 1
  end

  def _zero
    self.value = 0
  end

  def _load
    self.value = @hardware.memval
  end

  def _store
    @hardware.memval = self.value
  end

  def _padd
    @hardware.program_position_pointer = @hardware.program_position_pointer + self.value
  end

  def _dadd
    @hardware.memory_position_pointer = @hardware.memory_position_pointer + self.value
  end

  def _logic
    if @hardware.memval == 0
      self.value = 0
    else
      self.value = self.value & 1
    end
  end

  def _if
    if @hardware.memval != 0
      _padd
    end
  end

  def _intl0
    if self.value == 0
      @hardware.memval = STDIN.gets.to_i
    else
      STDOUT.print @hardware.memval
    end
  end

  def _ascl0
    if self.value == 0
      @hardware.memval = STDIN.gets[0]
    else
      STDOUT.print @hardware.memval.chr
    end
  end
end

#
# MathRing class
#
class MathRing <Ring
  def initialize(hardware)
    super
    @ring = [
    	{:name => "MAT:Noop", :proc => method("_noop")},
    	{:name => "MAT:Load", :proc => method("_load")},
    	{:name => "MAT:Store", :proc => method("_store")},
    	{:name => "MAT:Add", :proc => method("_add")},
    	{:name => "MAT:Mult", :proc => method("_mult")},
    	{:name => "MAT:Div", :proc => method("_div")},
    	{:name => "MAT:Zero", :proc => method("_zero")},
    	{:name => "MAT:<", :proc => method("_lessthan")},
    	{:name => "MAT:>", :proc => method("_greaterthan")},
    	{:name => "MAT:=", :proc => method("_equal")},
    	{:name => "MAT:Not", :proc => method("_not")},
    	{:name => "MAT:Neg", :proc => method("_neg")},
    ]
  end
  
  def _noop
  end

  def _load
    self.value = @hardware.memval
  end

  def _store
    @hardware.memval = self.value
  end

  def _add
    self.value = self.value + @hardware.memval
  end

  def _mult
    self.value = self.value * @hardware.memval
  end

  def _div
    self.value = self.value / @hardware.memval
  end

  def _zero
    self.value = 0
  end

  def _lessthan
    if self.value < @hardware.memval
      self.value = 1
    else
      self.value = 0
    end
  end

  def _greaterthan
    if self.value > @hardware.memval
      self.value = 1
    else
      self.value = 0
    end
  end

  def _equal
    if self.value == @hardware.memval
      self.value = 1
    else
      self.value = 0
    end
  end

  def _not
    if self.value != 0
      self.value = 0
    else
      self.value = 1
    end
  end

  def _neg
    self.value = self.value * -1
  end
end

#
# Whirl class (Interpreter)
#
class Whirl
  
  def initialize
    @rings = [OperationRing.new(self), MathRing.new(self)]
  end

  def program_position_pointer
    @program_position_pointer
  end
  
  def program_position_pointer=(ppp)
    @program_position_pointer = ppp
  end

  def memory_position_pointer
    @memory_position_pointer
  end
  
  def memory_position_pointer=(mpp)
    @memory_position_pointer = mpp
  end

  def memval
    @memval_hash[@memory_position_pointer] ||= 0
    return @memval_hash[@memory_position_pointer]
  end
  
  def memval=(value)
    @memval_hash[@memory_position_pointer] = value
  end

  def run(command_string, debug = false)
    @ring_index = 0
    @program_position_pointer = 0
    @memory_position_pointer = 0
    @memval_hash = {}
    @rings[0].reset
    @rings[1].reset
    
    puts "**** Whirl ****"

    prev_command = nil
    while (program_position_pointer < command_string.length)
      current_position = self.program_position_pointer
      command_char = command_string[current_position..current_position]
      case command_char
      when "0"
        puts "\t\tREVERSE!" if debug
        @rings[@ring_index]::reverse
        if prev_command == "0"
          cmd = @rings[@ring_index]::command
          puts "\t#{cmd[:name]}" if debug
          cmd[:proc].call
          @ring_index = 1 - @ring_index
          prev_command = nil
        else
          prev_command = command_char
        end
      when "1"
        puts "\t\tROTATE!" if debug
        @rings[@ring_index]::rotate
        prev_command = command_char
      else
        p command_char
        raise "Command not found. <#{command_char}>"
      end
      self.program_position_pointer += 1 if current_position == self.program_position_pointer
    end

    print "\n[end]\n\n"
  end
end

#
#TEST
#
whirl = Whirl.new

#TEST(1) x+y
code = "
011000001111000011111000001111000011111000001111000011111000
001100100000110011111000111000111100011001110000000001111100
01000111110011001111100010001100".gsub("\n", "")
whirl.run(code)

#TEST(2) 'Hello, World!'
#(Sample code by Kang Seonghoon.)
code = "
110011100111000001111100000001000011111000011111100000000010
000011001111100001100010000010011111000100000000000001001111
100000111110001000000000000000001000111110010000001100001111
100011000000000100111110011100111000111000001000111000001111
100000111110010000011111000110011111100001111000001111000001
110011111100001111000110011100000111000100011111000001111100
100000110000000111000001110001111100011111000111000001000001
000011000111110001000001000000011100000111001000111110001111
000001111000011111100001111110000011110000000000000000011110
000011100111000011110011111000111110001111100000100000000000
000000000000111110001110000001110000011100011100111110001000
100000000011100001111100110000000010011111000111100000111100
111100010011100000111110000011111001100111100010001111000000
000001000111110010000010011110011001110001000111110001100000
100011111000011110011100111111000111100000111100011111000000
011110000011100100001111000100011111001100011111000111100000
111001110001100111100100000000000000011111000001111100010010
000011100001111100100000100011100000111000110011110001001111
110001100000111100011111000111100000111001000011110001001111
100000111110000000011110000011110000000000000000111000001110
000011000001100000111000111000001100111110000111111001001110
000011111000001100011000001001111110000011100110011111000000
000111000001110000111100001100".gsub("\n", "")
whirl.run(code)

実行すると

$ ruby whirl_ruby.rb 
**** Whirl ****
1
3
4
[end]

**** Whirl ****
Hello, World!

[end]

という感じ。動くもんだなぁ。


公式のRuby*1は、さすがに整理されていて綺麗。
でもまぁ、仕様通りに動けばすべてOK!


これ、表示を「ピタゴラスイッチ」みたいなギミックいっぱいにすれば、見ている分には楽しいかも。
やんないけど。