Data-Visitor
view release on metacpan or search on metacpan
lib/Data/Visitor.pm view on Meta::CPAN
package Data::Visitor; # git description: v0.31-4-g7498abb
use Moose;
# ABSTRACT: Visitor style traversal of Perl data structures
our $VERSION = '0.32';
use Scalar::Util qw/blessed refaddr reftype weaken isweak/;
use overload ();
use Symbol ();
use Tie::ToObject;
no warnings 'recursion';
use namespace::clean -except => 'meta';
# the double not makes this no longer undef, so exempt from useless constant warnings in older perls
use constant DEBUG => not not our $DEBUG || $ENV{DATA_VISITOR_DEBUG};
use constant HAS_DATA_ALIAS => eval { +require Data::Alias; 1 };
has tied_as_objects => (
isa => "Bool",
is => "rw",
);
# currently broken
has weaken => (
isa => "Bool",
is => "rw",
default => 0,
);
sub trace {
my ( $self, $category, @msg ) = @_;
our %DEBUG;
if ( $DEBUG{$category} or !exists($DEBUG{$category}) ) {
$self->_print_trace("$self: " . join("",
( " " x ( $self->{depth} - 1 ) ),
( join(" ", "$category:", map { overload::StrVal($_) } @msg) ),
));
}
}
sub _print_trace {
my ( $self, @msg ) = @_;
warn "@msg\n";
}
sub visit {
my $self = shift;
local $self->{depth} = (($self->{depth}||0) + 1) if DEBUG;
my $seen_hash = local $self->{_seen} = ($self->{_seen} || {}); # delete it after we're done with the whole visit
my @ret;
foreach my $data ( @_ ) {
$self->trace( flow => visit => $data ) if DEBUG;
if ( my $refaddr = ref($data) && refaddr($data) ) { # only references need recursion checks
$seen_hash->{weak} ||= isweak($data) if $self->weaken;
if ( exists $seen_hash->{$refaddr} ) {
$self->trace( mapping => found_mapping => from => $data, to => $seen_hash->{$refaddr} ) if DEBUG;
push @ret, $self->visit_seen( $data, $seen_hash->{$refaddr} );
next;
} else {
$self->trace( mapping => no_mapping => $data ) if DEBUG;
}
}
if ( defined wantarray ) {
push @ret, scalar($self->visit_no_rec_check($data));
} else {
$self->visit_no_rec_check($data);
}
}
return ( @_ == 1 ? $ret[0] : @ret );
}
sub visit_seen {
my ( $self, $data, $result ) = @_;
return $result;
}
sub _get_mapping {
my ( $self, $data ) = @_;
$self->{_seen}{ refaddr($data) };
}
sub _register_mapping {
my ( $self, $data, $new_data ) = @_;
return $new_data unless ref $data;
$self->trace( mapping => register_mapping => from => $data, to => $new_data, in => (caller(1))[3] ) if DEBUG;
$self->{_seen}{ refaddr($data) } = $new_data;
}
sub visit_no_rec_check {
my ( $self, $data ) = @_;
if ( blessed($data) ) {
return $self->visit_object($_[1]);
} elsif ( ref $data ) {
return $self->visit_ref($_[1]);
}
return $self->visit_value($_[1]);
}
sub visit_object {
my ( $self, $object ) = @_;
$self->trace( flow => visit_object => $object ) if DEBUG;
if ( not defined wantarray ) {
$self->_register_mapping( $object, $object );
$self->visit_value($_[1]);
return;
} else {
return $self->_register_mapping( $object, $self->visit_value($_[1]) );
}
lib/Data/Visitor.pm view on Meta::CPAN
$self->_register_mapping( $glob, $new_glob );
no warnings 'misc'; # Undefined value assigned to typeglob
*$new_glob = $self->visit( *$glob{$_} || next ) for qw/SCALAR ARRAY HASH/;
return $self->retain_magic($_[1], $new_glob);
} else {
$self->_register_mapping( $glob, $glob );
$self->visit( *$glob{$_} || next ) for qw/SCALAR ARRAY HASH/;
return;
}
}
sub visit_tied_glob {
my ( $self, $tied, $glob ) = @_;
if ( defined wantarray ) {
my $new_glob = Symbol::gensym();
$self->_register_mapping( $glob, \$new_glob );
if ( blessed(my $new_tied = $self->visit_tied($_[1], $_[2])) ) {
$self->trace( data => tying => var => $new_glob, to => $new_tied ) if DEBUG;
tie *$new_glob, 'Tie::ToObject', $new_tied;
return $self->retain_magic($_[2], $new_glob);
} else {
return $self->visit_normal_glob($_[2]);
}
} else {
$self->_register_mapping( $glob, $glob );
$self->visit_tied($_[1], $_[2]);
return;
}
}
sub retain_magic {
my ( $self, $proto, $new ) = @_;
if ( blessed($proto) and !blessed($new) ) {
$self->trace( data => blessing => $new, ref $proto ) if DEBUG;
bless $new, ref $proto;
}
my $seen_hash = $self->{_seen};
if ( $seen_hash->{weak} ) {
#if ("$]" >= '5.022') {
# TODO: Data::Alias does not work on recent perls, but there is built-in aliasing support now.
# e.g. see what Var::Pairs 0.003004 did.
#}
if (HAS_DATA_ALIAS) {
my @weak_refs;
foreach my $value ( Data::Alias::deref($proto) ) {
if ( ref $value and isweak($value) ) {
push @weak_refs, refaddr $value;
}
}
if ( @weak_refs ) {
my %targets = map { refaddr($_) => 1 } @{ $self->{_seen} }{@weak_refs};
foreach my $value ( Data::Alias::deref($new) ) {
if ( ref $value and $targets{refaddr($value)}) {
push @{ $seen_hash->{weakened} ||= [] }, $value; # keep a ref around
weaken($value);
}
}
}
}
else {
die "Found a weak reference, but Data::Alias is not installed. You must install Data::Alias in order for this to work.";
}
}
# FIXME real magic, too
return $new;
}
sub visit_tied {
my ( $self, $tied, $var ) = @_;
$self->trace( flow => visit_tied => $tied ) if DEBUG;
$self->visit($_[1]); # as an object eventually
}
__PACKAGE__->meta->make_immutable if __PACKAGE__->meta->can("make_immutable");
__PACKAGE__;
__END__
=pod
=encoding UTF-8
=head1 NAME
Data::Visitor - Visitor style traversal of Perl data structures
=head1 VERSION
version 0.32
=head1 SYNOPSIS
# NOTE
# You probably want to use Data::Visitor::Callback for trivial things
package FooCounter;
use Moose;
extends qw(Data::Visitor);
has number_of_foos => (
isa => "Int",
is => "rw",
default => 0,
);
sub visit_value {
my ( $self, $data ) = @_;
if ( defined $data and $data eq "foo" ) {
$self->number_of_foos( $self->number_of_foos + 1 );
}
( run in 1.301 second using v1.01-cache-2.11-cpan-39bf76dae61 )