moved to class::xsaccessor
[language-befunge.git] / lib / Language / Befunge / Storage / 2D / Sparse.pm
blob6ec3badd668288034aa24781bd414ef9bb7f9116
2 # This file is part of Language::Befunge.
3 # Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the same terms as Perl itself.
10 package Language::Befunge::Storage::2D::Sparse;
12 use strict;
13 use warnings;
15 use Carp;
16 use aliased 'Language::Befunge::Vector' => 'LBV';
17 use Readonly;
19 use base qw{ Language::Befunge::Storage };
21 use Class::XSAccessor
22 accessors => {
23 _storage => '_storage',
24 _xmin => '_xmin',
25 _xmax => '_xmax',
26 _ymin => '_ymin',
27 _ymax => '_ymax',
30 Readonly my $SPACE => ' ';
33 # -- CONSTRUCTOR
36 # my $storage = LBS::2D::Sparse->new;
38 # Create a new storage.
40 sub new {
41 my ($class, $dims) = @_;
42 $dims //= 2;
43 croak("$class is only useful for 2-dimensional storage.")
44 unless $dims == 2;
45 my $self = {};
46 bless $self, $class;
47 $self->clear;
48 return $self;
53 # -- PUBLIC METHODS
55 #- storage update
58 # $storage->clear;
60 # Clear the storage.
62 sub clear {
63 my ($self) = @_;
64 $self->_xmin(0);
65 $self->_xmax(0);
66 $self->_ymin(0);
67 $self->_ymax(0);
68 $self->_storage( {} );
73 # my $size = $storage->store_binary( $code [, $position] );
75 # Store the given $code at the specified $position (defaulting to the
76 # origin coordinates).
78 # Return the size of the code inserted, as a vector.
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.
85 sub store_binary {
86 my ($self, $code, $position) = @_;
88 my $offset = $position;
89 $offset = LBV->new(0,0) unless defined $offset;
90 my $x = $offset->get_component(0);
91 my $y = $offset->get_component(1);
92 my $href = $self->_storage;
94 # enlarge min values if needed
95 $self->_xmin($x) if $self->_xmin > $x;
96 $self->_ymin($y) if $self->_ymin > $y;
98 # store data
99 foreach my $chr ( split //, $code ) {
100 $href->{"$x,$y"} = ord $chr
101 unless $chr eq $SPACE; # spaces do not overwrite - cf befunge specs
102 $x++;
105 # enlarge max values if needed
106 $x--; # one step too far
107 $self->_xmax($x) if $self->_xmax < $x;
108 $self->_ymax($y) if $self->_ymax < $y;
110 return LBV->new(length $code, 1);
115 # my $size = $storage->store( $code [, $position] );
117 # Store the given $code at the specified $position (defaulting to the
118 # origin coordinates).
120 # Return the size of the code inserted, as a vector.
122 # The code is a string, representing a block of Funge code. Rows are
123 # separated by newlines.
125 sub store {
126 my ($self, $code, $position) = @_;
128 my $offset = $position;
129 $offset = LBV->new(0,0) unless defined $offset;
130 my $dy = LBV->new(0,1);
132 # support for any eol convention
133 $code =~ s/\r\n/\n/g;
134 $code =~ s/\r/\n/g;
135 my @lines = split /\n/, $code;
137 # store data
138 my $maxlen = 0;
139 foreach my $line ( @lines ) {
140 $maxlen = length($line) if $maxlen < length($line);
141 $self->store_binary( $line, $offset );
142 $offset += $dy;
145 return LBV->new($maxlen, scalar(@lines));
149 # $storage->set_value( $offset, $value );
151 # Write the supplied $value in the storage at the specified $offset.
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) ).
157 sub set_value {
158 my ($self, $v, $val) = @_;
159 my ($x, $y) = $v->get_all_components();
161 # ensure we can set the value.
162 $self->_xmin($x) if $self->_xmin > $x;
163 $self->_xmax($x) if $self->_xmax < $x;
164 $self->_ymin($y) if $self->_ymin > $y;
165 $self->_ymax($y) if $self->_ymax < $y;
166 $self->_storage->{"$x,$y"} = $val;
171 #- data retrieval
174 # my $dims = $storage->get_dims;
176 # Return the dimensionality of the storage. For this module, the value is
177 # always 2.
179 sub get_dims { 2 }
183 # my $vmin = $storage->min;
185 # Return a LBV pointing to the lower bounds of the storage.
187 sub min {
188 my ($self) = @_;
189 return LBV->new($self->_xmin, $self->_ymin);
194 # my $vmax = $storage->max;
196 # Return a LBV pointing to the upper bounds of the storage.
198 sub max {
199 my ($self) = @_;
200 return LBV->new($self->_xmax, $self->_ymax);
205 # my $val = $storage->get_value( $offset );
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).
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) ).
215 sub get_value {
216 my ($self, $v) = @_;
217 my ($x, $y) = $v->get_all_components;
218 my $href = $self->_storage;
219 return exists $href->{"$x,$y"}
220 ? $href->{"$x,$y"}
221 : 32;
226 # my $chr = $storage->get_char( $offset );
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 "<np-0x4500>".
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.
236 sub get_char {
237 my ($self, $v) = @_;
238 return chr $self->get_value($v);
243 # my $str = $storage->rectangle( $pos, $size );
245 # Return a string containing the data/code in the rectangle defined by
246 # the supplied vectors.
248 sub rectangle {
249 my ($self, $start, $size) = @_;
250 my ($x, $y) = $start->get_all_components();
251 my ($w, $h) = $size->get_all_components();
253 # retrieve data
254 my @lines = ();
255 foreach my $j ( $y .. $y+$h-1 ) {
256 my $line = join '', map { $self->get_char( LBV->new($_,$j) ) } $x .. $x+$w-1;
257 push @lines, $line;
260 return join "\n", @lines;
264 #- misc methods
267 # my $href = $storage->labels_lookup;
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.
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)
279 # This allow to define some labels in the source code, to be used by
280 # Inline::Befunge (and maybe some exstensions).
282 sub labels_lookup {
283 my ($self) = @_;
284 my $labels = {}; # result
286 # lexicalled to improve speed
287 my $xmin = $self->_xmin;
288 my $xmax = $self->_xmax;
289 my $ymin = $self->_ymin;
290 my $ymax = $self->_ymax;
292 Y: foreach my $y ( $ymin .. $ymax ) {
293 X: foreach my $x ( $xmin .. $xmax ) {
294 next X unless $self->get_value(LBV->new($x,$y)) eq ord(';');
295 # found a semicolon, let's try...
296 VEC: foreach my $vec ( [1,0], [-1,0], [0,1], [0,-1] ) {
297 my ($label, $labx, $laby) = $self->_labels_try( $x, $y, @$vec );
298 defined($label) or next VEC;
300 # how exciting, we found a label!
301 exists $labels->{$label}
302 and croak "Help! I found two labels '$label' in the funge space";
303 $labels->{$label} = [
304 Language::Befunge::Vector->new($labx, $laby),
305 Language::Befunge::Vector->new(@$vec)
311 return $labels;
315 # -- PRIVATE METHODS
318 # $storage->_labels_try( $x, $y, $dx, $dy )
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.
324 sub _labels_try {
325 my ($self, $x, $y, $dx, $dy) = @_;
326 my $comment = '';
328 my $xmin = $self->_xmin;
329 my $xmax = $self->_xmax;
330 my $ymin = $self->_ymin;
331 my $ymax = $self->_ymax;
333 # fetch the whole comment stuff.
334 do {
335 # calculate the next cell coordinates.
336 $x += $dx; $y += $dy;
337 $x = $xmin if $xmax < $x;
338 $x = $xmax if $xmin > $x;
339 $y = $ymin if $ymax < $y;
340 $y = $ymax if $ymin > $y;
341 my $vec = LBV->new($x,$y);
342 $comment .= $self->get_char($vec);
343 } while ( $comment !~ /;.$/ );
345 # check if the comment matches the pattern.
346 $comment =~ /^:(\w[^\s;]*)[^;]*;.$/;
347 return ($1, $x, $y);
351 __END__
353 =head1 NAME
355 LBS::2D::Sparse - a 2D storage, using sparse hash
358 =head1 SYNOPSIS
360 my $storage = Language::Befunge::Storage::2D::Sparse->new;
361 $storage->clear;
362 $storage->store(<<EOF);
363 12345
364 67890
368 =head1 DESCRIPTION
370 This class implements a storage as defined in LBS. It makes the
371 assumption that we're in a 2D Funge space for efficiency reasons.
372 Therefore, it's only suited for befunge programs.
374 This storage is sparse, using a private hash with keys such as "$x,$y".
375 Any value of a non-existing key defaults to 32 (space), as defined by
376 funge specs.
380 =head1 PUBLIC METHODS
383 =head2 Constructor
386 =over 4
388 =item my $storage = LBS::2D::Sparse->new;
390 Create a new LBS object.
393 =back
397 =head2 Storage update
400 =over 4
403 =item $storage->clear;
405 Clear the storage.
408 =item my $size = $storage->store_binary( $code [, $position] );
410 Store the given C<$code> at the specified C<$position> (defaulting to
411 the origin coordinates).
413 Return the size of the code inserted, as a vector.
415 The code is a string, representing a block of Funge code. This is binary
416 insertion, that is, EOL sequences are stored in Funge-space instead of
417 causing the dimension counters to be resetted and incremented.
420 =item my $size = $storage->store( $code [, $position] );
422 Store the given $code at the specified $position (defaulting to the
423 origin coordinates).
425 Return the size of the code inserted, as a vector.
427 The code is a string, representing a block of Funge code. Rows are
428 separated by newlines.
431 =item $storage->set_value( $offset, $value );
433 Write the supplied C<$value> in the storage at the specified C<$offset>.
435 B</!\> As in Befunge, code and data share the same playfield, the number
436 stored can be either an instruction B<or> raw data (or even both... Eh,
437 that's Befunge! :o) ).
440 =back
444 =head2 Data retrieval
447 =over 4
450 =item my $dims = $storage->get_dims;
452 Return the dimensionality of the storage. For this module, the value is
453 always 2.
456 =item my $vmin = $storage->min;
458 Return a LBV pointing to the lower bounds of the storage.
461 =item my $vmax = $storage->max;
463 Return a LBV pointing to the upper bounds of the storage.
466 =item my $val = $storage->get_value( $offset );
468 Return the number stored in the torus at the specified C<$offset>. If
469 the value hasn't yet been set, it defaults to the ordinal value of a
470 space (ie, #32).
472 B</!\> As in Befunge, code and data share the same playfield, the number
473 returned can be either an instruction B<or> raw data (or even both... Eh,
474 that's Befunge! :o) ).
477 =item my $chr = $storage->get_char( $offset )
479 Return the character stored in the torus at the specified C<$offset>. If
480 the value is not between 0 and 255 (inclusive), get_char will return a
481 string that looks like C<< <np-0x4500> >>.
483 B</!\> As in Befunge, code and data share the same playfield, the
484 character returned can be either an instruction B<or> raw data. No
485 guarantee is made that the return value is printable.
488 =item my $str = $storage->rectangle( $pos, $size );
490 Return a string containing the data/code in the rectangle defined by
491 the supplied vectors.
494 =back
497 =head2 Miscellaneous methods
500 =over 4
503 =item my $href = $storage->labels_lookup;
505 Parse the storage to find sequences such as C<;:(\w[^\s;])[^;]*;>
506 and return a hash reference whose keys are the labels and the values
507 an anonymous array with four values: a vector describing the absolute
508 position of the character just after the trailing C<;>, and a
509 vector describing the velocity that leads to this label.
511 This method will only look in the four cardinal directions, and does
512 wrap basically like befunge93 (however, this should not be a problem
513 since we're only using cardinal directions)
515 This allow to define some labels in the source code, to be used by
516 C<Inline::Befunge> (and maybe some exstensions).
519 =begin pod_coverage
521 =item LBV - alias for Language::Befunge::Vector
523 =end pod_coverage
525 =back
529 =head1 SEE ALSO
531 L<Language::Befunge>.
534 =head1 AUTHOR
536 Jerome Quelin, C<< <jquelin@cpan.org> >>
539 =head1 COPYRIGHT & LICENSE
541 Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
543 This program is free software; you can redistribute it and/or modify
544 it under the same terms as Perl itself.
547 =cut