#!/nw/dev/usr/bin/perl -w

use strict;
use Symbol;
use builtin qw(min max);
use Fatal qw(open);
use ObjStore::REP::HashRecord qw($VERSION c_types $Fspec %align);

my $input;
for (my $arg=0; $arg < @ARGV; $arg++) {
    my $o = $ARGV[$arg];
    # no options yet;
    if ($input) {
	warn "$o ignored";
	next;
    } else {
	if ($o =~ s/.rec$//) {
	    $input = $o;
	} else {
	    warn "expecting $o.rec suffix";
	    $input = $o;
	}
    }
}

my ($C,$H) = (gensym,gensym);
my $rec = gensym;
open $rec, "$input.rec";

open $C, ">$input.c";
open $H, ">$input.h";

sub preamble {
    my $fh = shift;
    print $fh "// Yucky -*-C++-*- generated by HashRecord $VERSION at ".localtime()."\n";
    print $fh "//\n";
    print $fh "// DO NOT EDIT THIS FILE;\n";
    print $fh "//   ALL CHANGES WILL BE OVERWRITTEN!!\n";
    print $fh "//\n\n";
}

for ($C,$H) { preamble($_) }

print $H qq[#include "osp_hashrecord.h"\n];
print $C q[extern "C" {
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
}
#include <stddef.h>
];
print $C qq[#include "$input.h"\n\n];

my $class_RE = '(class|struct)\s+';

my %T;
for (c_types) { $T{$_} = 1 }

my %opt = (readonly => 0, index => 1, os_class => 1);
my $Class;
my $NextId;
my $fallback;
my @F;
my $l;
while ($l = <$rec>) {
    if ($l =~ m/$class_RE (\w+) \s [^;\{]* \{ /x) {
	print $H "// HR options: ".join(' ', map { ($opt{$_}?'':'!') . $_ }
					sort keys %opt)."\n";
	$Class = $2;
	$NextId = 0;
	next
    }
    if ($l =~ m/^\}\;/) {
	if (! @F) {
#	    warn "$Class has no recognizable fields (ignored)";
	} else {
	    generate($Class, \@F);
	}
	$Class = undef;
	$fallback = undef;
	@F=();
	next
    }
    if (!$Class and $l =~ m{ ^ // \s* HR \s+ options: \s+ (.*) $ }x) {
	my @opt = split /\s+/, $1;
	for my $o (@opt) {
	    if ($o =~ m/^(\!)?readonly$/) {
		$opt{readonly} = ! $1;
	    } elsif ($o =~ m/^(\!)?index$/) {
		$opt{index} = ! $1;
	    } elsif ($o =~ m/^(\!)?os_class$/) {
		$opt{os_class} = ! $1;
	    } else {
		warn "unknown option '$o' ignored";
	    }
	}
	$l = '';
	next;
    }
    next if !$Class;
    next if $l =~ m/^\s*$/;

    if ($l =~ /^alias: \s+ (\w+) \s+ (\w+) \s* $/x) {
	my ($one,$to) = ($1,$2);
	my $ok=0;
	for my $f (@F) {
	    next if $f->{name} ne $to;
	    my %copy = %$f;
	    delete $copy{'align'};
	    delete $copy{'text'};
	    push @F, { %copy, name => $one, len => length $one, alias => $to };
	    $ok=1;
	    last;
	}
	$l = '';
	warn "alias: '$to' not found (ignored)"
	    if !$ok;
	next;
    }

    my @l = split /\s+/, $l;
    shift @l if !length $l[0];
    $l[0] =~ s/^I(\d+)$/os_int$1/;
    next if (!exists $T{ $l[0] } or
	     $l[1] !~ s/^ (\w+) (\;)? $/$1/x);
    my $rest = join(' ', @l[2..$#l]);
    my $id = $NextId++;
    my $name = $l[1];
    $name = $1 if $rest =~ /\b rename\: \s (\w+) \b/x;
    $fallback = $l[1] if $name eq 'FALLBACK' && $l[0] eq 'OSPVptr';
    chop $l;
    $l .= "\t//" if $l !~ m'//';
    $l .= ' @'.$id;
    $l .= "\n";
    push @F, { type => $l[0], align => $align{$l[0]},
	       cname => $l[1], name => $name,
	       len => length $name, id => $id, text => $l };
    $l='';
} continue {
    print $H $l if $l;
}

sub generate {
    my ($class,$F) = @_;

    # HEADER FILE
    my @real = grep { !exists $_->{alias} } @$F;
    my $cnt = @real;
    my $bset = int ((31+$cnt)/32);
    if ($bset > 4) {
	warn "too many fields $bset > 128";
	$bset = 4;
    }
    print $H "// HR fields:\n";

    # re-order fields as little as possible
    for (my $al = max map { $_->{align} } @real; $al > 0; $al--) {
	for (grep { $_->{align} == $al } @real) {
	    print $H $_->{text};
	}
	if ($al == 4) {
	    print $H "    osp_bitset$bset HR_readonly; // HR\n"
		if $opt{readonly};
	    print $H "    osp_bitset$bset HR_indexed; // HR\n"
		if $opt{index};
	}
    }
    my $osc = $opt{os_class}? '':'//';

    print $H <<END;

  // HashRecord class info:
  static int HR_num_fields;
  static $Fspec HR_spec[$cnt];

  // HashRecord methods:
  virtual void make_constant();
  virtual int HR_get_num_fields();
  virtual int HR_key_2field(char *key, int klen);
  virtual $Fspec *HR_get_field_spec(int xx);
  virtual int HR_mod_field(int xx);
  virtual void HR_mod_field(osp_pathexam &exam, int xx);

  // you must implement:
  $osc virtual char *os_class(STRLEN *len);
END

    # FIELD SPEC TABLE
    print $C "osp_hashrec_field_spec $class\::HR_spec[$cnt] = {\n  ";
    print $C join(",\n  ",
		  map { "{ ".join(", ",
				  '"'.$_->{name}.'"',
				  length $_->{name},
				  "offsetof($class, $_->{cname})",
				  "FT_".$_->{type})." }" }
		  sort { $a->{id} <=> $b->{id} } @real);
    print $C "\n};\n";
    print $C "int $class\::HR_num_fields = $cnt;\n";
    print $C "\n";

    # METHODS
    print $C "void $class\::make_constant()\n";
    if ($opt{readonly}) {
	print $C "{ HR_readonly.set(); }\n";
    } else {
	print $C "{ OSPvFLAGS(this) |= OSPV_phrREADONLY; }\n";
    }
    print $C "int $class\::HR_get_num_fields() { return HR_num_fields; }\n";
    print $C "$Fspec * $class\::HR_get_field_spec(int xx)\n{ return &HR_spec[xx]; }\n";
    if ($fallback) {
	print $H "  OSSVPV *HR_get_fallback();\n";
	print $C "OSSVPV *$class\::HR_get_fallback() { return $fallback; }\n";
    }
    print $C "int $class\::HR_mod_field(int xx)\n";
    print $C "{\n";
    print $C "  if (HR_indexed[xx]) return 0;\n" if $opt{index};
    if ($opt{readonly}) {
	print $C "  if (HR_readonly[xx]) return 0;\n";
    } else {
	print $C "  if (OSPvFLAGS(this) & OSPV_phrREADONLY) return 0;\n";
    }
    print $C "  return 1;\n";
    print $C "}\n";
    print $C "void $class\::HR_mod_field(osp_pathexam &exam, int xx)\n";
    print $C "{\n";
    if ($opt{index}) {
	if ($opt{readonly}) {
	    print $C "  if (HR_readonly[xx]) return;\n";
	} else {
	    print $C "  if (OSPvFLAGS(this) & OSPV_phrREADONLY) return;\n";
	}
	print $C "  if (exam.get_mode() == 's') {\n";
	print $C "    if (HR_indexed[xx]) exam.set_conflict();\n";
	print $C "    else HR_indexed.set(xx);\n";
	print $C "  } else if (exam.get_mode() == 'u') HR_indexed.clr(xx);\n";
    } else {
	# !index
	print $C "  if (exam.get_mode() == 's') {\n";
	if ($opt{readonly}) {
	    print $C "    HR_readonly.set(xx);\n";
	} else {
	    print $C "    OSPvFLAGS(this) |= OSPV_phrREADONLY;\n";
	}
	print $C "  }\n";
    }
    print $C "}\n";
    print $C "int $class\::HR_key_2field(char *key, int klen)\n";
    print $C "{\n";
    print $C "  if (!klen) klen = strlen(key);\n";
    print $C "  if (!klen) return -1;\n";
    print $C "  switch (key[0]) {\n";
    my %k;
    for (@$F) { ++ $k{ substr($_->{name},0,1) } }
    delete $k{'_'};
    for my $start (sort keys %k) {
	print $C "  case '$start':\n";
	my @case = grep { $_->{name} =~ m/^$start/ } @$F;
	for (sort { $a->{len} <=> $b->{len} } @case) {
	    my $x = $_->{id};
	    if (! $_->{alias}) {
		print $C "    if (klen == $_->{len} && memcmp(key, HR_spec[$x].key, $_->{len}) == 0) return $x;\n";
	    } else {
		print $C qq[    if (klen == $_->{len} && memcmp(key, "$_->{name}", $_->{len}) == 0) {\n];
		print $C qq[      warn("Please use '$_->{alias}' instead of '$_->{name}'");\n];
		print $C "      return $x;\n";
		print $C "    }\n";
	    }
	}
	print $C "    break;\n";
    }
    print $C "  }\n";
    print $C "  return -1;\n";
    print $C "}\n";
    print $C "\n";
}

# verify binary footprint size < 1-5k per class



# Copyright © 1998 Joshua Nathaniel Pritikin.  All rights reserved.

__END__;
