gallerymaker/modules/compsub.pm
2014-12-11 21:58:53 -05:00

221 lines
7.1 KiB
Perl

package compsub;
sub define {
my %arg = @_;
# warning: alwyas create a new hash; %^H is saved, and altering
# values referenced by it, will have effect on it.
%^H{'compsub'} = \%(
%{ %^H{'compsub'} || \%() },
%arg,
);
}
1;
__END__
=head1 NAME
compsub - Package for defining compilation subroutines
=head1 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);
=head1 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.
=head1 DESCRIPTION
=head2 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.
=head2 Using compilation subs
Keywords for a compilation sub are lexically scoped, and can be declared for
example by a C<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.
=head2 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 C<foo>, which gets compiled using C<compfoo>.
To declare the function at compile-time, you usually have to define it in a BEGIN block
or in an C<import> routine.
When C<foo> is used C<compfoo> gets called with as argument the opcode for the
list following C<foo>. C<compfoo> is expected to return an opcode, this opcode
will be inserted in the place where C<foo> is called. Opcodes are instances of
C<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 C<B::OP> about creating and maniuplating op trees.
=item 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.
=item Examples
=over 4
=item Calling a subroutine or not depending on some global environment.
This example creates a compilation sub C<debuglog>, which calls C<dolog> if C<$ENV{DEBUG}>
is set. Thus checking for C<$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);
=item parsing arguments and declaring lexical variables
In this example a keyword C<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" );
}
=back
=back
=head2 IMPLEMENTATION
The hint hash C<%^H> is used to define the lexical scoped keyword. And is used during
tokenizing to find the subroutine. After it a C<listexpr> is expected by the parser. After
parsing the C<listexpr>, C<ck_compsub> calls the subroutine and returns the opcode return
by the sub.
=head1 AUTHOR
Gerard Goossen E<lt>F<gerard@tty.nl>E<gt>.
=head1 BUGS
=cut