File Coverage

blib/lib/Eval/WithLexicals.pm
Criterion Covered Total %
statement 57 59 96.6
branch 6 10 60.0
condition 2 3 66.6
subroutine 14 14 100.0
pod 2 4 50.0
total 81 90 90.0


line stmt bran cond sub pod time code
1             package Eval::WithLexicals;
2              
3 2     2   43273 use Moo;
  2         21743  
  2         9  
4 2     2   3368 use Moo::Role ();
  2         16991  
  2         64  
5 2     2   3811 use Sub::Quote;
  2         6506  
  2         1535  
6              
7             our $VERSION = '1.003005'; # 1.3.5
8             $VERSION = eval $VERSION;
9              
10             has lexicals => (is => 'rw', default => quote_sub q{ {} });
11              
12             {
13             my %valid_contexts = map +($_ => 1), qw(list scalar void);
14              
15             has context => (
16             is => 'rw', default => quote_sub(q{ 'list' }),
17             isa => sub {
18             my ($val) = @_;
19             die "Invalid context type $val - should be list, scalar or void"
20             unless $valid_contexts{$val};
21             },
22             );
23             }
24              
25             has in_package => (
26             is => 'rw', default => quote_sub q{ 'Eval::WithLexicals::Scratchpad' }
27             );
28              
29             has prelude => (
30             is => 'rw', default => quote_sub q{ 'use strictures 1;' }
31             );
32              
33             sub with_plugins {
34 1     1 1 13 my($class, @names) = @_;
35              
36 1         15 Moo::Role->create_class_with_roles($class,
37             map "Eval::WithLexicals::With$_", @names);
38             }
39              
40             sub setup_code {
41 6     6 0 5 my($self) = @_;
42 6         18 $self->prelude;
43             }
44              
45             sub capture_code {
46 12     12 0 91 ( qq{ BEGIN { Eval::WithLexicals::Util::capture_list() } } )
47             }
48              
49             sub eval {
50 12     12 1 140 my ($self, $to_eval) = @_;
51 12         22 local *Eval::WithLexicals::Cage::current_line;
52 12         17 local *Eval::WithLexicals::Cage::pad_capture;
53 12         16 local *Eval::WithLexicals::Cage::grab_captures;
54              
55 12         53 my $package = $self->in_package;
56 12         34 my $setup_code = join '', $self->setup_code,
57             # $_[2] being what is passed to _eval_do below
58             Sub::Quote::capture_unroll('$_[2]', $self->lexicals, 2);
59              
60 12         266 my $capture_code = join '', $self->capture_code;
61              
62 12         49 local our $current_code = qq!
63             ${setup_code}
64             sub Eval::WithLexicals::Cage::current_line {
65             package ${package};
66             #line 1 "(eval)"
67             ${to_eval}
68             ;sub Eval::WithLexicals::Cage::pad_capture { }
69             ${capture_code}
70             sub Eval::WithLexicals::Cage::grab_captures {
71             no warnings 'closure'; no strict 'vars';
72             package! # hide from PAUSE
73             .q! Eval::WithLexicals::VarScope;!;
74             # rest is appended by Eval::WithLexicals::Util::capture_list, called
75             # during parsing by the BEGIN block from capture_code.
76              
77 12         69 $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
78 12         1031 $self->_run(\&Eval::WithLexicals::Cage::current_line);
79             }
80              
81             sub _run {
82 12     12   17 my($self, $code) = @_;
83              
84 12         11 my @ret;
85 12         275 my $ctx = $self->context;
86 12 50       939 if ($ctx eq 'list') {
    0          
87 12         21 @ret = $code->();
88             } elsif ($ctx eq 'scalar') {
89 0         0 $ret[0] = $code->();
90             } else {
91 0         0 $code->();
92             }
93 11         47 $self->lexicals({
94 11         28 %{$self->lexicals},
95 11         47 %{$self->_grab_captures},
96             });
97 11         90 @ret;
98             }
99              
100             sub _grab_captures {
101 11     11   13 my ($self) = @_;
102 11         17 my $cap = Eval::WithLexicals::Cage::grab_captures();
103 11         48 foreach my $key (keys %$cap) {
104 8         34 my ($sigil, $name) = $key =~ /^(.)(.+)$/;
105 8         18 my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
106 8 100       386 if ($cap->{$key} eq eval "\\${var_scope_name}") {
107 1         3 delete $cap->{$key};
108             }
109             }
110 11         43 $cap;
111             }
112              
113             sub _eval_do {
114 12     12   15 my ($self, $text_ref, $lexical, $original) = @_;
115             local @INC = (sub {
116 15 100   15   103 if ($_[1] eq '/eval_do') {
117 2     2   11 open my $fh, '<', $text_ref;
  2         6  
  2         14  
  12         138  
118 12         2058 $fh;
119             } else {
120 3         1268 ();
121             }
122 12         75 }, @INC);
123 12 50       167 do '/eval_do' or die $@;
124             }
125              
126             {
127             package # hide from PAUSE
128             Eval::WithLexicals::Util;
129              
130 2     2   15 use B qw(svref_2object);
  2         3  
  2         361  
131              
132             sub capture_list {
133 12     12   1767 my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
134 12   66     263 my @names = grep defined && length && $_ ne '&', map $_->PV, grep $_->can('PV'),
135             svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
136 12         355 $Eval::WithLexicals::current_code .=
137             '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
138             ."\n}\n}\n1;\n";
139             }
140             }
141              
142             1;
143             __END__