#!/usr/bin/env perl
# A basic, non-optimizing Re:direction interpreter.
# Copyright (C) 2018 Alex Smith
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
use utf8;
use warnings;
use strict;
use feature 'unicode_strings';
use open IN => ":bytes", OUT => ":utf8", ":std";
use Text::Tabs;
use Encode qw/decode/;
use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
our $VERSION = 0.1;
sub HELP_MESSAGE {
print "-d\tPrint debug output";
print "-f\tPretty-print the packed and unpacked program to STDERR\n";
print "-p\tInterpret input as a packed program\n";
print "-A\tInput and output as character codes, not integers\n";
}
our ($opt_f, $opt_p, $opt_d, $opt_A);
getopts('fdpA');
undef $/;
$| = 1;
my $program = <>; <>;
my $input = <> // '';
# Unpack the program.
if ($opt_p) {
# We're using the packed representation.
my @charset = (" ", "♦", "◄", "▲", "►", "▼", "\n");
my $unpacked = '';
my $spaceweight = 3;
for my $char (split //, $program) {
my $code = ord $char;
if ($code == 0 || $code >= 252) {
# We use a special representation for long strings of
# spaces. Code 0 is " " for consistency with the other
# codes in the 0..251 range. However, codes 252 to 255
# represent strings of twice, three times, etc. as much.
# To avoid redundancy when multiple whitespace is used in
# a row, we use (little-endian) bijective base 5, i.e.
# each successive whitespace character has 5 times the
# width.
my $value = ($code == 0 ? 1 : $code - 250);
$unpacked .= ' ' x ($value * $spaceweight);
$spaceweight *= 5;
} else {
# Three packed characters, which aren't all spaces.
# Only the third can be a newline.
use integer;
$unpacked .= $charset[$code % 6];
$unpacked .= $charset[($code / 6) % 6];
$unpacked .= $charset[$code / 36];
$spaceweight = 3;
}
$program = $unpacked;
}
} else {
$program = decode('UTF-8', $program, Encode::FB_CROAK);
$program = expand($program);
$program =~ y/\x04\x11\x1E\x10\x1F+<^>v/♦◄▲►▼♦◄▲►▼/;
}
# Now pack the program.
my $unpacked = $program;
my $packed = '';
my %charset = (" " => 0, "♦" => 1, "◄" => 2,
"▲" => 3, "►" => 4, "▼" => 5, "\n" => 6);
my @spaceset = (undef, chr 0, chr 252, chr 253, chr 254, chr 255);
while (length $program && $program =~ s/^(\ {3,}|...|.?.?\n|.?.?\z)//) {
my $section = $1;
$section =~ s/(\n?)\z/ $1/ while length $section < 3;
while ((length $section) % 3) {
# It must be a whitespace section. Trim it down to a
# multiple of 3.
$program = " $program";
$section =~ s/\ \z// or die;
}
if ($section =~ /^ +\z/) {
my $l = length($section) / 3;
while ($l > 0) {
my $c = $l % 5;
$c or $c = 5;
$packed .= $spaceset[$c];
$l -= $c;
$l /= 5;
}
} else {
$section =~ /^(.)(.)(.)$/s or die;
my $char1 = $charset{$1} || 0;
my $char2 = $charset{$2} || 0;
my $char3 = $charset{$3} || 0;
my $code = $char3 * 36 + $char2 * 6 + $char1;
$packed .= chr $code;
}
}
$program = $unpacked;
$program =~ s/ +\n/\n/g;
$program =~ s/[ \n]+\z//;
my $ulength = length $program;
my $plength = length $packed;
if ($opt_f) {
print STDERR "Program ($ulength characters):\n
\n$program\n
\n\n";
print STDERR "Hexdump of packed representation ($plength bytes):\n\n",
unpack("H*", $packed), "\n
\n\n";
}
my %dx = ("◄" => -1, "►" => 1);
my %dy = ("▲" => -1, "▼" => 1);
my $dir = "►";
my $x = -1;
my $y = 0;
my $lx = -1;
my $ly = -1;
my @queue = ();
my @plines = split "\n", $program;
my $width = 0;
my $height = @plines;
length > $width and $width = length for @plines;
my @pmatrix = map [split //, $_], @plines;
$width and $height or
(print STDERR "Error: program has zero width or height\n"), exit 1;
my @inlist = ();
if ($opt_A) {
@inlist = map ord, split //, $input;
} else {
push @inlist, $1 while $input =~ s/(\d+)//;
}
for my $inelem (@inlist) {
push @queue, "►" for 1..$inelem;
push @queue, "▼";
}
while (1) {
$x += $dx{$dir} || 0;
$y += $dy{$dir} || 0;
$x += $width; $x %= $width;
$y += $height; $y %= $height;
if ($opt_d) {
local $" = "";
printf STDERR "(%6d,%6d) %s\n", $x, $y, "@queue";
}
my $curcell = $pmatrix[$y][$x] || ' ';
if ($dx{$curcell} || $dy{$curcell}) {
if ($lx == $x && $ly == $y) {
my $queuestr;
{ local $" = ""; $queuestr = "@queue"; }
my @outlist = ();
$queuestr =~ s/[^▼►]//g;
push @outlist, length $1 while $queuestr =~ s/^(►*)▼//;
print "@outlist\n" unless $opt_A;
print map chr, @outlist if $opt_A;
exit 0;
}
$lx = $x;
$ly = $y;
push @queue, $curcell;
$dir = $curcell;
} elsif ($curcell eq '♦') {
@queue or (print STDERR "Error: Queue underflow!\n"), exit 1;
$dir = shift @queue;
$lx = -1;
}
}