File Coverage

blib/lib/Language/SNUSP.pm
Criterion Covered Total %
statement 12 112 10.7
branch 0 58 0.0
condition 0 15 0.0
subroutine 4 12 33.3
pod 0 7 0.0
total 16 204 7.8


line stmt bran cond sub pod time code
1 2     2   1195 use strict; use warnings;
  2     2   4  
  2         46  
  2         5  
  2         2  
  2         1760  
2             package Language::SNUSP;
3             our $VERSION = '0.0.15';
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             "(n)ext (SPACE)stop/start (+)faster (-)slower (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 = 1;
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 2     2   18 no warnings 'uninitialized';
  2         2  
  2         550  
128 0 0         $key = ReadKey($pause ? 0 : $sleep);
129 0 0         if ($key =~ /^[\+\=]$/) {$sleep -= 0.01 if $sleep > 0.011}
  0 0          
    0          
    0          
    0          
    0          
130 0           elsif ($key eq '-') {$sleep += 0.01}
131 0           elsif ($key eq ' ') {$pause = not $pause}
132 0           elsif ($key eq 'n') {$pause = 1}
133 0           elsif ($key eq 'q') {last}
134             }
135 0           ReadMode(0);
136 0           endwin();
137             }
138              
139             sub trace_line {
140 0     0 0   my $n = 0;
141             my $display = join '', map {
142 0 0         $n++ == $index ? "[$_] " : "$_ "
  0            
143             } @data;
144 0           return "$count) \@${\scalar @stack} < $display>";
  0            
145             }
146              
147             sub get_options {
148 0     0 0   my ($class, @options) = @_;
149              
150 0           for my $option (@options) {
151 0 0         if ($option =~ /^(-v|--version)$/) {
152 2     2   9 no strict 'refs';
  2         2  
  2         543  
153 0           print qq!Language::SNUSP v${"VERSION"}!;
  0            
154 0           exit 0;
155             }
156 0 0         if ($option =~ /^(-\?|-h|--help)$/) {
157 0           die usage();
158 0           exit 0;
159             }
160 0 0         if ($option =~ /^(-d|--debug)$/) {
161 0           $debug = 1;
162 0           next;
163             }
164 0 0         if ($option =~ /^(-t|--trace)$/) {
165 0           $trace = 1;
166 0           next;
167             }
168 0 0         if ($option =~ /^-/) {
169 0           die "Unknown option: '$option'\n\n" . usage();
170             }
171 0 0         if ($file) {
172 0           push @args, $option;
173 0           next;
174             }
175 0 0         if (-f $option) {
176 0           $file = $option;
177             }
178             else {
179 0           die "Input file '$option' does not exist.\n";
180             }
181             }
182 0 0         die usage() if not $file;
183             }
184              
185             sub usage {
186 0     0 0   <<'...';
187             Usage:
188             snusp [options] input_file.snusp
189              
190             Options:
191             -d, --debug # Run program in the visual debugger
192             -t, --trace # Run with trace on
193             -v, --version # Print version and exit
194             -h, --help # Print help and exit
195             ...
196             }
197              
198             1;