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   51124 use Moo;
  2         33302  
  2         11  
4 2     2   3755 use Moo::Role ();
  2         13299  
  2         54  
5 2     2   1046 use Sub::Quote;
  2         6309  
  2         1580  
6              
7             our $VERSION = '1.003004'; # 1.3.4
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 10 my($class, @names) = @_;
35              
36 1         12 Moo::Role->create_class_with_roles($class,
37             map "Eval::WithLexicals::With$_", @names);
38             }
39              
40             sub setup_code {
41 6     6 0 7 my($self) = @_;
42 6         24 $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 131 my ($self, $to_eval) = @_;
51 12         24 local *Eval::WithLexicals::Cage::current_line;
52 12         18 local *Eval::WithLexicals::Cage::pad_capture;
53 12         16 local *Eval::WithLexicals::Cage::grab_captures;
54              
55 12         50 my $package = $self->in_package;
56 12         45 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         260 my $capture_code = join '', $self->capture_code;
61              
62 12         52 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         73 $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
78 12         964 $self->_run(\&Eval::WithLexicals::Cage::current_line);
79             }
80              
81             sub _run {
82 12     12   17 my($self, $code) = @_;
83              
84 12         13 my @ret;
85 12         263 my $ctx = $self->context;
86 12 50       997 if ($ctx eq 'list') {
    0          
87 12         23 @ret = $code->();
88             } elsif ($ctx eq 'scalar') {
89 0         0 $ret[0] = $code->();
90             } else {
91 0         0 $code->();
92             }
93 11         46 $self->lexicals({
94 11         34 %{$self->lexicals},
95 11         51 %{$self->_grab_captures},
96             });
97 11         87 @ret;
98             }
99              
100             sub _grab_captures {
101 11     11   12 my ($self) = @_;
102 11         18 my $cap = Eval::WithLexicals::Cage::grab_captures();
103 11         52 foreach my $key (keys %$cap) {
104 8         34 my ($sigil, $name) = $key =~ /^(.)(.+)$/;
105 8         16 my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
106 8 100       411 if ($cap->{$key} eq eval "\\${var_scope_name}") {
107 1         5 delete $cap->{$key};
108             }
109             }
110 11         42 $cap;
111             }
112              
113             sub _eval_do {
114 12     12   17 my ($self, $text_ref, $lexical, $original) = @_;
115             local @INC = (sub {
116 15 100   15   123 if ($_[1] eq '/eval_do') {
117 2     2   11 open my $fh, '<', $text_ref;
  2         7  
  2         13  
  12         137  
118 12         2088 $fh;
119             } else {
120 3         1292 ();
121             }
122 12         80 }, @INC);
123 12 50       165 do '/eval_do' or die $@;
124             }
125              
126             {
127             package # hide from PAUSE
128             Eval::WithLexicals::Util;
129              
130 2     2   12 use B qw(svref_2object);
  2         5  
  2         331  
131              
132             sub capture_list {
133 12     12   2019 my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
134 12   66     273 my @names = grep defined && length && $_ ne '&', map $_->PV, grep $_->can('PV'),
135             svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
136 12         395 $Eval::WithLexicals::current_code .=
137             '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
138             ."\n}\n}\n1;\n";
139             }
140             }
141              
142             1;
143             __END__