class Object

Public Instance Methods

Befunge98(source, stdout = StringIO.new, stdin = STDIN) click to toggle source
# File lib/befunge98.rb, line 3
def Befunge98 source, stdout = StringIO.new, stdin = STDIN
  code = source.tap{ |_| fail "empty source" if _.empty? }.split(?\n).map(&:bytes)

  stacks = [stack = []]
  pop = ->{ stack.pop || 0 }

  sx = sy = ox = oy = 0
  dx, dy = 1, 0
  x, y = -1, 0
  ds = [[0,1], [1,0], [0,-1], [-1,0]]
  go_west = ->{ dx, dy = *ds[3] }
  go_east = ->{ dx, dy = *ds[1] }
  go_north = ->{ dx, dy = *ds[2] }
  go_south = ->{ dx, dy = *ds[0] }
  reflect = ->{ dy, dx = [-dy, -dx] }
  move = lambda do
    y, x = y + dy, x + dx
    next if sy + oy + y >= 0 && code[sy + oy + y] && sx + ox + x >= 0 && code[sy + oy + y][sx + ox + x]
    top    = code. index{ |l| l.any?{ |i| i && i != 32 } }
    bottom = code.rindex{ |l| l.any?{ |i| i && i != 32 } }
    left  = code.map{ |l| l && l. index{ |c| c && c != 32 } }.compact.min
    right = code.map{ |l| l && l.rindex{ |c| c && c != 32 } }.compact.max
    STDERR.puts [top..bottom, left..right, [y, x], [dy, dx], code].inspect if ENV["DEBUG"]
    ty, tx = y, x
    next if loop do
      zy, zx = sy + oy + ty, sx + ox + tx
      break unless top <= zy && zy <= bottom && left <= zx && zx <= right
      break true if code[zy] && code[zy][zx] && code[zy][zx] != 32
      ty, tx = ty + dy, tx + dx
    end
    ty, tx = y - dy, x - dx
    loop do
      zy, zx = sy + oy + ty, sx + ox + tx
      break if dy > 0 && zy <  top || dy < 0 && zy > bottom ||
               dx > 0 && zx < left || dx < 0 && zx > right
      y, x = ty, tx if code[zy] && code[zy][zx] && code[zy][zx] != 32   # TODO: should this execute if it's in a 'hole'?
      ty, tx = ty - dy, tx - dx
    end
  end

  stringmode = jump_over = false
  iterate = 0

  get = ->{ (code[sy + oy + y] || [])[sx + ox + x] || 32 }
  loop do
    if iterate.zero?
      move[]
    else
      iterate -= 1
    end
    char = get[]
    STDERR.puts [[y, x], char.chr, stack].inspect if ENV["DEBUG"]

    next stack << char if stringmode && char.chr != ?"  # TODO: "SGML-style"
    next unless (33..126).include? char
    next if jump_over && char.chr != ?;
    case char.chr
      when ?; ; jump_over ^= true

      ### 93
      when ?" ; stringmode ^= true
      when ?0..?9 ; stack << char.chr.to_i
      when ?$ ; pop[]
      when ?: ; stack.concat [pop[]] * 2
      when ?\\ ; stack.concat [pop[], pop[]]
      when ?# ; move[]  # "adds the delta to the position"
      when ?> ; go_east[]
      when ?< ; go_west[]
      when ?^ ; go_north[]
      when ?v ; go_south[]
      when ?? ; [go_east, go_west, go_north, go_south].sample[]
      when ?+ ; stack << (pop[] + pop[])
      when ?- ; stack << -(pop[] - pop[])
      when ?* ; stack << (pop[] * pop[])
      when ?/ ; b, a = pop[], pop[]; stack << (b.zero? ? 0 : a / b)
      when ?% ; b, a = pop[], pop[]; stack << (b.zero? ? 0 : a % b)
      when ?| ; pop[].zero? ? go_south[] : go_north[]
      when ?_ ; pop[].zero? ? go_east[] : go_west[]
      when ?~ ; if c = stdin.getbyte then stack << c else reflect[] end
      when ?&
        getc = ->{ stdin.getc or (reflect[]; throw nil) }
        catch nil do
          nil until (?0..?9).include? c = getc[]
          while (?0..?9).include? cc = getc[] ; c << cc end
          stack << c.to_i
        end
      when ?, ; stdout.print pop[].chr
      when ?. ; stdout.print "#{pop[]} "
      when ?! ; stack << (pop[].zero? ? 1 : 0)
      when ?` ; stack << (pop[]<pop[] ? 1 : 0)
      ### Funge-98 Final Specification:
      # A Funge-98 program should also be able to rely on the memory mechanism acting as
      # if a cell contains blank space (ASCII 32) if it is unallocated, and setting memory
      # to be full of blank space cells upon actual allocation (program load, or p instruction)
      when ?g
        y, x = [y, x].tap{ y, x = pop[], pop[]; stack << get[] }
      when ?p
        py, px, v = pop[], pop[], pop[]
        if 0 > (d = sx + ox + px)
          sx -= d
          code.map!{ |l| Array.new(-d, 32) + l }
        end
        if 0 > (d = sy + oy + py)
          sy -= d
          code = Array.new(-d, []) + code
        end
        code[sy + oy + py] ||= []
        code[sy + oy + py][sx + ox + px] = v
      when ?@ ; return Struct.new(:stdout, :stack, :exitcode).new(stdout, stack, 0)

      ### 98
      when ?q ; return Struct.new(:stdout, :stack, :exitcode).new(stdout, stack, pop[])
      when ?a..?f ; stack << char - ?a.ord + 10
      when ?n ; stack.clear
      when ?'
        move[]
        stack << get[]
      when ?s
        move[]
        code[sy + oy + y] ||= []
        code[sy + oy + y][sx + ox + x] = pop[]
      when ?[ ; dy, dx = ds[(ds.index([dy, dx]) - 1) % 4]
      when ?] ; dy, dx = ds[(ds.index([dy, dx]) + 1) % 4]
      when ?w ; dy, dx = ds[(ds.index([dy, dx]) - (pop[] <=> pop[])) % 4]
      when ?r ; reflect[]
      when ?x ; dy, dx = [pop[], pop[]]
      when ?j
        if 0 < t = pop[]
          t.times{ move[] }
        else
          reflect[]
          (-t).times{ move[] }
          reflect[]
        end
      when ?k
        iterate = pop[]
        move[]
        char = get[]
        fail if char == 32 || char == ?;.ord
      when ?{
        toss = if 0 > n = pop[]
          stack.concat [0] * -n
          []
        else
          stack.pop n
        end
        stack << ox << oy
        ox += x + dx
        oy += y + dy
        stacks << stack = toss
      when ?}
        if 1 == stacks.size
          reflect[]
        elsif 0 > n = pop[]
          stacks.pop
          stack = stacks.last
          oy, ox = pop[], pop[]
          stack.pop -n
        else
          t = stack.pop n
          stacks.pop
          stack = stacks.last
          oy, ox = pop[], pop[]
          stack.concat t
        end
      when ?u
        if 1 == stacks.size
          reflect[]
        elsif 0 > n = pop[]
          -n.times{ stack[-2] << pop[] }
        else
          n.times{ stack << stack[-2].pop }
        end
      when ?i
        fail "TODO"
        s = ""
        s << c.chr until (c = pop[]).zero?
        f = pop[]
        va = [pop[], pop[]]
        # ...
      when ?o
        fail "TODO"
        s = ""
        s << c.chr until (c = pop[]).zero?
        f = pop[]
        # y, x = pop[], pop[]
        # h, w = pop[], pop[]
        # ...
      when ?=
        s = ""
        s << c.chr until (c = pop[]).zero?
        system s, out: File::NULL, err: File::NULL
        stack << $?.exitstatus
      when ?(, ?)
        fail "TODO"
        # pop[].times.inject(0){ |i,| i * 256 + pop[] }
        # reflect[]
      when ?y
        y = pop[]
        ss = stack.size
        stack << 0
        stack << 255
        stack << 0
        stack << Gem.loaded_specs.values.find{ |_| _.lib_dirs_glob == File.absolute_path(__dir__) }.version.segments.join.to_i
        stacK << 0
        stack << File::SEPARATOR.ord
        stack << 2
        stack << 0
        stack << 0
        stack << y << x
        stack << dy << dx
        stack << oy << ox
        stack << [0, 0] # TODO: 1 vector containing the least point which contains a non-space cell, relative to the origin (env)
        stack << [0, 0] # TODO: 1 vector containing the greatest point which contains a non-space cell, relative to the least point (env)
        t = Time.now
        stack << (t.year - 1900) * 256 * 256 + t.month * 256 + t.day
        stack << t.hour * 256 * 256 + t.min * 256 + t.sec
        stack << stacks.size
        stack.concat stacks.map &:size; stack[-1] = ss
        stack.concat ARGV.map{ |_| _.chars.map(&:ord) + [0] } + [0]
        stack.concat ENV.map{ |k, v| "#{k}=#{v}".chars.map(&:ord) + [0] } + [0]
        stack << stack[1-y].tap{ stack = stack.take ss } if y > 0

    end
  end
end