gboole has asked for the wisdom of the Perl Monks concerning the following question:

Can I "eval" something in such a way that any changes made to variables within the "eval" are forgotten? I would like a function EVAL() so that, for instance,

my $i = 0; EVAL("++$i; Do_Something()"); print "\$i equals $i\n";

would print "i equals 0", assuming Do_Something() does not change $i. We do not know in advance what variables may be used in the EVAL parameter.

Update: solution using threads given in Re^3, thanks to b4swine!

The reason I ask is that I am writing a Perl-Tk calculator with command and answer lines (called Command and Answer below). Command takes input from the keyboard but also from GUI buttons that update Answer like typical calculator buttons.

Keyboard entry is eval'ed (after some calculator-friendly preprocessing) as a Perl expression, and updates Answer only when special keys such as Return are encountered. However GUI buttons should update Answer more frequently, leading to difficulties. For instance, if $i has value zero, the line

(++$i)+1;

should of course give Answer "2". However if the ")+1" substring were entered via GUI buttons, then the following should happen:

")": evaluate first part, give Answer "1";

"*": do nothing for now;

"1": re-evaluate the whole string, and give Answer "2" (not "3" even though ++$i was evaluated again).

The whole string must be re-evaluated at this last step, since the user might have changed the contents of Command via the keyboard.

My aim is to make an app that perfectly mimics a typical calculator. Full compatibility with Perl is not desired, but I want scalar purely-numeric Perl expressions to evaluate correctly (except for some fairly useless expressions that are pre-interpreted in a calculator friendly way).

Replies are listed 'Best First'.
Re: eval something using private copy of variables
by snopal (Pilgrim) on Aug 19, 2007 at 16:06 UTC

    Wouldn't you just do something like this:

    eval { local ($var1, $var2, %hash1, %hash2); # do some junk }

    By localizing your vars, you retain the global values while working on local copies.

    The distinction being that 'my' creates a completely new variable in the block scope, while 'local' provides a local scope to a higher scope variable.

    == Desire is one product of absence. -- Stephen Opal ==
Re: eval something using private copy of variables
by educated_foo (Vicar) on Aug 19, 2007 at 16:36 UTC
    If the code you're evaluating is non-perverse, you can use a temporary package:
    my $pkg = 'AAAAAAAAAA'; sub EVAL { ++$pkg; if (wantarray) { my @res = eval <<EOS; package $pkg; @_ EOS eval 'undef %'.$pkg.'::'; @res; } else { my $res = eval <<EOS; package $pkg; @_ EOS eval 'undef %'.$pkg.'::'; $res; } }
      Thanks for the quick response, which seems very promising especially as I hadn't delved into namespaces before now. Unfortunately, while it solves one half of the problem (protecting outer copies of the variables), it does not solve the other (copying the outer variables to the inner namespace).

      For instance if I initialize $i to 3 via "$i=3;" or "local $i=3;" early in main, and then feed line input to EVAL via:

      for (;$line=<>;) { $v = EVAL($line); print "answer is $v\n"; }
      then $i is initially undefined in the local package, so the input "++$i" gives "answer is 1" every time, rather than the desired "answer is 4" every time.

      I would like to import from main all variables that are used in this particular call to EVAL(). More precisely, when EVAL($line) encounters a variable ($i, say) while evaluating $line:

      (b) if $i is already defined in the local namespace, use that value (could happen if $i were assigned earlier in the evaluation of the same $line).

      (a) otherwise, import $i from main;

      If the only solution is to explicitly import named variables we would need to parse $line to discover what variables it uses. This seems like an ugly non-ideal solution but it is feasible as a last resort because I only want a solution that works right for most scalar purely numeric expressions, rather than one that works when $line includes strings, function calls, etc. But is there a more beautiful solution?

        Seems like an overkill, but if you really want ALL your variables available for the user, that essentially is equivalent to making a copy of the entire symbol table, and handing it over the the user to mess with. The only way to reconstruct it is to save everything. That is essentially a fork or a thread, so this works
        use threads; my $x=1; my $threads = threads->new(sub {eval '(++$x)+1'}); print "The value of \$x before eval is $x\n"; my $val = $threads->join(); print "The eval returned $val\n"; print "The value of \$x after eval is $x\n";
        giving the result
        The value of $x before eval is 1 The eval returned 3 The value of $x after eval is 1
        It seems like overkill, but then, you asked for it. Less expensive might be to copy that part of the symbol table that you want shared with the user, and restore it after they are done.