The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w
use strict;

# Author: Andreas Marienborg <andreas.marienborg@gmail.com>
# Donated as is to xapian project. Use and modify as see fit

BEGIN {
    my $dir = shift || '../xapian-core';
    if ($dir eq '--help') {
	print "Syntax: $0 [<path to xapian-core source directory>]\n\n";
	print "This script needs to know the xapian-core source directory to find\n";
	print "exception_data.pm - by default it looks in '../xapian-core'.\n";
	exit 0;
    }
    push(@INC, $dir);
}

# We load the exception data from xapian-core.
use exception_data qw(@baseclasses @classes %subclasses);

# Generate typemaps for Xapian::Error and its subclasses.
my $tm = "typemap-errorclasses";
open(TM, ">", "$tm.tmp")
    or die "cannot write '$tm.tmp': $!\n";

foreach (@baseclasses, @classes) {
    my ($classname, $parent, $desc) = split /\t/;
    print TM "$classname\tO_OBJECT\n";
    print TM "$classname *\tO_OBJECT\n";
}
close TM;

rename "$tm.tmp", $tm
    or die "Failed to rename '$tm.tmp' to '$tm': $!\n";

my $fnm = "Xapian/Error.pm";
open F, '>', "$fnm.tmp" or die $!;

print F <<'END';
package Search::Xapian::Error;

=head1 NAME

Search::Xapian::Error - Base class for all exceptions in Search::Xapian

=head1 DESCRIPTION

This is an abstract class in C++, i.e. it cannot be instantiated directly.
In Perl there is no such concept, but you should not need to create instances
of this class yourself.

=head1 METHODS

All exception objects have the following methods

=head2 get_msg

Returns a string with a descriptive error message, useful for outputing

=head2 get_type

The type of this error (e.g. "DocNotFoundError").

=head2 get_context

Optional context information, returned as a string

=head2 get_error_string

Returns any error string from errno or similar associated with this error

=cut

use 5.006;
use strict;
use warnings;

require DynaLoader;

END

foreach my $subclass (@{$subclasses{'Error'}}) {
    print F "use Search::Xapian::$subclass;\n";
}

print F <<'END';

our @ISA = qw(DynaLoader);

# Preloaded methods go here.

# In a new thread, copy objects of this class to unblessed, undef values.
sub CLONE_SKIP { 1 }

sub new {
    my $class = shift;
    my ($self);
    bless $self, $class;

    return $self;
}

1;
END

close F or die $!;
rename "$fnm.tmp", "$fnm" or die $!;

foreach ('Error', @baseclasses, @classes) {
    my ($classname, $parent, $full_description) = split /\t/;

    # XS/CLASSNAME.xs
    $fnm = "XS/$classname.xs";
    open F, '>', "$fnm.tmp" or die $!;

    print F <<"END";
MODULE = Search::Xapian\t PACKAGE = Search::Xapian::$classname

PROTOTYPES: ENABLE

string
${classname}::get_type()

string
${classname}::get_msg()

string
${classname}::get_context()

const char *
${classname}::get_error_string()

void
${classname}::DESTROY()
END

    if (exists $subclasses{$classname}) {
	print F "\n";
	foreach my $subclass (@{$subclasses{$classname}}) {
	    print F "INCLUDE: XS/$subclass.xs\n";
	}
    }

    close F or die $!;
    rename "$fnm.tmp", "$fnm" or die $!;

    next if $classname eq 'Error';

    $full_description =~ s!^[/ ]\*[*/]?!!mg;
    $full_description =~ s!\*\/$!!mg; # ! to unconfuse vim

    my ($heading, $desc) = split('\n\n', $full_description, 2);
    $desc ||= '';

    # Xapian/CLASSNAME.pm
    $fnm = "Xapian/$classname.pm";
    open F, '>', "$fnm.tmp" or die $!;

    print F <<"END";
package Search::Xapian::$classname;

=head1 NAME

Search::Xapian::$classname - $heading

=head1 DESCRIPTION

$desc
=cut

use 5.006;
use strict;
use warnings;

require DynaLoader;

# For compatibility with XS Search::Xapian < 1.2.3 which still threw strings
# in some cases.
use overload '""' => sub { "Exception: ".\$_[0]->get_msg };

END

    if (exists $subclasses{$classname}) {
	foreach my $subclass (@{$subclasses{$classname}}) {
	    print F "use Search::Xapian::$subclass;\n";
	}
	print F "\n";
    }

    print F <<"END";
our \@ISA = qw(DynaLoader Search::Xapian::$parent);

1;
END

    close F or die $!;
    rename "$fnm.tmp", "$fnm" or die $!;
}

# write new handle_exception.cc

$fnm = "handle_exception.cc";
open F, '>', "$fnm.tmp" or die $!;

print F <<'END';
#include <xapian.h>

extern "C" {
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
}

/* handle_exception function
 *
 * called in catch blocks to croak or rethrow in perl land
 */

void handle_exception(void) {
    try {
        throw;
END

foreach (reverse @classes) {
    my ($classname, $parent, $full_description) = split /\t/;

    print F <<"END";
    } catch (const Xapian::$classname & error) {
	SV * errsv = get_sv("\@", TRUE);
	sv_setref_pv(errsv, "Search::Xapian::$classname", (void *) new Xapian::$classname(error));
        croak(Nullch);
END
}

print F <<'END';
    } catch (const std::exception & error) {
        croak( "std::exception: %s", error.what());
    } catch (...) {
        croak("something terrible happened");
    }
}
END

close F or die $!;
rename "$fnm.tmp", "$fnm" or die $!;