#!/usr/local/bin/perl

# $B<B83Cf!#%3%"$O40N;!#%U%!%$%k0z?t$r<h$C$?$j!#(B

# C $B%=!<%9MQ$N9=B$BNDj5A$+$i!"%P%$%H%*%U%;%C%H$r5a$a!"(B
# CPP $BMQ$K(B #define $B$N7A$G=PNO$9$k!#(B
# Usage: offset.pl define-file
# define-file $B$O(B common.h $B$J$I(B
# $B=PNO$5$l$k$?$a$K$O!"(B
# typedef struct TAG_NAME {
# ...
# };
# $B$N(B TAG_NAME $B$,I,?\!#(B
# $BG[Ns@k8@$NE:;z?t(B \[.+?\] $B$O(B $B:o=|!#(B
# $B%]%$%s%?$N(B \* $B$b:o=|!#(B
# $B$H$j$"$($:(B struct, union $BDj5A$N%M%9%H$O(B $B%5%]!<%H$7$J$$!#(B
# $B$D$^$j(B $B$3$l0J9_(B $B=i$a$F8=$l$k(B '}' $B$^$G!#(B
# $B%Q!<%9$NJ}?K$O(B
# TYPE_SPECIFIER IDENT;
# TYPE_SPECIFIER IDENT1, *IDENT2;
# $B$J$I$N7A$,$"$k!#(B
# ; $B$,Mh$k$^$G$r(B $B2~9T%3!<%I$rL5;k$7$F0l$D$N%l%3!<%I$H$9$k!#(B
# $B$=$N%l%3!<%I$N:G=i$NMWAG$O(B TYPE_SPECIFIER $B$J$N$G:o=|!#(B
# (TYPE_SPECIFIER $B$O(B 1 word $B$H$O8B$i$J$$!#(B ; or , $B$N(B
# $BD>A0$N<1JL;R(B $B$NA0$^$G$,(B TYPE_SPECIFIER $B$G$"$k!#(B)
# split $B$G(B , $B$r6h@Z$j$H$7$F$5$i$KJ,3d!#(B
# $B;D$k$N$O7?;XDj;R$J$I$,$J$/$J$C$F(B $B<1JL;R$@$1$N%j%9%H$H$J$k!#(B

while (<>) {
    $IN .= $_;
}

# $B%3%a%s%H$r:o=|(B
$IN =~ s/\/\*.*?\*\///sg;

# $B9=B$BNL>(B
$TAGNAME = $IN;
$TAGNAME =~ s/^typedef struct (\w+) \{.*/\1/s;

# $B9=B$BNDj5A$N;O$^$k$^$G$r:o=|(B ($B%M%9%HIT2D(B)
$IN =~ s/.*?\{//gs;

# $B9=B$BNDj5A$N=*$o$j(B $B0J9_$r:o=|(B ($B%M%9%HIT2D(B)
$IN =~ s/\}.*//gs;

# $B$3$N>uBV$r%P%C%/%"%C%W(B
$IN_ORG = $IN;

# $B%W%j%W%m%;%C%5;X<(9T$r:o=|(B
$IN =~ s/^#.*//gm;

# $BG[NsE:;z$N:o=|(B
$IN =~ s/\[.+?\]//gs;

# $B%]%$%s%?;XDj;R$N:o=|(B
$IN =~ s/\*//gs;

# $B6u9T!"9TF,$N6uGr$r:o=|(B
$IN =~ s/^\s+//gm;

# $B9TKv$N6uGr$r:o=|(B
$IN =~ s/;\s+/;/gm;

@list = split(/;/, $IN);

$OUTNAME = "/tmp/offset-tmp$$.c";
open OUT, ">" . $OUTNAME;

print OUT <<EOF;
#include <ultra64.h>
#include <stdio.h>
#ifndef _128
#define _128
#endif
#ifndef _64
#define _64
#endif
#ifndef _32
#define _32
#endif
#ifndef _16
#define _16
#endif
typedef struct s_ {
$IN_ORG
} s_;
struct s_ s;
void alignerr(char *member, int i, int ofs)
{
  fprintf(stderr, "Error: member \'%s\' must be %dbit aligned (current offset is 0x%x)\\n", member, i << 3, ofs);
  exit(-1);
}
void alignwarn(char *member, int ofs, int lastofs)
{
  fprintf(stderr, "Warning: member \'%s\' is aligned (current/last offset 0x%x/0x%x)\\n", member, ofs, lastofs);
}
int main() {
  int i, j;
  j = 0;
EOF

foreach $line (@list) {
# $B%"%i%$%s%a%s%H;XDj;R(B (_64, _32 $B$J$I(B) $B$rH4$-=P$9!#(B
    $align = $line;
    $align =~ s/^\s*\_(\d+).+/\1/;
    if ($align != 0) {
	if ($align & 0x07) {
	    print STDERR "$line\n", "Error: alignment specifier ($align) is invalid\n";
	    die;
	}
	$align >>= 3;
    }
# $B7?;XDj;R$r:o=|!"<1JL;R$N$_H4$-=P$9(B
#    $line =~ s/[a-zA-Z0-9_ ]+\s+(\w+\s*[;,]{0,1}.*)/\1/;
    $line =~ s/(\w|\s)+\s+(\w+\s*[;,]{0,1}.*)/\2/;

# , $B$GJ#?t$N<1JL;R$,@k8@$5$l$F$$$k$H$-$O!"7?;XDj;R$H4V0c$C$F:o=|$7$J$$$3$H(B
    foreach $word (split(/\,/, $line)) {
# $B$G!"%+%s%^$NA08e$K6uGr$,F~$C$F$$$?$j$9$k!#(B
	$word =~ s/\s+//g;
	print OUT "  i = (int)&s.$word - (int)&s;";
	if ($align > 1) {
	    print OUT "  if (i & ", $align - 1, ") alignerr(\"$word\", $align, i);\n";
	}
	print OUT "  if (i != j) alignwarn(\"$word\", i, j);\n";
	print OUT '  printf("#define OFFSET_', uc($word), ' 0x%x\n", i);', "\n";
	print OUT "  j = i + sizeof(s.$word);\n";
#	print $word, "\n";
    }
}
# $B9=B$BN$NBg$-$5(B: RSP DMA $B$r;H$&$3$H$,A0Ds$@$,!"$5$i$K(B
# DD $B$r;H$&$3$H$b9M$($F(B 16-byte aligned
print OUT '  printf("#define SIZEOF_', uc($TAGNAME), ' 0x%x\n", ',
"(sizeof(s) + 15) & 0xfffffff0);\n";

print OUT '  if (((sizeof(s) + 15) & 0xfffffff0) != sizeof(s))
    fprintf(stderr, "Warning: sizeof(the structure)=0x%x, not 64bit-aligned\\n",
            sizeof(s));
';

# #define, #ifndef, #else, #endif $B$N$_(B $BCj=P(B
@define_list = split /\n/, $IN_ORG;

print OUT '  printf("#ifndef __OFFSET_PL__\n");', "\n";
foreach (@define_list) {
    chomp;
    if (/^\#define/) {
	print OUT '  printf("', $_, '\n");', "\n";
    }
    if (/^\#ifndef/) {
	print OUT '  printf("', $_, '\n");', "\n";
    }
    if (/^\#else/) {
	print OUT '  printf("', $_, '\n");', "\n";
    }
    if (/^\#endif/) {
	print OUT '  printf("', $_, '\n");', "\n";
    }
}
print OUT '  printf("#endif\n");', "\n";
print OUT '  return 0;
}
';

close OUT;

$i = system("cc -o /tmp/offset-tmp$$ $OUTNAME");
if ($i) { die; }

$i = system("/tmp/offset-tmp$$");
unlink("/tmp/offset-tmp$$");
unlink($OUTNAME);
if ($i) { die; }
