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

Reply via email to