#!/usr/bin/perl


sub write_h($);
sub write_c($);


if (scalar(@ARGV) < 1)
{
  printf "Usage: genwrapper [-I include_path] <source> [output]\n\n";
  exit 0;
}

$cppflags = '';
$sourcefull = '';
if ($ARGV[0] eq '-I')
{
  shift;
  $inc = shift;
  $cppflags = "-I '$inc'";
  $sourcefull = "$inc/";
}

$source = $ARGV[0];
$sourcefull .= $ARGV[0];
if (scalar(@ARGV) > 1)
{
  $output = $ARGV[1];
}
else
{
  # Get output name from source base name
  $output = $source;
  $output =~ s/\.h$//; # remove .h extension if present
  $output =~ s/.*\///; # remove path
}

printf "source: '$source'\n";
printf "output: '${output}_wrapper{.h,.c}'\n";

$sym_nb = 0;

open ($in, "sed -e s/\#include// '$sourcefull'|gcc $cppflags -E - |") or die "error: cannot open source file '$source'";
open ($out_h, ">${output}_wrapper.h") or die "error: cannot open output file '${output}_wrapper.h'";
open ($out_c, ">${output}_wrapper.c") or die "error: cannot open output file '${output}_wrapper.c'";

sub parse_decl($)
{
  @decl = split (/ /, shift);
  $type = join (' ', @decl[0 .. scalar(@decl)-2]);
  $name = @decl[scalar(@decl)-1];
  if ($name =~ /^\*/) {
    $type .= " *";
    $name =~ s/^\*//;
  }
  return ($type, $name);
}

# Let's parse input
$symtable = "";
while (<$in>)
{
  chomp;
  next if (! /.+\(.*\).*;.*/); # Let's say this one catches C function declarations
  next if (/\( *\*/);          # Eliminate ptr to functions declaration

  $open_paren = index ($_, '(');
  $declstr = substr ($_, 0, $open_paren);
  $declstr =~ s/^extern +//;
  ($return, $name) = parse_decl ($declstr);
  $symtable[$sym_nb]{'name'} = $name;
  $symtable[$sym_nb]{'return'} = $return;

  $closed_paren = index ($_, ')');
  @args = split (/,/, substr($_, $open_paren+1, $closed_paren-$open_paren-1));
  $argc = 0;
  for $i (@args) {
    $i =~ s/^\ *|\ $*//;
    ($arg_type, $arg_name) = parse_decl ($i);
    next if ($arg_name =~ /void/);
    $symtable[$sym_nb]{'args'}[$argc]{'type'} = $arg_type;
    $symtable[$sym_nb]{'args'}[$argc]{'name'} = $arg_name;

    $argc++;
  }
  $symtable[$sym_nb]{'argc'} = $argc;

  $sym_nb++;
}
close ($in);

# Generate outputs
write_h ($out_h);
close ($out_h);
write_c ($out_c);
close ($out_c);

# Done
printf "parsed $sym_nb function prototypes\n\n";


#
# Subs
#


sub write_h($)
{
  my $out = shift;
  printf $out <<EOF;
#ifndef _${output}_wrapper_h_
#define _${output}_wrapper_h_

#include "dw.h"

extern dw_syminfo_t _symtab_${output}[];

#endif /* _${output}_wrapper_h_ */

EOF
}

sub write_c($)
{
  my $out = shift;
  printf $out <<EOF;
#include "$source"
#include <stdlib.h>
#include "dw.h"

#define FLAGS DW_FLAG_CALL


/*
 * Pointers to original symbols in wrapped DLL
 */

EOF

  for $i (0 .. $sym_nb-1)
  {
    printf $out "$symtable[$i]{'return'} (*_p_$symtable[$i]{'name'}) (";
    for $j (0 .. $symtable[$i]{'argc'}-1)
    {
      if ($j > 0) { printf $out ", "; }
      printf $out "$symtable[$i]{'args'}[$j]{'type'} $symtable[$i]{'args'}[$j]{'name'}";
    }
    printf $out ");\n";
  }

  printf $out <<EOF;


/*
 * Function args info table
 */

EOF

  for $i (0 .. $sym_nb-1)
  {
    $name = $symtable[$i]{'name'};
    printf $out "dw_param_t _i_${name}[] = { ";
    for $j (0 .. $symtable[$i]{'argc'}-1)
    {
      if ($j > 0) { printf $out ", "; }
      printf $out "{\"$symtable[$i]{'args'}[$j]{'type'}\", \"$symtable[$i]{'args'}[$j]{'name'}\"}";
    }
    printf $out " };\n";
  }

  printf $out <<EOF;


/*
 * Symbol information table 
 */

dw_syminfo_t _symtab_${output}[] =
{
EOF

  for $i (0 .. $sym_nb-1)
  {
    $name = $symtable[$i]{'name'};
    printf $out "  {\"$name\", FLAGS, (void**)&_p_$name, ";
    $ret = $symtable[$i]{'return'};
    if ($ret =~ /void/) {
      printf $out "NULL, 0, ";
    }
    else {
      printf $out "\"$ret\", 0, ";
    }
    printf $out "$symtable[$i]{'argc'}, _i_$name},\n";
  }

  printf $out <<EOF;
  {NULL}
};


/*
 * Wrappers
 */


EOF

  for $i (0 .. $sym_nb-1)
  {
    $name = $symtable[$i]{'name'};
    $ret  = $symtable[$i]{'return'};
    $args_decl = '';
    $args_call = '';
    for $j (0 .. $symtable[$i]{'argc'}-1)
    {
      if ($j > 0) { $args_decl .= ', '; $args_call .= ', '; }
      $args_decl .= "$symtable[$i]{'args'}[$j]{'type'} $symtable[$i]{'args'}[$j]{'name'}";
      $args_call .= $symtable[$i]{'args'}[$j]{'name'};
    }
    $coma = ($args_call eq '') ? '' : ', ';
    printf $out <<EOF;
$ret $name ($args_decl)
{
  dw_syminfo_t* sym = &(_symtab_${output}[$i]);
  if (_p_$name)
  {
EOF
    if ($ret =~ /void/)
    {
      printf $out <<EOF;
    if (sym->flags & DW_FLAG_CALL) (*_p_$name)($args_call);
    if (sym->flags & DW_FLAG_TRACE) dw_do (sym${coma}${args_call});
  }
  else
    if (sym->flags & DW_FLAG_TRACE) dw_na (sym${coma}${args_call});
EOF
    }
    else
    {
      printf $out <<EOF;
    $ret result = (sym->flags & DW_FLAG_CALL) ? (*_p_$name)($args_call) : ($ret)sym->ret_default;
    if (sym->flags & DW_FLAG_TRACE) dw_do_r (sym, (dw_ret_t)result${coma}${args_call});
    return result;
  }
  else
  {
    if (sym->flags & DW_FLAG_TRACE) dw_na (sym${coma}${args_call});
    return ($ret)sym->ret_default;
  }
EOF
    }
    printf $out "}\n\n";
  }
}
