From: David Leadbeater Date: Tue, 11 Jan 2011 22:09:34 +0000 (+0000) Subject: Put core back into core, persistence becomes a plugin X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit%2FEval-WithLexicals.git;a=commitdiff_plain;h=d9087132689f27e3409cf79308b90a67ee7a9ab2 Put core back into core, persistence becomes a plugin --- diff --git a/lib/Eval/WithLexicals.pm b/lib/Eval/WithLexicals.pm index 7028702..95194a8 100644 --- a/lib/Eval/WithLexicals.pm +++ b/lib/Eval/WithLexicals.pm @@ -1,12 +1,140 @@ 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 diff --git a/lib/Eval/WithLexicals/Role/Eval.pm b/lib/Eval/WithLexicals/Role/Eval.pm deleted file mode 100644 index 823f2a8..0000000 --- a/lib/Eval/WithLexicals/Role/Eval.pm +++ /dev/null @@ -1,126 +0,0 @@ -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; diff --git a/lib/Eval/WithLexicals/Role/PreludeEachTime.pm b/lib/Eval/WithLexicals/Role/PreludeEachTime.pm deleted file mode 100644 index 4f5981e..0000000 --- a/lib/Eval/WithLexicals/Role/PreludeEachTime.pm +++ /dev/null @@ -1,10 +0,0 @@ -package Eval::WithLexicals::Role::PreludeEachTime; -use Moo::Role; - -around setup_code => sub { - my $orig = shift; - my($self) = @_; - ($self->prelude, $orig->(@_)); -}; - -1; diff --git a/lib/Eval/WithLexicals/Role/LexicalHints.pm b/lib/Eval/WithLexicals/WithHintPersistence.pm similarity index 80% rename from lib/Eval/WithLexicals/Role/LexicalHints.pm rename to lib/Eval/WithLexicals/WithHintPersistence.pm index 8acc369..92d5e77 100644 --- a/lib/Eval/WithLexicals/Role/LexicalHints.pm +++ b/lib/Eval/WithLexicals/WithHintPersistence.pm @@ -1,4 +1,4 @@ -package Eval::WithLexicals::Role::LexicalHints; +package Eval::WithLexicals::WithHintPersistence; use Moo::Role; our($hints, %hints); @@ -41,18 +41,19 @@ sub _capture_unroll_global { ); } -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[ } ], } }; @@ -61,7 +62,7 @@ around capture_code => sub { 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; diff --git a/t/hints.t b/t/hints.t index a885dd4..62cecc8 100644 --- a/t/hints.t +++ b/t/hints.t @@ -4,9 +4,9 @@ my $strictures_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') ],