line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Data::Reach;
|
2
|
6
|
|
|
6
|
|
160047
|
use strict;
|
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
231
|
|
3
|
6
|
|
|
6
|
|
38
|
use warnings;
|
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
249
|
|
4
|
6
|
|
|
6
|
|
36
|
use Carp qw/carp croak/;
|
|
6
|
|
|
|
|
9
|
|
|
6
|
|
|
|
|
454
|
|
5
|
6
|
|
|
6
|
|
38
|
use Scalar::Util qw/blessed reftype/;
|
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
628
|
|
6
|
6
|
|
|
6
|
|
9402
|
use overload;
|
|
6
|
|
|
|
|
7892
|
|
|
6
|
|
|
|
|
44
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '1.00';
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# main entry point
|
11
|
|
|
|
|
|
|
sub reach ($@) {
|
12
|
42
|
|
|
42
|
1
|
4689
|
my ($root, @path) = @_;
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# loop until either @path or the datastructure under $root is exhausted
|
15
|
42
|
|
|
|
|
57
|
while (1) {
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# exit conditions
|
18
|
137
|
100
|
|
|
|
481
|
return undef if !defined $root;
|
19
|
133
|
100
|
|
|
|
280
|
return $root if !@path;
|
20
|
106
|
|
|
|
|
116
|
my $path0 = shift @path;
|
21
|
106
|
100
|
|
|
|
164
|
return undef if !defined $path0;
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# otherwise, walk down one step into the datastructure and loop again
|
24
|
105
|
100
|
|
|
|
324
|
$root = blessed $root ? _step_down_obj($root, $path0)
|
25
|
|
|
|
|
|
|
: _step_down_raw($root, $path0);
|
26
|
|
|
|
|
|
|
}
|
27
|
|
|
|
|
|
|
}
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# get inner data within a raw datastructure
|
30
|
|
|
|
|
|
|
sub _step_down_raw {
|
31
|
94
|
|
|
94
|
|
101
|
my ($data, $key) = @_;
|
32
|
|
|
|
|
|
|
|
33
|
94
|
|
100
|
|
|
211
|
my $reftype = reftype $data || '';
|
34
|
|
|
|
|
|
|
|
35
|
94
|
100
|
|
|
|
158
|
if ($reftype eq 'HASH') {
|
|
|
100
|
|
|
|
|
|
36
|
61
|
|
|
|
|
138
|
return $data->{$key};
|
37
|
|
|
|
|
|
|
}
|
38
|
|
|
|
|
|
|
elsif ($reftype eq 'ARRAY') {
|
39
|
25
|
100
|
|
|
|
95
|
if ($key =~ /^-?\d+$/) {
|
40
|
24
|
|
|
|
|
52
|
return $data->[$key];
|
41
|
|
|
|
|
|
|
}
|
42
|
|
|
|
|
|
|
else {
|
43
|
1
|
|
|
|
|
150
|
croak "cannot reach index '$key' within an array";
|
44
|
|
|
|
|
|
|
}
|
45
|
|
|
|
|
|
|
}
|
46
|
|
|
|
|
|
|
else {
|
47
|
8
|
50
|
|
|
|
27
|
my $kind = $reftype ? "${reftype}REF"
|
|
|
100
|
|
|
|
|
|
48
|
|
|
|
|
|
|
: defined ref $data ? "SCALAR"
|
49
|
|
|
|
|
|
|
: "undef";
|
50
|
8
|
50
|
|
|
|
32
|
my $article = $kind =~ /^[aeiou]/i ? "an" : "a";
|
51
|
8
|
|
|
|
|
1024
|
croak "cannot reach '$key' within $article $kind";
|
52
|
|
|
|
|
|
|
}
|
53
|
|
|
|
|
|
|
}
|
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# get inner data within an object
|
57
|
|
|
|
|
|
|
sub _step_down_obj {
|
58
|
18
|
|
|
18
|
|
25
|
my ($obj, $key) = @_;
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# pragmata that may modify our algorithm -- see L
|
61
|
18
|
|
|
|
|
134
|
my $hint_hash = (caller(1))[10];
|
62
|
18
|
|
100
|
|
|
87
|
my $use_overloads = $hint_hash->{'Data::Reach::use_overloads'} // 1; # default
|
63
|
18
|
|
100
|
|
|
62
|
my $peek_blessed = $hint_hash->{'Data::Reach::peek_blessed'} // 1; # default
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# choice 1 : call named method in object
|
66
|
18
|
|
100
|
|
|
302
|
my @call_method = split $;, $hint_hash->{'Data::Reach::call_method'} || '';
|
67
|
|
|
|
|
|
|
METH_NAME:
|
68
|
18
|
|
|
|
|
42
|
foreach my $meth_name (@call_method) {
|
69
|
20
|
100
|
|
|
|
132
|
my $meth =$obj->can($meth_name)
|
70
|
|
|
|
|
|
|
or next METH_NAME;
|
71
|
8
|
|
|
|
|
22
|
return $obj->$meth($key);
|
72
|
|
|
|
|
|
|
}
|
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# choice 2 : use overloaded methods -- active by default
|
75
|
10
|
100
|
|
|
|
18
|
if ($use_overloads) {
|
76
|
9
|
100
|
100
|
|
|
27
|
return $obj->[$key] if overload::Method($obj, '@{}')
|
77
|
|
|
|
|
|
|
&& $key =~ /^-?\d+$/;
|
78
|
7
|
50
|
|
|
|
1548
|
return $obj->{$key} if overload::Method($obj, '%{}');
|
79
|
|
|
|
|
|
|
}
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# choice 3 : use the object's internal representation -- active by default
|
82
|
8
|
100
|
|
|
|
173
|
if ($peek_blessed) {
|
83
|
7
|
|
|
|
|
16
|
return _step_down_raw($obj, $key);
|
84
|
|
|
|
|
|
|
}
|
85
|
|
|
|
|
|
|
else {
|
86
|
1
|
|
|
|
|
87
|
croak "cannot reach '$key' within an object of class " . ref $obj;
|
87
|
|
|
|
|
|
|
}
|
88
|
|
|
|
|
|
|
}
|
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# the 'import' method does 2 things : a) export the 'reach' function,
|
93
|
|
|
|
|
|
|
# like the regular Exporter, but possibly with a change of name;
|
94
|
|
|
|
|
|
|
# b) implement optional changes to the algorithm, lexically scoped
|
95
|
|
|
|
|
|
|
# through the %^H hint hash (see L).
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
my %seen_pkg; # remember which packages we already exported into
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub import {
|
100
|
10
|
|
|
10
|
|
76
|
my $class = shift;
|
101
|
10
|
|
|
|
|
22
|
my $pkg = caller;
|
102
|
10
|
|
|
|
|
11
|
my $export_as;
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# cheap parsing of import parameters -- I wish I could implement that
|
105
|
|
|
|
|
|
|
# with given/when, but unfortunately those constructs were dropped in v5.18.
|
106
|
10
|
|
|
|
|
36
|
while (my $option = shift) {
|
107
|
9
|
100
|
|
|
|
40
|
if ($option eq 'reach') {
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
108
|
1
|
|
|
|
|
4
|
$export_as = 'reach';
|
109
|
|
|
|
|
|
|
}
|
110
|
|
|
|
|
|
|
elsif ($option eq 'as') {
|
111
|
3
|
|
|
|
|
4
|
$export_as = shift;
|
112
|
3
|
50
|
|
|
|
37
|
defined $export_as
|
113
|
|
|
|
|
|
|
or croak "use Data::Reach : no export name after 'as'";
|
114
|
|
|
|
|
|
|
}
|
115
|
|
|
|
|
|
|
elsif ($option eq 'call_method') {
|
116
|
5
|
50
|
|
|
|
16
|
my $methods = shift
|
117
|
|
|
|
|
|
|
or croak "use Data::Reach : no method name after 'call_method'";
|
118
|
5
|
100
|
100
|
|
|
36
|
$methods = join $;, @$methods if (ref $methods || '') eq 'ARRAY';
|
119
|
5
|
|
|
|
|
34
|
$^H{"Data::Reach::call_method"} = $methods;
|
120
|
|
|
|
|
|
|
}
|
121
|
|
|
|
|
|
|
elsif ($option eq 'peek_blessed') {
|
122
|
0
|
|
|
|
|
0
|
$^H{"Data::Reach::peek_blessed"} = 1;
|
123
|
|
|
|
|
|
|
}
|
124
|
|
|
|
|
|
|
elsif ($option eq 'use_overloads') {
|
125
|
0
|
|
|
|
|
0
|
$^H{"Data::Reach::use_overloads"} = 1;
|
126
|
|
|
|
|
|
|
}
|
127
|
|
|
|
|
|
|
else {
|
128
|
0
|
|
|
|
|
0
|
croak "use Data::Reach : unknown option : $option";
|
129
|
|
|
|
|
|
|
}
|
130
|
|
|
|
|
|
|
}
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# export the 'reach' function into caller's package, under name $export_as
|
133
|
10
|
100
|
66
|
|
|
722
|
if (! exists $seen_pkg{$pkg}) {
|
|
|
50
|
|
|
|
|
|
134
|
5
|
|
100
|
|
|
24
|
$export_as //= 'reach'; # default export name
|
135
|
5
|
100
|
|
|
|
29
|
if ($export_as) { # because it could be an empty string
|
136
|
6
|
|
|
6
|
|
5284
|
no strict 'refs';
|
|
6
|
|
|
|
|
13
|
|
|
6
|
|
|
|
|
1406
|
|
137
|
4
|
|
|
|
|
6
|
*{$pkg . "::" . $export_as} = \&reach;
|
|
4
|
|
|
|
|
29
|
|
138
|
|
|
|
|
|
|
}
|
139
|
5
|
|
|
|
|
2968
|
$seen_pkg{$pkg} = $export_as;
|
140
|
|
|
|
|
|
|
}
|
141
|
|
|
|
|
|
|
elsif ($export_as && $export_as ne $seen_pkg{$pkg}) {
|
142
|
0
|
|
|
|
|
0
|
carp "ignored request to import Data::Reach::reach as '$export_as' into "
|
143
|
|
|
|
|
|
|
. "package $pkg, because it was already imported as '$seen_pkg{$pkg}'!";
|
144
|
|
|
|
|
|
|
}
|
145
|
|
|
|
|
|
|
}
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub unimport {
|
149
|
2
|
|
|
2
|
|
15
|
my $class = shift;
|
150
|
2
|
|
|
|
|
6
|
while (my $option = shift) {
|
151
|
2
|
|
|
|
|
393
|
$^H{"Data::Reach::$option"} = '';
|
152
|
|
|
|
|
|
|
# NOTE : mark with a false value, instead of deleting from the
|
153
|
|
|
|
|
|
|
# hint hash, in order to distinguish options explicitly turned off
|
154
|
|
|
|
|
|
|
# from default options
|
155
|
|
|
|
|
|
|
}
|
156
|
|
|
|
|
|
|
}
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
1;
|
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
__END__
|