package Devel::EvalContext;
{ package main; sub Devel::EvalContext::_hygenic_eval { eval $_[0] } }
use strict;
use warnings;
use PadWalker qw(peek_sub);
use Carp;
use Data::Alias qw(alias);
use B ();
our $VERSION = "0.09";
our $TRACING = 0;
# public interface needs:
#
# create an empty context
# create an empty context from here (is this possible?)
# clone a context
# evaluate in a context and get new context
# inspect hints and variables
# global vars allowing bits to talk without using closures or lexicals
our $_new_context;
sub _warn {
warn @_ if $TRACING;
}
sub _warnblock {
_warn " | $_\n" for split /\n/, $_[0];
}
sub _warndump {
require YAML;
_warnblock(YAML::Dump($_[0]));
}
sub _magic_code {
qq{
#line 1 "_magic_code"
sub {
$_[0]
#line 3 "_magic_code"
eval \$_[0];
}
};
}
sub _save_context {
my $evalcv = delete $_new_context->{evalcv};
_warn "saving context for ", $evalcv->object_2svref, "\n";
$_new_context->{saved}++; # this confirms that the code has been compiled
# should I do my own pp version?
my $v = peek_sub $evalcv->object_2svref;
$_new_context->{vars} = {};
while (my ($key, $val) = each %$v) {
next if $key =~ /^.__repl_/;
_warn " processing: $key => $val\n";
$_new_context->{vars}{$key} = $val;
}
# save hints
# hrm I'm getting the wrong values
$_new_context->{hints}->{'$^H'} = $^H & ~(256);
$_new_context->{hints}->{'%^H'} = \%^H;
$_new_context->{hints}->{'$^W'} = $^W;
$_new_context->{hints}->{'${^WARNING_BITS}'} = ${^WARNING_BITS};
}
# New context
sub new { return bless \{}, $_[0] }
sub trace {
my ($s, $t) = @_;
if ($t) {
$$s->{trace} = $t;
}
return $$s->{trace};
}
# Run a context
sub run {
my ($cxt, $code) = @_;
local $TRACING = $$cxt->{trace};
_warn "+", ("-" x 71), "\n";
_warn "context_eval: {", $code, "} using ", $cxt, "/", $$cxt, "\n";
local $_new_context = undef;
# I bet I could write a PP version of this using B
my $recreate_context = qq[\n#line 1 "<recreate_context>"\n];
for my $var_name (qw($^H $^W ${^WARNING_BITS})) {
my $val = $$cxt->{hints}{$var_name} || 0;
$recreate_context .=
qq[BEGIN { $var_name = $val; }\n];
}
$recreate_context .=
q[BEGIN { %^H = %{$$cxt->{hints}{'%^H'} || {}}; }] . "\n";
for my $var_name (keys %{$$cxt->{vars}}) {
my $sigil = substr $var_name, 0, 1;
$recreate_context .=
qq[my $var_name; Data::Alias::alias $var_name = ] .
qq[$sigil\{\$\$cxt->{vars}->{'$var_name'}};\n];
}
$recreate_context .= qq[package main;\n];
$recreate_context .= q[
BEGIN {
local *^H = \do{my$x=$^H};
# local *^H = {%^H};
local *^W = \do{my$x=$^W};
local *{^WARNING_BITS} = \do{my$x=${^WARNING_BITS}};
}
] if 0;
my $prologue = q[
#line 1 "<prologue>"
Devel::EvalContext::_save_context();
BEGIN {
$Devel::EvalContext::_new_context->{evalcv} =
B::svref_2object(sub{})->OUTSIDE->OUTSIDE;
}
];
$prologue .= "{ no warnings; " .
join(" ", map "$_;", keys %{$$cxt->{vars}}) . " }\n";
# TODO: make this eval hygenic
my $evaluator = eval do {
my $m = _magic_code($recreate_context);
_warn "magic_code:\n"; _warnblock $m;
$m
};
if ($@) {
croak "Devel::EvalContext::run: internal error: $@";
}
if ($TRACING) {
require B::Deparse;
_warn "evaluator:\n"; _warnblock(B::Deparse->new->coderef2text($evaluator));
}
$code = qq[$prologue\n#line 1 "<interactive>"\n$code\n];
_warn "code:\n"; _warnblock($code);
my $user_retval = $evaluator->($code);
my $user_error = $@;
# A = $user_error
# B = $_new_context->{saved}
# 0 : we're screwed, compiled but not run, but no errors reported
# A : compile error, retval invalid, not run
# B : retval okay, compile & run ok
# AB : runtime error, retval invalid, compile ok
if ($_new_context->{saved}) {
# frob it to make sure we keep the variables
# This does the same thing as the variable mentioning in the prologue
$_new_context->{vars} = {%{$$cxt->{vars}}, %{$_new_context->{vars}}};
_warn "new context:\n";
_warndump($_new_context);
}
$_new_context->{trace} = $TRACING;
if (ref($user_error) or $user_error ne '') {
if ($_new_context->{saved}) { # runtime error
$$cxt = $_new_context;
return ($user_error, undef);
} else { # compile error
die $user_error;
}
return;
}
# success below here
# no error so we expect the save to have worked
croak "Devel::EvalContext::run: internal error: not saved but no error"
unless $_new_context->{saved};
_warn "retval: ", $user_retval, "\n";
$$cxt = $_new_context;
return (undef, $user_retval);
}
1;
__END__
=head1 NAME
Devel::EvalContext - Save lexicals and hints between calls to eval
=head1 SYNOPSIS
use Devel::EvalContext;
my $cxt = Devel::EvalContext->new;
$cxt->run(q{ my $a = 5; });
$cxt->run(q{ print $a; });
=head1 DESCRIPTION
Sometimes it's necessary to run some code that creates lexicals and then
run I<more> code that uses the same ones. Perhaps it's in an interactive
development environment.
However, unlike Scheme, perl's eval primitive doesn't take an extra
parameter to specify the environment to evaluate inside of. This module
corrects this deficit.
=head1 METHODS
=head2 new
Create a new, empty context. It has no lexicals and hints are set to zero.
=head2 run
Equivalent to eval but with a context specified.
=head1 SUPPORT
Please send bugs, queries or encouragement to
E<lt>bug-Devel-EvalContext@rt.cpan.orgE<gt> or E<lt>bsmith@cpan.orgE<gt>.
=head1 SEE ALSO
L<perlfunc>
=head1 AUTHOR
Benjamin Smith E<lt>bsmith@cpan.orgE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2006 by Benjamin Smith.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.
=cut