Home Bobbity flop

Ruby ETA Interpreter

This is the implementation of Mike Taylor's ETA language in Ruby.

In case you haven't come across Ruby before, it is an extremely well designed object oriented programming language, which has a number of interesting features. I have tried to use some of these in this implementation, but to learn more visit the Ruby home page.

This interpreter comes in at under 100 lines of Ruby, less than 1.8k of code - not quite as small as the smalleta perl script, but nearly. But I've not compressed it in any way, it just came out like that. Such is the beauty of Ruby. I didn't even feel the need to put any comments in. Such is the beauty of Ruby.

Implementation Details

I thought it would be worthwhile giving a short commentary on how the thing works, because it may not be obvious to a non Ruby programmer. And I didn't put any comments in (see above).

The guts of the program centres around the ETA class, which does just about everything.

When you make a new ETA object, it reads in its input program, and sets itself up ready to go.
Note that we override the stack instance's pop method in order to detect stack underflows at this point.

The main body of the program steps through the input program using the eachnextline iterator, and the eachchar iterator.

The very inside of this loop calls the correct ETA object method according to the character in the input. Notice there is something particularly cunning here... when we begin to interpret a number (the 'n' command) we actually override the send method that usually calls the correct command method. This new method interprets the number, and restores the parent's send method when it's finished.

The error handling comes out particularly nicely. The Ruby raise, and begin-rescue-end mechanism is very easy to use, and works a treat.

There's not much else to say - it really is pretty self documenting...

Source Code

You can download the program here.

#  ETA interpreter  S.D.Sykes
#  v1.0    9th May 2001
#  v1.1    22 July 2001 Added exceptions for bad transfers and out of range chars on output
#  v1.2    30 Oct  2003 Changed % to remainder to match ref interpreter results for negative modulos

class ETA
  attr :curline
  def initialize
    @stack= Array.new
    @prog= $<.readlines
    @curline= 1
    @curnumber= 0
    def @stack.pop
      p= super
      raise if p.nil?
      p
    end
  end

  def eachnextline
    while @curline < @prog.size.next && @curline > 0
      @nextline= @curline.next
      prevline = @curline
      yield @curline
      @curline= @nextline
    end
    if (@curline > @prog.size.next || @curline < 0)
      raise TransferError.new(prevline)
    end
  end

  def eachchar(lineno)
    @prog[lineno-1].each_byte {|c| yield c.chr.downcase if lineno == @curline}
  end
  
  def e
    a= @stack.pop
    b= @stack.pop
    @stack.push b/a
    @stack.push b.remainder(a)
  end
  
  def a
    @stack.push(@nextline)
  end

  def s
    a= @stack.pop
    b= @stack.pop
    @stack.push(b-a)
  end

  def i
    a= $stdin.getc
    a= -1 if a.nil?
    @stack.push(a)
  end

  def h
    a= @stack.pop
    raise if a.abs.next > @stack.size
    if a > 0
      @stack.push(@stack[-a-1])
      @stack.delete_at(-a-2)
    else
      @stack.push(@stack[a-1])
    end
  end
  
  def o
    o= @stack.pop
    if (o < 256 && o >= 0)
      print o.chr
    else
      raise OutputError
    end
  end

  def t
     a= @stack.pop
     if @stack.pop.nonzero?
       @nextline= a
       @curline= 0
     end
  end

  def n
    def self.send(c)
      if c == "e"
        @stack.push(@curnumber)
        @curnumber= 0
        class <<self
          remove_method :send
        end
      else
        @curnumber= @curnumber * 7 + ["h","t","a","o","i","n","s"].index(c)
      end
    end
  end
end

class OutputError < Exception
end
class TransferError < Exception
  attr_reader :prevline
  def initialize(pl)
    @prevline = pl
  end
end

begin
  p=ETA.new
  p.eachnextline do |lineno|
    p.eachchar(lineno) {|c| p.send(c) if c=~ /e|t|a|o|n|i|s|h/}
  end
rescue OutputError
  $stderr.print "Output char out of range at line #{p.curline}\n"
rescue TransferError => te
  $stderr.print "Transfer error: non existent line #{p.curline} from line #{te.prevline}\n"
rescue RuntimeError
  $stderr.print "Stack underflow at line #{p.curline}\n"
rescue ZeroDivisionError
  $stderr.print "Division by zero at line #{p.curline}\n"
rescue Interrupt
  $stderr.print "Interrupted at line #{p.curline}\n"
end