package Clean::Eval;
use strict;
use warnings;

our $VERSION = '0.000001';

use Importer Importer => 'import';

our @EXPORT_OK = qw/clean_eval clean_string_eval last_error/;

use overload(
    'bool' => sub { 0 },
    '""'   => sub { $_[0]->{error} },
);

sub to_string { $_[0]->{error} }

my $LAST_ERR;

sub last_error { $LAST_ERR }

sub clean_eval(&)        { goto &_clean_eval }
sub clean_string_eval($) { goto &_clean_eval }

sub _clean_eval {
    my $code = shift;
    local $@ = "";

    my ($pkg, $file, $line);

    if (ref($code) eq 'CODE') {
        return 1 if eval { $code->(); 1 };
        ($pkg, $file, $line) = caller;
    }
    else {
        ($pkg, $file, $line) = caller;
        return 1 if eval qq{#line $line "$file"\n$code; 1};
    }

    return $LAST_ERR = bless({error => $@, package => $pkg, file => $file, line => $line}, __PACKAGE__);
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Clean::Eval - Run code under C<eval> without leaking C<$@> and get a rich
error object back on failure.

=head1 DESCRIPTION

Perl's built-in C<eval> is the standard way to trap exceptions, but it has two
long-standing ergonomic problems:

=over 4

=item *

It modifies the global C<$@>, which can be clobbered by destructors or other
code running during stack unwind, leading to lost or corrupted error messages.

=item *

The return value of C<eval> can be ambiguous: a successful eval that
legitimately returns a false value is indistinguishable from a failure unless
you check C<$@>.

=back

C<Clean::Eval> wraps C<eval> in a way that avoids both problems. It localizes
C<$@> so the caller's copy is never touched, returns a true value on success,
and returns an overloaded error object on failure. The error object stringifies
to the trapped error and is always false in boolean context, so a single
C<if> check is enough to distinguish success from failure regardless of what
the wrapped code returned.

Both a block form (C<clean_eval { ... }>) and a string form
(C<clean_string_eval $code>) are provided. The string form rewrites C<#line>
information so that any error reports the file and line of the caller, not an
anonymous C<(eval N)>.

=head1 SYNOPSIS

    use Clean::Eval qw/clean_eval clean_string_eval last_error/;

    # Block form
    if (my $ret = clean_eval { do_something_risky() }) {
        # success path - $ret is a true value (1)
    }
    else {
        # failure path - $ret is the error object
        warn "Failed: $ret\n";          # stringifies to error message
        warn "  at $ret->{file} line $ret->{line}\n";
    }

    # String form (compiles at runtime). No need to add a trailing
    # "; 1" - it is appended for you so the success path is unambiguous.
    my $ret = clean_string_eval 'use SomeOptionalModule';
    unless ($ret) {
        warn "Optional dep missing: $ret\n";
    }

    # Retrieve the most recent error from anywhere
    my $last = last_error();

=head1 EXPORTS

Nothing is exported by default. The three functions below may be imported
individually using L<Importer>-style syntax:

    use Clean::Eval qw/clean_eval clean_string_eval last_error/;

=over 4

=item $ret = clean_eval { BLOCK }

Run C<BLOCK> under C<eval>. On success returns C<1>. On failure returns a
C<Clean::Eval> error object (see L</"ERROR OBJECT">). C<$@> in the caller's
scope is not touched.

The prototype is C<(&)>, so the block form works without a leading C<sub>.

=item $ret = clean_string_eval $STRING

Run C<$STRING> as Perl code under C<eval>. On success returns C<1>. On failure
returns a C<Clean::Eval> error object.

A C<#line> directive is prepended to C<$STRING> using the caller's filename
and line number, so any error or warning produced by the eval'd code refers
to the source location of the C<clean_string_eval> call rather than to an
anonymous eval string.

A trailing C<; 1> is also appended to C<$STRING>, so you do not need to
remember the usual C<eval "...; 1"> success guard - the return value is
unambiguously C<1> on success regardless of what the final statement in
C<$STRING> evaluates to. Including the C<; 1> yourself is harmless.

The prototype is C<($)>, so a single scalar argument is taken.

=item $err = last_error()

Return the most recent error object produced by C<clean_eval> or
C<clean_string_eval> anywhere in the program, or C<undef> if no error has
been recorded yet. Useful for code paths that discarded the return value or
want to inspect a previous failure after the fact.

B<Caveat:> C<last_error> is a global slot and is subject to the same class
of bug that makes raw C<$@> fragile. If a C<DESTROY> method (or anything
else running during stack unwind) calls C<clean_eval> or
C<clean_string_eval>, it will overwrite the global and the error you
actually cared about will be lost. C<last_error> is a convenience, not a
guarantee - the only robust way to inspect a particular failure is to
capture the return value of C<clean_eval>/C<clean_string_eval> directly at
the call site and keep it in a lexical of your own.

=back

=head1 ERROR OBJECT

On failure both C<clean_eval> and C<clean_string_eval> return a blessed
hashref of class C<Clean::Eval>. It overloads stringification and boolean
context:

=over 4

=item *

Boolean context: always false. This lets C<if (my $ret = clean_eval { ... })>
work without ambiguity even when the block legitimately returns a false value.

=item *

String context: the trapped error (the value C<$@> had inside the eval).

=back

The object is a plain hashref with the following keys:

=over 4

=item error

The trapped error message (string or object).

=item package

The package the call was made from.

=item file

The file the call was made from.

=item line

The line the call was made from.

=back

An additional C<to_string> method is provided that returns the error message;
it is equivalent to stringifying the object.

=head1 WHY NOT JUST USE C<eval>?

You can, but you have to be careful. The idiomatic safe pattern looks like:

    my $ok = eval { ...; 1 };
    if (!$ok) {
        my $err = $@;
        ...
    }

This is correct but verbose, and the C<; 1> trailer is easy to forget. The
C<$@> variable is also famously fragile: destructors that run during stack
unwind can call C<eval> themselves and reset it before you read it. Localizing
C<$@> the way C<Clean::Eval> does avoids that class of bug entirely.

=head1 SEE ALSO

L<Try::Tiny>, L<Syntax::Keyword::Try>, L<Feature::Compat::Try>.

=head1 SOURCE

The source code repository for Clean-Eval can be found at
F<https://github.com/exodist/Clean-Eval/>.

=head1 MAINTAINERS

=over 4

=item Chad Granum E<lt>exodist@cpan.orgE<gt>

=back

=head1 AUTHORS

=over 4

=item Chad Granum E<lt>exodist@cpan.orgE<gt>

=back

=head1 COPYRIGHT

Copyright 2026 Chad Granum E<lt>exodist7@gmail.comE<gt>.

This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

See F<http://dev.perl.org/licenses/>

=cut
