compsub - Package for defining compilation subroutines


NAME

compsub - Package for defining compilation subroutines


SYNOPSIS

    package SomeThing;
    sub import {
        compsub::define( foo => sub { return $_[0] || B::OP->new('null', 0) } );
    }
    ...
    package main;
    use Something;
    ...
    foo $a, $b = (1, 2);


WARNING

This module will create segmentation faults if you don't know how to use it properly. You are expected to be familiar with the perl internals to use this module properly.


DESCRIPTION

Compilation subroutines

Compilation subroutines are lexically scoped keywords with perl sub attached to it which gets called during compile time. When the keyword is used the arguments to it are normally parsed, but after that the perl sub attached to the keyword is called with as argument the argument of its keyword as opcode tree. The sub returns an opcode tree. Thus the sub receive an opcode tree, and returns a opcode tree.

Using compilation subs

Keywords for a compilation sub are lexically scoped, and can be declared for example by a use Module. Use the name of the compilation sub to call it.

    foo $arg1, $arg2, @args;

The compilation sub is not affected by parenteses, i.e.

foo($arg1, $arg2), @args

@args is still passed to foo. The compilation sub is called after parsing the arguments, and the compile time setting changed and declarations made by the compilation sub do NOT affect the arguments. What the compilation sub exactly does (at compile and run-time) is up to the sub. Things it can do at compile time are for example: pre-process constant arguments, do some basic argument verfication, declare other compilation subs, inline calls. Things it can do at run-time are for example: call a subroutine with its normal arguments, do nothing, evaluate one of its arguments multiple times, call multiple subroutines with the same arguments.

Writing compilation subs

A compilation subs are lexical scope, and can be declared into the lexical scope currently being compiled using:

    compsub::define( foo => \&compfoo )

This defines a compilation subroutine foo, which gets compiled using compfoo. To declare the function at compile-time, you usually have to define it in a BEGIN block or in an import routine. When foo is used compfoo gets called with as argument the opcode for the list following foo. compfoo is expected to return an opcode, this opcode will be inserted in the place where foo is called. Opcodes are instances of B::OP or one of its subclasses.

There is no verification of the "correctness" of the opcode tree generated, so you may easily created opcode trees which wrong and generate segfaults or manipulate random memory and stuff like that.

See also B::OP about creating and maniuplating op trees.

Freeing opcodes

Opcodes discarded are not automaticly freed. The opcodes are freed normally with freeing of the sub. If you want to discard an opcode you have to explicitly call 'free' on it. This also applies to the opcode passed to the compsub, i.e. if it isn't used in the opcode tree returned by you, you should free the it.

Examples
Calling a subroutine or not depending on some global environment.

This example creates a compilation sub debuglog, which calls dolog if $ENV{DEBUG} is set. Thus checking for $ENV{DEBUG} is done at compile time, and if it not set no code is executed at run-time.

  sub dolog {
      print $log, @_;
  }
  
  # this subroutines compiles when $ENV{DEBUG} is set to calling 'dolog' with its arguments,
  # otherwise it will do nothing (including NOT evaluting its arguments).
  sub compdebuglog {
     my $op = shift;
     if ($ENV{DEBUG}) {
       my $cvop = B::SVOP->new('const', 0, *dolog);
       $op = B::LISTOP->new('list', 0, ($op ? ($op, $cvop) : ($cvop, undef)));
       return B::UNOP->new('entersub', B::OPf_STACKED|B::OPf_SPECIAL, $op);
     } else {
       $op && $op->free; # we don't use $op, so we must explicitly free it.
       return B::OP->new('null', 0);
     }
  }
  
  compsub::define( debuglog => \&compdebuglog );
  
  ...
  
  debuglog Dump($complexvar);
parsing arguments and declaring lexical variables

In this example a keyword params is created. This keyword expects a list of compile-time constant string arguments, and as last argument a hashref. It creates a lexical scope variable for each string argument. At run-time the lexical scoped variables set to the hash value with their name.

    # assumes argument like: 'foo' => \$foo, 'bar' => \$bar, { @_ }
    sub parseparams {
        my $values = pop @_;
        while (my $name = shift @_) {
            $_[0] = $values->{$name};
            shift @_;
        }
    }
    # assumes argument like C<'foo', 'bar', { @_ }>
    # this will be converted like C<parseparams('foo', \(my $foo), 'bar', \(my $bar), { @_ })>
    sub compparams {
        my $op = shift;
        $op or return B::UNOP->new('null', 0);
        my $kid = $op->first;
        while (ref $kid ne "B::NULL") {
            if ($kid->name eq "const") {
                # allocate a 'my' variable
                my $targ = B::PAD::allocmy( '$' . ${ $kid->sv->object_2svref } );
                # introduce the 'my' variable, and insert it into the list of argument.
                my $padsv = B::OP->new('padsv', B::OPf_MOD);
                $padsv->set_private(B::OPpLVAL_INTRO);
                $padsv->set_targ($targ);
                $padsv->set_sibling($kid->sibling);
                $kid->set_sibling($padsv);
                $kid = $padsv;
            } elsif ($kid->name eq "list" or $kid->name eq "pushmark") {
                # ignore
            } elsif ($kid->name eq "anonhash") {
                # ignore, assume it is the last item in the list.
            } else {
                die "Expected constant opcode but got " . $kid->name;
            }
            $kid = $kid->sibling;
        }
        my $cvop = B::SVOP->new('const', 0, *parseparams);
        $op = B::LISTOP->new('list', 0, ($op ? ($op, $cvop) : ($cvop, undef)));
        my $entersubop = B::UNOP->new('entersub', B::OPf_STACKED|B::OPf_SPECIAL, $op);
        return $entersubop;
    }
    BEGIN {
        compsub::define( params => \&compparams )
    }
    {
        sub foobar {
            params 'foo', 'bar', { @_ };
            is $foo, 'foo-value', '$foo declared and initialized';
            is $bar, 'bar-value';
        }
        foobar( foo => "foo-value", bar => "bar-value" );
    }

IMPLEMENTATION

The hint hash %^H is used to define the lexical scoped keyword. And is used during tokenizing to find the subroutine. After it a listexpr is expected by the parser. After parsing the listexpr, ck_compsub calls the subroutine and returns the opcode return by the sub.


AUTHOR

Gerard Goossen <gerard@tty.nl>.


BUGS

 compsub - Package for defining compilation subroutines