Alarm Clock Radio/implementation.rb

From Esolang
Jump to: navigation, search

This is an implementation of Alarm Clock Radio in Ruby by User:Conor O'Brien.

Implementation

#!/usr/bin/ruby
require 'optparse'
# alarm clock radio

# because Notepad++ freaks out with "%="
def clamp(val, max)
    val % max
end

class AlarmClockRadio
    OP_HR       = ">"
    OP_MIN      = "+"
    OP_LOOP     = "["
    OP_RETZ     = "]"
    OP_READ     = ","
    OP_WRITE    = "."
    OP_SNOOZE   = "@"
    OP_PAD      = "}"
    def AlarmClockRadio.tokens(code)
        code.scan(/(([+.>,@])\2*|[\[\]}])/).map(&:first)
    end
    
    def initialize(program, padsize = 60, init_pad = 60, cell_max = 256)
        @memory = [0] * init_pad
        @padsize = padsize
        @code = AlarmClockRadio::tokens program
        @ptr = 0
        @i = 0
        @cell_max = cell_max
        @loops = {}
        
        preprocess_loops
    end
    
    def preprocess_loops
        locs = []
        (0...@code.size).each { |i|
            op = @code[i]
            if op == OP_LOOP
                locs << i
            elsif op == OP_RETZ
                src = locs.pop
                @loops[src] = i
                @loops[i] = src - 1
                # to compensate for @i += 1
            end
        }
    end
    
    def pad
        left = @memory.index &:nonzero?
        right = @memory.rindex &:nonzero?
        dist = right - left rescue 0
        dist += 2
        # inclusive distance??
        @memory = @memory[0...dist]
        @memory.concat [0] * @padsize
    end
    
    def step
        cur = @code[@i]
        head = cur[0]
        repetend = cur.size
        
        case head
            when OP_HR
                @ptr += repetend
                @ptr = clamp @ptr, @memory.size
            
            when OP_MIN
                @memory[@ptr] += repetend
                @memory[@ptr] = clamp @memory[@ptr], @cell_max
            
            when OP_LOOP
                if @memory[@ptr].zero?
                    @i = @loops[@i]
                end
            
            when OP_RETZ
                unless @memory[@ptr].zero?
                    @i = @loops[@i]
                end
            
            when OP_READ
                repetend.times {
                    @memory[@ptr] = STDIN.getc.ord
                }
            
            when OP_WRITE
                STDOUT.putc @memory[@ptr] * repetend
            
            when OP_SNOOZE
                sleep @memory[@ptr] * repetend
            
            when OP_PAD
                pad
        end
        
        @i += 1
    end
    
    def running?
        @i < @code.size
    end
    
    def run
        step while running?
    end
    
    def debug
        p @memory
    end
end

options = {
    init_cell_size: 60,
    init_pad_size: 60,
    arguments: false,
    cell_max: 256,
    debug: false,
}
NAME = File.basename __FILE__
parser = OptionParser.new { |opts|
    opts.banner = "Usage: #{NAME} [options]"
    
    opts.on("-O", "--out-cell-size=INT", Integer, "Set initial cell size (PAD)") { |v|
        options[:init_cell_size] = v
    }
    
    opts.on("-o", "--out-pad-size=INT", Integer, "Set initial pad size (PADSIZE)") { |v|
        p v
        options[:init_pad_size] = v
    }
    
    opts.on("-m", "--max=INT", Integer, "Set cell max (default: 256)") { |v|
        options[:cell_max] = v
    }
    
    opts.on("-a", "--arguments", "Read from command line arguments instead") { |v|
        options[:arguments] = v
    }
    
    opts.on("-d", "--debug", "Debugs final state") { |v|
        options[:debug] = v
    }
}
parser.parse!

if ARGV.empty?
    STDERR.puts parser
    exit
end

source = ARGV.first

if options[:arguments]
    source = ARGV.join
else
    source = File.read source
end

a = AlarmClockRadio.new(
    source,
    options[:init_pad_size],
    options[:init_cell_size],
    options[:cell_max],
)    
a.run
a.debug if options[:debug]