⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

{{implementation|SNUSP}}{{collection|RCSNUSP}} These [[Ruby]] implementations of SNUSP profiles are partially derived from [[RCSNUSP/Tcl]]. == Core SNUSP ==

$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"
# brainfuck
#helloworld = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
#
# http://c2.com/cgi/wiki?SnuspLanguage -- "Is there a simple way to translate an arbitrary Brainfuck program into Core SNUSP?"
#
# a[bc]d
#
#translates to
#
# a!/=?\d
#   \cb/ 
#
#helloworld = <<'PROGRAM'
#++++++++++!/
### =======================
?\>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
#           \-<<<<+>+++>++++++++++>+++++++>/
#PROGRAM

helloWorld = <<'PROGRAM'
/++++!/
### =====
?\>++.>+.+++++++..+++\
\+++\ | /+>+++++++>/ /++++++++++<<.++>./
$+++/ | \+++++++++>\ \+++++.>.+++.-----\
      \==-<<<<+>+++/ /=.>.+>.--------.-/
PROGRAM
snusp = CoreSNUSP.new(helloWorld)
snusp.run
puts "done"

end

Output:

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

== Modular SNUSP ==

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"
# this program requires two characters to be read on standard input:
# 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

Output:

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

61
done

== Bloated SNUSP == User input is still line-oriented -- must press Enter before Ruby sees input.

require 'rc_modularsnusp.rb'

# here, need input to be non-blocking, so other "threads" can keep working.
# we'll have to simulate blocking when reading a character from stdin.  see read() below
require 'fcntl'
$stdin.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)

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

class BloatedSNUSP < ModularSNUSP
  Dispatch = self.superclass::Dispatch.update({
    ":" => :up,
    ";" => :down,
    "&" => :split,
    "%" => :rand,
  })
  
  def initialize(text, args={})
    super
    @dptr = [0,0]
    @threads = {}
    @thread_counter = 0
    add_thread
  end
  
  def add_thread
    @thread_counter += 1
    @threads[@thread_counter] = SNUSPThread.new(@pc.dup, @pdir, @execution_stack, @dptr.dup)
  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 read
    # we want input to be blocking.  However, actual blocking on stdin will halt
    # all other running "threads".  So, what we do here is to set stdin to be
    # non-blocking (at top of this file), and if we fail to read a character,
    # we backup the program counter so we attempt to read again at the next tick.
    char =  if @input.nil?
              begin
                $stdin.sysread(1)
              rescue SystemCallError
                nil
              end
            else
              @input.slice!(0)
            end
    if char.nil?
      p "no data to read" if $DEBUG
      # backup, so we can poll again in the next tick.
      reverse_direction = {:up => :down, :down => :up, :right => :left, :left => :right}
      @pdir = reverse_direction[@pdir]
      move
      @pdir = reverse_direction[@pdir]
    else
      @data[@dptr] = char 
      p "read '#{@data[@dptr]}'" if $DEBUG
    end
  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

# This prints out a random number of random length:
puts "Bloated: random"
random = <<'PROGRAM'
$!/+++++++++%++++++++++++++++++++++++++++++++++++++++++++++++.!/-\
  \
### ==============================
<#!?%+++++++++++++++>===\?/
PROGRAM
snusp = BloatedSNUSP.new(random)
snusp.run
puts "done"

# 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"

$stdin.read  # clear stdin before next interactive program

# programs from  http://www.baumanfamily.com/john/esoteric.html
puts "Bloated: randout"
randout = <<'PROGRAM'
    //?\!
### .=
/!=\/\/\/\/=!\\
    |\-/++\      |  ++++++++  \/
    |  +  +      |  ++++++++
    |  +  +      |  ++++++++
    |  +  +      <  \/\/\/\/
$==&\=+/  \+=%>?!/====#
    | 
    |
    |
    \==>==,==#
PROGRAM
snusp = BloatedSNUSP.new(randout)
snusp.run
puts "done"

end

Output:

```txt $ ruby rc_bloatedsnusp.rb Bloated: random 3476158785073done Bloated: BangBang !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!j!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! done Bloated: randout 35382407644156390115:505854:46783082311335:4940492329597706::155 2done
</div>