#!/Perl

# READ Makefile.am
my $debug_on3 = 0;
my $debug_on4 = 0;
my $verbose = 0;
my $am_file = '';
my $exit_status = 0;

# String constants.
my $IGNORE_PATTERN = "^##([^#].*)?\$";
my $WHITE_PATTERN = "^[ \t]*\$";
my $COMMENT_PATTERN = "^#";
my $IF_PATTERN = "^if[ \t]+\([A-Za-z][A-Za-z0-9_]*\)[ \t]*\(#.*\)?\$";
my $ELSE_PATTERN = "^else[ \t]*\(#.*\)?\$";
my $ENDIF_PATTERN = "^endif[ \t]*\(#.*\)?\$";
my $RULE_PATTERN = "^([\$a-zA-Z_.][-.a-zA-Z0-9_(){}/\$]*) *:([^=].*|)\$";
my $SUFFIX_RULE_PATTERN = "^\\.([a-zA-Z]+)\\.([a-zA-Z]+)\$";
my $MACRO_PATTERN = "^([A-Za-z][A-Za-z0-9_]*)[ \t]*([:+]?)=[ \t]*(.*)\$";
my $BOGUS_MACRO_PATTERN = "^([^ \t]*)[ \t]*([:+]?)=[ \t]*(.*)\$";

# Hash table of AM_CONDITIONAL variables seen in configure.
my %configure_cond = ();
# This holds the names which are targets.  These also appear in
# %contents.
my %targets = ();
# This holds the line numbers at which various elements of
# %contents are defined.
my %content_lines = ();
my @conditional_stack = ();
my %contents = ();
my %conditional = ();
my $output_vars = '';
my $output_trailer = '';
# This maps the source extension of a suffix rule to its
# corresponding output extension.
my %suffix_rules = ();
# Hash table of discovered configure substitutions.  Keys are names,
# values are `FILE:LINE' strings which are used by error message
# generation.
my %configure_vars = ();
my %am_vars = ();
my %def_type = ();
my @var_list = ();

my @include_stack = ();
my %content_seen = ();
my $relative_dir = '';

sub read_am_file {
    $am_file = shift;
    open( AM_FILE, $am_file )
    or mydie( "Can't open $am_file: $!\n" );

    prt( "$pack: reading $am_file\n" ) if $verbose;
	my @am_file_sources = ();
    my $saw_bk = 0;
    my $was_rule = 0;
    my $spacing = '';
    my $comment = '';
    my $last_var_name = '';
    my $blank = 0;
    my $cond_true = 2; # undetermined ...

    while (<AM_FILE>)
    {
        if (/$IGNORE_PATTERN/o)
        {
            # Merely delete comments beginning with two hashes.
        }
        elsif (/$WHITE_PATTERN/o)
        {
            # Stick a single white line before the incoming macro or rule.
            $spacing = "\n";
            $blank = 1;
        }
        elsif (/$COMMENT_PATTERN/o)
        {
            # Stick comments before the incoming macro or rule.  Make
            # sure a blank line preceeds first block of comments.
            $spacing = "\n" unless $blank;
            $blank = 1;
            $comment .= $spacing . $_;
            $spacing = '';
        }
        else
        {
            last;
        }
    }

    $output_vars .= $comment . "\n";
    $comment = '';
    $spacing = "\n";
    my $source_suffix_pattern = '';

    my $is_ok_macro = 0;
    while ($_)
    {
        if ($debug_on3) {
            my $aml = $_;
            chomp $aml;
            $aml =~ s/\r$//; # and remove CR, if present
            prt( "$. [$aml]\n" );
        }
        $_ .= "\n"
            unless substr ($_, -1, 1) eq "\n";

        if (/$IGNORE_PATTERN/o)
        {
            # Merely delete comments beginning with two hashes.
        }
        elsif (/$WHITE_PATTERN/o)
        {
            # Stick a single white line before the incoming macro or rule.
            $spacing = "\n";
            &am_line_error ($., "blank line following trailing backslash")
                if $saw_bk;
        }
        elsif (/$COMMENT_PATTERN/o)
        {
            # Stick comments before the incoming macro or rule.
            $comment .= $spacing . $_;
            $spacing = '';
            &am_line_error ($., "comment following trailing backslash")
                if $saw_bk;
        }
        elsif ($saw_bk)
        {
			# continuation of previous line(s) ...
            if ($was_rule)
            {
                $output_trailer .= join ('', @conditional_stack) . $_;
                $saw_bk = /\\$/;
            }
            else
            {
                $saw_bk = /\\$/;
                # Chop newline and backslash if this line is
                # continued.  ensure trailing whitespace exists.
                chop if $saw_bk;
                chop if $saw_bk;
				# decide if to be added to 'contents' ...
				my $add_it = 0; # assume NO
                if ( defined $contents{$last_var_name} ) {
                    if ( @conditional_stack ) {
                        if ( $conditional_stack[$#conditional_stack] =~ /_TRUE\@$/ ) {
                            # we are in the if TRUE state
                            if ($cond_true == 1) {
								$add_it = 1;
							}
						} else {
							# we are in a FALSE state
                            if ($cond_true != 1) {
								$add_it = 1;
							}
						}
					} else { 
						###prt( "No conditional stack for this macro! lv=[$last_var_name] ".
						###	"add_it\n" ) if ($debug_on3);
						$add_it = 1; # so add it
					}
				} else { # NO contents for this var
					prt( "NO contents for [$last_var_name]! ".
						"Technical this is an ERROR, but for now add_it...\n" ) if ($debug_on3);
					$add_it = 1;
				}

				if ($add_it) {
					$contents{$last_var_name} .= ' '
						unless $contents{$last_var_name} =~ /\s$/;
					$contents{$last_var_name} .= $_;
					if (@conditional_stack)
					{
						$conditional{$last_var_name} .= &quote_cond_val ($_);
					}
					prt( "End $last_var_name = [".
						trim_line($contents{$last_var_name})."]...\n" )
						if ( ! $saw_bk && $debug_on3);
					if ( !$saw_bk && ($last_var_name =~ /._SOURCES$/) ) {
						prt( "NOTE list of SOURCE files...\n" ) if ($debug_on4);
						prt( "[".trim_line($contents{$last_var_name})."]...\n" ) if ($debug_on4);
						push(@am_file_sources, split( / /, $contents{$last_var_name}));
					}
				} else {
					prt( "Warning: Discarding this [$_] ... CHECK ME!\n" ) if ($debug_on3);
				}
            }
        }
        elsif (/$IF_PATTERN/o)
        {
            if ( defined $configure_cond{$1} ) {
                ###if ( defined $cfg_defines{$1} )
                if ($configure_cond{$1} == 2) {
                    prt( "Found if $1, and condition is TRUE (see cfg_defines) ...\n" ) if ($debug_on3);
                    $cond_true = 1;
                } else {
                    prt( "Found if $1, but NOT in cfg_defines ...\n" ) if ($debug_on3);
                    $cond_true = 0;
                }
            } else {
                &am_line_error ($., "$1 does not appear in AM_CONDITIONAL");
            }
            push (@conditional_stack, "\@" . $1 . "_TRUE\@");
        }
        elsif (/$ELSE_PATTERN/o)
        {
            if (! @conditional_stack)
            {
                &am_line_error ($., "else without if");
            }
            elsif ($conditional_stack[$#conditional_stack] =~ /_FALSE\@$/)
            {
                &am_line_error ($., "else after else");
            }
            else
            {
                $conditional_stack[$#conditional_stack]
                    =~ s/_TRUE\@$/_FALSE\@/;
            }
        }
        elsif (/$ENDIF_PATTERN/o)
        {
            if (! @conditional_stack)
            {
                &am_line_error ($., ": endif without if");
            }
            else
            {
                pop @conditional_stack;
            }
            $cond_true = 2; # undetermined ...
        }
        elsif (/$RULE_PATTERN/o)
        {
            # Found a rule.
            $was_rule = 1;
            if (defined $contents{$1}
                && (@conditional_stack
                ? ! defined $conditional{$1}
                : defined $conditional{$1}))
            {
                &am_line_error ($1,
                        "$1 defined both conditionally and unconditionally");
            }
            # Value here doesn't matter; for targets we only note
            # existence.
            $contents{$1} = 1;
            $targets{$1} = 1;
            my $cond_string = join ('', @conditional_stack);
            if (@conditional_stack)
            {
                if ($conditional{$1})
                {
                    &check_ambiguous_conditional ($1, $cond_string);
                    $conditional{$1} .= ' ';
                }
                else
                {
                    $conditional{$1} = '';
                }
                $conditional{$1} .= $cond_string . ' 1';
            }
            $content_lines{$1} = $.;
            $output_trailer .= $comment . $spacing . $cond_string . $_;
            $comment = $spacing = '';
            $saw_bk = /\\$/;

            # Check the rule for being a suffix rule. If so, store in
            # a hash.

            my $source_suffix;
            my $object_suffix;

            if (($source_suffix, $object_suffix) = ($1 =~ $SUFFIX_RULE_PATTERN)) 
            {
              $suffix_rules{$source_suffix} = $object_suffix;
              prt( "Sources ending in .$source_suffix become .$object_suffix\n" ) if $verbose;
              $source_suffix_pattern = "(" . join('|', keys %suffix_rules) . ")";
            }

            # FIXME: make sure both suffixes are in SUFFIXES? Or set
            # SUFFIXES from suffix_rules?
        }
        elsif (($is_ok_macro = /$MACRO_PATTERN/o)
               || /$BOGUS_MACRO_PATTERN/o)
        {
            prt( "Found a macro definition. 1[$1] 2[$2] 3[$3] ...\n" ) if ($debug_on3);
            $was_rule = 0;
            $last_var_name = $1;
            if (defined $contents{$1}
            && (@conditional_stack
                ? ! defined $conditional{$1}
                : defined $conditional{$1}))
            {
                &am_line_error ($1,
                        "$1 defined both conditionally and unconditionally");
            }
            my $value;
            if ($3 ne '' && substr ($3, -1) eq "\\")
            {
                $value = substr ($3, 0, length ($3) - 1);
            }
            else
            {
                $value = $3;
            }
            my $type = $2;
            if ($type eq '+')
            {
                if (! defined $contents{$last_var_name}
                    && defined $configure_vars{$last_var_name})
                {
                    $contents{$last_var_name} = '@' . $last_var_name . '@';
                }
                $contents{$last_var_name} .= ' ' . $value;
            }
            else
            {
                if ( defined $contents{$last_var_name} ) {
                    if ( @conditional_stack ) {
                        if ( $conditional_stack[$#conditional_stack] =~ /_TRUE\@$/ ) {
                            # we are in the if TRUE state
                            if ($cond_true == 1) {
                                if ($debug_on3) {
                                    if ( $contents{$last_var_name} ne $value ) {
                                        prt( "NB: [$last_var_name] = [$contents{$last_var_name}]" .
                                            " replaced with [$value] (am_file=$am_file line=$.)".
											(/\\$/ ? "saw_bk" : "end") . "\n" );
                                    }
                                }
                                $contents{$last_var_name} = $value;
                                $content_lines{$last_var_name} = $.;
                            }
                        } else {
                            # we are in the else FALSE state
                            if ($cond_true != 1) {
                                if ($debug_on3) {
                                    if ( $contents{$last_var_name} ne $value ) {
                                        prt( "NB: [$last_var_name] = [$contents{$last_var_name}]" .
                                            " replaced with [$value] (am_file=$am_file line=$.)".
											(/\\$/ ? "saw_bk" : "end") . "\n" );
                                    }
                                }
                                $contents{$last_var_name} = $value;
                                $content_lines{$last_var_name} = $.;
                            }
                        }
                    }
                } else {
                    prt( "First setting [$last_var_name] = [$value] " .
                        "(am_file=$am_file line=$.) ".
						(/\\$/ ? "saw_bk" : "end") . "\n" ) if ($debug_on3);
                    $contents{$last_var_name} = $value;
                    # The first assignment to a macro sets the line
                    # number.  Ideally I suppose we would associate line
                    # numbers with random bits of text.
                    $content_lines{$last_var_name} = $.;
                }
            }
            my $cond_string = join ('', @conditional_stack);
            if (@conditional_stack)
            {
                my $found = 0;
                my $val;
                if ($conditional{$last_var_name})
                {
                    if ($type eq '+')
                    {
                        # If we're adding to the conditional, and it
                        # exists, then we might want to simply replace
                        # the old value with the new one.
                        my (@new_vals, @cond_vals);
                        @cond_vals = split (' ', $conditional{$last_var_name});
                        while (@cond_vals)
                        {
                            $vcond = shift (@cond_vals);
                            push (@new_vals, $vcond);
                            if (&conditional_same ($vcond, $cond_string))
                            {
                                $found = 1;
                                $val = (&unquote_cond_val (shift (@cond_vals))
                                    . ' ' . $value);
                                push (@new_vals, &quote_cond_val ($val));
                            }
                            else
                            {
                                push (@new_vals, shift (@cond_vals));
                            }
                        }
                        if ($found)
                        {
                            $conditional{$last_var_name}
                                = join (' ', @new_vals);
                        }
                    }

                    if (! $found)
                    {
                        &check_ambiguous_conditional ($last_var_name,
                                          $cond_string);
                        $conditional{$last_var_name} .= ' ';
                        $val = $value;
                    }
                }
                else
                {
                    $conditional{$last_var_name} = '';
                    $val = $contents{$last_var_name};
                }
                if (! $found)
                {
                    $conditional{$last_var_name} .= ($cond_string
                                     . ' '
                                     . &quote_cond_val ($val));
                }
            }
            # FIXME: this doesn't always work correctly; it will group
            # all comments for a given variable, no matter where
            # defined.
            $am_vars{$last_var_name} = $comment . $spacing;
            $def_type{$last_var_name} = ($type eq ':') ? ':' : '';
            push (@var_list, $last_var_name);
            $comment = $spacing = '';
            $saw_bk = /\\$/;

            # Error if bogus.
            &am_line_error ($., "bad macro name \`$last_var_name'")
                if ! $is_ok_macro;
        }
        elsif (/$INCLUDE_PATTERN/o)
        {
                my ($path) = $1;

                if ($path =~ s/^\$\(top_srcdir\)\///)
                {
                    push (@include_stack, "\$\(top_srcdir\)/$path");
                }
                else
                {
                    $path =~ s/\$\(srcdir\)\///;
                    push (@include_stack, "\$\(srcdir\)/$path");
                    $path = $relative_dir . "/" . $path;
                }
                &read_am_file ($path);
        }
        else
        {
            # This isn't an error; it is probably a continued rule.
            # In fact, this is what we assume.
            $was_rule = 1;
            $output_trailer .= ($comment . $spacing
                    . join ('', @conditional_stack) . $_);
            $comment = $spacing = '';
            $saw_bk = /\\$/;
        }

        $_ = <AM_FILE>;
    }

    close(AM_FILE);
    $output_trailer .= $comment;

    &am_error ("unterminated conditionals: " . join (' ', @conditional_stack))
    if (@conditional_stack);

	return @am_file_sources;
}

sub am_error {
    my $msg = shift;
    mydie( $msg."\n" );
}


sub am_line_error
{
    my ($symbol, @args) = @_;
    prt( "am_line_error: am_file=[$am_file] sym=[$symbol] arg0=[$args[0]] ...\n" ) if ($debug_on3);
    if ($symbol && "$symbol" ne '-1')
    {
    my ($file) = "${am_file}";

    if ($symbol =~ /^\d+$/)
    {
        # SYMBOL is a line number, so just add the colon.
        $file .= ':' . $symbol;
    }
    elsif (defined $content_lines{$symbol})
    {
        # SYMBOL is a variable defined in Makefile.am, so add the
        # line number we saved from there.
        $file .= ':' . $content_lines{$symbol};
    }
    elsif (defined $configure_vars{$symbol})
    {
        # SYMBOL is a variable defined in configure.ac, so add the
        # appropriate line number.
        $file = $configure_vars{$symbol};
    }
    else
    {
        # Couldn't find the line number.
    }
    warn $file, ": ", join (' ', @args), "\n";
    $exit_status = 1;
    }
    else
    {
    &am_error (@args);
    }
}

# See if a conditional is true.  Both arguments are conditional
# strings.  This returns true if the first conditional is true when
# the second conditional is true.
sub conditional_true_when
{
    my ($cond, $when) = @_;

    # Check the easy case first.
    if ($cond eq $when)
    {
    return 1;
    }

    # Check each component of $cond, which looks @COND1@@COND2@.
    foreach my $comp (split ('@', $cond))
    {
    # The way we split will give null strings between each
    # condition.
    next if ! $comp;

    if (index ($when, '@' . $comp . '@') == -1)
    {
        return 0;
    }
    }

    return 1;
}

# Check for an ambiguous conditional.  This is called when a variable
# or target is being defined conditionally.  If we already know about
# a definition that is true under the same conditions, then we have an
# ambiguity.
sub check_ambiguous_conditional
{
    my ($var_name, $cond) = @_;
    my (@cond_vals) = split (' ', $conditional{$var_name});
    while (@cond_vals)
    {
    my ($vcond) = shift (@cond_vals);
    shift (@cond_vals);
    if (&conditional_true_when ($vcond, $cond)
        || &conditional_true_when ($cond, $vcond))
    {
        prt( "$var_name multiply defined in condition\n" );
    }
    }
}

# Quote a value in order to put it in $conditional.  We need to quote
# spaces, and we need to handle null strings, so that we can later
# retrieve values by splitting on space.
sub quote_cond_val
{
    my ($val) = @_;
    $val =~ s/ /\001/g;
    $val =~ s/\t/\003/g;
    $val = "\002" if $val eq '';
    return $val;
}

# this function seems MISSING - maybe never used!
sub unquote_cond_val {
    my ($val) = @_;
    $val =~ s/\001/ /g;
    $val =~ s/\003/\t/g;
    $val = '' if $val eq "\002";
    return $val;
}

sub initialize_per_input {
    # These two variables are used when generating each Makefile.in.
    # They hold the Makefile.in until it is ready to be printed.
    $output_vars = '';
    $output_trailer = '';

    # This holds the contents of a Makefile.am, as parsed by
    # read_am_file.
    %contents = ();

    # This holds the names which are targets.  These also appear in
    # %contents.
    %targets = ();

    # For a variable or target which is defined conditionally, this
    # holds an array of the conditional values.  The array is composed
    # of pairs of condition strings (the variables which configure
    # will substitute) and values (the value of a target is
    # meaningless).  For an unconditional variable, this is empty.
    %conditional = ();

    # This holds the line numbers at which various elements of
    # %contents are defined.
    %content_lines = ();

    # This holds a 1 if a particular variable was examined.
    %content_seen = ();

    # This is the conditional stack.
    @conditional_stack = ();

    # This holds the set of included files.
    @include_stack = ();

    # This holds the "relative directory" of the current Makefile.in.
    # Eg for src/Makefile.in, this is "src".
    $relative_dir = '';

    # This maps the source extension of a suffix rule to its
    # corresponding output extension.
    %suffix_rules = ();

}

sub trim_line {
    my ($l) = shift;
    chomp $l;
    $l =~ s/\r$//; # and remove CR, if present
    $l =~ s/\t/ /g;
    $l =~ s/\s\s/ /g while ($l =~ /\s\s/);
    #$l = substr($l,1) while ($l =~ /^\s/);
    #$l = substr($l,0,length($l)-1) while (($l =~ /\s$/)&&(length($l)));
    for ($l) {
        s/^\s+//;
        s/\s+$//;
    }
    return $l;
}


1;

# eof - amfile01.pl

