—use
5.014;
use
strict;
use
warnings;
no
warnings
'void'
;
use
Carp ();
use
Exporter::Tiny ();
use
PadWalker ();
use
Parse::Keyword ();
use
Module::Runtime ();
use
Scalar::Util ();
use
Sub::Util ();
package
Kavorka;
our
$AUTHORITY
=
'cpan:TOBYINK'
;
our
$VERSION
=
'0.039'
;
our
@ISA
=
qw( Exporter::Tiny )
;
our
@EXPORT
=
qw( fun method )
;
our
@EXPORT_OK
=
qw( fun method after around before override augment classmethod objectmethod multi )
;
our
%EXPORT_TAGS
= (
modifiers
=> [
qw( after around before )
],
allmodifiers
=> [
qw( after around before override augment )
],
);
our
%IMPLEMENTATION
= (
after
=>
'Kavorka::Sub::After'
,
around
=>
'Kavorka::Sub::Around'
,
augment
=>
'Kavorka::Sub::Augment'
,
before
=>
'Kavorka::Sub::Before'
,
classmethod
=>
'Kavorka::Sub::ClassMethod'
,
f
=>
'Kavorka::Sub::Fun'
,
fun
=>
'Kavorka::Sub::Fun'
,
func
=>
'Kavorka::Sub::Fun'
,
function
=>
'Kavorka::Sub::Fun'
,
method
=>
'Kavorka::Sub::Method'
,
multi
=>
'Kavorka::Multi'
,
objectmethod
=>
'Kavorka::Sub::ObjectMethod'
,
override
=>
'Kavorka::Sub::Override'
,
);
our
%INFO
;
sub
info
{
my
$me
=
shift
;
my
$code
=
$_
[0];
$INFO
{
$code
};
}
sub
guess_implementation
{
my
$me
=
shift
;
$IMPLEMENTATION
{
$_
[0]};
}
sub
compose_implementation
{
shift
;
Moo::Role->create_class_with_roles(
@_
);
}
sub
_exporter_validate_opts
{
my
$class
=
shift
;
$^H{
'Kavorka/package'
} =
$_
[0]{into};
$_
[0]{replace} = 1
unless
exists
$_
[0]{replace};
}
sub
_fqname ($;$)
{
my
$name
=
shift
;
my
(
$package
,
$subname
);
$name
=~ s{'}{::}g;
if
(
$name
=~ /::/)
{
(
$package
,
$subname
) =
$name
=~ m{^(.+)::(\w+)$};
}
else
{
my
$caller
=
@_
?
shift
: $^H{
'Kavorka/package'
};
(
$package
,
$subname
) = (
$caller
,
$name
);
}
return
wantarray
? (
$package
,
$subname
) :
"$package\::$subname"
;
}
sub
_exporter_fail
{
my
$me
=
shift
;
my
(
$name
,
$args
,
$globals
) =
@_
;
my
$implementation
=
$args
->{
'implementation'
}
//
$me
->guess_implementation(
$name
)
//
$me
;
my
$into
=
$globals
->{into};
Module::Runtime::use_package_optimistically(
$implementation
);
{
my
$traits
=
$globals
->{traits} //
$args
->{traits};
$implementation
=
$me
->compose_implementation(
$implementation
,
@$traits
)
if
$traits
;
}
$implementation
->can(
'parse'
)
or Carp::croak(
"No suitable implementation for keyword '$name'"
);
# Workaround for RT#95786 which might be caused by a bug in the Perl
# interpreter.
# Also RT#98666 is why we can't just call undefer_all.
for
(
keys
%Sub::Defer::DEFERRED
) {
no
warnings;
Sub::Defer::undefer_sub(
$_
)
if
$Sub::Defer::DEFERRED
{
$_
} &&
$Sub::Defer::DEFERRED
{
$_
}[0] =~ /\AKavorkaX?\b/;
}
# Kavorka::Multi (for example) needs to know what Kavorka keywords are
# currently in scope.
$^H{
'Kavorka'
} .=
"$name=$implementation "
;
# This is the code that gets called at run-time.
#
my
$code
= Sub::Util::set_subname(
"$me\::$name"
,
sub
{
unless
(Scalar::Util::blessed(
$_
[0]) and
$_
[0]->DOES(
'Kavorka::Sub'
))
{
return
$implementation
->bypass_custom_parsing(
$name
,
$into
, \
@_
);
}
my
$subroutine
=
shift
;
# Post-parse clean-up
$subroutine
->_post_parse();
# Store $subroutine for introspection
$INFO
{
$subroutine
->body } =
$subroutine
;
# Install sub
my
@r
=
wantarray
?
$subroutine
->install_sub
:
scalar
(
$subroutine
->install_sub);
# Workarounds for closure issues in Parse::Keyword
if
(
$subroutine
->is_anonymous)
{
my
$orig
=
$r
[0];
my
$caller_vars
= PadWalker::peek_my(1);
@r
= Sub::Util::set_subname(
$subroutine
->
package
.
"::__ANON__"
,
sub
{
$subroutine
->_poke_pads(
$caller_vars
);
goto
$orig
;
});
&Scalar::Util::set_prototype
(
$r
[0],
$_
)
for
grep
defined
,
prototype
(
$orig
);
$INFO
{
$r
[0] } =
$subroutine
;
Scalar::Util::weaken(
$INFO
{
$r
[0] });
}
else
{
$subroutine
->_poke_pads( PadWalker::peek_my(1) );
}
# Prevents a cycle between %INFO and $subroutine.
Scalar::Util::weaken(
$subroutine
->{body})
unless
Scalar::Util::isweak(
$subroutine
->{body});
wantarray
?
@r
:
$r
[0];
},
);
# This joins up the code above with our custom parsing via
# Parse::Keyword
#
Parse::Keyword::install_keyword_handler(
$code
=> Sub::Util::set_subname(
"$me\::parse_$name"
,
sub
{
local
$Carp::CarpLevel
=
$Carp::CarpLevel
+ 1;
my
$subroutine
=
$implementation
->parse(
keyword
=>
$name
);
return
(
sub
{ (
$subroutine
,
$args
) },
!!
$subroutine
->declared_name,
);
},
),
);
# Symbol for Exporter::Tiny to export
return
(
$name
=>
$code
);
}
1;
__END__
=pod
=encoding utf-8
=for stopwords invocant invocants lexicals unintuitive yada globals
=head1 NAME
Kavorka - function signatures with the lure of the animal
=head1 SYNOPSIS
use Kavorka;
fun maxnum (Num @numbers) {
my $max = shift @numbers;
for (@numbers) {
$max = $_ if $max < $_;
}
return $max;
}
my $biggest = maxnum(42, 3.14159, 666);
=head1 STATUS
Kavorka is still at a very early stage of development; there are likely
to be many bugs that still need to be shaken out. Certain syntax
features are a little odd and may need to be changed in incompatible
ways.
=head1 DESCRIPTION
Kavorka provides C<fun> and C<method> keywords for declaring functions
and methods. It uses Perl 5.14's keyword API, so should work more
reliably than source filters or L<Devel::Declare>-based modules.
The syntax provided by Kavorka is largely inspired by Perl 6, though
it has also been greatly influenced by L<Method::Signatures> and
L<Function::Parameters>.
For information using the keywords exported by Kavorka:
=over
=item *
L<Kavorka::Manual::Functions>
=item *
L<Kavorka::Manual::Methods>
=item *
L<Kavorka::Manual::MethodModifiers>
=item *
L<Kavorka::Manual::MultiSubs>
=back
=head2 Exports
=over
=item C<< -default >>
Exports C<fun> and C<method>.
=item C<< -modifiers >>
Exports C<before>, C<after>, and C<around>.
=item C<< -allmodifiers >>
Exports C<before>, C<after>, C<around>, C<augment>, and C<override>.
=item C<< -all >>
Exports C<fun>, C<method>, C<before>, C<after>, C<around>,
C<augment>, C<override>, C<classmethod>, C<objectmethod>,
and C<multi>.
=back
For example:
# Everything except objectmethod and multi...
use Kavorka qw( -default -allmodifiers classmethod );
You can rename imported functions:
use Kavorka method => { -as => 'meth' };
You can provide alternative implementations:
# use My::Sub::Method instead of Kavorka::Sub::Method
use Kavorka method => { implementation => 'My::Sub::Method' };
Or add traits to the default implementation:
use Kavorka method => { traits => ['My::Sub::Role::Foo'] };
See L<Exporter::Tiny> for more tips.
=head2 Function Introspection API
The coderef for any sub created by Kavorka can be passed to the
C<< Kavorka->info >> method. This returns a blessed object that
does the L<Kavorka::Sub> role.
fun foo (:$x, :$y) { }
my $info = Kavorka->info(\&foo);
my $function_name = $info->qualified_name;
my @named_params = $info->signature->named_params;
say $named_params[0]->named_names->[0]; # says 'x'
See L<Kavorka::Sub>, L<Kavorka::Signature> and
L<Kavorka::Parameter> for further details.
If you're using Moose, consider using L<MooseX::KavorkaInfo> to expose
Kavorka method signatures via the meta object protocol.
L<Kavorka::Manual::API> provides more details and examples using the
introspection API.
=head1 CAVEATS
=over
=item *
As noted in L<Kavorka::Manual::PrototypeAndAttributes>, subroutine
attributes don't work properly for anonymous functions.
=item *
This module is based on L<Parse::Keyword>, which has a chronically
broken implementation of closures. Kavorka uses L<PadWalker> to attempt
to work around the problem. This mostly seems to work, but you may
experience some problems in edge cases, especially for anonymous
functions and methods.
=item *
If importing Kavorka's method modifiers into Moo/Mouse/Moose classes,
pay attention to load order:
use Moose;
use Kavorka -all; # ok
If you do it this way, Moose's C<before>, C<after>, and C<around>
keywords will stomp on top of Kavorka's...
use Kavorka -all;
use Moose; # STOMP, STOMP, STOMP! :-(
This can lead to delightfully hard to debug errors.
=back
=head1 BUGS
If seeing test failures on threaded Perl 5.21+, it may be a bug in
L<Devel::CallParser> 0.002.
Try installing L<Alt::Devel::CallParser::ButWorking>.
Please report any other bugs to
=head1 SUPPORT
B<< IRC: >> support is available through in the I<< #moops >> channel
on L<irc.perl.org|http://www.irc.perl.org/channels.html>.
=head1 SEE ALSO
L<Kavorka::Manual>.
B<< Inspirations: >>
L<Function::Parameters>,
L<Method::Signatures>.
L<http://en.wikipedia.org/wiki/The_Conversion_(Seinfeld)>.
=head1 AUTHOR
Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2013-2014, 2017 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=head1 DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.