Add persistent hints
[p5sagit/Eval-WithLexicals.git] / lib / Eval / WithLexicals / Role / Eval.pm
CommitLineData
148445b9 1package Eval::WithLexicals::Role::Eval;
2use Moo::Role;
3use Sub::Quote;
4
5has lexicals => (is => 'rw', default => quote_sub q{ {} });
6
7{
8 my %valid_contexts = map +($_ => 1), qw(list scalar void);
9
10 has context => (
11 is => 'rw', default => quote_sub(q{ 'list' }),
12 isa => sub {
13 my ($val) = @_;
14 die "Invalid context type $val - should be list, scalar or void"
15 unless $valid_contexts{$val};
16 },
17 );
18}
19
20has in_package => (
21 is => 'rw', default => quote_sub q{ 'Eval::WithLexicals::Scratchpad' }
22);
23
24has prelude => (
25 is => 'rw', default => quote_sub q{ 'use strictures 1;' }
26);
27
28sub setup_code {
29 my ($self) = @_;
30
31 return Sub::Quote::capture_unroll('$_[2]', $self->lexicals, 2);
32}
33
34sub capture_code {
35 ( qq{ BEGIN { Eval::WithLexicals::Util::capture_list() } } )
36}
37
38sub eval {
39 my ($self, $to_eval) = @_;
40 local *Eval::WithLexicals::Cage::current_line;
41 local *Eval::WithLexicals::Cage::pad_capture;
42 local *Eval::WithLexicals::Cage::grab_captures;
43
44 my $package = $self->in_package;
45 my $setup_code = join '', $self->setup_code;
46 my $capture_code = join '', $self->capture_code;
47
48 local our $current_code = qq!
49${setup_code}
50sub Eval::WithLexicals::Cage::current_line {
51package ${package};
52#line 1 "(eval)"
53${to_eval}
54;sub Eval::WithLexicals::Cage::pad_capture { }
55${capture_code}
56sub Eval::WithLexicals::Cage::grab_captures {
57 no warnings 'closure'; no strict 'vars';
58 package Eval::WithLexicals::VarScope;!;
59 # rest is appended by Eval::WithLexicals::Util::capture_list, called
60 # during parsing by the BEGIN block from capture_code.
61
62 $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
63 $self->_run(\&Eval::WithLexicals::Cage::current_line);
64}
65
66sub _run {
67 my($self, $code) = @_;
68
69 my @ret;
70 my $ctx = $self->context;
71 if ($ctx eq 'list') {
72 @ret = $code->();
73 } elsif ($ctx eq 'scalar') {
74 $ret[0] = $code->();
75 } else {
76 $code->();
77 }
78 $self->lexicals({
79 %{$self->lexicals},
80 %{$self->_grab_captures},
81 });
82 @ret;
83}
84
85sub _grab_captures {
86 my ($self) = @_;
87 my $cap = Eval::WithLexicals::Cage::grab_captures();
88 foreach my $key (keys %$cap) {
89 my ($sigil, $name) = $key =~ /^(.)(.+)$/;
90 my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
91 if ($cap->{$key} eq eval "\\${var_scope_name}") {
92 delete $cap->{$key};
93 }
94 }
95 $cap;
96}
97
98sub _eval_do {
99 my ($self, $text_ref, $lexical, $original) = @_;
100 local @INC = (sub {
101 if ($_[1] eq '/eval_do') {
102 open my $fh, '<', $text_ref;
103 $fh;
104 } else {
105 ();
106 }
107 }, @INC);
108 do '/eval_do' or die $@;
109}
110
111{
112 package Eval::WithLexicals::Util;
113
114 use B qw(svref_2object);
115
116 sub capture_list {
117 my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
118 my @names = grep $_ ne '&', map $_->PV, grep $_->isa('B::PV'),
119 svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
120 $Eval::WithLexicals::Role::Eval::current_code .=
121 '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
122 ."\n}\n}\n1;\n";
123 }
124}
125
1261;