#!/usr/bin/perl -w
# (c) 2010, Joe Perches <joe@perches.com>
#
# Convert "typedef struct foo {...} bar" to "struct bar {...}"
#
# usage: perl detypedef.perl [OPTIONS] <patch>
#
# Licensed under the terms of the GNU GPL License version 2

use strict;
use Getopt::Long qw(:config no_auto_abbrev);

my $P = $0;
my $V = '0.01';

my $from = "";
my $to = "";
my $tag = "";

my $version = 0;
my $help = 0;

my $FILE;

sub usage {
    print <<EOT;
usage: $P [options] file
version: $V

    --from => struct typedef {} from; to convert
    --to => name to convert to
    --tag => optional "typedef struct tag { foo } from" value

By default, the from value is prefixed with tag and _ like:
	typedef struct _from {
		type	name;
		...
	} from;
and
	typedef struct tagfrom {
		type	name;
		...
	} from;

If tag is specified, then only this form is converted
	typedef struct tag {
		type	name;
		...
	} from;

If annotation __packed is used, it is ignored.
Other __attribute__ annotations are not parsed and could cause problems.

Other options:
  --version => show version
  --help => show this help information
EOT
}

if (!GetOptions(
    'from=s' => \$from,
    'to=s' => \$to,
    'tag=s' => \$tag,
    'v|version' => \$version,
    'h|help|usage' => \$help,
)) {
    die "$P: invalid argument - use --help if necessary\n";
}

if ($help != 0) {
    usage();
    exit 0;
}

if ($version != 0) {
    print("${P} ${V}\n");
    exit 0;
}

$tag = $from if ("$tag" eq "");

if ("$from" eq "" || "$to" eq "") {
    print("Must specify --from= and --to=\n");
    exit 0;
}

my $match_balanced_braces = qr/(\{(?:[^\{\}]++|(?-1))*\})/;

my $leading_tag_values = "(tag|_)?";

for my $filename (@ARGV) {

    open($FILE, '<', "$filename") ||
	die "$P: $filename: open failed - $!\n";
    my $text = do { local($/) ; <$FILE> };
    close($FILE);

    $text =~ s/\b$from\b/struct $to/g;
    $text =~ s/\btypedef struct\s+$leading_tag_values$tag/typedef struct $to/g;
    $text =~ s/\b$leading_tag_values$tag\b/struct $to/g;
    $text =~ s/\bstruct\s+$to\b[ \t]+{/struct $to {/g;
    $text =~ s/\bstruct\s+$to\b[ \t]+/struct $to /g;
    $text =~ s/\btypedef\s+struct\s+struct _$to\s*{/struct $to {/g;
    $text =~ s/\btypedef\s+struct\s+struct $to\s*{\b/struct $to {/g;
    $text =~ s/\bstruct\s+struct\s+$to\s*([{\*])/struct $to $1/g;
    $text =~ s/\bstruct\s+struct\s+$to\b/struct $to/g;
    $text =~ s/\bstruct\s*($match_balanced_braces)\s*struct\s+$to\b/struct $to $1/g;
    $text =~ s/\bstruct\s*($match_balanced_braces)\s*__packed\s+struct\s+$to\b/struct $to $1 __packed/g;
    $text =~ s/\btypedef\s+struct $to\s*{/struct $to {/g;
    $text =~ s/}\s*__packed\s*struct\s+$to\b[ \t]*;/} __packed;/g;
    $text =~ s/}\s*struct\s+$to\b[ \t]*;/};/g;
    $text =~ s/}\s*__packed\s+struct\s+$to,\s*\*\s*P_?$from\b[ \t]*;/}$1;/gi;
    $text =~ s/}\s+struct\s+$to,\s*\*\s*P_?$from\b[ \t]*;/};/gi;
    $text =~ s/\bP_?$from\b\s*/struct $to \*/g;

    open($FILE, '>', "$filename") ||
	die "$P: $filename: open failed - $!\n";
    print $FILE $text;
    close($FILE);

}
