Execute SNUSP/Ruby

From Rosetta Code
Revision as of 15:18, 1 August 2009 by rosettacode>Glennj (→‎Modular SNUSP: pass user input as a string)
Execute SNUSP/Ruby is an implementation of SNUSP. Other implementations of SNUSP.
Execute SNUSP/Ruby is part of RCSNUSP. You may find other members of RCSNUSP at Category:RCSNUSP.

With gratitude to RCSNUSP/Tcl

Core SNUSP

<lang ruby>$stdout.sync = true $stdin.sync = true

class CoreSNUSP

 Bounce = {
   :ruld => {:right => :up,   :left => :down, :up => :right, :down => :left },
   :lurd => {:right => :down, :left => :up,   :up => :left,  :down => :right}
 }
 Delta = {:up => [-1, 0], :down => [1, 0], :left => [0,-1], :right => [0, 1]}
 Dispatch = Hash.new(:unknown).update({
   ">" => :right,
   "<" => :left,
   "+" => :incr,
   "-" => :decr,
   "/" => :ruld,
   "\\"=> :lurd,
   "?" => :skipz,
   "!" => :skip,
   "." => :write,
   "," => :read,
   '"' => :dump,
 })
 def initialize(text, args={})
   @data = Hash.new(0)
   @dptr = 0
   @program, @pc = read_program(text)
   @height = @program.size
   @width  = @program[0].nil? ? 0 : @program[0].size
   @pdir = :right
   @input = args[:input]
 end
 def read_program(text)
   pc = [0,0]
   program = []
   max = 0
   text.each_line.each_with_index do |line, lineno|
     line.chomp!
     max = [max, line.length].max
     if not (idx = line.index("$")).nil?
       pc = [lineno, idx]
     end
     program << line.split(//)
   end
   # pad short lines
   program.map! {|row| row.concat([""] * (max - row.size))}
   [program, pc]
 end
 def current_command
   @program[@pc[0]].nil? and return nil
   @program[@pc[0]][@pc[1]]
 end
 def run
   p @program if $DEBUG
   command = current_command
   p [@pc, command] if $DEBUG
   catch :have_exit_command do
     until command.nil?
       self.send Dispatch[command]
       move
       command = current_command
       p [@pc, command] if $DEBUG
     end
   end
 end
 def move
   delta = Delta[@pdir]
   @pc = [ @pc[0] + delta[0], @pc[1] + delta[1]]
   if @pc[0] < 0 or @pc[0] > @height or @pc[1] < 0 or @pc[1] > @width
     raise IndexError, "program counter out of bounds: #{@pc.inspect}"
   end
 end
 def right
   @dptr += 1
 end
 def left
   @dptr -= 1
 end
 def incr
   if @dptr < 0
     raise IndexError, "data pointer less than zero: #@dptr"
   end
   @data[@dptr] += 1
   p ["data:",@dptr, @data[@dptr]] if $DEBUG
 end
 def decr
   if @dptr < 0
     raise IndexError, "data pointer less than zero: #@dptr"
   end
   @data[@dptr] -= 1
   p ["data:",@dptr, @data[@dptr]] if $DEBUG
 end
 def ruld
   p @pdir if $DEBUG
   @pdir = Bounce[:ruld][@pdir]
   p @pdir if $DEBUG
 end
 def lurd
   p @pdir if $DEBUG
   @pdir = Bounce[:lurd][@pdir]
   p @pdir if $DEBUG
 end
 def skipz
   p ["data:",@dptr, @data[@dptr]] if $DEBUG
   move if @data[@dptr] == 0
 end
 def skip
   move
 end
 def write
   p "output: #{@data[@dptr]} = '#{@data[@dptr].chr}'" if $DEBUG
   $stdout.print @data[@dptr].chr
 end
 def read
   @data[@dptr] =  if @input.nil?
                     # blocking read from stdin
                     $stdin.getc
                   else
                     @input.slice!(0)
                   end
   p "read '#{@data[@dptr]}'" if $DEBUG
 end
 def dump
   p [@dptr, @data]
 end
 def unknown; end

end

if __FILE__ == $0

puts "Empty Program 1" emptyProgram = snusp = CoreSNUSP.new(emptyProgram) snusp.run puts "done"

puts "Hello World"

  1. brainfuck
  2. helloworld = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
  3. http://c2.com/cgi/wiki?SnuspLanguage -- "Is there a simple way to translate an arbitrary Brainfuck program into Core SNUSP?"
  4. a[bc]d
  5. translates to
  6. a!/=?\d
  7. \cb/
  8. helloworld = <<'PROGRAM'
  9. ++++++++++!/=============================?\>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
  10. \-<<<<+>+++>++++++++++>+++++++>/
  11. PROGRAM

helloWorld = <<'PROGRAM' /++++!/===========?\>++.>+.+++++++..+++\ \+++\ | /+>+++++++>/ /++++++++++<<.++>./ $+++/ | \+++++++++>\ \+++++.>.+++.-----\

     \==-<<<<+>+++/ /=.>.+>.--------.-/

PROGRAM snusp = CoreSNUSP.new(helloWorld) snusp.run puts "done"

end</lang>

Output:

$ ruby rc_coresnusp.rb
Empty Program 1
done
Hello World
Hello World!
done

Modular SNUSP

<lang ruby>require 'rc_coresnusp.rb'

class ModularSNUSP < CoreSNUSP

 Dispatch = self.superclass::Dispatch.update({
   "@" => :enter,
   "#" => :leave,
 })
 
 def initialize(text, args={})
   super
   @execution_stack = []
 end
 
 def enter
   p @execution_stack if $DEBUG
   current_pc = @pc
   move
   @execution_stack << [@pc, @pdir]
   p @execution_stack if $DEBUG
   @pc = current_pc
 end
 
 def leave
   p @execution_stack if $DEBUG
   throw :have_exit_command if @execution_stack.empty?
   @pc, @pdir = @execution_stack.pop
   p @execution_stack if $DEBUG
 end

end

if __FILE__ == $0

puts "Empty Program 2" emptyProgram = '$#' snusp = ModularSNUSP.new(emptyProgram) snusp.run puts "done"

puts "Goodbye World" goodbyeWorld = <<'PROGRAM' @\G.@\o.o.@\d.--b.@\y.@\e.>@\comma.@\.<-@\W.+@\o.+++r.------l.@\d.>+.!=>\

|   |     \@------|#  |    \@@+@@++|+++#-    \\               -  /+++++/
|   \@@@@=+++++#  |   \===--------!\===!\-----|-------#-------/  \+++++.# nl
\@@+@@@+++++#     \!#+++++++++++++++++++++++#!/

PROGRAM snusp = ModularSNUSP.new(goodbyeWorld) snusp.run puts "done"

puts "Modular: Ackerman"

  1. this program requires two characters to be read on standard input:
  2. the numbers for j and i in that order

ackerman = <<'PROGRAM' @\\ /==!/atoi------------------------------------------------# / / | | /=========\!==\!====\ (recursion) >\,@/>,@/=ACK==!\?\<+# | | | A(0,j) -> j+1 > j i \<?\+>-@/# | | A(i,0) -> A(i-1,1) @ \@\>@\->@/@\<-@/# A(i,j) -> A(i-1, A(i, j-1)) \=\ [0]->[2] | | | > + #/?<<+>>-\!==/ | \==!/-<<+>>?\# [2]->[0] + + \->>+<<?/# | #\?>>+<<-/ + + | + + \@\>>>@\<<# copy [1]->[3] and advance + + [1]->[3][4] | | + + #/?<<<+>+>>-\!====/ \==!/-<<<+>>>?\# [4]->[1] + + \->>+>+<<<?/# #\?>>>+<<<-/ + + + + print newline + + /-\ + \+++CR.---NL.!\?/<<# | | /<=!/!#++++\ \========@\@/.>@/.# /+\ atoi

         |       /=\/+++\ wiki
/==div===/       \!\++++/
|                   \++/
|    /-\             \/
\?\<!\?/#!===+<<<\      /-\ wiki
  \<==@\>@\>>!/?!/=<?\>!\?/<<#
       |  |  #\->->+</
       \=!\=?!/->>+<<?\#
             #\?<<+>>-/

PROGRAM snusp = ModularSNUSP.new(ackerman, :input => '33') # calculate A(3,3) snusp.run puts puts "done"

end</lang> Output:

$ echo '33' | ruby rc_modularsnusp.rb
Empty Program 2
done
Goodbye World
Goodbye, World!
done
Modular: Ackerman

61done

Bloated SNUSP

The threading is not correctly implemented -- the "BangBang" example is not working.

<lang ruby>require 'rc_modularsnusp.rb'

SNUSPThread = Struct.new(:pc, :pdir, :execution_stack, :dptr)

class BloatedSNUSP < ModularSNUSP

 Dispatch = self.superclass::Dispatch.update({
   ":" => :up,
   ";" => :down,
   "&" => :split,
   "%" => :rand,
 })
 
 def initialize(text)
   super
   @dptr = [0,0]
   @threads = {}
   @thread_counter = 0
   add_thread
 end
 
 def add_thread
   @thread_counter += 1
   @threads[@thread_counter] = SNUSPThread.new(@pc, @pdir, @execution_stack, @dptr)
 end
 
 def run
   p @program if $DEBUG
   
   until @threads.empty?
     stopped_thread_ids = []
     # threads are run in arbitrary order
     p @threads if $DEBUG
     @threads.each_key do |thread_id|
       thread = @threads[thread_id]
       
       @pc = thread.pc
       @pdir = thread.pdir
       @dptr = thread.dptr
       @execution_stack = thread.execution_stack
       
       if tick(thread_id)
         # save state
         thread.pc = @pc
         thread.pdir = @pdir
         thread.dptr = @dptr
         thread.execution_stack = @execution_stack
       else
         stopped_thread_ids << thread_id
       end
     end
     stopped_thread_ids.each {|id| @threads.delete(id)}
   end
 end
 
 def tick(thread_id)
   command = current_command
   p [thread_id, @pc, command] if $DEBUG
   return false if command.nil?  # thread complete
   return false if self.send(Dispatch[command]) == :have_exit_command
   move
   command = current_command
   p [@pc, command] if $DEBUG
   true
 end
 
 def leave
   p @execution_stack if $DEBUG
   return :have_exit_command if @execution_stack.empty?
   @pc, @pdir = @execution_stack.pop
   p @execution_stack if $DEBUG
 end
 
 def left
   @dptr[0] -= 1
 end
 def right
   @dptr[0] += 1
 end
 def up
   @dptr[1] -= 1
 end
 def down
   @dptr[1] += 1
 end
 def incr
   if @dptr[0] < 0 or @dptr[1] < 0
     raise IndexError, "data pointer less than zero: #{@dptr.inspect}"
   end
   @data[@dptr] += 1
   p ["data:",@dptr, @data[@dptr]] if $DEBUG
 end
 def decr
   if @dptr[0] < 0 or @dptr[1] < 0
     raise IndexError, "data pointer less than zero: #{@dptr.inspect}"
   end
   @data[@dptr] -= 1
   p ["data:",@dptr, @data[@dptr]] if $DEBUG
 end
 
 def split
   move
   add_thread
 end
 
 def rand
   @data[@dptr] = Kernel.rand(1+@data[@dptr])
 end

end

if __FILE__ == $0

  1. This prints out a random number of random length:

puts "Bloated: random" random = <<'PROGRAM' $!/+++++++++%++++++++++++++++++++++++++++++++++++++++++++++++.!/-\

 \====================================<#!?%+++++++++++++++>===\?/

PROGRAM snusp = BloatedSNUSP.new(random) snusp.run puts "done"

  1. This (from the esolangs wiki) prints exclamation marks until you press a key:

puts "Bloated: BangBang" bang = <<'PROGRAM'

                   /==.==<==\
                   |        |
    /+++++++++++==&\==>===?!/==<<==#
    \+++++++++++\  |

$==>==+++++++++++/ \==>==,==# PROGRAM snusp = BloatedSNUSP.new(bang) snusp.run puts "done"

end </lang>

Output:

$ ruby rc_bloatedsnusp.rb
Bloated: random
426844146737605042562646108481049454738617585534012246132619612443400509062done
Bloated: BangBang

The program hangs there, awaiting user input, without printing any exclamation marks. Hmmm.