File Coverage

blib/lib/Language/Befunge/Storage/2D/Sparse.pm
Criterion Covered Total %
statement 121 121 100.0
branch 42 42 100.0
condition 2 2 100.0
subroutine 20 20 100.0
pod 12 12 100.0
total 197 197 100.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of Language::Befunge.
3             # Copyright (c) 2001-2009 Jerome Quelin, all rights reserved.
4             #
5             # This program is free software; you can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7             #
8             #
9              
10             package Language::Befunge::Storage::2D::Sparse;
11              
12 65     65   75954 use strict;
  65         139  
  65         3578  
13 65     65   370 use warnings;
  65         328  
  65         1870  
14              
15 65     65   459 use Carp;
  65         123  
  65         6412  
16 65     65   69180 use aliased 'Language::Befunge::Vector' => 'LBV';
  65         57583  
  65         411  
17 65     65   90752 use Readonly;
  65         288007  
  65         4872  
18              
19 65     65   631 use base qw{ Language::Befunge::Storage };
  65         144  
  65         59877  
20              
21             use Class::XSAccessor
22 65         1150 accessors => {
23             _storage => '_storage',
24             _xmin => '_xmin',
25             _xmax => '_xmax',
26             _ymin => '_ymin',
27             _ymax => '_ymax',
28 65     65   575 };
  65         132  
29              
30             Readonly my $SPACE => ' ';
31              
32              
33             # -- CONSTRUCTOR
34              
35             #
36             # my $storage = LBS::2D::Sparse->new;
37             #
38             # Create a new storage.
39             #
40             sub new {
41 78     78 1 4001 my ($class, $dims) = @_;
42 78   100     723 $dims //= 2;
43 78 100       600 croak("$class is only useful for 2-dimensional storage.")
44             unless $dims == 2;
45 76         181 my $self = {};
46 76         481 bless $self, $class;
47 76         297 $self->clear;
48 76         848 return $self;
49             }
50              
51              
52              
53             # -- PUBLIC METHODS
54              
55             #- storage update
56              
57             #
58             # $storage->clear;
59             #
60             # Clear the storage.
61             #
62             sub clear {
63 286     286 1 2654 my ($self) = @_;
64 286         906 $self->_xmin(0);
65 286         750 $self->_xmax(0);
66 286         621 $self->_ymin(0);
67 286         675 $self->_ymax(0);
68 286         2042 $self->_storage( {} );
69             }
70              
71              
72             #
73             # my $size = $storage->store_binary( $code [, $position] );
74             #
75             # Store the given $code at the specified $position (defaulting to the
76             # origin coordinates).
77             #
78             # Return the size of the code inserted, as a vector.
79             #
80             # The code is a string, representing a block of Funge code. This is
81             # binary insertion, that is, EOL sequences are stored in Funge-space
82             # instead of causing the dimension counters to be resetted and
83             # incremented.
84             #
85             sub store_binary {
86 370     370 1 1402 my ($self, $code, $position) = @_;
87              
88 370         475 my $offset = $position;
89 370 100       926 $offset = LBV->new(0,0) unless defined $offset;
90 370         1057 my $x = $offset->get_component(0);
91 370         746 my $y = $offset->get_component(1);
92 370         771 my $href = $self->_storage;
93              
94             # enlarge min values if needed
95 370 100       1164 $self->_xmin($x) if $self->_xmin > $x;
96 370 100       1023 $self->_ymin($y) if $self->_ymin > $y;
97              
98             # store data
99 370         1683 foreach my $chr ( split //, $code ) {
100 4618 100       12871 $href->{"$x,$y"} = ord $chr
101             unless $chr eq $SPACE; # spaces do not overwrite - cf befunge specs
102 4618         31029 $x++;
103             }
104              
105             # enlarge max values if needed
106 370         1235 $x--; # one step too far
107 370 100       1391 $self->_xmax($x) if $self->_xmax < $x;
108 370 100       1102 $self->_ymax($y) if $self->_ymax < $y;
109              
110 370         2589 return LBV->new(length $code, 1);
111             }
112              
113              
114             #
115             # my $size = $storage->store( $code [, $position] );
116             #
117             # Store the given $code at the specified $position (defaulting to the
118             # origin coordinates).
119             #
120             # Return the size of the code inserted, as a vector.
121             #
122             # The code is a string, representing a block of Funge code. Rows are
123             # separated by newlines.
124             #
125             sub store {
126 224     224 1 18932 my ($self, $code, $position) = @_;
127              
128 224         333 my $offset = $position;
129 224 100       1926 $offset = LBV->new(0,0) unless defined $offset;
130 224         1079 my $dy = LBV->new(0,1);
131              
132             # support for any eol convention
133 224         748 $code =~ s/\r\n/\n/g;
134 224         410 $code =~ s/\r/\n/g;
135 224         947 my @lines = split /\n/, $code;
136              
137             # store data
138 224         401 my $maxlen = 0;
139 224         466 foreach my $line ( @lines ) {
140 361 100       1056 $maxlen = length($line) if $maxlen < length($line);
141 361         992 $self->store_binary( $line, $offset );
142 361         4556 $offset += $dy;
143             }
144              
145 224         1875 return LBV->new($maxlen, scalar(@lines));
146             }
147              
148              
149             # $storage->set_value( $offset, $value );
150             #
151             # Write the supplied $value in the storage at the specified $offset.
152             #
153             # /!\ As in Befunge, code and data share the same playfield, the
154             # number stored can be either an instruction or raw data (or even
155             # both... Eh, that's Befunge! :o) ).
156             #
157             sub set_value {
158 27     27 1 149 my ($self, $v, $val) = @_;
159 27         106 my ($x, $y) = $v->get_all_components();
160              
161             # ensure we can set the value.
162 27 100       140 $self->_xmin($x) if $self->_xmin > $x;
163 27 100       130 $self->_xmax($x) if $self->_xmax < $x;
164 27 100       186 $self->_ymin($y) if $self->_ymin > $y;
165 27 100       1317 $self->_ymax($y) if $self->_ymax < $y;
166 27         176 $self->_storage->{"$x,$y"} = $val;
167             }
168              
169              
170              
171             #- data retrieval
172              
173             #
174             # my $dims = $storage->get_dims;
175             #
176             # Return the dimensionality of the storage. For this module, the value is
177             # always 2.
178             #
179 4     4 1 3221 sub get_dims { 2 }
180              
181              
182             #
183             # my $vmin = $storage->min;
184             #
185             # Return a LBV pointing to the lower bounds of the storage.
186             #
187             sub min {
188 6474     6474 1 8844 my ($self) = @_;
189 6474         51272 return LBV->new($self->_xmin, $self->_ymin);
190             }
191              
192              
193             #
194             # my $vmax = $storage->max;
195             #
196             # Return a LBV pointing to the upper bounds of the storage.
197             #
198             sub max {
199 6474     6474 1 8093 my ($self) = @_;
200 6474         51961 return LBV->new($self->_xmax, $self->_ymax);
201             }
202              
203              
204             #
205             # my $val = $storage->get_value( $offset );
206             #
207             # Return the number stored in the torus at the specified $offset. If
208             # the value hasn't yet been set, it defaults to the ordinal value of a
209             # space (ie, #32).
210             #
211             # /!\ As in Befunge, code and data share the same playfield, the
212             # number returned can be either an instruction or raw data (or even
213             # both... Eh, that's Befunge! :o) ).
214             #
215             sub get_value {
216 12234     12234 1 17046 my ($self, $v) = @_;
217 12234         34050 my ($x, $y) = $v->get_all_components;
218 12234         22525 my $href = $self->_storage;
219 12234 100       79610 return exists $href->{"$x,$y"}
220             ? $href->{"$x,$y"}
221             : 32;
222             }
223              
224              
225             #
226             # my $chr = $storage->get_char( $offset );
227             #
228             # Return the character stored in the torus at the specified $offset. If
229             # the value is not between 0 and 255 (inclusive), get_char will return a
230             # string that looks like "".
231             #
232             # /!\ As in Befunge, code and data share the same playfield, the
233             # character returned can be either an instruction or raw data. No
234             # guarantee is made that the return value is printable.
235             #
236             sub get_char {
237 9096     9096 1 28933 my ($self, $v) = @_;
238 9096         16193 return chr $self->get_value($v);
239             }
240              
241              
242             #
243             # my $str = $storage->rectangle( $pos, $size );
244             #
245             # Return a string containing the data/code in the rectangle defined by
246             # the supplied vectors.
247             #
248             sub rectangle {
249 15     15 1 1701 my ($self, $start, $size) = @_;
250 15         88 my ($x, $y) = $start->get_all_components();
251 15         48 my ($w, $h) = $size->get_all_components();
252              
253             # retrieve data
254 15         28 my @lines = ();
255 15         112 foreach my $j ( $y .. $y+$h-1 ) {
256 30         82 my $line = join '', map { $self->get_char( LBV->new($_,$j) ) } $x .. $x+$w-1;
  220         1140  
257 30         103 push @lines, $line;
258             }
259              
260 15         86 return join "\n", @lines;
261             }
262              
263              
264             #- misc methods
265              
266             #
267             # my $href = $storage->labels_lookup;
268             #
269             # Parse the storage to find sequences such as ";:(\w[^\s;])[^;]*;"
270             # and return a hash reference whose keys are the labels and the values
271             # an anonymous array with four values: a vector describing the absolute
272             # position of the character just after the trailing ";", and a
273             # vector describing the velocity that leads to this label.
274             #
275             # This method will only look in the four cardinal directions, and does
276             # wrap basically like befunge93 (however, this should not be a problem
277             # since we're only using cardinal directions)
278             #
279             # This allow to define some labels in the source code, to be used by
280             # Inline::Befunge (and maybe some exstensions).
281             #
282             sub labels_lookup {
283 4     4 1 70 my ($self) = @_;
284 4         9 my $labels = {}; # result
285              
286             # lexicalled to improve speed
287 4         13 my $xmin = $self->_xmin;
288 4         8 my $xmax = $self->_xmax;
289 4         9 my $ymin = $self->_ymin;
290 4         9 my $ymax = $self->_ymax;
291              
292 4         11 Y: foreach my $y ( $ymin .. $ymax ) {
293 27         320 X: foreach my $x ( $xmin .. $xmax ) {
294 434 100       2299 next X unless $self->get_value(LBV->new($x,$y)) eq ord(';');
295             # found a semicolon, let's try...
296 21         81 VEC: foreach my $vec ( [1,0], [-1,0], [0,1], [0,-1] ) {
297 82         273 my ($label, $labx, $laby) = $self->_labels_try( $x, $y, @$vec );
298 82 100       262 defined($label) or next VEC;
299              
300             # how exciting, we found a label!
301 11 100       53 exists $labels->{$label}
302             and croak "Help! I found two labels '$label' in the funge space";
303 10         126 $labels->{$label} = [
304             Language::Befunge::Vector->new($labx, $laby),
305             Language::Befunge::Vector->new(@$vec)
306             ];
307             }
308             }
309             }
310              
311 3         16 return $labels;
312             }
313              
314              
315             # -- PRIVATE METHODS
316              
317             #
318             # $storage->_labels_try( $x, $y, $dx, $dy )
319             #
320             # Try in the specified direction if the funge space matches a label
321             # definition. Return undef if it wasn't a label definition, or the name
322             # of the label if it was a valid label.
323             #
324             sub _labels_try {
325 82     82   106 my ($self, $x, $y, $dx, $dy) = @_;
326 82         108 my $comment = '';
327              
328 82         241 my $xmin = $self->_xmin;
329 82         241 my $xmax = $self->_xmax;
330 82         111 my $ymin = $self->_ymin;
331 82         116 my $ymax = $self->_ymax;
332              
333             # fetch the whole comment stuff.
334 82         94 do {
335             # calculate the next cell coordinates.
336 724         1085 $x += $dx; $y += $dy;
  724         794  
337 724 100       1810 $x = $xmin if $xmax < $x;
338 724 100       1223 $x = $xmax if $xmin > $x;
339 724 100       1252 $y = $ymin if $ymax < $y;
340 724 100       1105 $y = $ymax if $ymin > $y;
341 724         3755 my $vec = LBV->new($x,$y);
342 724         1975 $comment .= $self->get_char($vec);
343             } while ( $comment !~ /;.$/ );
344              
345             # check if the comment matches the pattern.
346 82         231 $comment =~ /^:(\w[^\s;]*)[^;]*;.$/;
347 82         267 return ($1, $x, $y);
348             }
349              
350             1;
351             __END__