bump version
[p5sagit/Eval-WithLexicals.git] / lib / Eval / WithLexicals.pm
1 package Eval::WithLexicals;
2
3 use Moo;
4 use Moo::Role ();
5 use Sub::Quote;
6
7 our $VERSION = '1.003001'; # 1.3.1
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   my($class, @names) = @_;
35
36   Moo::Role->create_class_with_roles($class,
37     map "Eval::WithLexicals::With$_", @names);
38 }
39
40 sub setup_code {
41   my($self) = @_;
42   $self->prelude;
43 }
44
45 sub capture_code {
46   ( qq{ BEGIN { Eval::WithLexicals::Util::capture_list() } } )
47 }
48
49 sub eval {
50   my ($self, $to_eval) = @_;
51   local *Eval::WithLexicals::Cage::current_line;
52   local *Eval::WithLexicals::Cage::pad_capture;
53   local *Eval::WithLexicals::Cage::grab_captures;
54
55   my $package = $self->in_package;
56   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   my $capture_code = join '', $self->capture_code;
61
62   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   $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
78   $self->_run(\&Eval::WithLexicals::Cage::current_line);
79 }
80
81 sub _run {
82   my($self, $code) = @_;
83
84   my @ret;
85   my $ctx = $self->context;
86   if ($ctx eq 'list') {
87     @ret = $code->();
88   } elsif ($ctx eq 'scalar') {
89     $ret[0] = $code->();
90   } else {
91     $code->();
92   }
93   $self->lexicals({
94     %{$self->lexicals},
95     %{$self->_grab_captures},
96   });
97   @ret;
98 }
99
100 sub _grab_captures {
101   my ($self) = @_;
102   my $cap = Eval::WithLexicals::Cage::grab_captures();
103   foreach my $key (keys %$cap) {
104     my ($sigil, $name) = $key =~ /^(.)(.+)$/;
105     my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
106     if ($cap->{$key} eq eval "\\${var_scope_name}") {
107       delete $cap->{$key};
108     }
109   }
110   $cap;
111 }
112
113 sub _eval_do {
114   my ($self, $text_ref, $lexical, $original) = @_;
115   local @INC = (sub {
116     if ($_[1] eq '/eval_do') {
117       open my $fh, '<', $text_ref;
118       $fh;
119     } else {
120       ();
121     }
122   }, @INC);
123   do '/eval_do' or die $@;
124 }
125
126 {
127   package # hide from PAUSE
128     Eval::WithLexicals::Util;
129
130   use B qw(svref_2object);
131
132   sub capture_list {
133     my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
134     my @names = grep $_ ne '&', map $_->PV, grep $_->isa('B::PV'),
135       svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
136     $Eval::WithLexicals::current_code .=
137       '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
138       ."\n}\n}\n1;\n";
139   }
140 }
141
142 1;
143 __END__
144
145 =head1 NAME
146
147 Eval::WithLexicals - pure perl eval with persistent lexical variables
148
149 =head1 SYNOPSIS
150
151   # file: bin/tinyrepl
152
153   #!/usr/bin/env perl
154
155   use strictures 1;
156   use Eval::WithLexicals;
157   use Term::ReadLine;
158   use Data::Dumper;
159   use Getopt::Long;
160
161   GetOptions(
162     "plugin=s" => \my @plugins
163   );
164
165   $SIG{INT} = sub { warn "SIGINT\n" };
166
167   { package Data::Dumper; no strict 'vars';
168     $Terse = $Indent = $Useqq = $Deparse = $Sortkeys = 1;
169     $Quotekeys = 0;
170   }
171
172   my $eval = @plugins
173    ? Eval::WithLexicals->with_plugins(@plugins)->new
174    : Eval::WithLexicals->new;
175
176   my $read = Term::ReadLine->new('Perl REPL');
177   while (1) {
178     my $line = $read->readline('re.pl$ ');
179     exit unless defined $line;
180     my @ret; eval {
181       local $SIG{INT} = sub { die "Caught SIGINT" };
182       @ret = $eval->eval($line); 1;
183     } or @ret = ("Error!", $@);
184     print Dumper @ret;
185   }
186
187   # shell session:
188
189   $ perl -Ilib bin/tinyrepl 
190   re.pl$ my $x = 0;
191   0
192   re.pl$ ++$x;
193   1
194   re.pl$ $x + 3;
195   4
196   re.pl$ ^D
197   $
198
199 =head1 METHODS
200
201 =head2 new
202
203   my $eval = Eval::WithLexicals->new(
204     lexicals => { '$x' => \1 },      # default {}
205     in_package => 'PackageToEvalIn', # default Eval::WithLexicals::Scratchpad
206     context => 'scalar',             # default 'list'
207     prelude => 'use warnings',       # default 'use strictures 1'
208   );
209
210 =head2 eval
211
212   my @return_value = $eval->eval($code_to_eval);
213
214 =head2 lexicals
215
216   my $current_lexicals = $eval->lexicals;
217
218   $eval->lexicals(\%new_lexicals);
219
220 =head2 in_package
221
222   my $current_package = $eval->in_package;
223
224   $eval->in_package($new_package);
225
226 =head2 context
227
228   my $current_context = $eval->context;
229
230   $eval->context($new_context); # 'list', 'scalar' or 'void'
231
232 =head2 prelude
233
234 Code to run before evaling code. Loads L<strictures> by default.
235
236   my $current_prelude = $eval->prelude;
237
238   $eval->prelude(q{use warnings}); # only warnings, not strict.
239
240 =head2 with_plugins
241
242   my $eval = Eval::WithLexicals->with_plugins("HintPersistence")->new;
243
244 Construct a class with the given plugins. Plugins are roles located under
245 a package name like C<Eval::WithLexicals::With*>.
246
247 Current plugins are:
248
249 =over 4
250
251 =item * HintPersistence
252
253 When enabled this will persist pragams and other compile hints between evals
254 (for example the L<strict> and L<warnings> flags in effect). See
255 L<Eval::WithLexicals::WithHintPersistence> for further details.
256
257 =back
258
259 =head1 AUTHOR
260
261 Matt S. Trout <mst@shadowcat.co.uk>
262
263 =head1 CONTRIBUTORS
264
265 David Leadbeater <dgl@dgl.cx>
266
267 haarg - Graham Knop (cpan:HAARG) <haarg@cpan.org>
268
269 =head1 COPYRIGHT
270
271 Copyright (c) 2010 the Eval::WithLexicals L</AUTHOR> and L</CONTRIBUTORS>
272 as listed above.
273
274 =head1 LICENSE
275
276 This library is free software and may be distributed under the same terms
277 as perl itself.
278
279 =cut