NAME
traverse - Provides a generic pre-order traversal algorithm with
user-supplied callbacks.
SYNOPSIS
use traverse qw(:constants);
# Call a method on a complex structure of objects
traverse { $_[0] == TR_OBJECT && $_->some_method } $struct;
# Produce some Data::Dumper-like output
my $d = 0;
traverse { $_[0] == TR_ARRAY
&& print ' ' x ( $d+=2 ), "[\n";
$_[0] == TR_HASH
&& print ' ' x ( $d+=2 ), "{\n";
$_[0] == TR_SCALAR
&& print ' ' x $d, "'$_',\n";
$_[0] == TR_KEY
&& print ' ' x $d, "$_ => ";
$_[0] == TR_ARRAY_END
&& print ' ' x ( $d-=2 ), "],\n";
$_[0] == TR_HASH_END
&& print ' ' x ( $d-=2 ), "},\n";
} $struct;
# Turn a structure into XML
my $d = 0;
my $xml = "\n";
$xml .= join "\n",
traverse { $_[0] == TR_ARRAY
&& ' ' x ( $d += 2 ) . '';
$_[0] == TR_HASH
&& ' ' x ( $d += 2 ) . '';
$_[0] == TR_SCALAR
&& ' ' x $d . " - $_
";
$_[0] == TR_KEY
&& ' ' x $d . "";
$_[0] == TR_KEY_END
&& ' ' x $d . '';
$_[0] == TR_HASH_END
&& ' ' x ( $d -= 2 ) . '';
$_[0] == TR_ARRAY_END
&& ' ' x ( $d -= 2 ) . '';
} $struct;
$xml .= "\n";
print $xml;
DESCRIPTION
traverse.pm exports a single subroutine, "traverse",
and a number of constants. "traverse" takes a BLOCK
or CODE reference and a reference to an arbitrarily
complex data structure (such as a hash of hashes arrays
or an array of arrays of hashes of arrays of objects)
and traverses the structure in pre-order. Each item is
available in the BLOCK or subroutine in $_, and the
first argument is a constant which tells you
what kind of thing the item is. In list context,
"traverse" returns a list of whatever was returned by
the BLOCK or subroutine. In scalar
context, it returns an ARRAY reference to the same list.
CONSTANTS
TR_ARRAY - The item in $_ is an ARRAY reference.
TR_HASH - The item in $_ is a HASH reference.
TR_OBJECT - The item in $_ is an object.
TR_KEY - The item in $_ is a hash key
TR_KEY_END - Passed after a hash key. $_ is the same as
before.
TR_SCALAR - The item in $_ is a hash or array scalar
element.
TR_ARRAY_END - Passed after an array is completed. $_ is
the same ARRAY reference as when it started.
TR_HASH_END - Passed after a hash is completed. $_ is
the same HASH reference as when it started.
TR_CODE - The item in $_ is a CODE reference.
####
package traverse;
use strict;
require Exporter;
use Carp;
sub traverse(&$);
use constant { TR_ARRAY => 1,
TR_HASH => 2,
TR_OBJECT => 3,
TR_KEY => 4,
TR_KEY_END => 5,
TR_SCALAR => 6,
TR_ARRAY_END => 7,
TR_HASH_END => 8,
TR_CODE => 9 };
our @ISA = ('Exporter');
our @EXPORT = ('&traverse');
our @EXPORT_OK = ('TR_ARRAY',
'TR_HASH',
'TR_OBJECT',
'TR_KEY',
'TR_KEY_END',
'TR_SCALAR',
'TR_ARRAY_END',
'TR_HASH_END',
'TR_CODE',
'&traverse');
our %EXPORT_TAGS = ( constants =>
[ 'TR_ARRAY',
'TR_HASH',
'TR_OBJECT',
'TR_KEY',
'TR_KEY_END',
'TR_SCALAR',
'TR_ARRAY_END',
'TR_HASH_END',
'TR_CODE',
'&traverse' ] );
# wrap this around _do_it to hide return array
sub traverse(&$) {
my ($code, $root) = @_;
my $ret = _do_it($code, $root, [ ] );
return wantarray ? @$ret : $ret;
}
sub _do_it {
my ($code, $root, $ret) = @_;
my $type = ref $root;
if( $type eq 'ARRAY' ) {
_run(TR_ARRAY, $code, $root, $ret);
foreach my $node ( @$root ) {
_do_it( $code, $node, $ret );
}
_run(TR_ARRAY_END, $code, $root, $ret);
} elsif ( $type eq 'HASH' ) {
_run(TR_HASH, $code, $root, $ret);
foreach my $key ( keys %$root ) {
_run(TR_KEY, $code, $key, $ret);
_do_it( $code, $root->{$key}, $ret );
_run(TR_KEY_END, $code, $key, $ret);
}
_run(TR_HASH_END, $code, $root, $ret);
} elsif ( $type eq 'CODE' ) {
_run(TR_CODE, $code, $root, $ret);
} elsif ( $type =~ /=/ ) {
_run(TR_OBJECT, $code, $root, $ret);
} elsif ( !$type ) {
_run(TR_SCALAR, $code, $root, $ret);
} else {
croak "Unsupported type $type in traverse.";
}
return $ret;
}
sub _run {
my ( $const, $code, $item, $ret ) = @_;
$_ = $item;
if( my @rets = $code->($const) ) {
push @$ret, @rets;
}
}
1;