File Coverage

lib/Devel/Chitin/GetVarAtLevel.pm
Criterion Covered Total %
statement 9 82 10.9
branch 0 46 0.0
condition 0 21 0.0
subroutine 3 10 30.0
pod 0 2 0.0
total 12 161 7.4


line stmt bran cond sub pod time code
1 1     1   1867 use strict;
  1         1  
  1         23  
2 1     1   4 use warnings;
  1         1  
  1         582  
3              
4             package Devel::Chitin::GetVarAtLevel;
5              
6             our $VERSION = '0.16';
7              
8             sub evaluate_complex_var_at_level {
9 0     0 0   my($expr, $level) = @_;
10              
11             # try and figure out what vars we're dealing with
12 0           my($sigil, $base_var, $open, $index, $close)
13             = $expr =~ m/([\@|\$])(\w+)(\[|\{)(.*)(\]|\})/;
14              
15 0 0         my $varname = ($open eq '[' ? '@' : '%') . $base_var;
16 0           my $var_value = get_var_at_level($varname, $level);
17 0 0         return unless $var_value;
18              
19 0           my @indexes = _parse_index_expression($index, $level);
20              
21 0           my @retval;
22 0 0         if ($open eq '[') {
23             # indexing the list
24 0           @retval = @$var_value[@indexes];
25             } else {
26             # hash
27 0           @retval = @$var_value{@indexes};
28             }
29 0 0         return (@retval == 1) ? $retval[0] : \@retval;
30             }
31              
32             # Parse out things that could go between the brackets/braces in
33             # an array/hash expression. Hopefully this will be good enough,
34             # otherwise we'll need a real grammar
35             my %matched_close = ( '(' => '\)', '[' => '\]', '{' => '\}');
36             sub _parse_index_expression {
37 0     0     my($string, $level) = @_;
38              
39 0           my @indexes;
40 0 0         if ($string =~ m/qw([([{])\s*(.*)$/) { # @list[qw(1 2 3)]
    0          
41 0           my $close = $matched_close{$1};
42 0           $2 =~ m/(.*)\s*$close/;
43 0           @indexes = split(/\s+/, $1);
44             } elsif ($string =~ m/(\S+)\s*\.\.\s*(\S+)/) { # @list[1 .. 4]
45 0           @indexes = (_parse_index_element($1, $level) .. _parse_index_element($2, $level));
46             } else { # @list[1,2,3]
47 0           @indexes = map { _parse_index_element($_, $level) }
  0            
48             split(/\s*,\s*/, $string);
49             }
50 0           return @indexes;
51             }
52              
53             sub _parse_index_element {
54 0     0     my($string, $level) = @_;
55              
56 0 0         if ($string =~ m/^(\$|\@|\%)/) {
    0          
57 0           my $value = get_var_at_level($string, $level);
58 0           return _dereferenced_value($string, $value);
59             } elsif ($string =~ m/('|")(\w+)\1/) {
60 0           return $2;
61             } else {
62 0           return $string;
63             }
64             }
65              
66             sub _dereferenced_value {
67 0     0     my($string, $value) = @_;
68 0           my $sigil = substr($string, 0, 1);
69 0 0 0       if (($sigil eq '@') and (ref($value) eq 'ARRAY')) {
    0 0        
70 0           return @$value;
71              
72             } elsif (($sigil eq '%') and (ref($value) eq 'HASH')) {
73 0           return %$value;
74              
75             } else {
76 0           return $value;
77             }
78             }
79              
80             sub get_var_at_level {
81 0     0 0   my($varname, $level) = @_;
82 0 0         return if ($level < 0); # reject inspection into our frame
83              
84 0           require PadWalker;
85              
86 0           my($first_program_frame_pw, $first_program_frame) = _first_program_frame();
87              
88 0 0 0       if ($varname !~ m/^[\$\@\%\*]/) {
    0          
    0          
89             # not a variable at all, just return it
90 0           return $varname;
91              
92             } elsif ($varname eq '@_' or $varname eq '@ARG') {
93             # handle these special, they're implemented as local() vars, so we'd
94             # really need to eval at some higher stack frame to inspect it if we could
95             # (that would make this whole enterprise easier). We can fake it by using
96             # caller's side effect
97              
98             # Count how many eval frames are between here and there.
99             # caller() counts them, but PadWalker does not
100             {
101 0           package DB;
102 1     1   6 no warnings 'void';
  1         1  
  1         390  
103 0           (caller($level + $first_program_frame))[3];
104             }
105 0           my @args = @DB::args;
106 0           return \@args;
107              
108             } elsif ($varname =~ m/\[|\}/) {
109             # Not a simple variable name, maybe a complicated expression
110             # like @list[1,2,3]. Try to emulate something like eval_at_level()
111 0           return evaluate_complex_var_at_level($varname, $level);
112             }
113              
114 0   0       my $h = eval { PadWalker::peek_my( ($level + $first_program_frame_pw) || 1); };
  0            
115              
116 0 0         unless (exists $h->{$varname}) {
117             # not a lexical, try our()
118 0   0       $h = PadWalker::peek_our( ($level + $first_program_frame_pw) || 1);
119             }
120              
121 0 0         if (exists $h->{$varname}) {
    0          
    0          
122             # it's a simple varname, padwalker found it
123 0 0 0       if (ref($h->{$varname}) eq 'SCALAR' or ref($h->{$varname}) eq 'REF' or ref($h->{$varname}) eq 'VSTRING') {
      0        
124 0           return ${ $h->{$varname} };
  0            
125             } else {
126 0           return $h->{$varname};
127             }
128              
129             } elsif (my($sigil, $bare_varname) = ($varname =~ m/^([\$\@\%\*])(\w+)$/)) {
130             # a varname without a package, try in the package at
131             # that caller level
132 0           my($package) = caller($level + $first_program_frame);
133 0   0       $package ||= 'main';
134              
135 0           my $expanded_varname = $sigil . $package . '::' . $bare_varname;
136 0           my @value = eval( $expanded_varname );
137 0           return _context_return($sigil, \@value);
138              
139             } elsif ($varname =~ m/^([\$\@\%\*])\w+(::\w+)*(::)?$/) {
140             # a varname with a package
141 0           my $sigil = $1;
142 0           my @value = eval($varname);
143 0           return _context_return($sigil, \@value);
144             }
145              
146             }
147              
148             sub _context_return {
149 0     0     my($sigil, $list) = @_;
150 0 0         if (@$list < 2) {
    0          
151 0           return $list->[0];
152             } elsif ($sigil eq '%') {
153 0           my %hash = @$list;
154 0           return \%hash;
155             } else {
156 0           return $list;
157             }
158             }
159              
160             # How many frames between here and the program, both for PadWalker (which
161             # doesn't count eval frames) and caller (which does)
162             sub _first_program_frame {
163 0     0     my $evals = 0;
164 0           for(my $level = 1;
165             my ($package, $filename, $line, $subroutine) = caller($level);
166             $level++
167             ) {
168 0 0         if ($subroutine eq 'DB::DB') {
    0          
169 0           return ($level - $evals, $level - 1); # -1 to skip this frame
170             } elsif ($subroutine eq '(eval)') {
171 0           $evals++;
172             }
173             }
174 0           return;
175             }
176              
177             1;
178              
179             __END__