#!/usr/bin/perl
use strict;
# See https://rt.cpan.org/Public/Bug/Display.html?id=57341
use Getopt::Long;
use GCC::TranslationUnit;
use C::DynaLib::Parse qw(pack_types process_struct process_func);

my ($cc, $inc, $code, $file, $type);
GetOptions( "cc=s" 		=> \$cc,
	    "I=s" 		=> \$inc,
	    "code=s" 		=> \$code,
	    "file=s",		=> \$file,
	    "t=s" 		=> \$type );
$inc = "-I$inc" if $inc;

sub declare_func {
  my $decl = shift;
  if ($type eq 'c_dynalib') {
    print "DeclareSub($decl->{name}, ",
        pack_types($decl->{retn}),", ",
	pack_types($decl->{parms}),");\n";
  } else {
    my $parms = join ", ", @{$decl->{parms}};
    $parms =~ s/, $//;
    print "$decl->{retn} $decl->{name}( $parms );\n";
  }
}

sub declare_struct {
  my $decl = shift;
  if ($type eq 'c_dynalib') {
    my $names = join '","', @{$decl->{names}};
    print qq(Define C::DynaLib::Struct("$decl->{name}",
			  "$decl->{packnames}",
			  ["$names"}]);
);
  } else {
    print "\n$decl->{type} $decl->{name} {";
    for (0..@$decl->{names}) {
      print $decl->{types}->[$_];
      print "\t",$decl->{names}->[$_];
      print ":",$decl->{sizees}->[$_],"\n";
    }
    print "}\n;"
  }
}

my @post;
my %records;
my $filter;

my $header = shift;
if ($type) {
  my %FFIs = map { $_ => 1 } qw( C::DynaLib FFI Win32::API P5NCI CTypes );
  die "Error: Unknown -t $type. Valid: ".join(", ",keys(%FFIs)) ."\n"
    unless $FFIs{$type};
  print "use $type;\n";
  print "# generated by hparse.pl $header;\n";
  $type =~ s/::/_/g;
  $type = lc($type); # c_dynalib ffi win32_api p5nci ctypes
}

my $node;
# .h or .hpp or .hh
$header .= ".h" if !$file and !$code and $header and $header !~ /\.h/;
if ($header and $header =~ /\.h(h|pp)$/ and !$cc) {
  # for c++ support with newer g++ see
  # https://rt.cpan.org/Public/Bug/Display.html?id=57349 (v1.01)
  $cc = "g++";
}
$cc = 'gcc' unless $cc; # check Config?
if ($code) {
  $filter = $header;
  $node = C::DynaLib::Parse::GCC_prepare($code, "$cc $inc");
} elsif ($file) {
  $filter = $header;
  $node = C::DynaLib::Parse::GCC_prepare("#include \"$file\"\n", "$cc $inc");
} else {
  $header = "stdlib.h" unless $header;
  $filter = shift;
  $node = C::DynaLib::Parse::GCC_prepare("#include <$header>\n", "$cc $inc");
}

while ($node) {
  if ($node->isa('GCC::Node::function_decl')
      and ($filter ? $node->name->identifier =~ /$filter/
	           : $node->name->identifier !~ /^_/))
  {
    declare_func process_func($node);
  }
  if ($node->isa('GCC::Node::record_type')
      and ($filter ? $node->name->identifier =~ /$filter/
	           : $node->name->identifier !~ /^_/))
  {
    declare_struct process_struct($node);
  }
} continue {
  $node = $node->chain;
}

POST:
while ($node = shift @C::DynaLib::Parse::post) {
  #print "\n(", ref $node, ")";
  if ($node->isa('GCC::Node::record_type')) {
   declare_struct process_struct($node);
  }
}

__END__

=pod

=head1 NAME

hparse

=head1 DESCRIPTION

Parse function signatures for FFI from gcc4 -fdump-translation-unit

Also parses record types (union, struct) if used as arguments
of the used functions.

Note that the output should be compiler independent. So you CAN use
gcc for creating FFI signatures for shared libraries compiled with
other compilers. Theoretically.

=head1 SYNOPSIS

  hparse.pl [OPTIONS] [header] [function-regex]

  hparse.pl stdio.h '^fprintf$'

  hparse.pl --code "int __cdecl ioctl (int __fd, int __cmd, ...)"

=head1 OPTIONS

  -t  FFI-TYPE  - dump in the given FFI format (todo)

  --cc gcc      - use given gcc
  -I            - use given include path

  --code string - parse string, not any header
  --file file   - parse file, not any header

=head2 FFI-TYPES (todo)

  * C::DynaLib
  * FFI
  * Win32::API
  * P5NCI
  * Ctypes

=head1 EXAMPLES

=head2 hparse.pl stdio.h '^fr'

frexp
  return=double
  parms=double, *int, void
  align=8, return-align=64

frexpf
  return=float
  parms=float, *int, void
  align=8, return-align=32

frexpl
  return=long double
  parms=long double, *int, void
  align=8, return-align=32

free
  return=void
  parms=*void, void
  align=8, return-align=8

freopen
  return=*FILE
  parms=const *char, const *char, *FILE, void
  align=8, return-align=32

fread
  return=size_t
  parms=*void, size_t, size_t, *FILE, void
  align=8, return-align=32

=head2 hparse.pl poll.h 'poll'

poll
  return=int
  parms=struct pollfd *, nfds_t, int, void
  align=8, return-align=32

struct pollfd  (align=32)
  {
    int fd (align=32)
    short int events (align=16)
    short int revents (align=16)
  }

=head1 TODO

Resolve size_t, nfds_t => integer_type

Calling convention _stdcall, _cdecl, _fastcall

Align syntax for the FFI's?

Varargs ... not detected

  ./hparse.pl --code "int __cdecl ioctl (int __fd, int __cmd, ...);" ioctl

  ioctl
    return=int
    parms=int, int
    align=8, return-align=32


=cut
