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