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