File Coverage

blib/lib/Devel/EvalContext.pm
Criterion Covered Total %
statement 84 94 89.3
branch 11 18 61.1
condition 3 5 60.0
subroutine 13 15 86.6
pod 2 3 66.6
total 113 135 83.7


line stmt bran cond sub pod time code
1             package Devel::EvalContext;
2              
3 0     0   0 { package main; sub Devel::EvalContext::_hygenic_eval { eval $_[0] } }
4              
5 5     5   146364 use strict;
  5         14  
  5         203  
6 5     5   26 use warnings;
  5         10  
  5         172  
7              
8 5     5   4461 use PadWalker qw(peek_sub);
  5         6350  
  5         413  
9 5     5   37 use Carp;
  5         10  
  5         381  
10 5     5   4956 use Data::Alias qw(alias);
  5         6758  
  5         378  
11 5     5   34 use B ();
  5         9  
  5         7495  
12              
13             our $VERSION = "0.09";
14              
15             our $TRACING = 0;
16              
17             # public interface needs:
18             #
19             # create an empty context
20             # create an empty context from here (is this possible?)
21             # clone a context
22             # evaluate in a context and get new context
23             # inspect hints and variables
24              
25             # global vars allowing bits to talk without using closures or lexicals
26             our $_new_context;
27              
28             sub _warn {
29 857 50   857   2938 warn @_ if $TRACING;
30             }
31             sub _warnblock {
32 53     53   154461 _warn " | $_\n" for split /\n/, $_[0];
33             }
34             sub _warndump {
35 17     17   2974 require YAML;
36 17         32498 _warnblock(YAML::Dump($_[0]));
37             }
38              
39             sub _magic_code {
40 18     18   73 qq{
41             #line 1 "_magic_code"
42             sub {
43             $_[0]
44             #line 3 "_magic_code"
45             eval \$_[0];
46             }
47             };
48             }
49              
50             sub _save_context {
51 17     17   4379 my $evalcv = delete $_new_context->{evalcv};
52 17         97 _warn "saving context for ", $evalcv->object_2svref, "\n";
53              
54 17         53 $_new_context->{saved}++; # this confirms that the code has been compiled
55              
56             # should I do my own pp version?
57 17         192 my $v = peek_sub $evalcv->object_2svref;
58 17         50 $_new_context->{vars} = {};
59 17         749 while (my ($key, $val) = each %$v) {
60 31 50       82 next if $key =~ /^.__repl_/;
61 31         122 _warn " processing: $key => $val\n";
62 31         151 $_new_context->{vars}{$key} = $val;
63             }
64              
65             # save hints
66             # hrm I'm getting the wrong values
67 17         718 $_new_context->{hints}->{'$^H'} = $^H & ~(256);
68 17         43 $_new_context->{hints}->{'%^H'} = \%^H;
69 17         48 $_new_context->{hints}->{'$^W'} = $^W;
70 17         1055 $_new_context->{hints}->{'${^WARNING_BITS}'} = ${^WARNING_BITS};
71             }
72              
73             # New context
74 5     5 1 921 sub new { return bless \{}, $_[0] }
75              
76             sub trace {
77 0     0 0 0 my ($s, $t) = @_;
78 0 0       0 if ($t) {
79 0         0 $$s->{trace} = $t;
80             }
81 0         0 return $$s->{trace};
82             }
83              
84             # Run a context
85             sub run {
86 18     18 1 6799 my ($cxt, $code) = @_;
87 18         70 local $TRACING = $$cxt->{trace};
88 18         74 _warn "+", ("-" x 71), "\n";
89 18         143 _warn "context_eval: {", $code, "} using ", $cxt, "/", $$cxt, "\n";
90              
91 18         36 local $_new_context = undef;
92              
93             # I bet I could write a PP version of this using B
94 18         45 my $recreate_context = qq[\n#line 1 ""\n];
95 18         43 for my $var_name (qw($^H $^W ${^WARNING_BITS})) {
96 54   50     251 my $val = $$cxt->{hints}{$var_name} || 0;
97 54         169 $recreate_context .=
98             qq[BEGIN { $var_name = $val; }\n];
99             }
100             $recreate_context .=
101 18         45 q[BEGIN { %^H = %{$$cxt->{hints}{'%^H'} || {}}; }] . "\n";
102 18         30 for my $var_name (keys %{$$cxt->{vars}}) {
  18         73  
103 23         49 my $sigil = substr $var_name, 0, 1;
104 23         84 $recreate_context .=
105             qq[my $var_name; Data::Alias::alias $var_name = ] .
106             qq[$sigil\{\$\$cxt->{vars}->{'$var_name'}};\n];
107             }
108 18         46 $recreate_context .= qq[package main;\n];
109 18         24 $recreate_context .= q[
110             BEGIN {
111             local *^H = \do{my$x=$^H};
112             # local *^H = {%^H};
113             local *^W = \do{my$x=$^W};
114             local *{^WARNING_BITS} = \do{my$x=${^WARNING_BITS}};
115             }
116             ] if 0;
117              
118 18         33 my $prologue = q[
119             #line 1 ""
120             Devel::EvalContext::_save_context();
121             BEGIN {
122             $Devel::EvalContext::_new_context->{evalcv} =
123             B::svref_2object(sub{})->OUTSIDE->OUTSIDE;
124             }
125             ];
126 18         108 $prologue .= "{ no warnings; " .
127 18         33 join(" ", map "$_;", keys %{$$cxt->{vars}}) . " }\n";
128              
129             # TODO: make this eval hygenic
130 18         44 my $evaluator = eval do {
131 18         57 my $m = _magic_code($recreate_context);
132 18         42 _warn "magic_code:\n"; _warnblock $m;
  18         40  
133 18         2095 $m
134             };
135 18 50       4243 if ($@) {
136 0         0 croak "Devel::EvalContext::run: internal error: $@";
137             }
138              
139 18 50       57 if ($TRACING) {
140 0         0 require B::Deparse;
141 0         0 _warn "evaluator:\n"; _warnblock(B::Deparse->new->coderef2text($evaluator));
  0         0  
142             }
143              
144 18         62 $code = qq[$prologue\n#line 1 ""\n$code\n];
145 18         51 _warn "code:\n"; _warnblock($code);
  18         38  
146              
147 18         76 my $user_retval = $evaluator->($code);
148 18         437 my $user_error = $@;
149              
150             # A = $user_error
151             # B = $_new_context->{saved}
152             # 0 : we're screwed, compiled but not run, but no errors reported
153             # A : compile error, retval invalid, not run
154             # B : retval okay, compile & run ok
155             # AB : runtime error, retval invalid, compile ok
156              
157 18 100       62 if ($_new_context->{saved}) {
158             # frob it to make sure we keep the variables
159             # This does the same thing as the variable mentioning in the prologue
160 17         25 $_new_context->{vars} = {%{$$cxt->{vars}}, %{$_new_context->{vars}}};
  17         58  
  17         78  
161              
162 17         60 _warn "new context:\n";
163 17         37 _warndump($_new_context);
164             }
165              
166 18         310 $_new_context->{trace} = $TRACING;
167              
168 18 100 66     140 if (ref($user_error) or $user_error ne '') {
169 2 100       8 if ($_new_context->{saved}) { # runtime error
170 1         3 $$cxt = $_new_context;
171 1         11 return ($user_error, undef);
172             } else { # compile error
173 1         9 die $user_error;
174             }
175 0         0 return;
176             }
177             # success below here
178              
179             # no error so we expect the save to have worked
180 16 50       62 croak "Devel::EvalContext::run: internal error: not saved but no error"
181             unless $_new_context->{saved};
182              
183 16         41 _warn "retval: ", $user_retval, "\n";
184              
185 16         34 $$cxt = $_new_context;
186 16         122 return (undef, $user_retval);
187             }
188              
189             1;
190              
191             __END__