#!/usr/local/bin/perl

# 実験中。コアは完了。ファイル引数を取ったり。

# C ソース用の構造体定義から、バイトオフセットを求め、
# CPP 用に #define の形で出力する。
# Usage: offset.pl define-file
# define-file は common.h など
# 出力されるためには、
# typedef struct TAG_NAME {
# ...
# };
# の TAG_NAME が必須。
# 配列宣言の添字数 \[.+?\] は 削除。
# ポインタの \* も削除。
# とりあえず struct, union 定義のネストは サポートしない。
# つまり これ以降 初めて現れる '}' まで。
# パースの方針は
# TYPE_SPECIFIER IDENT;
# TYPE_SPECIFIER IDENT1, *IDENT2;
# などの形がある。
# ; が来るまでを 改行コードを無視して一つのレコードとする。
# そのレコードの最初の要素は TYPE_SPECIFIER なので削除。
# (TYPE_SPECIFIER は 1 word とは限らない。 ; or , の
# 直前の識別子 の前までが TYPE_SPECIFIER である。)
# split で , を区切りとしてさらに分割。
# 残るのは型指定子などがなくなって 識別子だけのリストとなる。

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

# コメントを削除
$IN =~ s/\/\*.*?\*\///sg;

# 構造体名
$TAGNAME = $IN;
$TAGNAME =~ s/^typedef struct (\w+) \{.*/\1/s;

# 構造体定義の始まるまでを削除 (ネスト不可)
$IN =~ s/.*?\{//gs;

# 構造体定義の終わり 以降を削除 (ネスト不可)
$IN =~ s/\}.*//gs;

# この状態をバックアップ
$IN_ORG = $IN;

# プリプロセッサ指示行を削除
$IN =~ s/^#.*//gm;

# 配列添字の削除
$IN =~ s/\[.+?\]//gs;

# ポインタ指定子の削除
$IN =~ s/\*//gs;

# 空行、行頭の空白を削除
$IN =~ s/^\s+//gm;

# 行末の空白を削除
$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) {
# アラインメント指定子 (_64, _32 など) を抜き出す。
    $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;
    }
# 型指定子を削除、識別子のみ抜き出す
#    $line =~ s/[a-zA-Z0-9_ ]+\s+(\w+\s*[;,]{0,1}.*)/\1/;
    $line =~ s/(\w|\s)+\s+(\w+\s*[;,]{0,1}.*)/\2/;

# , で複数の識別子が宣言されているときは、型指定子と間違って削除しないこと
    foreach $word (split(/\,/, $line)) {
# で、カンマの前後に空白が入っていたりする。
	$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";
    }
}
# 構造体の大きさ: RSP DMA を使うことが前提だが、さらに
# DD を使うことも考えて 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, #if, #undef のみ 抽出
@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";
    } elsif (/^\#ifndef/) {
	print OUT '  printf("', $_, '\n");', "\n";
    } elsif (/^\#else/) {
	print OUT '  printf("', $_, '\n");', "\n";
    } elsif (/^\#endif/) {
	print OUT '  printf("', $_, '\n");', "\n";
    } elsif (/^\#if/) {
	print OUT '  printf("', $_, '\n");', "\n";
    } elsif (/^\#undef/) {
	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; }
