App-REPL
view release on metacpan or search on metacpan
use PadWalker 'peek_my';
use PPI;
use PPI::Find;
use Data::Dumper;
use Symbol;
use Term::ReadLine;
$App::REPL::DEBUG = 0;
{ my $in_package = 'App::REPL';
sub in_package { @_ ? $in_package = shift : $in_package }
}
# ----------------------------------------------------------------------
# Added RESET as the color somehow bleeds into the prompt
# -- when we use Term::ReadLine
{ my $prompt;
my $term = Term::ReadLine->new('iperl');
sub pnew { $prompt = RESET . in_package . ' _ ' }
sub pcont { $prompt = RESET . in_package . '. ' }
sub prompt {
my $s = $term->readline($prompt);
$term->addhistory($s) if defined($s) and $s =~ /\S/;
$s
}
pnew;
$term->ornaments(0)
}
sub eek { print STDERR BOLD RED @_, "\n"; goto REPL }
# ----------------------------------------------------------------------
# Magic. This allows 'my' variables assigned within the eval to carry
# through subsequent evals -- unless the eval'd returns from the eval,
# in which case the next eval will get the same variables.
#--
use constant PRO_IN => <<'EOP';
use App::REPL;
use strict;
no warnings 'void';
EOP
sub PRO {
my $r = "no strict 'refs';\n"
. "package @{[in_package]};\n";
my $h = do { no strict 'refs'; ${in_package . '::REPL::env'} || {}};
for (keys %$h) {
/^(.)/;
$r .= "my $_ = $1" . q,{${", . in_package . q,::REPL::env"}->, . "{'$_'}};\n"
}
$r . PRO_IN
}
use constant EPI => <<'EOE';
;
no strict 'refs';
for (Symbol::qualify('')) { s/::$//; main::in_package($_) }
${main::in_package . '::REPL::env'} = PadWalker::peek_my(0)
EOE
# ----------------------------------------------------------------------
# More magic. This finds the final statement of some Perl, wherever
# that statement may be (even if its result cannot escape the overall
# evaluation), and saves its value in $App::REPL::ret
#--
$App::REPL::ret = '';
{ my $f = PPI::Find->new(sub { shift->isa('PPI::Statement') });
sub save_ret {
my $d = shift;
# don't even try if it contains something troublesome.
return $d->serialize if has_troublesome($d);
my @s = $f->in($d);
for (reverse @s) {
next if within_constructor($_, $d);
print Dumper $d if $App::REPL::DEBUG > 1;
unshift @{$_->{children}},
bless({content => '$App::REPL::ret'}, 'PPI::Token::Symbol'),
bless({content => '='}, 'PPI::Token::Operator');
return $d->serialize
}
# try and save the whole thing
return '$App::REPL::ret = ' . $d->serialize if @s;
# give up
$d->serialize
}
}
{ my %troublesome = map { $_, 1 } qw(sub package use require my our local);
my $f = PPI::Find->new(sub {
return 0 unless (my $e = shift)->isa('PPI::Token::Word');
return 1 if exists $troublesome{$e->{content}};
0
});
sub has_troublesome { $f->in(shift) }
}
sub dump_ret {
return if ref $_[0] eq 'CODE';
print BOLD CYAN Dumper $App::REPL::ret if $App::REPL::ret;
}
{ my $fc = PPI::Find->new(sub { $_[0]->isa('PPI::Structure::Constructor')
or $_[0]->isa('PPI::Structure::Block') });
sub within_constructor {
my ($s, $d) = @_;
my $fs = PPI::Find->new(sub { shift eq $s });
for ($fc->in($d)) {
return 1 for $fs->in($_);
}
0
}
}
# ----------------------------------------------------------------------
# The PPI here handles the rest of the magic: it detects unfinished
# blocks and such so that the repl can request more lines until they
# complete. Note that this does -not- handle e.g. qw(
#--
( run in 1.266 second using v1.01-cache-2.11-cpan-39bf76dae61 )