I want to use Devel::Declare
to inject multiple lines of Perl code. However, Devel::Declare::set_linestr()
cannot deal with multiple lines.
Normally I would join multiple statements together as a single line. These statements must be on separate lines to preserve their line numbers for error reporting purposes. This is to solve this bug in Method::Signatures and this related bug. I'm open to alternative solutions.
For example, Method::Signatures currently turns this code...
use Method::Signatures;
func hello(
$who = "World",
$greeting = get_greeting($who)
) {
die "$greeting, $who";
}
...into this...
func \&hello; sub hello { BEGIN { Method::Signatures->inject_scope('') }; my $who = (@_ > 0) ? ($_[0]) : ( get_greeting($who)); my $greeting = (@_ > 1) ? ($_[1]) : ( "Hello"); Method::Signatures->too_many_args_error(2) if @_ > 2;
die "$greeting, $who";
}
die $who
then reports line 4 instead of line 7.
I would like it to instead be this (or perhaps something involving #line
).
func \&hello; sub hello { BEGIN { Method::Signatures->inject_scope('') };
my $who = (@_ > 0) ? ($_[0]) : ( "World");
my $greeting = (@_ > 1) ? ($_[1]) : ( get_greeting($who));
Method::Signatures->too_many_args_error(2) if @_ > 2;
die "$greeting, $who";
}
Not only does this faithfully reproduce the line numbers, should get_greeting
croak it will report having been called from the correct line.
As per your own answer, the following works:
sub __empty() { '' }
sub parse_proto {
my $self = shift;
return q[print __LINE__."\n"; Foo::__empty(
);print __LINE__."\n"; Foo::__empty(
);print __LINE__."\n";];
}
But introduces unacceptable overhead because the __empty()
function must be called for every parameter. The overhead can be eliminated by calling __empty()
conditionally using a condition which will never evaluate to true.
sub __empty() { '' }
sub parse_proto {
my $self = shift;
return q[print __LINE__."\n"; 0 and Foo::__empty(
);print __LINE__."\n"; 0 and Foo::__empty(
);print __LINE__."\n";];
}
I figured out a way to do it, but it's hacky and slow.
I noticed you could inject strings with literal newlines in them and they'd work. I reckon the parser knows enough to keep going past a newline in a string. I figured one can exploit that to trick the parser to keep going.
package Foo;
use strict;
use warnings;
use v5.12;
use parent "Devel::Declare::MethodInstaller::Simple";
sub import {
my $class = shift;
my $caller = caller;
$class->install_methodhandler(
into => $caller,
name => 'method'
);
}
sub parse_proto {
my $self = shift;
return q[print __LINE__."\n"; my $__empty = q{
};print __LINE__."\n"; $__empty = q{
};print __LINE__."\n";];
}
1;
And it works... except __LINE__
doesn't get incremented. Glitch in the Perl parser? I tried a regex with a newline in it, that didn't increment the line either.
But a subroutine does work!
sub __empty() { '' }
sub parse_proto {
my $self = shift;
return q[print __LINE__."\n"; Foo::__empty(
);print __LINE__."\n"; Foo::__empty(
);print __LINE__."\n";];
}
And it's a constant subroutine call, Perl should optimize it out, right? Alas, no. It seems the newline in the call fools the optimizer. On the upside, this avoids a "Useless use of a constant in void context" warning. On the down side, it introduces a subroutine call for each parameter and that's an unacceptable amount of overhead to add to every subroutine call.
Maybe someone else can come up with a clever way to squeak a newline into Perl syntax?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With