#!/usr/bin/perl -w
# $Id: report-mapsyms,v 1.2 2015/01/17 15:47:27 tom Exp $
# -----------------------------------------------------------------------------
# Copyright 2015 by Thomas E. Dickey
#
#                         All Rights Reserved
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the
# sale, use or other dealings in this Software without prior written
# authorization.
# -----------------------------------------------------------------------------
# Read one or more ".map" files, generate a report showing
# a) scope (global or local)
# b) symbol
# c) node-label
#
# Optionally show only the number of symbols in each scope.

use strict;

$| = 1;

use Getopt::Std;

our ( $opt_d, $opt_s, $opt_v );

sub open_file($) {
    my $path = shift;
    printf STDERR ".. open %s\n", $path if ($opt_v);
    open FP, "$path" || die "cannot open $path";
}

# parse the given ".map" file
sub read_map_file($) {
    my $path = shift;
    my %result;

    if ( -f $path ) {
        &open_file("$path");
        my (@data) = <FP>;
        close FP;
        my %node;
        my $label = "";
        my $scope = "";
        my $ident = "";

        for my $n ( 0 .. $#data ) {
            chomp( $data[$n] );
            my $line = $data[$n];
            $line =~ s/^\s+//;
            $line =~ s/\s*([:;{}]+)\s*/$1/g;
            $line =~ s/\s+/ /g;
            while ( $line ne "" ) {
                last if ( $line =~ /^#.*/ );
                if ( $line =~ /^[[:alnum:]_.]+{/ ) {
                    $label = $line;
                    $label =~ s/{.*//;
                    $line  =~ s/^[^{]*{//;
                    printf "LABEL %d:%s->%s\n", $n, $data[$n], $label
                      if ($opt_d);
                }
                elsif ( $line =~ /^(global|local):/ ) {
                    $scope = $line;
                    $scope =~ s/:.*//;
                    $line  =~ s/^[^:]*://;
                    printf "SCOPE %d:%s->%s\n", $n, $data[$n], $scope
                      if ($opt_d);
                }
                elsif ( $line =~ /^[[:alnum:]_*]+;/ ) {
                    $ident = $line;
                    $ident =~ s/;.*//;
                    $line  =~ s/^[^;]*;//;
                    if ( $ident ne "_*" ) {
                        my %obj;
                        $obj{LABEL}   = $label;
                        $obj{SCOPE}   = $scope;
                        $obj{IDENT}   = $ident;
                        $node{$ident} = \%obj;
                        printf "IDENT %d:%s->%s\n", $n, $data[$n], $ident
                          if ($opt_d);
                    }
                }
                elsif ( $line =~ /}([[:alnum:]_.]+)?;/ ) {
                    $line =~ s/^[^;]*;//;
                    for my $key ( keys %node ) {
                        my %obj   = %{ $node{$key} };
                        my $ident = $obj{IDENT};

                        $result{$ident} = \%obj;
                    }
                    delete $node{$_} for keys %node;
                }
                else {
                    printf "?? %d:%s\n", $n, $line;
                    last;
                }
                $line =~ s/^\s+//;
            }
        }
    }
    return %result;
}

sub report_maps($) {
    my %symbols = %{ $_[0] };
    for my $key ( sort keys %symbols ) {
        my %obj = %{ $symbols{$key} };
        printf "%s\t%s\t%s\n", $obj{IDENT}, $obj{SCOPE}, $obj{LABEL};
    }
}

sub summary_maps($) {
    my %symbols = %{ $_[0] };
    my %scopes;
    my $total = 0;
    for my $key ( sort keys %symbols ) {
        my %obj = %{ $symbols{$key} };
        $scopes{ $obj{SCOPE} } += 1;
        ++$total;
    }
    printf "%d symbols (", $total;
    my $first = 1;
    for my $key ( sort keys %scopes ) {
        if ($first) {
            $first = 0;
        }
        else {
            printf ", ";
        }
        printf "%d %s", $scopes{$key}, $key;
    }
    printf ")\n";
}

sub merge_maps($$) {
    my $input       = $_[0];
    my %old_symbols = %{ $_[1] };
    my %new_symbols = &read_map_file($input);
    for my $key ( keys %old_symbols ) {
        my %obj   = %{ $old_symbols{$key} };
        my $ident = $obj{IDENT};
        $new_symbols{$ident} = \%obj;
    }

    # &report_maps(\%new_symbols);
    return %new_symbols;
}

sub main::HELP_MESSAGE() {
    printf STDERR <<EOF
Usage: $0 [options] mapfile1 [mapfile2 ...]

Options:

-d         debug, shows parsed values
-s         summary
-v         verbose, shows files opened
EOF
      ;
    exit;
}

&getopts('dsv') || &main::HELP_MESSAGE;
if ( $#ARGV >= 0 ) {
    my %all_symbols;
    while ( $#ARGV >= 0 ) {
        %all_symbols = &merge_maps( shift @ARGV, \%all_symbols );
    }
    if ($opt_s) {
        &summary_maps( \%all_symbols );
    }
    else {
        &report_maps( \%all_symbols );
    }
}
else {
    printf STDERR "expected at least one input map-file\n";
    &main::HELP_MESSAGE;
}

1;
