only pass the keys into _make_compiler
[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};
8
9use Carp;
10use overload ();
11use Scalar::Util qw(reftype);
12use Try::Tiny;
13
14sub eval_closure {
15 my (%args) = @_;
8e1b3d7b 16
efb592ef 17 $args{source} = _canonicalize_source($args{source});
8e1b3d7b 18 _validate_env($args{environment} ||= {});
efb592ef 19
3efcc087 20 $args{source} = _line_directive($args{description}) . $args{source}
21 if defined $args{description};
22
409b8f41 23 my ($code, $e) = _clean_eval_closure(@args{qw(source environment)});
efb592ef 24
25 croak("Failed to compile source: $e\n\nsource:\n$args{source}")
26 unless $code;
27
28 return $code;
29}
30
31sub _canonicalize_source {
32 my ($source) = @_;
33
34 if (defined($source)) {
35 if (ref($source)) {
36 if (reftype($source) eq 'ARRAY'
37 || overload::Method($source, '@{}')) {
38 return join "\n", @$source;
39 }
40 elsif (overload::Method($source, '""')) {
41 return "$source";
42 }
43 else {
44 croak("The 'source' parameter to eval_closure must be a "
45 . "string or array reference");
46 }
47 }
48 else {
49 return $source;
50 }
51 }
52 else {
53 croak("The 'source' parameter to eval_closure is required");
54 }
55}
56
8e1b3d7b 57sub _validate_env {
58 my ($env) = @_;
59
60 croak("The 'environment' parameter must be a hashref")
61 unless reftype($env) eq 'HASH';
62
63 for my $var (keys %$env) {
b3bd5eb8 64 croak("Environment key '$var' should start with \@, \%, or \$")
8e1b3d7b 65 unless $var =~ /^([\@\%\$])/;
66 croak("Environment values must be references, not $env->{$var}")
67 unless ref($env->{$var});
68 }
69}
70
3efcc087 71sub _line_directive {
72 my ($description) = @_;
73
74 return qq{#line 1 "$description"\n};
75}
76
efb592ef 77sub _clean_eval_closure {
f3c27658 78 my ($source, $captures) = @_;
efb592ef 79
a30f41f7 80 if ($ENV{EVAL_CLOSURE_PRINT_SOURCE}) {
f3c27658 81 _dump_source(_make_compiler_source(@_));
a30f41f7 82 }
efb592ef 83
447800b5 84 my @capture_keys = keys %$captures;
85 my ($compiler, $e) = _make_compiler($source, @capture_keys);
f3c27658 86 my $code;
87 if (defined $compiler) {
447800b5 88 $code = $compiler->(@$captures{@capture_keys});
f3c27658 89 }
26eb0e7a 90
b86710e9 91 if (defined($code) && (!ref($code) || ref($code) ne 'CODE')) {
3eb05ecb 92 $e = "The 'source' parameter must return a subroutine reference, "
93 . "not $code";
26eb0e7a 94 undef $code;
26eb0e7a 95 }
96
18b5b42a 97 return ($code, $e);
efb592ef 98}
99
f3c27658 100sub _make_compiler {
101 local $@;
102 local $SIG{__DIE__};
103 my $compiler = eval _make_compiler_source(@_);
104 my $e = $@;
105 return ($compiler, $e);
106}
107
108sub _make_compiler_source {
447800b5 109 my ($source, @capture_keys) = @_;
f3c27658 110 my $i = 0;
efb592ef 111 return join "\n", (
f3c27658 112 'sub {',
efb592ef 113 (map {
f3c27658 114 'my ' . $_ . ' = ' . substr($_, 0, 1) . '{$_[' . $i++ . ']};'
447800b5 115 } @capture_keys),
efb592ef 116 $source,
f3c27658 117 '}',
efb592ef 118 );
119}
120
121sub _dump_source {
409b8f41 122 my ($source) = @_;
efb592ef 123
124 my $output;
125 if (try { require Perl::Tidy }) {
126 Perl::Tidy::perltidy(
127 source => \$source,
128 destination => \$output,
129 );
130 }
131 else {
132 $output = $source;
133 }
134
409b8f41 135 warn "$output\n";
efb592ef 136}
137
1381;