File Coverage

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


line stmt bran cond sub pod time code
1             package Eval::WithLexicals;
2              
3 2     2   94331 use Moo;
  2         15651  
  2         8  
4 2     2   2492 use Moo::Role ();
  2         12608  
  2         40  
5 2     2   516 use Sub::Quote;
  2         6834  
  2         1180  
6              
7             our $VERSION = '1.003006'; # v1.3.6
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 70 my($class, @names) = @_;
35              
36 1         10 Moo::Role->create_class_with_roles($class,
37             map "Eval::WithLexicals::With$_", @names);
38             }
39              
40             sub setup_code {
41 6     6 0 11 my($self) = @_;
42 6         24 $self->prelude;
43             }
44              
45             sub capture_code {
46 12     12 0 95 ( qq{ BEGIN { Eval::WithLexicals::Util::capture_list() } } )
47             }
48              
49             sub eval {
50 12     12 1 135 my ($self, $to_eval) = @_;
51 12         27 local *Eval::WithLexicals::Cage::current_line;
52 12         19 local *Eval::WithLexicals::Cage::pad_capture;
53 12         17 local *Eval::WithLexicals::Cage::grab_captures;
54              
55 12         39 my $package = $self->in_package;
56 12         28 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         277 my $capture_code = join '', $self->capture_code;
61              
62 12         44 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         55 $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
78 12         1124 $self->_run(\&Eval::WithLexicals::Cage::current_line);
79             }
80              
81             sub _run {
82 12     12   27 my($self, $code) = @_;
83              
84 12         14 my @ret;
85 12         255 my $ctx = $self->context;
86 12 50       108 if ($ctx eq 'list') {
    0          
87 12         24 @ret = $code->();
88             } elsif ($ctx eq 'scalar') {
89 0         0 $ret[0] = $code->();
90             } else {
91 0         0 $code->();
92             }
93             $self->lexicals({
94 11         29 %{$self->lexicals},
95 11         40 %{$self->_grab_captures},
  11         26  
96             });
97 11         93 @ret;
98             }
99              
100             sub _grab_captures {
101 11     11   16 my ($self) = @_;
102 11         19 my $cap = Eval::WithLexicals::Cage::grab_captures();
103 11         51 foreach my $key (keys %$cap) {
104 8         34 my ($sigil, $name) = $key =~ /^(.)(.+)$/;
105 8         21 my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
106 8 100       378 if ($cap->{$key} eq eval "\\${var_scope_name}") {
107 1         4 delete $cap->{$key};
108             }
109             }
110 11         40 $cap;
111             }
112              
113             sub _eval_do {
114 12     12   29 my ($self, $text_ref, $lexical, $original) = @_;
115             local @INC = (sub {
116 15 100   15   128 if ($_[1] eq '/eval_do') {
117 2     2   12 open my $fh, '<', $text_ref;
  2         2  
  2         10  
  12         147  
118 12         1466 $fh;
119             } else {
120 3         654 ();
121             }
122 12         70 }, @INC);
123 12 50       198 do '/eval_do' or die $@;
124             }
125              
126             {
127             package # hide from PAUSE
128             Eval::WithLexicals::Util;
129              
130 2     2   14 use B qw(svref_2object);
  2         4  
  2         286  
131              
132             sub capture_list {
133 12     12   2256 my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
134 12   100     292 my @names = grep defined && length > 1, map $_->PV, grep $_->can('PV'),
135             svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
136 12         435 $Eval::WithLexicals::current_code .=
137             '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
138             ."\n}\n}\n1;\n";
139             }
140             }
141              
142             1;
143             __END__