This is a longish message describing some obstacles I'm
encountering with implementing eval() in perl6, especially
as it relates to handling of lexical (my) variables. IIRC
there are quite a few tests in the Perl 6/Pugs test suite
that expect a working eval(), so we may need this capability
sooner rather than later. (Where possible we will also see
about rewriting tests to avoid eval().)
Let's begin by looking at a simple eval call in Perl 6:
sub foo() {
my $a = 'hello';
my $b = 'say $a';
eval($b);
}
The argument to eval() is compiled and executed at runtime as
if it were nested within the lexical scope of the block, thus
the '$a' in the string ends up referring to $a of the block
containing the eval().
Ignoring lexicals for a bit, a naive PIR implementation of
an eval() function for perl6 could look something like:
.sub 'eval'
.param string source
$P0 = compreg 'Perl6'
$P1 = $P0.'compile'(source)
.return ($P1)
.end
The 'compile' method above internally translates the source
into a set of PIR instructions, and then calls the imcc compiler
to return an invokable subroutine object. No problem, as long
as we don't need lexicals. Once lexicals are involved, we
need a way to say that the source code being compiled is
scoped within the caller to eval. Parrot seems to be
missing a few pieces needed for that.
For the rest of this message, rather than worry about the details
of how the perl6 compiler and the compiler tools work, let's just
look at the case of creating an "eval"-like function for PIR.
In other words, I want to be able to call a sub named 'eval'
and pass it a string containing a pir subroutine that is
to be lexically nested within the current executing sub. Thus
a PIR equivalent of the Perl 6 'foo' sub above might look like:
.sub 'foo'
## my $a = 'hello';
$P0 = new 'String'
$P0 = 'hello'
.lex '$a', $P0
## my $b = 'say $a';
$P1 = new 'String'
$P1 = <<' END'
.sub 'anon' :outer('foo')
.local pmc a
a = find_lex '$a'
say a
.end
END
.lex '$b', $P1
## eval($b);
$P2 = find_lex '$b'
'eval'($P2)
.end
.sub 'eval'
.param pmc code
say "Code to eval:"
say code
say "Compiling code..."
$P0 = compreg 'PIR'
$P1 = $P0(code)
say "Invoking code..."
.return $P1()
.end
Running the above gives me:
$ ./parrot outer.pir
Code to eval:
.sub 'anon' :outer('foo')
.local pmc a
a = find_lex '$a'
say a
.end
Compiling code...
Invoking code...
Segmentation fault (core dumped)
$
It appears that imcc is unable to handle :outer() flags when
they cross source code boundaries or to be able to use :outer()
to refer to subroutines currently stored in the symbol table.
(There may also be an issue in how one would use :outer() for
nameless subs, but I can come up with workarounds for that.)
So, we need some way to be able to dynamically compile new
subroutines that are treated as lexically nested within an
existing sub.
-----
One temporary workaround that I considered for this problem
would be to have eval() use introspection on its caller to
create a "wrapper sub" that duplicates the lexical environment of
the caller, and then use that as the target of an :outer()
flag when it's passed to imcc. For example, the code
passed to imcc would end up looking something like:
.sub 'wrapper' :lex
## get lexpads
.local pmc my, caller
$P0 = getinterp
my = $P0['lexpad'; 0]
caller = $P0['lexpad'; 1]
## copy caller's lexpad entries into my lexpad
$P0 = caller['$a']
my['$a'] = $P0
$P0 = caller['$b']
my['$b'] = $P0
## now call the wrapped sub
'anon'()
.end
.sub 'anon' :outer('wrapper')
.local pmc a
a = find_lex '$a'
say a
.end
Unfortunately, LexPads don't seem to be open for
much in the way of introspection. Although it seems
to be possible to grab individual known symbols from
a LexPad, there doesn't seem to be a way to get a list
of all symbols in the LexPad:
$ cat x.pir
.sub 'foo'
$P0 = new 'String'
.lex '$x', $P0
$P0 = '$x value'
$P1 = new 'String'
.lex '$y', $P1
$P1 = '$y value'
## display my lexical symbol table
bar()
.end
.sub 'bar'
## get the caller's lexpad
.local pmc caller
$P0 = getinterp
caller = $P0['lexpad';1]
## direct lookup -- display caller's lexical $x (works)
.local pmc x
x = caller['$x']
say x
## symbol table display -- display symbols in caller's lexpad (fails)
.local pmc iter
iter = new 'Iterator', caller
iter_loop:
unless iter goto iter_end
$S0 = shift iter
say $S0
goto iter_loop
iter_end:
.end
$ ./parrot x.pir
$x value
get_string_keyed() not implemented in class 'LexPad'
current instr.: 'bar' pc 53 (x.pir:30)
called from Sub 'foo' pc 19 (x.pir:10)
$
Suggestions and comments on this topic welcomed.
Pm