#!/usr/bin/perl -w
use strict;

# read embed.fnc and regen/opcodes, needed by regen/embed.pl and makedef.pl

require 5.004;	# keep this compatible, an old perl is all we may have before
                # we build the new one

# Records the current pre-processor state:
my @state;
# Nested structure to group functions by the pre-processor conditions that
# control when they are compiled:
my %groups;

sub current_group {
    my $group = \%groups;
    # Nested #if blocks are effectively &&ed together
    # For embed.fnc, ordering within the && isn't relevant, so we can
    # sort them to try to group more functions together.
    foreach (sort @state) {
	$group->{$_} ||= {};
	$group = $group->{$_};
    }
    return $group->{''} ||= [];
}

sub add_level {
    my ($level, $indent, $wanted) = @_;
    my $funcs = $level->{''};
    my @entries;
    if ($funcs) {
	if (!defined $wanted) {
	    @entries = @$funcs;
	} else {
	    foreach (@$funcs) {
		if ($_->[0] =~ /A/) {
		    push @entries, $_ if $wanted eq 'A';
		} elsif ($_->[0] =~ /E/) {
		    push @entries, $_ if $wanted eq 'E';
		} else {
		    push @entries, $_ if $wanted eq '';
		}
	    }
	}
	@entries = sort {$a->[2] cmp $b->[2]} @entries;
    }
    foreach (sort grep {length $_} keys %$level) {
	my @conditional = add_level($level->{$_}, $indent . '  ', $wanted);
	push @entries,
	    ["#${indent}if $_"], @conditional, ["#${indent}endif"]
		if @conditional;
    }
    return @entries;
}

sub setup_embed {
    my $prefix = shift || '';
    open IN, '<', $prefix . 'embed.fnc' or die $!;

    my @embed;
    my %seen;
    my $macro_depth = 0;

    while (<IN>) {
	chomp;
	next if /^:/;
	next if /^$/;
	while (s|\\$||) {
	    $_ .= <IN>;
	    chomp;
	}
	s/\s+$//;
	my @args;
	if (/^\s*(#|$)/) {
	    @args = $_;
	}
	else {
	    @args = split /\s*\|\s*/, $_;
	}
	if (@args == 1) {
            if ($args[0] !~ /^#\s*(?:if|ifdef|ifndef|else|endif)/) {
                die "Illegal line $. '$args[0]' in embed.fnc";
            }
            $macro_depth++ if $args[0] =~/^#\s*if(n?def)?\b/;
            $macro_depth-- if $args[0] =~/^#\s*endif\b/;
            die "More #endif than #if in embed.fnc:$." if $macro_depth < 0;
	}
        else  {
	    die "Illegal line (less than 3 fields) in embed.fnc:$.: $_"
                unless @args >= 3;
            my $name = $args[2];
            # only check for duplicates outside of #if's - otherwise
            # they may be alternate definitions of the same function
            if ($macro_depth == 0) {
                die "Duplicate function name: '$name' in embed.fnc:$."
                    if exists $seen{$name};
            }
            $seen{$name} = 1;
        }

	push @embed, \@args;
    }
    die "More #if than #endif by the end of embed.fnc" if $macro_depth != 0;

    close IN or die "Problem reading embed.fnc: $!";

    open IN, '<', $prefix . 'regen/opcodes' or die $!;
    {
	my %syms;

	while (<IN>) {
	    chomp;
	    next unless $_;
	    next if /^#/;
	    my $check = (split /\t+/, $_)[2];
	    next if $syms{$check}++;

	    # These are all indirectly referenced by globals.c.
	    push @embed, ['pR', 'OP *', $check, 'NN OP *o'];
	}
    }
    close IN or die "Problem reading regen/opcodes: $!";

    # Cluster entries in embed.fnc that have the same #ifdef guards.
    # Also, split out at the top level the three classes of functions.
    # Output structure is actually the same as input structure - an
    # (ordered) list of array references, where the elements in the
    # reference determine what it is - a reference to a 1-element array is a
    # pre-processor directive, a reference to 2+ element array is a function.

    my $current = current_group();

    foreach (@embed) {
	if (@$_ > 1) {
	    push @$current, $_;
	    next;
	}
	$_->[0] =~ s/^#\s+/#/;
	$_->[0] =~ /^\S*/;
	$_->[0] =~ s/^#ifdef\s+(\S+)/#if defined($1)/;
	$_->[0] =~ s/^#ifndef\s+(\S+)/#if !defined($1)/;
	if ($_->[0] =~ /^#if\s*(.*)/) {
	    push @state, $1;
	} elsif ($_->[0] =~ /^#else\s*$/) {
	    die "Unmatched #else in embed.fnc" unless @state;
	    $state[-1] = "!($state[-1])";
	} elsif ($_->[0] =~ m!^#endif\s*(?:/\*.*\*/)?$!) {
	    die "Unmatched #endif in embed.fnc" unless @state;
	    pop @state;
	} else {
	    die "Unhandled pre-processor directive '$_->[0]' in embed.fnc";
	}
	$current = current_group();
    }

    return ([add_level(\%groups, '')],
	    [add_level(\%groups, '', '')],    # core
	    [add_level(\%groups, '', 'E')],   # ext
	    [add_level(\%groups, '', 'A')]);  # api
}

1;
