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

