File Coverage

blib/lib/Language/Befunge/Storage.pm
Criterion Covered Total %
statement 149 149 100.0
branch 42 42 100.0
condition 3 3 100.0
subroutine 16 16 100.0
pod 9 9 100.0
total 219 219 100.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of Language-Befunge
3             #
4             # This software is copyright (c) 2003 by Jerome Quelin.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9 72     72   968 use strict;
  72         1256  
  72         2529  
10 72     72   247 use warnings;
  72         77  
  72         2472  
11              
12             package Language::Befunge::Storage;
13             # ABSTRACT: a generic Storage base class for Language::Befunge
14             $Language::Befunge::Storage::VERSION = '5.000';
15 72     72   231 use Carp;
  72         61  
  72         3100  
16 72     72   268 use Language::Befunge::Vector;
  72         79  
  72         400  
17 72     72   1505 use Language::Befunge::IP;
  72         76  
  72         710  
18 72     72   3267 use aliased 'Language::Befunge::Vector' => 'LBV';
  72         2259  
  72         337  
19              
20              
21             # -- PUBLIC METHODS
22              
23              
24             #
25             # store( code, [vector] )
26             #
27             # Store the given code at the specified vector. If the coordinates
28             # are omitted, then the code is stored at the origin (0, 0).
29             #
30             # Return the size of the code inserted, as a vector.
31             #
32             # The code is a string, representing a block of Funge code. Rows are
33             # separated by newlines. Planes are separated by form feeds. A complete list of
34             # separators follows:
35             #
36             # Axis Delimiter
37             # X (none)
38             # Y \n
39             # Z \f
40             # 4 \0
41             #
42             # The new-line and form-feed delimiters are in the Funge98 spec. However, there
43             # is no standardized separator for dimensions above Z. Currently, dimensions 4
44             # and above use \0, \0\0, \0\0\0, etc. These are dangerously ambiguous, but are
45             # the only way I can think of to retain reverse compatibility. Suggestions for
46             # better delimiters are welcome. (Using XML would be really ugly, I'd prefer not
47             # to.)
48             #
49             sub store {
50 71     71 1 1896 my ($self, $code, $base) = @_;
51 71         86 my $nd = $$self{nd};
52 71 100       166 $base = Language::Befunge::Vector->new_zeroes($$self{nd}) unless defined $base;
53              
54             # support for any eol convention
55 71         110 $code =~ s/\r\n/\n/g;
56 71         71 $code =~ s/\r/\n/g;
57              
58             # The torus is a tree of arrays of numbers.
59             # The tree is N levels deep, where N is the number of dimensions.
60             # Each number is the ordinal value of the character held in this cell.
61              
62 71         120 my @separators = ("", "\n", "\f");
63 71         137 push(@separators, "\0"x($_-3)) for (4..$nd); # , "\0", "\0\0", "\0\0\0"...
64 71         118 my $separators = join("", @separators);
65 71         127 my %separators = ( map { $separators[$_] => $_ } (1..@separators-1));
  142         279  
66 71         98 my @sizes = map { 0 } (1..$nd);
  142         147  
67 71         83 my @newvalues;
68 71         158 my $this = $base->copy;
69 71         147 while(length($code)) {
70 1692         1546 my $value = substr($code, 0, 1, '');
71 1692 100       1822 if(index($separators, $value) > -1) {
72 105 100       143 last unless length $code;
73 85         74 my $d = $separators{$value};
74 85         132 my $new = $this->get_component($d) + 1;
75 85         124 $this->set_component($d, $new);
76 85 100       126 $sizes[$d] = $new if $new > $sizes[$d];
77 85         117 foreach my $i (0..$d-1) {
78 86         121 my $last = $this->get_component($i);
79 86         124 $this->set_component($i, $base->get_component($i));
80 86 100       201 $sizes[$i] = $last if $last > $sizes[$i];
81             }
82             } else {
83 1587         2068 my $last = $this->get_component(0);
84 1587 100       1917 unless($value eq ' ') {
85 921         1195 push(@newvalues, [$this->copy, ord($value)]);
86 921 100       1255 $sizes[0] = $last if $last > $sizes[0];
87             }
88 1587         2151 $this->set_component(0, $last + 1);
89             }
90             }
91              
92 71 100       108 return unless scalar @newvalues;
93              
94             # Figure out the rectangle size and the end-coordinate (max).
95 69         71 my $size = Language::Befunge::Vector->new(map { $_ + 1 } @sizes);
  138         203  
96 69         134 my $max = Language::Befunge::Vector->new(@sizes);
97 69         171 $size -= $base;
98              
99             # Enlarge torus to make sure our new values will fit.
100 69         140 $self->expand( $base );
101 69         104 $self->expand( $max );
102              
103             # Store code.
104 69         90 foreach my $pair (@newvalues) {
105 921         1368 $self->set_value(@$pair);
106             }
107              
108 69         438 return $size;
109             }
110              
111              
112             #
113             # store_binary( code, [vector] )
114             #
115             # Store the given code at the specified coordinates. If the coordinates
116             # are omitted, then the code is stored at the Origin(0, 0) coordinates.
117             #
118             # Return the size of the code inserted, as a vector.
119             #
120             # This is binary insertion, that is, EOL and FF sequences are stored in
121             # Funge-space instead of causing the dimension counters to be reset and
122             # incremented. The data is stored all in one row.
123             #
124             sub store_binary {
125 24     24 1 2031 my ($self, $code, $base) = @_;
126 24         29 my $nd = $$self{nd};
127             $base = Language::Befunge::Vector->new_zeroes($$self{nd})
128 24 100       75 unless defined $base;
129              
130             # The torus is a tree of arrays of numbers.
131             # The tree is N levels deep, where N is the number of dimensions.
132             # Each number is the ordinal value of the character held in this cell.
133              
134 24         31 my @sizes = length($code);
135 24         69 push(@sizes,1) for(2..$nd);
136              
137             # Figure out the min, max, and size
138 24         49 my $size = Language::Befunge::Vector->new(@sizes);
139 24         27 my $max = Language::Befunge::Vector->new(map { $_ - 1 } (@sizes));
  48         74  
140 24         60 $max += $base;
141              
142             # Enlarge torus to make sure our new values will fit.
143 24         46 $self->expand( $base );
144 24         40 $self->expand( $max );
145              
146             # Store code.
147 24         42 for(my $v = $base->copy; defined($v); $v = $v->rasterize($base, $max)) {
148 354         396 my $char = substr($code, 0, 1, "");
149 354 100       503 next if $char eq " ";
150 321         544 $self->set_value($v, ord($char));
151             }
152 24         56 return $size;
153             }
154              
155              
156             #
157             # get_char( vector )
158             #
159             # Return the character stored in the torus at the specified location. If
160             # the value is not between 0 and 255 (inclusive), get_char will return a
161             # string that looks like "".
162             #
163             # B As in Funge, code and data share the same playfield, the
164             # character returned can be either an instruction B raw data. No
165             # guarantee is made that the return value is printable.
166             #
167             sub get_char {
168 4305     4305 1 2965 my $self = shift;
169 4305         2564 my $v = shift;
170 4305         6013 my $ord = $self->get_value($v);
171             # reject invalid ascii
172 4305 100 100     11464 return sprintf("",$ord) if ($ord < 0 || $ord > 255);
173 4290         11659 return chr($ord);
174             }
175              
176              
177             #
178             # my $str = rectangle( start, size )
179             #
180             # Return a string containing the data/code in the specified rectangle.
181             #
182             sub rectangle {
183 35     35 1 42 my ($self, $v1, $v2) = @_;
184 35         42 my $nd = $$self{nd};
185              
186             # Fetch the data.
187 35         40 my $data = "";
188 35         27 my $min = $v1;
189 35         84 foreach my $d (0..$nd-1) {
190             # each dimension must >= 1, otherwise the rectangle will be empty.
191 67 100       105 return "" unless $v2->get_component($d);
192             # ... but we need to offset by -1, to calculate $max
193 61         96 $v2->set_component($d, $v2->get_component($d) - 1);
194             }
195 29         71 my $max = $v1 + $v2;
196             # No separator is used for the first dimension, for obvious reasons.
197             # Funge98 specifies lf/cr/crlf for a second-dimension separator.
198             # Funge98 specifies a form feed for a third-dimension separator.
199             # Funge98 doesn't specify what dimensions 4 and above should use.
200             # We use increasingly long strings of null bytes.
201             # (4d uses 1 null byte, 5d uses 2, 6d uses 3, etc)
202 29         48 my @separators = "";
203 29 100       57 push(@separators,"\n") if $nd > 1;
204 29 100       51 push(@separators,"\f") if $nd > 2;
205 29         53 push(@separators,"\0"x($_-3)) for (4..$nd); # , "\0", "\0\0", "\0\0\0"...
206 29         52 my $prev = $min->copy;
207 29         47 for(my $v = $min->copy; defined($v); $v = $v->rasterize($min, $max)) {
208 336         403 foreach my $d (0..$$self{nd}-1) {
209 699 100       941 $data .= $separators[$d]
210             if $prev->get_component($d) != $v->get_component($d);
211             }
212 336         230 $prev = $v;
213 336         436 $data .= $self->get_char($v);
214             }
215 29         125 return $data;
216             }
217              
218              
219             # expand( vector )
220              
221             # Expand the storage range to include the specified point, if necessary.
222             # This version of expand() is meant for Sparse modules; it only adjusts the min
223             # and max vectors with no other effect. Non-sparse modules should supercede
224             # this method to do something more meaningful.
225              
226             sub expand {
227 448     448 1 283 my ($self, $v) = @_;
228 448         338 my $min = $$self{min};
229 448         279 my $max = $$self{max};
230 448         506 foreach my $d (0..$$self{nd}-1) {
231 896 100       1095 $min->set_component($d, $v->get_component($d))
232             if $v->get_component($d) < $min->get_component($d);
233 896 100       1123 $max->set_component($d, $v->get_component($d))
234             if $v->get_component($d) > $max->get_component($d);
235             }
236             }
237              
238              
239             #- misc methods
240              
241             #
242             # my %labels = labels_lookup( )
243             #
244             # Parse the Lahey space to find sequences such as C<;:(\w[^\s;])[^;]*;>
245             # and return a hash reference whose keys are the labels and the values
246             # an anonymous array with two vectors: a vector describing the absolute
247             # position of the character B the trailing C<;>, and a
248             # vector describing the velocity that lead to this label.
249             #
250             # This method will only look in the cardinal directions; west, east,
251             # north, south, up, down and so forth.
252             #
253             # This allow to define some labels in the source code, to be used by
254             # C (and maybe some extensions).
255             #
256             sub labels_lookup {
257 12     12 1 107 my $self = shift;
258 12         17 my $labels = {};
259              
260 12         24 my ($min, $max) = ($$self{min}, $$self{max});
261 12         13 my $nd = $$self{nd};
262 12         18 my @directions = ();
263 12         27 foreach my $dimension (0..$nd-1) {
264             # build the array of (non-diagonal) vectors
265 24         49 my $v1 = Language::Befunge::Vector->new_zeroes($nd);
266 24         89 my $v2 = $v1->copy;
267 24         42 $v1->set_component($dimension,-1);
268 24         27 push(@directions,$v1);
269 24         36 $v2->set_component($dimension, 1);
270 24         37 push(@directions,$v2);
271             }
272            
273 12         19 R: for(my $this = $min->copy; defined($this); $this = $this->rasterize($min, $max)) {
274 1371         1233 V: for my $v (@directions) {
275 1551 100       1615 next R unless $self->get_char($this) eq ";";
276 243         369 my ($label, $loc) = $self->_labels_try( $this, $v );
277 243 100       658 next V unless defined($label);
278              
279             # How exciting, we found a label!
280             croak "Help! I found two labels '$label' in the funge space"
281 33 100       129 if exists $labels->{$label};
282 30         149 $$labels{$label} = [$loc, $v];
283             }
284             }
285              
286 9         32 return $labels;
287             }
288              
289              
290             #
291             # my $dims = get_dims()
292             #
293             # Returns the number of dimensions this storage object operates in.
294             #
295             sub get_dims {
296 7     7 1 3903 my $self = shift;
297 7         25 return $$self{nd};
298             }
299              
300              
301             #
302             # my $vector = min()
303             #
304             # Returns a Vector object, pointing at the beginning of the torus.
305             # If nothing has been stored to a negative offset, this Vector will
306             # point at the origin (0,0).
307             #
308             sub min {
309 729     729 1 2514 my $self = shift;
310 729         1216 return $$self{min}->copy;
311             }
312              
313              
314             #
315             # my $vector = max()
316             #
317             # Returns a Vector object, pointing at the end of the torus.
318             # This is usually the largest position which has been written to.
319             #
320             sub max {
321 618     618 1 441 my $self = shift;
322 618         909 return $$self{max}->copy;
323             }
324              
325              
326             # -- PRIVATE METHODS
327              
328             #
329             # $storage->_labels_try( $x, $y, $dx, $dy )
330             #
331             # Try in the specified direction if the funge space matches a label
332             # definition. Return undef if it wasn't a label definition, or the name
333             # of the label if it was a valid label.
334             #
335             sub _labels_try {
336 243     243   206 my ($self, $start, $delta) = @_;
337 243         183 my $comment = "";
338 243         190 my $wrapping = $$self{wrapping};
339 243         551 my $ip = Language::Befunge::IP->new($$self{nd});
340 243         284 my $min = $self->min;
341 243         298 my $max = $self->max;
342 243         363 $ip->set_position($start->copy);
343 243         264 $ip->set_delta($delta);
344              
345             # Fetch the whole comment stuff.
346 243         165 do {
347             # Calculate the next cell coordinates.
348 2202         1989 my $v = $ip->get_position;
349 2202         1700 my $d = $ip->get_delta;
350              
351             # now, let's move the ip.
352 2202         3179 $v += $d;
353              
354 2202 100       3190 if ( $v->bounds_check($min, $max) ) {
355 1995         2513 $ip->set_position( $v );
356             } else {
357 207         402 $wrapping->wrap( $self, $ip );
358             }
359              
360 2202         2634 $comment .= $self->get_char($ip->get_position());
361             } while ( $comment !~ /;.$/ );
362              
363             # Check if the comment matches the pattern.
364 243         301 $comment =~ /^:(\w[^\s;]*)[^;]*;.$/;
365 243         1807 return ($1, $ip->get_position());
366             }
367              
368              
369             1;
370              
371             __END__