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