Put core back into core, persistence becomes a plugin
[p5sagit/Eval-WithLexicals.git] / lib / Eval / WithLexicals.pm
CommitLineData
6f914695 1package Eval::WithLexicals;
2
3use Moo;
d9087132 4use Moo::Role ();
5use Sub::Quote;
6f914695 6
1de34059 7our $VERSION = '1.001000'; # 1.1.0
8d080039 8$VERSION = eval $VERSION;
9
d9087132 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"
20 unless $valid_contexts{$val};
21 },
22 );
23}
24
25has in_package => (
26 is => 'rw', default => quote_sub q{ 'Eval::WithLexicals::Scratchpad' }
27);
28
29has prelude => (
30 is => 'rw', default => quote_sub q{ 'use strictures 1;' }
31);
32
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
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;
54
55 my $package = $self->in_package;
56 my $setup_code = join '', $self->setup_code,
57 Sub::Quote::capture_unroll('$_[2]', $self->lexicals, 2);
58
59 my $capture_code = join '', $self->capture_code;
60
61 local our $current_code = qq!
62${setup_code}
63sub Eval::WithLexicals::Cage::current_line {
64package ${package};
65#line 1 "(eval)"
66${to_eval}
67;sub Eval::WithLexicals::Cage::pad_capture { }
68${capture_code}
69sub Eval::WithLexicals::Cage::grab_captures {
70 no warnings 'closure'; no strict 'vars';
71 package Eval::WithLexicals::VarScope;!;
72 # rest is appended by Eval::WithLexicals::Util::capture_list, called
73 # during parsing by the BEGIN block from capture_code.
74
75 $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
76 $self->_run(\&Eval::WithLexicals::Cage::current_line);
77}
78
79sub _run {
80 my($self, $code) = @_;
81
82 my @ret;
83 my $ctx = $self->context;
84 if ($ctx eq 'list') {
85 @ret = $code->();
86 } elsif ($ctx eq 'scalar') {
87 $ret[0] = $code->();
88 } else {
89 $code->();
90 }
91 $self->lexicals({
92 %{$self->lexicals},
93 %{$self->_grab_captures},
94 });
95 @ret;
96}
97
98sub _grab_captures {
99 my ($self) = @_;
100 my $cap = Eval::WithLexicals::Cage::grab_captures();
101 foreach my $key (keys %$cap) {
102 my ($sigil, $name) = $key =~ /^(.)(.+)$/;
103 my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
104 if ($cap->{$key} eq eval "\\${var_scope_name}") {
105 delete $cap->{$key};
106 }
107 }
108 $cap;
109}
110
111sub _eval_do {
112 my ($self, $text_ref, $lexical, $original) = @_;
113 local @INC = (sub {
114 if ($_[1] eq '/eval_do') {
115 open my $fh, '<', $text_ref;
116 $fh;
117 } else {
118 ();
119 }
120 }, @INC);
121 do '/eval_do' or die $@;
122}
123
124{
125 package Eval::WithLexicals::Util;
126
127 use B qw(svref_2object);
128
129 sub capture_list {
130 my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
131 my @names = grep $_ ne '&', map $_->PV, grep $_->isa('B::PV'),
132 svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
133 $Eval::WithLexicals::current_code .=
134 '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
135 ."\n}\n}\n1;\n";
136 }
137}
6f914695 138
8d080039 139=head1 NAME
140
141Eval::WithLexicals - pure perl eval with persistent lexical variables
142
143=head1 SYNOPSIS
144
145 # file: bin/tinyrepl
146
147 #!/usr/bin/env perl
148
149 use strictures 1;
150 use Eval::WithLexicals;
151 use Term::ReadLine;
152 use Data::Dumper;
153
154 $SIG{INT} = sub { warn "SIGINT\n" };
155
156 { package Data::Dumper; no strict 'vars';
157 $Terse = $Indent = $Useqq = $Deparse = $Sortkeys = 1;
158 $Quotekeys = 0;
159 }
160
161 my $eval = Eval::WithLexicals->new;
162 my $read = Term::ReadLine->new('Perl REPL');
163 while (1) {
164 my $line = $read->readline('re.pl$ ');
165 exit unless defined $line;
166 my @ret; eval {
167 local $SIG{INT} = sub { die "Caught SIGINT" };
168 @ret = $eval->eval($line); 1;
169 } or @ret = ("Error!", $@);
170 print Dumper @ret;
171 }
172
173 # shell session:
174
175 $ perl -Ilib bin/tinyrepl
176 re.pl$ my $x = 0;
177 0
178 re.pl$ ++$x;
179 1
180 re.pl$ $x + 3;
181 4
182 re.pl$ ^D
183 $
184
185=head1 METHODS
186
187=head2 new
188
189 my $eval = Eval::WithLexicals->new(
190 lexicals => { '$x' => \1 }, # default {}
191 in_package => 'PackageToEvalIn', # default Eval::WithLexicals::Scratchpad
192 context => 'scalar', # default 'list'
3044b1da 193 prelude => 'use warnings', # default 'use strictures 1'
8d080039 194 );
195
196=head2 eval
197
198 my @return_value = $eval->eval($code_to_eval);
199
200=head2 lexicals
201
202 my $current_lexicals = $eval->lexicals;
203
204 $eval->lexicals(\%new_lexicals);
205
206=head2 in_package
207
208 my $current_package = $eval->in_package;
209
210 $eval->in_package($new_package);
211
212=head2 context
213
214 my $current_context = $eval->context;
215
216 $eval->context($new_context); # 'list', 'scalar' or 'void'
217
3044b1da 218=head2 prelude
219
220Code to run before evaling code. Loads L<strictures> by default.
221
222 my $current_prelude = $eval->prelude;
223
224 $eval->prelude(q{use warnings}); # only warnings, not strict.
225
8d080039 226=head1 AUTHOR
227
228Matt S. Trout <mst@shadowcat.co.uk>
229
230=head1 CONTRIBUTORS
231
9a82ac00 232David Leadbeater <dgl@dgl.cx>
8d080039 233
234=head1 COPYRIGHT
235
236Copyright (c) 2010 the Eval::WithLexicals L</AUTHOR> and L</CONTRIBUTORS>
237as listed above.
238
239=head1 LICENSE
240
241This library is free software and may be distributed under the same terms
242as perl itself.
243
244=cut
245
6f914695 2461;