support lexical subs with the '&' sigil
[gitmo/Eval-Closure.git] / lib / Eval / Closure.pm
CommitLineData
efb592ef 1package Eval::Closure;
b3bd5eb8 2use strict;
3use warnings;
efb592ef 4use Sub::Exporter -setup => {
5 exports => [qw(eval_closure)],
ce19c70b 6 groups => { default => [qw(eval_closure)] },
efb592ef 7};
ed9a00ae 8# ABSTRACT: safely and cleanly create closures via string eval
efb592ef 9
10use Carp;
11use overload ();
12use Scalar::Util qw(reftype);
13use Try::Tiny;
14
a55b2a20 15use constant HAS_LEXICAL_SUBS => $] >= 5.018;
16
ed9a00ae 17=head1 SYNOPSIS
18
2e6086ab 19 use Eval::Closure;
20
21 my $code = eval_closure(
22 source => 'sub { $foo++ }',
23 environment => {
24 '$foo' => \1,
25 },
26 );
27
28 warn $code->(); # 1
29 warn $code->(); # 2
30
31 my $code2 = eval_closure(
32 source => 'sub { $code->() }',
33 ); # dies, $code isn't in scope
34
ed9a00ae 35=head1 DESCRIPTION
36
2e6086ab 37String eval is often used for dynamic code generation. For instance, C<Moose>
38uses it heavily, to generate inlined versions of accessors and constructors,
39which speeds code up at runtime by a significant amount. String eval is not
40without its issues however - it's difficult to control the scope it's used in
9b61f781 41(which determines which variables are in scope inside the eval), and it's easy
42to miss compilation errors, since eval catches them and sticks them in $@
43instead.
2e6086ab 44
9b61f781 45This module attempts to solve these problems. It provides an C<eval_closure>
fa287851 46function, which evals a string in a clean environment, other than a fixed list
9b61f781 47of specified variables. Compilation errors are rethrown automatically.
2e6086ab 48
ed9a00ae 49=cut
50
51=func eval_closure(%args)
52
2e6086ab 53This function provides the main functionality of this module. It is exported by
54default. It takes a hash of parameters, with these keys being valid:
55
56=over 4
57
58=item source
59
60The string to be evaled. It should end by returning a code reference. It can
61access any variable declared in the C<environment> parameter (and only those
62variables). It can be either a string, or an arrayref of lines (which will be
63joined with newlines to produce the string).
64
65=item environment
66
67The environment to provide to the eval. This should be a hashref, mapping
68variable names (including sigils) to references of the appropriate type. For
69instance, a valid value for environment would be C<< { '@foo' => [] } >> (which
70would allow the generated function to use an array named C<@foo>). Generally,
71this is used to allow the generated function to access externally defined
72variables (so you would pass in a reference to a variable that already exists).
73
a55b2a20 74In perl 5.18 and greater, the environment hash can contain variables with a
75sigil of C<&>. This will create a lexical sub in the evaluated code (see
76L<feature/The 'lexical_subs' feature>). Using a C<&> sigil on perl versions
77before lexical subs were available will throw an error.
78
2e6086ab 79=item description
80
81This lets you provide a bit more information in backtraces. Normally, when a
82function that was generated through string eval is called, that stack frame
83will show up as "(eval n)", where 'n' is a sequential identifier for every
84string eval that has happened so far in the program. Passing a C<description>
85parameter lets you override that to something more useful (for instance,
86L<Moose> overrides the description for accessors to something like "accessor
c4318911 87foo at MyClass.pm, line 123").
2e6086ab 88
75e6988b 89=item line
90
91This lets you override the particular line number that appears in backtraces,
c8d4a65f 92much like the C<description> option. The default is 1.
75e6988b 93
5617e966 94=item terse_error
95
96Normally, this function appends the source code that failed to compile, and
97prepends some explanatory text. Setting this option to true suppresses that
98behavior so you get only the compilation error that Perl actually reported.
99
2e6086ab 100=back
101
ed9a00ae 102=cut
103
efb592ef 104sub eval_closure {
105 my (%args) = @_;
8e1b3d7b 106
efb592ef 107 $args{source} = _canonicalize_source($args{source});
8e1b3d7b 108 _validate_env($args{environment} ||= {});
efb592ef 109
c8d4a65f 110 $args{source} = _line_directive(@args{qw(line description)})
111 . $args{source}
04918f80 112 if defined $args{description} && !($^P & 0x10);
3efcc087 113
409b8f41 114 my ($code, $e) = _clean_eval_closure(@args{qw(source environment)});
efb592ef 115
5617e966 116 if (!$code) {
117 if ($args{terse_error}) {
118 die "$e\n";
119 }
120 else {
121 croak("Failed to compile source: $e\n\nsource:\n$args{source}")
122 }
123 }
efb592ef 124
125 return $code;
126}
127
128sub _canonicalize_source {
129 my ($source) = @_;
130
131 if (defined($source)) {
132 if (ref($source)) {
133 if (reftype($source) eq 'ARRAY'
134 || overload::Method($source, '@{}')) {
135 return join "\n", @$source;
136 }
137 elsif (overload::Method($source, '""')) {
138 return "$source";
139 }
140 else {
141 croak("The 'source' parameter to eval_closure must be a "
142 . "string or array reference");
143 }
144 }
145 else {
146 return $source;
147 }
148 }
149 else {
150 croak("The 'source' parameter to eval_closure is required");
151 }
152}
153
8e1b3d7b 154sub _validate_env {
155 my ($env) = @_;
156
157 croak("The 'environment' parameter must be a hashref")
158 unless reftype($env) eq 'HASH';
159
160 for my $var (keys %$env) {
a55b2a20 161 if (HAS_LEXICAL_SUBS) {
162 croak("Environment key '$var' should start with \@, \%, \$, or \&")
163 unless $var =~ /^([\@\%\$\&])/;
164 }
165 else {
166 croak("Environment key '$var' should start with \@, \%, or \$")
167 unless $var =~ /^([\@\%\$])/;
168 }
8e1b3d7b 169 croak("Environment values must be references, not $env->{$var}")
170 unless ref($env->{$var});
171 }
172}
173
3efcc087 174sub _line_directive {
75e6988b 175 my ($line, $description) = @_;
176
c8d4a65f 177 $line = 1 unless defined($line);
3efcc087 178
75e6988b 179 return qq{#line $line "$description"\n};
3efcc087 180}
181
efb592ef 182sub _clean_eval_closure {
1a2acf75 183 my ($source, $captures) = @_;
efb592ef 184
25ef0135 185 my @capture_keys = sort keys %$captures;
186
a30f41f7 187 if ($ENV{EVAL_CLOSURE_PRINT_SOURCE}) {
25ef0135 188 _dump_source(_make_compiler_source($source, @capture_keys));
a30f41f7 189 }
efb592ef 190
447800b5 191 my ($compiler, $e) = _make_compiler($source, @capture_keys);
f3c27658 192 my $code;
193 if (defined $compiler) {
447800b5 194 $code = $compiler->(@$captures{@capture_keys});
f3c27658 195 }
26eb0e7a 196
b86710e9 197 if (defined($code) && (!ref($code) || ref($code) ne 'CODE')) {
3eb05ecb 198 $e = "The 'source' parameter must return a subroutine reference, "
199 . "not $code";
26eb0e7a 200 undef $code;
26eb0e7a 201 }
202
18b5b42a 203 return ($code, $e);
efb592ef 204}
205
fa287851 206sub _make_compiler {
207 my $source = _make_compiler_source(@_);
74225234 208
fa287851 209 return @{ _clean_eval($source) };
f3c27658 210}
211
0fb2ea46 212sub _clean_eval {
01a39ce3 213 local $@;
214 local $SIG{__DIE__};
215 my $compiler = eval $_[0];
216 my $e = $@;
217 [ $compiler, $e ];
0fb2ea46 218}
219
e6c246fb 220$Eval::Closure::SANDBOX_ID = 0;
221
f3c27658 222sub _make_compiler_source {
447800b5 223 my ($source, @capture_keys) = @_;
e6c246fb 224 $Eval::Closure::SANDBOX_ID++;
f3c27658 225 my $i = 0;
efb592ef 226 return join "\n", (
e6c246fb 227 "package Eval::Closure::Sandbox_$Eval::Closure::SANDBOX_ID;",
f3c27658 228 'sub {',
a55b2a20 229 (map { _make_lexical_assignment($_, $i++) } @capture_keys),
230 $source,
f3c27658 231 '}',
efb592ef 232 );
233}
234
a55b2a20 235sub _make_lexical_assignment {
236 my ($key, $index) = @_;
237 my $sigil = substr($key, 0, 1);
238 my $name = substr($key, 1);
239 if (HAS_LEXICAL_SUBS && $sigil eq '&') {
240 my $tmpname = '$__' . $name . '__' . $index;
241 return 'use feature "lexical_subs"; '
242 . 'no warnings "experimental::lexical_subs"; '
243 . 'my ' . $tmpname . ' = $_[' . $index . ']; '
244 . 'my sub ' . $name . ' { goto ' . $tmpname . ' }';
245 }
246 else {
247 return 'my ' . $key . ' = ' . $sigil . '{$_[' . $index . ']};';
248 }
249}
250
efb592ef 251sub _dump_source {
409b8f41 252 my ($source) = @_;
efb592ef 253
254 my $output;
255 if (try { require Perl::Tidy }) {
256 Perl::Tidy::perltidy(
257 source => \$source,
258 destination => \$output,
9688c823 259 argv => [],
efb592ef 260 );
261 }
262 else {
263 $output = $source;
264 }
265
409b8f41 266 warn "$output\n";
efb592ef 267}
268
ed9a00ae 269=head1 BUGS
270
271No known bugs.
272
273Please report any bugs through RT: email
274C<bug-eval-closure at rt.cpan.org>, or browse to
275L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Eval-Closure>.
276
277=head1 SEE ALSO
278
279=over 4
280
281=item * L<Class::MOP::Method::Accessor>
282
283This module is a factoring out of code that used to live here
284
285=back
286
287=head1 SUPPORT
288
289You can find this documentation for this module with the perldoc command.
290
291 perldoc Eval::Closure
292
293You can also look for information at:
294
295=over 4
296
297=item * AnnoCPAN: Annotated CPAN documentation
298
299L<http://annocpan.org/dist/Eval-Closure>
300
301=item * CPAN Ratings
302
303L<http://cpanratings.perl.org/d/Eval-Closure>
304
305=item * RT: CPAN's request tracker
306
307L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Eval-Closure>
308
309=item * Search CPAN
310
311L<http://search.cpan.org/dist/Eval-Closure>
312
313=back
314
315=head1 AUTHOR
316
317Jesse Luehrs <doy at tozt dot net>
318
319Based on code from L<Class::MOP::Method::Accessor>, by Stevan Little and the
320Moose Cabal.
321
322=cut
323
efb592ef 3241;