1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
package match::simple::sugar;
use 5.006001;
use strict;
use warnings;
use Exporter::Tiny;
use Carp qw( croak );
use Scalar::Util qw( blessed );
use match::simple qw( match );
BEGIN {
$match::simple::sugar::AUTHORITY = 'cpan:TOBYINK';
$match::simple::sugar::VERSION = '0.012';
my $strict = 0;
$ENV{$_} && $strict++ for qw(
EXTENDED_TESTING
AUTHOR_TESTING
RELEASE_TESTING
PERL_STRICT
);
eval qq{
sub STRICT () { !! $strict }
sub LAX () { ! $strict }
};
}
our @ISA = qw( Exporter::Tiny );
our @EXPORT = qw( when then numeric match );
my $then_class = __PACKAGE__ . '::then';
my $numeric_class = __PACKAGE__ . '::numeric';
sub when {
my @things = @_;
my $then = pop @things;
if ( blessed $then and $then->isa( $then_class ) ) {
if ( match $_, \@things ) {
no warnings 'exiting';
$then->();
next;
}
}
else {
croak "when: expects then";
}
return;
}
sub _check_coderef {
my $coderef = shift;
require B;
local *B::OP::__match_simple_sugar_callback = sub {
my $name = $_[0]->name;
croak "Block appears to contain a `$name` statement; not suitable for use with when/then"
if match $name, [ qw/ wantarray return redo last next / ];
return;
};
B::svref_2object( $coderef )->ROOT->B::walkoptree( '__match_simple_sugar_callback' );
}
sub then (&) {
my $coderef = shift;
_check_coderef $coderef if STRICT;
bless $coderef, $then_class;
}
sub numeric ($) {
my $n = shift;
bless \$n, $numeric_class;
}
{
my $check = sub {
my ( $x, $y ) = map {
( blessed $_ and $_->isa( $numeric_class ) )
? $$_
: $_;
} @_[0, 1];
no warnings qw( numeric );
defined $x and defined $y and !ref $x and !ref $y and $x == $y;
};
no strict 'refs';
*{"$numeric_class\::MATCH"} = $check;
}
1;
__END__
=pod
=encoding utf-8
=for stopwords smartmatch recurses
=head1 NAME
match::simple::sugar - a few extras for match::simple
=head1 SYNOPSIS
This module provides a C<given>/C<when> substitute for L<match::simple>.
use match::simple::sugar;
for ( $var ) {
when 'foo', then { ... };
when 'bar', 'baz', then { ... };
...; # otherwise
}
It also provides a function for numeric matching (because L<match::simple>
always assumes you want stringy matching if the right-hand-side is a defined
non-reference value).
use match::simple::sugar;
for ( $var ) {
when numeric 0, then { ... };
when numeric 1, then { ... };
...; # otherwise
}
=head1 DESCRIPTION
This module exports three functions C<when>, C<then>, and C<numeric>,
and also re-exports C<match> from L<match::simple>.
=head2 C<when> and C<then>
The C<when> and C<then> functions are intended to be used together,
inside a C<< for ( SCALAR ) { ... } >> block. The block acts as a
topicalizer (it sets C<< $_ >>) and also a control-flow mechanism
(C<when> can use C<next> to jump out of it). Any other use of C<when>
and C<then> is unsupported.
=head3 C<< when( @values, $then ) >>
The C<when> function accepts a list of values, followed by a special
C<< $then >> argument.
If C<< $_ >> matches (according to the definition in L<match::simple>)
any of the values, then the C<< $then >> argument will be executed, and
C<when> will use the Perl built-in C<next> keyword to jump out of the
surrounding C<for> block.
=head3 C<< then { ... } >>
The C<then> function takes a block of code and returns an object suitable
for use as C<when>'s C<< $then >> argument.
In the current implementation, the block of code should not inspect
C<< @_ >> or C<wantarray>, and should not use the C<return>, C<next>,
C<last>, or C<redo> keywords. (If you set any of the C<PERL_STRICT>,
C<EXTENDED_TESTING>, C<AUTHOR_TESTING>, or C<RELEASE_TESTING> environment
variables to true, then match::simple::sugar will I<try> to enforce this!
This is intended to catch faulty C<then> blocks when running your test
suite.)
=head2 C<numeric>
The C<numeric> function accepts a number and returns a blessed object
which has a C<MATCH> method. The C<MATCH> method returns true if it is
called with a single defined non-referece scalar that is numerically
equal to the original number passed to C<numeric>. Example:
numeric( '5.0' )->MATCH( '5.000' ); # true
This is intended for use in cases like:
if ( match $var, numeric 1 ) {
...;
}
=head1 BUGS
Please report any bugs to
L<https://github.com/tobyink/p5-match-simple/issues>.
=head1 SEE ALSO
L<match::simple>.
This module uses L<Exporter::Tiny>.
=head1 AUTHOR
Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
This module is inspired by a talk I gave to
L<Boston.PM|https://boston-pm.github.io/>.
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2023 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.
|