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             # Copyright (c) 2008 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;
11              
12 73     73   45697 use strict;
  73         148  
  73         3197  
13 73     73   408 use warnings;
  73         144  
  73         2410  
14              
15 73     73   427 use Carp;
  73         153  
  73         5420  
16 73     73   408 use Language::Befunge::Vector;
  73         134  
  73         997  
17 73     73   17548 use Language::Befunge::IP;
  73         164  
  73         891  
18 73     73   7947 use aliased 'Language::Befunge::Vector' => 'LBV';
  73         5332  
  73         521  
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 9063 my ($self, $code, $base) = @_;
51 71         197 my $nd = $$self{nd};
52 71 100       348 $base = Language::Befunge::Vector->new_zeroes($$self{nd}) unless defined $base;
53              
54             # support for any eol convention
55 71         199 $code =~ s/\r\n/\n/g;
56 71         145 $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         191 my @separators = ("", "\n", "\f");
63 71         192 push(@separators, "\0"x($_-3)) for (4..$nd); # , "\0", "\0\0", "\0\0\0"...
64 71         165 my $separators = join("", @separators);
65 71         207 my %separators = ( map { $separators[$_] => $_ } (1..@separators-1));
  142         465  
66 71         171 my @sizes = map { 0 } (1..$nd);
  142         249  
67 71         116 my @newvalues;
68 71         257 my $this = $base->copy;
69 71         217 while(length($code)) {
70 1692         4252 my $value = substr($code, 0, 1, '');
71 1692 100       3971 if(index($separators, $value) > -1) {
72 105 100       248 last unless length $code;
73 85         388 my $d = $separators{$value};
74 85         206 my $new = $this->get_component($d) + 1;
75 85         177 $this->set_component($d, $new);
76 85 100       206 $sizes[$d] = $new if $new > $sizes[$d];
77 85         171 foreach my $i (0..$d-1) {
78 86         178 my $last = $this->get_component($i);
79 86         362 $this->set_component($i, $base->get_component($i));
80 86 100       479 $sizes[$i] = $last if $last > $sizes[$i];
81             }
82             } else {
83 1587         3129 my $last = $this->get_component(0);
84 1587 100       3184 unless($value eq ' ') {
85 921         3428 push(@newvalues, [$this->copy, ord($value)]);
86 921 100       2033 $sizes[0] = $last if $last > $sizes[0];
87             }
88 1587         6425 $this->set_component(0, $last + 1);
89             }
90             }
91              
92 71 100       181 return unless scalar @newvalues;
93              
94             # Figure out the rectangle size and the end-coordinate (max).
95 69         134 my $size = Language::Befunge::Vector->new(map { $_ + 1 } @sizes);
  138         737  
96 69         346 my $max = Language::Befunge::Vector->new(@sizes);
97 69         527 $size -= $base;
98              
99             # Enlarge torus to make sure our new values will fit.
100 69         362 $self->expand( $base );
101 69         545 $self->expand( $max );
102              
103             # Store code.
104 69         985 foreach my $pair (@newvalues) {
105 921         4696 $self->set_value(@$pair);
106             }
107              
108 69         1075 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 7677 my ($self, $code, $base) = @_;
126 24         50 my $nd = $$self{nd};
127 24 100       201 $base = Language::Befunge::Vector->new_zeroes($$self{nd})
128             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         53 my @sizes = length($code);
135 24         95 push(@sizes,1) for(2..$nd);
136              
137             # Figure out the min, max, and size
138 24         126 my $size = Language::Befunge::Vector->new(@sizes);
139 24         58 my $max = Language::Befunge::Vector->new(map { $_ - 1 } (@sizes));
  48         168  
140 24         110 $max += $base;
141              
142             # Enlarge torus to make sure our new values will fit.
143 24         82 $self->expand( $base );
144 24         149 $self->expand( $max );
145              
146             # Store code.
147 24         228 for(my $v = $base->copy; defined($v); $v = $v->rasterize($base, $max)) {
148 354         1500 my $char = substr($code, 0, 1, "");
149 354 100       880 next if $char eq " ";
150 321         1029 $self->set_value($v, ord($char));
151             }
152 24         202 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 5497 my $self = shift;
169 4305         5141 my $v = shift;
170 4305         12990 my $ord = $self->get_value($v);
171             # reject invalid ascii
172 4305 100 100     32955 return sprintf("",$ord) if ($ord < 0 || $ord > 255);
173 4290         26751 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 2567 my ($self, $v1, $v2) = @_;
184 35         79 my $nd = $$self{nd};
185              
186             # Fetch the data.
187 35         61 my $data = "";
188 35         64 my $min = $v1;
189 35         111 foreach my $d (0..$nd-1) {
190             # each dimension must >= 1, otherwise the rectangle will be empty.
191 67 100       296 return "" unless $v2->get_component($d);
192             # ... but we need to offset by -1, to calculate $max
193 61         304 $v2->set_component($d, $v2->get_component($d) - 1);
194             }
195 29         143 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         92 my @separators = "";
203 29 100       92 push(@separators,"\n") if $nd > 1;
204 29 100       77 push(@separators,"\f") if $nd > 2;
205 29         75 push(@separators,"\0"x($_-3)) for (4..$nd); # , "\0", "\0\0", "\0\0\0"...
206 29         115 my $prev = $min->copy;
207 29         139 for(my $v = $min->copy; defined($v); $v = $v->rasterize($min, $max)) {
208 336         724 foreach my $d (0..$$self{nd}-1) {
209 699 100       2843 $data .= $separators[$d]
210             if $prev->get_component($d) != $v->get_component($d);
211             }
212 336         520 $prev = $v;
213 336         1244 $data .= $self->get_char($v);
214             }
215 29         207 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 611 my ($self, $v) = @_;
228 448         673 my $min = $$self{min};
229 448         664 my $max = $$self{max};
230 448         858 foreach my $d (0..$$self{nd}-1) {
231 896 100       3198 $min->set_component($d, $v->get_component($d))
232             if $v->get_component($d) < $min->get_component($d);
233 896 100       4059 $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 297 my $self = shift;
258 12         30 my $labels = {};
259              
260 12         35 my ($min, $max) = ($$self{min}, $$self{max});
261 12         35 my $nd = $$self{nd};
262 12         27 my @directions = ();
263 12         35 foreach my $dimension (0..$nd-1) {
264             # build the array of (non-diagonal) vectors
265 24         309 my $v1 = Language::Befunge::Vector->new_zeroes($nd);
266 24         79 my $v2 = $v1->copy;
267 24         68 $v1->set_component($dimension,-1);
268 24         48 push(@directions,$v1);
269 24         88 $v2->set_component($dimension, 1);
270 24         53 push(@directions,$v2);
271             }
272            
273 12         94 R: for(my $this = $min->copy; defined($this); $this = $this->rasterize($min, $max)) {
274 1371         2165 V: for my $v (@directions) {
275 1551 100       3340 next R unless $self->get_char($this) eq ";";
276 243         617 my ($label, $loc) = $self->_labels_try( $this, $v );
277 243 100       1372 next V unless defined($label);
278              
279             # How exciting, we found a label!
280 33 100       293 croak "Help! I found two labels '$label' in the funge space"
281             if exists $labels->{$label};
282 30         156 $$labels{$label} = [$loc, $v];
283             }
284             }
285              
286 9         59 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 5934 my $self = shift;
297 7         39 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 618     618 1 6304 my $self = shift;
310 618         3122 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 865 my $self = shift;
322 618         3008 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   341 my ($self, $start, $delta) = @_;
337 243         302 my $comment = "";
338 243         437 my $wrapping = $$self{wrapping};
339 243         1021 my $ip = Language::Befunge::IP->new($$self{nd});
340 243         557 my $min = $self->min;
341 243         497 my $max = $self->max;
342 243         2540 $ip->set_position($start->copy);
343 243         616 $ip->set_delta($delta);
344              
345             # Fetch the whole comment stuff.
346 243         276 do {
347             # Calculate the next cell coordinates.
348 2202         3961 my $v = $ip->get_position;
349 2202         3257 my $d = $ip->get_delta;
350              
351             # now, let's move the ip.
352 2202         6647 $v += $d;
353              
354 2202 100       7188 if ( $v->bounds_check($min, $max) ) {
355 1995         5403 $ip->set_position( $v );
356             } else {
357 207         677 $wrapping->wrap( $self, $ip );
358             }
359              
360 2202         6297 $comment .= $self->get_char($ip->get_position());
361             } while ( $comment !~ /;.$/ );
362              
363             # Check if the comment matches the pattern.
364 243         796 $comment =~ /^:(\w[^\s;]*)[^;]*;.$/;
365 243         4112 return ($1, $ip->get_position());
366             }
367              
368              
369             1;
370             __END__