#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2024 -- leonerd@leonerd.org.uk

package Syntax::Keyword::Assert 0.01;

use v5.14;
use warnings;

use Carp;

require XSLoader;
XSLoader::load( __PACKAGE__, our $VERSION );

=head1 NAME

C<Syntax::Keyword::Assert> - debugging checks that throw exceptions

=head1 SYNOPSIS

=for highlighter language=perl

   use v5.14;
   use Syntax::Keyword::Assert;

   my @items;

   ...

   sub get_next_item {
      assert(@items);
      return shift @items;
   }

=head1 DESCRIPTION

This module provides a syntax plugin that implements a keyword which checks
the truth of its expression, throwing an exception if it is false.

This is similar to the same keyword found in C and maybe other languages.

By default assertions are enabled, but they can be entirely disabled by
setting the environment variable C<PERL_ASSERT_ENABLED> to zero before the
module is loaded.

=for highlighter language=bash

   $ PERL_ASSERT_ENABLED=0 perl ...

When disabled, the entire expression is skipped over at compiletime, meaning
that it has exactly zero run-time performance effect. This can be useful for
disabling such checks in high-performance production environments. Knowing
that they will have no run-time effect in such cases, developers may be more
willing to insert assertion checks into code that can reveal issues during
development and testing in non-critical places.

Because of this disable mode, assertion expressions should not contain any
side-effects as those effects will not happen when assertions are disabled.

=cut

=head1 KEYWORDS

=for highlighter language=perl

=head2 assert

   assert(EXPR);

The C<assert> keyword evaluates its expression in scalar context. If it has a
true value then nothing further happens. If it is false, then an exception is
thrown.

If the expression is given as a numerical or string equality test, then the
assertion prints the LHS and RHS values separately if it fails, giving a more
useful failure message.

=cut

sub import
{
   my $pkg = shift;
   my $caller = caller;

   $pkg->import_into( $caller, @_ );
}

sub unimport
{
   my $pkg = shift;
   my $caller = caller;

   $pkg->unimport_into( $caller, @_ );
}

sub import_into   { shift->apply( sub { $^H{ $_[0] }++ },      @_ ) }
sub unimport_into { shift->apply( sub { delete $^H{ $_[0] } }, @_ ) }

sub apply
{
   my $pkg = shift;
   my ( $cb, $caller, @syms ) = @_;

   my %syms = map { $_ => 1 } @syms;
   $cb->( "Syntax::Keyword::Assert/assert" );

   croak "Unrecognised import symbols @{[ keys %syms ]}" if keys %syms;
}

=head1 TODO

=over 4

=item *

Customisable failure message.

=item *

Finer control of whether assertion checks are enabled. Per-package/scope/file?

=item *

Random 1-of-n sampling mode for enabling.

=item *

Inspection into other binary comparison operators.

=item *

Inspection of other assertions like reftype tests?

=back

=cut

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;
