Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error message and change assert syntax. #4

Merged
merged 14 commits into from
Dec 15, 2024
52 changes: 13 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ Syntax::Keyword::Assert - assert keyword for Perl with zero runtime cost in prod
```perl
use Syntax::Keyword::Assert;

sub hello($name) {
assert { defined $name };
say "Hello, $name!";
}

hello("Alice"); # => Hello, Alice!
hello(); # => Dies when STRICT mode is enabled
my $name = 'Alice';
assert( $name eq 'Bob' );
# => Assertion failed ("Alice" eq "Bob")
```

# DESCRIPTION
Expand All @@ -25,45 +21,23 @@ Syntax::Keyword::Assert introduces a lightweight assert keyword to Perl, designe

When STRICT mode is enabled, assert statements are checked at runtime. Default is enabled. If the assertion fails (i.e., the block returns false), the program dies with an error. This is particularly useful for catching errors during development or testing.

`$ENV{PERL_ASSERT_ENABLED}` can be used to control STRICT mode.

```
BEGIN { $ENV{PERL_ASSERT_ENABLED} = 0 } # Disable STRICT mode
```

- **Zero Runtime Cost**

When STRICT mode is disabled, the assert blocks are completely ignored at compile phase, resulting in zero runtime cost. This makes Syntax::Keyword::Assert ideal for use in production environments, as it does not introduce any performance penalties when assertions are not needed.

- **Simple Syntax**

The syntax is straightforward—assert BLOCK—making it easy to integrate into existing code.

## STRICT Mode Control

If `$ENV{PERL_ASSERT_ENABLED}` is trusy, STRICT mode is enabled. Otherwise, it is disabled. Default is enabled.
The syntax is dead simple. Just use the assert keyword followed by a block that returns a boolean value.

```perl
BEGIN { $ENV{PERL_ASSERT_ENABLED} = 0 } # Disable STRICT mode

use Syntax::Keyword::Assert;

assert { 1 == 1 }; # Always passes
assert { 0 == 1 }; # Block is ignored, no runtime cost
```

SEE ALSO:
[Bench ](https://metacpan.org/pod/%20https%3A#github.com-kfly8-Syntax-Keyword-Assert-blob-main-bench-compare-no-assertion.pl)

# TIPS

## Verbose error messages

If you set `$Carp::Verbose = 1`, you can see stack traces when an assertion fails.

```perl
use Syntax::Keyword::Assert;
use Carp;

assert {
local $Carp::Verbose = 1;
0;
}
```
```
assert( $name eq 'Bob' );
```

# SEE ALSO

Expand Down
2 changes: 1 addition & 1 deletion bench/compare-no-assertion.pl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ BEGIN

# Function with assertion block but it is ignored at runtime
sub with_assertion($message) {
assert { defined $message };
assert( defined $message );
return $message;
}

Expand Down
1 change: 1 addition & 0 deletions builder/MyBuilder.pm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ sub new {

my @flags = @{ $build->extra_compiler_flags };
push @flags, XS::Parse::Keyword::Builder->extra_compiler_flags;
push @flags, '-Ihax';

$build->extra_compiler_flags( @flags );

Expand Down
22 changes: 22 additions & 0 deletions hax/newUNOP_CUSTOM.c.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* Before perl 5.22 under -DDEBUGGING, various new*OP() functions throw assert
* failures on OP_CUSTOM.
* https://rt.cpan.org/Ticket/Display.html?id=128562
*/

#define newUNOP_CUSTOM(func, flags, first) S_newUNOP_CUSTOM(aTHX_ func, flags, first)
static OP *S_newUNOP_CUSTOM(pTHX_ OP *(*func)(pTHX), U32 flags, OP *first)
{
UNOP *unop;
#if HAVE_PERL_VERSION(5,22,0)
unop = (UNOP *)newUNOP(OP_CUSTOM, flags, first);
#else
NewOp(1101, unop, 1, UNOP);
unop->op_type = (OPCODE)OP_CUSTOM;
unop->op_first = first;
unop->op_flags = (U8)(flags | OPf_KIDS);
unop->op_private = (U8)(1 | (flags >> 8));
#endif
unop->op_ppaddr = func;
return (OP *)unop;
}

67 changes: 67 additions & 0 deletions hax/sv_numeq.c.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* vi: set ft=c : */
#ifndef sv_numeq_flags
# define sv_numeq_flags(lhs, rhs, flags) S_sv_numeq_flags(aTHX_ lhs, rhs, flags)
static bool S_sv_numeq_flags(pTHX_ SV *lhs, SV *rhs, U32 flags)
{
if(flags & SV_GMAGIC) {
if(lhs)
SvGETMAGIC(lhs);
if(rhs)
SvGETMAGIC(rhs);
}
if(!lhs)
lhs = &PL_sv_undef;
if(!rhs)
rhs = &PL_sv_undef;
if(!(flags & SV_SKIP_OVERLOAD) && (SvAMAGIC(lhs) || SvAMAGIC(rhs))) {
SV *ret = amagic_call(lhs, rhs, eq_amg, 0);
if(ret)
return SvTRUE(ret);
}
/* We'd like to call Perl_do_ncmp, except that isn't an exported API function
* Here's a near-copy of it for num-equality testing purposes */
#ifndef HAVE_BOOL_SvIV_please_nomg
/* Before perl 5.18, SvIV_please_nomg() was void-returning */
SvIV_please_nomg(lhs);
SvIV_please_nomg(rhs);
#endif
if(
#ifdef HAVE_BOOL_SvIV_please_nomg
SvIV_please_nomg(rhs) && SvIV_please_nomg(lhs)
#else
SvIOK(lhs) && SvIOK(rhs)
#endif
) {
/* Compare as integers */
switch((SvUOK(lhs) ? 1 : 0) | (SvUOK(rhs) ? 2 : 0)) {
case 0: /* IV == IV */
return SvIVX(lhs) == SvIVX(rhs);
case 1: /* UV == IV */
{
const IV riv = SvUVX(rhs);
if(riv < 0)
return 0;
return (SvUVX(lhs) == riv);
}
case 2: /* IV == UV */
{
const IV liv = SvUVX(lhs);
if(liv < 0)
return 0;
return (liv == SvUVX(rhs));
}
case 3: /* UV == UV */
return SvUVX(lhs) == SvUVX(rhs);
}
}
else {
/* Compare NVs */
NV const rnv = SvNV_nomg(rhs);
NV const lnv = SvNV_nomg(lhs);
return lnv == rnv;
}
}
#endif
#ifndef sv_numeq
# define sv_numeq(lhs, rhs) sv_numeq_flags(lhs, rhs, 0)
#endif
26 changes: 26 additions & 0 deletions hax/sv_streq.c.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* vi: set ft=c : */
#ifndef sv_streq_flags
# define sv_streq_flags(lhs, rhs, flags) S_sv_streq_flags(aTHX_ lhs, rhs, flags)
static bool S_sv_streq_flags(pTHX_ SV *lhs, SV *rhs, U32 flags)
{
if(flags & SV_GMAGIC) {
if(lhs)
SvGETMAGIC(lhs);
if(rhs)
SvGETMAGIC(rhs);
}
if(!lhs)
lhs = &PL_sv_undef;
if(!rhs)
rhs = &PL_sv_undef;
if(!(flags & SV_SKIP_OVERLOAD) && (SvAMAGIC(lhs) || SvAMAGIC(rhs))) {
SV *ret = amagic_call(lhs, rhs, seq_amg, 0);
if(ret)
return SvTRUE(ret);
}
return sv_eq_flags(lhs, rhs, 0);
}
#endif
#ifndef sv_streq
# define sv_streq(lhs, rhs) sv_streq_flags(lhs, rhs, 0)
#endif
51 changes: 10 additions & 41 deletions lib/Syntax/Keyword/Assert.pm
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ sub apply {
Carp::croak "Unrecognised import symbols @{[ keys %syms ]}" if keys %syms;
}

# called from Assert.xs
sub _croak {
goto &Carp::croak;
}

1;
__END__

Expand All @@ -55,13 +50,9 @@ Syntax::Keyword::Assert - assert keyword for Perl with zero runtime cost in prod

use Syntax::Keyword::Assert;

sub hello($name) {
assert { defined $name };
say "Hello, $name!";
}

hello("Alice"); # => Hello, Alice!
hello(); # => Dies when STRICT mode is enabled
my $name = 'Alice';
assert( $name eq 'Bob' );
# => Assertion failed ("Alice" eq "Bob")

=head1 DESCRIPTION

Expand All @@ -73,43 +64,21 @@ Syntax::Keyword::Assert introduces a lightweight assert keyword to Perl, designe

When STRICT mode is enabled, assert statements are checked at runtime. Default is enabled. If the assertion fails (i.e., the block returns false), the program dies with an error. This is particularly useful for catching errors during development or testing.

=item B<Zero Runtime Cost>

When STRICT mode is disabled, the assert blocks are completely ignored at compile phase, resulting in zero runtime cost. This makes Syntax::Keyword::Assert ideal for use in production environments, as it does not introduce any performance penalties when assertions are not needed.

=item B<Simple Syntax>

The syntax is straightforward—assert BLOCK—making it easy to integrate into existing code.

=back

=head2 STRICT Mode Control

If C<$ENV{PERL_ASSERT_ENABLED}> is trusy, STRICT mode is enabled. Otherwise, it is disabled. Default is enabled.
C<$ENV{PERL_ASSERT_ENABLED}> can be used to control STRICT mode.

BEGIN { $ENV{PERL_ASSERT_ENABLED} = 0 } # Disable STRICT mode

use Syntax::Keyword::Assert;

assert { 1 == 1 }; # Always passes
assert { 0 == 1 }; # Block is ignored, no runtime cost

SEE ALSO:
L<Bench | https://github.com/kfly8/Syntax-Keyword-Assert/blob/main/bench/compare-no-assertion.pl>
=item B<Zero Runtime Cost>

=head1 TIPS
When STRICT mode is disabled, the assert blocks are completely ignored at compile phase, resulting in zero runtime cost. This makes Syntax::Keyword::Assert ideal for use in production environments, as it does not introduce any performance penalties when assertions are not needed.

=head2 Verbose error messages
=item B<Simple Syntax>

If you set C<$Carp::Verbose = 1>, you can see stack traces when an assertion fails.
The syntax is dead simple. Just use the assert keyword followed by a block that returns a boolean value.

use Syntax::Keyword::Assert;
use Carp;
assert( $name eq 'Bob' );

assert {
local $Carp::Verbose = 1;
0;
}
=back

=head1 SEE ALSO

Expand Down
Loading
Loading