From: Matt S Trout Date: Sat, 4 Dec 2010 17:20:51 +0000 (+0000) Subject: initial import X-Git-Tag: v1.000000~7 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=6f914695d80ef46fe45ac39758eb956f0f5389a6;p=p5sagit%2FEval-WithLexicals.git initial import --- 6f914695d80ef46fe45ac39758eb956f0f5389a6 diff --git a/bin/tinyrepl b/bin/tinyrepl new file mode 100644 index 0000000..352ad97 --- /dev/null +++ b/bin/tinyrepl @@ -0,0 +1,19 @@ +use strictures 1; +use Eval::WithLexicals; +use Term::ReadLine; +use Data::Dumper::Concise; +use Try::Tiny; + +my $eval = Eval::WithLexicals->new; +my $read = Term::ReadLine->new('Perl REPL'); +while (1) { + my $line = $read->readline('re.pl$ '); + exit unless defined $line; + my @ret; try { + local $SIG{INT} = sub { die "Caught SIGINT" }; + @ret = $eval->eval($line); + } catch { + @ret = ("Error!", $_); + }; + print Dumper @ret; +} diff --git a/lib/Eval/WithLexicals.pm b/lib/Eval/WithLexicals.pm new file mode 100644 index 0000000..1517ef7 --- /dev/null +++ b/lib/Eval/WithLexicals.pm @@ -0,0 +1,87 @@ +package Eval::WithLexicals; + +use Moo; +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' } +); + +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 $setup = Sub::Quote::capture_unroll('$_[2]', $self->lexicals, 2); + my $package = $self->in_package; + local our $current_code = qq!use strictures 1; +${setup} +sub Eval::WithLexicals::Cage::current_line { +package ${package}; +${to_eval} +;sub Eval::WithLexicals::Cage::pad_capture { } +BEGIN { Eval::WithLexicals::Util::capture_list() } +sub Eval::WithLexicals::Cage::grab_captures { + no warnings 'closure'; no strict 'refs'; + package Eval::WithLexicals::Cage;!; + $self->_eval_do(\$current_code, $self->lexicals); + my @ret; + my $ctx = $self->context; + if ($ctx eq 'list') { + @ret = Eval::WithLexicals::Cage::current_line(); + } elsif ($ctx eq 'scalar') { + $ret[0] = Eval::WithLexicals::Cage::current_line(); + } else { + Eval::WithLexicals::Cage::current_line(); + } + $self->lexicals({ + %{$self->lexicals}, + %{Eval::WithLexicals::Cage::grab_captures()} + }); + @ret; +} + +sub _eval_do { + my ($self, $text_ref) = @_; + local @INC = (sub { + if ($_[1] eq '/eval_do') { + open my $fh, '<', $text_ref; + $fh; + } else { + (); + } + }, @INC); + do '/eval_do' or die "Error: $@\nCompiling: $$text_ref"; +} + +{ + package Eval::WithLexicals::Util; + + use B qw(svref_2object); + + sub capture_list { + my $pad_capture = \&Eval::WithLexicals::Cage::pad_capture; + my @names = 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"; + } +} + +1; diff --git a/t/simple.t b/t/simple.t new file mode 100644 index 0000000..e4316f3 --- /dev/null +++ b/t/simple.t @@ -0,0 +1,30 @@ +use strictures 1; +use Test::More; +use Eval::WithLexicals; + +my $eval = Eval::WithLexicals->new; + +is_deeply( + [ $eval->eval('my $x; $x++; $x;') ], + [ 1 ], + 'Basic eval ok' +); + +is_deeply( + $eval->lexicals, { '$x' => \1 }, + 'Lexical stored ok' +); + +is_deeply( + [ $eval->eval('$x+1') ], + [ 2 ], + 'Use lexical ok' +); + +is_deeply( + [ $eval->eval('{ my $x = 0 }; $x') ], + [ 1 ], + 'Inner scope plus lexical ok' +); + +done_testing;