File Coverage

blib/lib/Language/SNUSP.pm
Criterion Covered Total %
statement 9 109 8.2
branch 0 58 0.0
condition 0 15 0.0
subroutine 3 11 27.2
pod 0 7 0.0
total 12 200 6.0


line stmt bran cond sub pod time code
1 2     2   2740 use strict; use warnings;
  2     2   7  
  2         88  
  2         14  
  2         5  
  2         4390  
2             package Language::SNUSP;
3             our $VERSION = '0.0.13';
4              
5             my $input = ''; # SNUSP input
6             my $code = ''; # 2D code matrix
7             my $width = 1; # 2D code width
8             my $pos = 0; # 2D code execution pointer
9             my $max = 0; # Maximum pos value (length of code)
10             my $dir = 1; # Execution direction:
11             # 1=right -1=left $width=down -$width=up
12             my @args = (); # Program input list
13             my @data = (0); # Data slots
14             my $index = 0; # Data slot index
15             my @stack = (); # Subroutine call stack
16             my $count = 0; # Execution counter
17              
18             # I/O handlers
19             my $put = sub { print shift };
20             my $get = sub { substr shift(@args), 0, 1 };
21              
22             # SNUSP opcode handler lookup table.
23             my %ops = (
24             '>' => sub { $data[++$index] ||= 0 },
25             '<' => sub { --$index >= 0 or $dir = 0 },
26             '+' => sub { ++$data[$index] },
27             '-' => sub { --$data[$index] },
28             ',' => sub { $data[$index] = ord $get->() },
29             '.' => sub { $put->(chr $data[$index]) },
30             '/' => sub { $dir = -$width / $dir },
31             '\\' => sub { $dir = $width / $dir },
32             '!' => sub { $pos += $dir },
33             '?' => sub { $pos += $dir if $data[$index] == 0 },
34             '@' => sub { push @stack, [ $pos + $dir, $dir ] },
35             '#' => sub { @stack ? ($pos, $dir) = @{pop @stack} : $dir = 0 },
36             "\n" => sub { $dir = 0 },
37             );
38              
39             # Runtime flags
40             my $file; # Input SNUSP file
41             my $trace = 0; # Run with trace execution
42             my $debug = 0; # Run with 2D Curses debugger
43              
44             sub run {
45 0     0 0   my ($class, @args) = @_;
46 0           $class->get_options(@args);
47              
48 0 0         open my $fh, '<', $file or die "Can't open '$file' for input.\n";
49 0           $input = do { local $/; <$fh> };
  0            
  0            
50 0           close $fh;
51              
52 0           for ($input =~ /^.*\n/gm) {
53 0           $code .= $_;
54 0 0         $width = length if length > $width;
55             }
56 0           $code =~ s/^.*/$& . ' ' x ($width - length $&) . "\n"/gem;
  0            
57 0           $max = length($code) - 1;
58 0           $width += 2;
59 0           $pos = $code =~ /\$/ * $-[0];
60              
61 0 0         $trace ? run_trace() :
    0          
62             $debug ? run_debug() :
63             run_normal();
64              
65 0           exit $data[$index];
66             }
67              
68             sub run_normal {
69 0     0 0   while ($dir) {
70 0 0         if (my $op = $ops{substr $code, $pos, 1}) { &$op }
  0            
71 0           $pos += $dir;
72 0 0 0       last if $pos < 0 or $pos > $max;
73             }
74             }
75              
76             sub run_trace {
77 0     0 0   while ($dir) {
78 0           my $char = substr $code, $pos, 1;
79 0           $count++;
80 0           print trace_line() . "\n";
81 0 0         if (my $op = $ops{$char}) { &$op }
  0            
82 0           $pos += $dir;
83 0 0 0       last if $pos < 0 or $pos > $max;
84 0 0         print "\n" if $char eq '.';
85             }
86             }
87              
88             sub run_debug {
89 0     0 0   require Curses; Curses->import;
  0            
90 0           require Term::ReadKey; Term::ReadKey->import;
  0            
91              
92 0           initscr();
93 0           ReadMode(3);
94              
95 0           my $y = 0;
96 0           addstr(
97             $y++, 0,
98             "(+)faster (-)slower (SPACE)stop/start (n)ext (q)uit",
99             );
100 0           my $top = ++$y;
101 0           addstr($y++, 0, $&) while $code =~ /.+/g;
102              
103 0           my $key = '';
104 0           my $sleep = 0.1;
105 0           my $pause = 0;
106              
107 0           my $out = '';
108 0     0     $put = sub { $out .= shift };
  0            
109              
110 0           while(1) {
111 0 0 0       if ($dir and (not $pause or $key eq "n")) {
      0        
112 0           $count++;
113 0 0         if (my $op = $ops{substr $code, $pos, 1}) { &$op }
  0            
114 0 0 0       last if $pos < 0 or $pos > $max;
115 0           $pos += $dir;
116 0 0         $pause = 1 if $dir == 0;
117             }
118              
119             {
120 0           addstr($top - 1, 0, trace_line());
  0            
121 0           addstr($y, 0, $out);
122 0           clrtoeol();
123 0           move(int($pos / $width) + $top, $pos % $width);
124 0           refresh();
125             }
126              
127 0 0         $key = ReadKey($pause ? 0 : $sleep);
128 0 0         if ($key =~ /^[\+\=]$/) {$sleep -= 0.01 if $sleep > 0.011}
  0 0          
  0 0          
    0          
    0          
    0          
129 0           elsif ($key eq '-') {$sleep += 0.01}
130 0           elsif ($key eq ' ') {$pause = not $pause}
131 0           elsif ($key eq 'n') {$pause = 1}
132             elsif ($key eq 'q') {last}
133             }
134 0           ReadMode(0);
135 0           endwin();
136             }
137              
138             sub trace_line {
139 0     0 0   my $n = 0;
140 0 0         my $display = join '', map {
141 0           $n++ == $index ? "[$_] " : "$_ "
142             } @data;
143 0           return "$count) \@${\scalar @stack} < $display>";
  0            
144             }
145              
146             sub get_options {
147 0     0 0   my ($class, @options) = @_;
148              
149 0           for my $option (@options) {
150 0 0         if ($option =~ /^(-v|--version)$/) {
151 2     2   33 no strict 'refs';
  2         4  
  2         1045  
152 0           print qq!Language::SNUSP v${"VERSION"}!;
  0            
153 0           exit 0;
154             }
155 0 0         if ($option =~ /^(-\?|-h|--help)$/) {
156 0           die usage();
157 0           exit 0;
158             }
159 0 0         if ($option =~ /^(-d|--debug)$/) {
160 0           $debug = 1;
161 0           next;;
162             }
163 0 0         if ($option =~ /^(-t|--trace)$/) {
164 0           $trace = 1;
165 0           next;
166             }
167 0 0         if ($option =~ /^-/) {
168 0           die "Unknown option: '$option'\n\n" . usage();
169             }
170 0 0         if ($file) {
171 0           push @args, $option;
172 0           next;
173             }
174 0 0         if (-f $option) {
175 0           $file = $option;
176             }
177             else {
178 0           die "Input file '$option' does not exist.\n";
179             }
180             }
181 0 0         die usage() if not $file;
182             }
183              
184             sub usage {
185 0     0 0   <<'...';
186             Usage:
187             snusp [options] input_file.snusp
188              
189             Options:
190             -d, --debug # Run program in the visual debugger
191             -t, --trace # Run with trace on
192             -v, --version # Print version and exit
193             -h, --help # Print help and exit
194             ...
195             }
196              
197             1;