The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use 5.010000;
use autodie;
use Text::MicroTemplate;
use Text::MicroTemplate::File;

my $CB_C = <<'...';
? my @callbacks = @_;

? for my $cb (@callbacks) {
<?= $cb->{type} ?> tmh_cb_<?= $cb->{name} ?>(<?= $cb->{params} ?>) {
    dTHX; dSP; bool is_null = 0;
    SV** rcb = hv_fetchs((HV*)opaque, "<?= $cb->{name} ?>", 0);
    <? if ($cb->{type} eq 'void') { ?>
    if (!rcb) { return; }
    <? } else { ?>
    if (!rcb) { return 0; }
    <? } ?>
    CB_HEADER("<?= $cb->{name} ?>");
    <? for my $a (@{$cb->{args}}) { ?>
        <?= $a ?>;
    <? } ?>
    CB_FOOTER;
    <? if ($cb->{type} eq 'int') { ?>
    return is_null ? 0 : 1;
    <? } ?>
}
? }
...

my $CB_INC = <<'...';
? my @callbacks = @_;

? for my $cb (@callbacks) {
void
<?= $cb->{name} ?>(tmh_callbacks* self, SV *code)
CODE:
    self->callbacks.<?= $cb->{name} ?> = tmh_cb_<?= $cb->{name} ?>;
    hv_stores(self->custom_opaque, "<?= $cb->{name} ?>", newSVsv(code));

? }
...

&main;

sub main {
    my @callbacks = scan_callbacks();
    render_c(@callbacks);
    render_inc(@callbacks);
    render_callbacks_pod(@callbacks);
}

sub render_c {
    my @callbacks = @_;
    my $tmt = Text::MicroTemplate->new(
        template => $CB_C,
        escape_func => sub { shift },
    );
    my $c = (eval $tmt->code())->(@callbacks);
    spew('lib/Text/Markdown/gen.callback.c', $c);
}

sub render_inc {
    my @callbacks = @_;
    my $tmt = Text::MicroTemplate->new(
        template => $CB_INC,
        escape_func => sub { shift },
    );
    my $c = (eval $tmt->code())->(@callbacks);
    spew('lib/Text/Markdown/gen.callback.inc', $c);
}

sub render_callbacks_pod {
    my @callbacks = @_;
    my $tmt = Text::MicroTemplate::File->new(
        escape_func => sub { shift },
        include_path => 'author/tmpl/',
    );
    my $c = $tmt->render_file('Callbacks.pod', @callbacks);
    spew('lib/Text/Markdown/Hoedown/Callbacks.pod', $c);
}

sub spew {
    my $fname = shift;
    open my $fh, '>', $fname
        or Carp::croak("Can't open '$fname' for writing: '$!'");
    print {$fh} $_[0];
}

sub scan_callbacks {
    open my $fh, '<', 'hoedown/src/markdown.h';
    my $content = do { local $/; <$fh> };
    $content =~ s/struct hoedown_callbacks {(.*?)}//sm or die "Invalid markdown.h";
    my @callbacks;
    for my $line (split /\n/, $1) {
        if ($line =~ /\A\s*(.*?)\s+\(\*(\w+)\)\((.*)\);/) {
            my ($type, $name, $opts) = ($1, $2, $3);
            my @opts = split /,/, $opts;
            shift @opts;
            pop @opts;
            my @args;
            my @pp_args;
            for (@opts) {
                s/\A\s*//;
                s/\s*\z//;
                if ($_ =~ /\Aconst\s+struct hoedown_buffer \*(\w+)\z/) {
                    push @args, "PUSHBUF($1)";
                    push @pp_args, "\$$1:Str";
                } elsif ($_ =~ /\Aint (\w+)\z/) {
                    push @args, "mXPUSHi($1)";
                    push @pp_args, "\$$1:Int";
                } elsif ($_ =~ /\Aunsigned int (\w+)\z/) {
                    push @args, "mXPUSHu($1)";
                    push @pp_args, "\$$1:UInt";
                } elsif ($_ =~ /\Aenum hoedown_autolink type\z/) {
                    push @args, "mXPUSHi(type)";
                    push @pp_args, "\$type:Int";
                } else {
                    die "Unknown: $_";
                }
            }

            push @callbacks, +{
                type   => $type,
                name   => $name,
                args   => \@args,
                pp_args => \@pp_args,
                params => $opts,
            };
        }
    }
    return @callbacks;
}