package Eval::WithLexicals;
use Moo;
+use Moo::Role ();
+use Sub::Quote;
our $VERSION = '1.001000'; # 1.1.0
$VERSION = eval $VERSION;
-with 'Eval::WithLexicals::Role::Eval';
-with 'Eval::WithLexicals::Role::PreludeEachTime';
+has lexicals => (is => 'rw', default => quote_sub q{ {} });
+
+{
+ my %valid_contexts = map +($_ => 1), qw(list scalar void);
+
+ has context => (
+ is => 'rw', default => quote_sub(q{ 'list' }),
+ isa => sub {
+ my ($val) = @_;
+ die "Invalid context type $val - should be list, scalar or void"
+ unless $valid_contexts{$val};
+ },
+ );
+}
+
+has in_package => (
+ is => 'rw', default => quote_sub q{ 'Eval::WithLexicals::Scratchpad' }
+);
+
+has prelude => (
+ is => 'rw', default => quote_sub q{ 'use strictures 1;' }
+);
+
+sub with_plugins {
+ my($class, @names) = @_;
+
+ Moo::Role->create_class_with_roles($class,
+ map "Eval::WithLexicals::With$_", @names);
+}
+
+sub setup_code {
+ my($self) = @_;
+ $self->prelude;
+}
+
+sub capture_code {
+ ( qq{ BEGIN { Eval::WithLexicals::Util::capture_list() } } )
+}
+
+sub eval {
+ my ($self, $to_eval) = @_;
+ local *Eval::WithLexicals::Cage::current_line;
+ local *Eval::WithLexicals::Cage::pad_capture;
+ local *Eval::WithLexicals::Cage::grab_captures;
+
+ my $package = $self->in_package;
+ my $setup_code = join '', $self->setup_code,
+ Sub::Quote::capture_unroll('$_[2]', $self->lexicals, 2);
+
+ my $capture_code = join '', $self->capture_code;
+
+ local our $current_code = qq!
+${setup_code}
+sub Eval::WithLexicals::Cage::current_line {
+package ${package};
+#line 1 "(eval)"
+${to_eval}
+;sub Eval::WithLexicals::Cage::pad_capture { }
+${capture_code}
+sub Eval::WithLexicals::Cage::grab_captures {
+ no warnings 'closure'; no strict 'vars';
+ package Eval::WithLexicals::VarScope;!;
+ # rest is appended by Eval::WithLexicals::Util::capture_list, called
+ # during parsing by the BEGIN block from capture_code.
+
+ $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
+ $self->_run(\&Eval::WithLexicals::Cage::current_line);
+}
+
+sub _run {
+ my($self, $code) = @_;
+
+ my @ret;
+ my $ctx = $self->context;
+ if ($ctx eq 'list') {
+ @ret = $code->();
+ } elsif ($ctx eq 'scalar') {
+ $ret[0] = $code->();
+ } else {
+ $code->();
+ }
+ $self->lexicals({
+ %{$self->lexicals},
+ %{$self->_grab_captures},
+ });
+ @ret;
+}
+
+sub _grab_captures {
+ my ($self) = @_;
+ my $cap = Eval::WithLexicals::Cage::grab_captures();
+ foreach my $key (keys %$cap) {
+ my ($sigil, $name) = $key =~ /^(.)(.+)$/;
+ my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
+ if ($cap->{$key} eq eval "\\${var_scope_name}") {
+ delete $cap->{$key};
+ }
+ }
+ $cap;
+}
+
+sub _eval_do {
+ my ($self, $text_ref, $lexical, $original) = @_;
+ local @INC = (sub {
+ if ($_[1] eq '/eval_do') {
+ open my $fh, '<', $text_ref;
+ $fh;
+ } else {
+ ();
+ }
+ }, @INC);
+ do '/eval_do' or die $@;
+}
+
+{
+ package Eval::WithLexicals::Util;
+
+ use B qw(svref_2object);
+
+ sub capture_list {
+ my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
+ my @names = grep $_ ne '&', map $_->PV, grep $_->isa('B::PV'),
+ svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
+ $Eval::WithLexicals::current_code .=
+ '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
+ ."\n}\n}\n1;\n";
+ }
+}
=head1 NAME
+++ /dev/null
-package Eval::WithLexicals::Role::Eval;
-use Moo::Role;
-use Sub::Quote;
-
-has lexicals => (is => 'rw', default => quote_sub q{ {} });
-
-{
- my %valid_contexts = map +($_ => 1), qw(list scalar void);
-
- has context => (
- is => 'rw', default => quote_sub(q{ 'list' }),
- isa => sub {
- my ($val) = @_;
- die "Invalid context type $val - should be list, scalar or void"
- unless $valid_contexts{$val};
- },
- );
-}
-
-has in_package => (
- is => 'rw', default => quote_sub q{ 'Eval::WithLexicals::Scratchpad' }
-);
-
-has prelude => (
- is => 'rw', default => quote_sub q{ 'use strictures 1;' }
-);
-
-sub setup_code {
- my ($self) = @_;
-
- return Sub::Quote::capture_unroll('$_[2]', $self->lexicals, 2);
-}
-
-sub capture_code {
- ( qq{ BEGIN { Eval::WithLexicals::Util::capture_list() } } )
-}
-
-sub eval {
- my ($self, $to_eval) = @_;
- local *Eval::WithLexicals::Cage::current_line;
- local *Eval::WithLexicals::Cage::pad_capture;
- local *Eval::WithLexicals::Cage::grab_captures;
-
- my $package = $self->in_package;
- my $setup_code = join '', $self->setup_code;
- my $capture_code = join '', $self->capture_code;
-
- local our $current_code = qq!
-${setup_code}
-sub Eval::WithLexicals::Cage::current_line {
-package ${package};
-#line 1 "(eval)"
-${to_eval}
-;sub Eval::WithLexicals::Cage::pad_capture { }
-${capture_code}
-sub Eval::WithLexicals::Cage::grab_captures {
- no warnings 'closure'; no strict 'vars';
- package Eval::WithLexicals::VarScope;!;
- # rest is appended by Eval::WithLexicals::Util::capture_list, called
- # during parsing by the BEGIN block from capture_code.
-
- $self->_eval_do(\$current_code, $self->lexicals, $to_eval);
- $self->_run(\&Eval::WithLexicals::Cage::current_line);
-}
-
-sub _run {
- my($self, $code) = @_;
-
- my @ret;
- my $ctx = $self->context;
- if ($ctx eq 'list') {
- @ret = $code->();
- } elsif ($ctx eq 'scalar') {
- $ret[0] = $code->();
- } else {
- $code->();
- }
- $self->lexicals({
- %{$self->lexicals},
- %{$self->_grab_captures},
- });
- @ret;
-}
-
-sub _grab_captures {
- my ($self) = @_;
- my $cap = Eval::WithLexicals::Cage::grab_captures();
- foreach my $key (keys %$cap) {
- my ($sigil, $name) = $key =~ /^(.)(.+)$/;
- my $var_scope_name = $sigil.'Eval::WithLexicals::VarScope::'.$name;
- if ($cap->{$key} eq eval "\\${var_scope_name}") {
- delete $cap->{$key};
- }
- }
- $cap;
-}
-
-sub _eval_do {
- my ($self, $text_ref, $lexical, $original) = @_;
- local @INC = (sub {
- if ($_[1] eq '/eval_do') {
- open my $fh, '<', $text_ref;
- $fh;
- } else {
- ();
- }
- }, @INC);
- do '/eval_do' or die $@;
-}
-
-{
- package Eval::WithLexicals::Util;
-
- use B qw(svref_2object);
-
- sub capture_list {
- my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture;
- my @names = grep $_ ne '&', map $_->PV, grep $_->isa('B::PV'),
- svref_2object($pad_capture)->OUTSIDE->PADLIST->ARRAYelt(0)->ARRAY;
- $Eval::WithLexicals::Role::Eval::current_code .=
- '+{ '.join(', ', map "'$_' => \\$_", @names).' };'
- ."\n}\n}\n1;\n";
- }
-}
-
-1;
+++ /dev/null
-package Eval::WithLexicals::Role::PreludeEachTime;
-use Moo::Role;
-
-around setup_code => sub {
- my $orig = shift;
- my($self) = @_;
- ($self->prelude, $orig->(@_));
-};
-
-1;
-package Eval::WithLexicals::Role::LexicalHints;
+package Eval::WithLexicals::WithHintPersistence;
use Moo::Role;
our($hints, %hints);
);
}
-around setup_code => sub {
- my $orig = shift;
+sub setup_code {
my($self) = @_;
# Only run the prelude on the first eval, hints will be set after
# that.
if($self->first_eval) {
$self->first_eval(0);
- return $self->prelude, $orig->(@_);
+ return $self->prelude;
} else {
- # Seems we can't use the technique of passing via @_ for code in a BEGIN block
- return q[ BEGIN { ], _capture_unroll_global('$Eval::WithLexicals::Cage::hints', $self->hints, 2), q[ } ],
- $orig->(@_);
+ # Seems we can't use the technique of passing via @_ for code in a BEGIN
+ # block
+ return q[ BEGIN { ],
+ _capture_unroll_global('$Eval::WithLexicals::Cage::hints', $self->hints, 2),
+ q[ } ],
}
};
my($self) = @_;
( q{ sub Eval::WithLexicals::Cage::capture_hints {
- no warnings 'closure';
+ no warnings 'closure'; # XXX: can we limit the scope of this?
my($hints, %hints);
BEGIN { $hints = $^H; %hints = %^H; }
return q{$^H} => \$hints, q{%^H} => \%hints;
BEGIN { $strictures_hints = $^H }
use Test::More;
-use Eval::WithLexicals::PersistHints;
+use Eval::WithLexicals;
-my $eval = Eval::WithLexicals::PersistHints->new(prelude => '');
+my $eval = Eval::WithLexicals->with_plugins("HintPersistence")->new(prelude => '');
is_deeply(
[ $eval->eval('$x = 1') ],