#!/usr/bin/perl
## --------------------------------------------------------------------------
##
##   Copyright 1996-2009 The NASM Authors - All Rights Reserved
##   See the file AUTHORS included with the NASM distribution for
##   the specific copyright holders.
##
##   Redistribution and use in source and binary forms, with or without
##   modification, are permitted provided that the following
##   conditions are met:
##
##   * Redistributions of source code must retain the above copyright
##     notice, this list of conditions and the following disclaimer.
##   * Redistributions in binary form must reproduce the above
##     copyright notice, this list of conditions and the following
##     disclaimer in the documentation and/or other materials provided
##     with the distribution.
##
##     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
##     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
##     INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
##     MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
##     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
##     CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
##     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
##     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
##     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
##     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
##     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
##     OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
##     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##
## --------------------------------------------------------------------------

#
# macros.pl   produce macros.c from standard.mac
#

require 'phash.ph';
require 'asm/pptok.ph';

use bytes;

my $fname;
my $line = 0;
my $index      = 0;
my $tasm_count = 0;
my @pname;

# Default names for various bytes
for (my $o = 0; $o < 256; $o++) {
    my $c = chr($o);
    if ($o < 32 || $o > 126) {
	$pname[$o] = sprintf("%d", $o);
    } elsif ($c =~ /^[\'\"\\]$/) {
	$pname[$o] = "\'\\".$c."\'";
    } else {
	$pname[$o] = "\'".$c."\'";
    }
}

#
# Print out a string as a character array
#
sub charcify(@) {
    my $l = '';
    my ($c, $o);
    my $space = 1;
    my $quote = 0;

    foreach $o (unpack("C*", join('',@_))) {
	$c = pack("C", $o);
	if ($quote) {
	    if ($o == $quote) {
		$quote = 0;
	    }
	} elsif ($c =~ /^[\'\"\`]$/) {
	    $quote = $o;
	} else {
	    if ($c eq ' ') {
		next if ($space);
		$o = 32;
		$c = ' ';
		$space = 1;
	    } elsif ($o < 32 || $o > 126) {
		$space = 1;	# Implicit space after compacted directive
	    } else {
		$space = 0;
	    }
	}
	$l .= $pname[$o];
	$l .= ',';
    }
    return $l;
}


#
# Generate macros.c
#
open(OUT, '>', 'macros/macros.c') or die "unable to open macros.c\n";

print OUT "/*\n";
print OUT " * Do not edit - this file auto-generated by macros.pl from:\n";
print OUT " *   ", join("\n *   ", @ARGV), "\n";
print OUT " */\n";
print OUT "\n";
print OUT "#include \"tables.h\"\n";
print OUT "#include \"nasmlib.h\"\n";
print OUT "#include \"hashtbl.h\"\n";
print OUT "#include \"outform.h\"\n";
print OUT "\n";

my $name = undef;
my $npkg = 0;
my @pkg_list   = ();
my %pkg_number = ();
my $pkg;
my @out_list   = ();
my @std_list   = ();
my $outfmt;
my $lastname;
my $z;

my @pptok_list = sort { $pptok_hash{$a} <=> $pptok_hash{$b} } keys %pptok_hash;
my %pnum;

foreach my $pt (@pptok_list) {
    my $n = $pptok_hash{$pt};
    if ($pt !~ /[A-Z]/ && $n < 256-96) {
	$n = ($n+128) & 255;
	(my $et = $pt) =~ s/^\%/p_/;
	printf OUT "#define %-24s %3d\n", $et, $n;
	$pnum{$pt} = $n;
	$pname[$n] = $et;
    }
}
printf OUT "#define %-24s %3d\n\n", 'EOL', 127;

foreach $args ( @ARGV ) {
    my @file_list = glob ( $args );
    foreach $fname ( @file_list ) {
        open(INPUT,'<', $fname) or die "$0: $fname: $!\n";
	while (<INPUT>) {
	    $line++;
	    chomp;
	    while (/^(.*)\\$/) {
		$_ = $1;
		$_ .= <INPUT>;
		chomp;
		$line++;
	    }
	    s/^\s*(([^\'\"\;]|\"[^\"]*\"|\'[^\']*\')*?)\s*(\;.*)?$/$1/;
	    s/\s+/ /g;
	    next if ($_ eq '');

	    if (m/^OUT:\s*(\S.*)$/) {
		undef $pkg;
		my @out_alias = split(/\s+/, $1);
		if (defined($name)) {
		    printf OUT "        /* %4d */ EOL\n", $index++;
		    print OUT "};\n#endif\n";
		    undef $name;
		}
		$index = 0;
		print OUT "\n";
		my $pfx = '#if';
		foreach my $al (@out_alias) {
		    print OUT $pfx, " defined(OF_\U${al}\E)";
		    $pfx = ' ||';
		}
		$name = $out_alias[0] . '_stdmac';
		print OUT "\nconst unsigned char ${name}[] = {\n";
		print OUT "    /* From $fname */\n";
		$lastname = $fname;
	    } elsif (m/^STD:\s*(\S+)$/) {
		undef $pkg;
		my $std = $1;
		if (defined($name)) {
		    printf OUT "        /* %4d */ EOL\n", $index++;
		    print OUT "};\n#endif\n";
		    undef $name;
		}
		$index = 0;
		print OUT "\n#if 1";
		$name = 'nasm_stdmac_' . $std;
		print OUT "\nconst unsigned char ${name}[] = {\n";
		print OUT "    /* From $fname */\n";
		$lastname = $fname;
	    } elsif (m/^USE:\s*(\S+)$/) {
		$pkg = $1;
		if (defined($pkg_number{$pkg})) {
		    die "$0: $fname: duplicate package: $pkg\n";
		}
		if (defined($name)) {
		    printf OUT "        /* %4d */ EOL\n", $index++;
		    print OUT "};\n#endif\n";
		    undef $name;
		}
		$index = 0;
		print OUT "\n#if 1";
		$name = 'nasm_usemac_' . $pkg;
		print OUT "\nstatic const unsigned char ${name}[] = {\n";
		print OUT "    /* From $fname */\n";
		$lastname = $fname;
		push(@pkg_list, $pkg);
		$pkg_number{$pkg} = $npkg++;
		$z = pack("C", $pnum{'%define'})."__?USE_\U$pkg\E?__";
		printf OUT "        /* %4d */ %sEOL,\n", $index, charcify($z);
		$z = pack("C", $pnum{'%defalias'})."__USE_\U$pkg\E__ __?USE\U$pkg\E?__";
		printf OUT "        /* %4d */ %sEOL,\n", $index, charcify($z);
		$index += length($z)+1;
	    } else {
		my($s1, $s2, $pd, $ws);

		if (!defined($name)) {
		    die "$0: $fname: macro declarations outside a known block\n";
		}

		$s1 = $_;
		$s2 = '';
		while ($s1 =~ /(\%[a-zA-Z_][a-zA-Z0-9_]*)((\s+)(.*)|)$/) {
		    $s2 .= "$'";
		    $pd = $1;
		    $ws = $3;
		    $s1 = $4;
		    if (defined($pnum{$pd})) {
			$s2 .= pack("C", $pnum{$pd});
		    } else {
			$s2 .= $pd.$ws;
		    }
		}
		$s2 .= $s1;
		if (length($s2) > 0) {
		    if ($lastname ne $fname) {
			print OUT "\n    /* From $fname */\n";
			$lastname = $fname;
		    }
		    printf OUT "        /* %4d */ %sEOL,\n",
			$index, charcify($s2);
		    $index += length($s2)+1;
		}
	    }
	}
        close(INPUT);
    }
}

if (defined($name)) {
    printf OUT "        /* %4d */ EOL\n", $index++;
    print OUT "};\n#endif\n";
    undef $name;
}

my @hashinfo = gen_perfect_hash(\%pkg_number);
if (!@hashinfo) {
    die "$0: no hash found\n";
}
# Paranoia...
verify_hash_table(\%pkg_number, \@hashinfo);
my ($n, $sv, $g) = @hashinfo;
die if ($n & ($n-1));

printf OUT "const int use_package_count = %d;\n\n", $npkg;

print OUT "const struct use_package *nasm_find_use_package(const char *name)\n";
print OUT "{\n";
print OUT "    static const struct use_package packages[$npkg] = {\n";
my $ix = 0;
foreach $pkg (@pkg_list) {
    printf OUT "        { \"%s\", nasm_usemac_%s, %d },\n",
	$pkg, $pkg, $ix++;
}
print OUT "    };\n";

# Put a large value in unused slots.  This makes it extremely unlikely
# that any combination that involves unused slot will pass the range test.
# This speeds up rejection of unrecognized tokens, i.e. identifiers.
print OUT "#define UNUSED_HASH_ENTRY (65535/3)\n";

print OUT "    static const int16_t hash1[$n] = {\n";
for ($i = 0; $i < $n; $i++) {
    my $h = ${$g}[$i*2+0];
    print OUT "        ", defined($h) ? $h : 'UNUSED_HASH_ENTRY', ",\n";
}
print OUT "    };\n";

print OUT "    static const int16_t hash2[$n] = {\n";
for ($i = 0; $i < $n; $i++) {
    my $h = ${$g}[$i*2+1];
    print OUT "        ", defined($h) ? $h : 'UNUSED_HASH_ENTRY', ",\n";
}
print OUT "    };\n";

print OUT  "    uint32_t k1, k2;\n";
print OUT  "    uint64_t crc;\n";
# For correct overflow behavior, "ix" should be unsigned of the same
# width as the hash arrays.
print OUT  "    uint16_t ix;\n";
print OUT  "\n";

printf OUT "    crc = crc64i(UINT64_C(0x%08x%08x), name);\n",
    $$sv[0], $$sv[1];
print  OUT "    k1 = (uint32_t)crc;\n";
print  OUT "    k2 = (uint32_t)(crc >> 32);\n";
print  OUT "\n";
printf OUT "    ix = hash1[k1 & 0x%x] + hash2[k2 & 0x%x];\n", $n-1, $n-1;
printf OUT "    if (ix >= %d)\n", scalar(@pkg_list);
print OUT  "        return NULL;\n";
print OUT  "\n";
print OUT  "    if (nasm_stricmp(packages[ix].package, name))\n";
print OUT  "        return NULL;\n";
print OUT  "\n";
print OUT  "    return &packages[ix];\n";
print OUT  "}\n";

close(OUT);
