#!/usr/bin/perl

# showasm 1.0 shows assembler output intertwined with the corresponding 
#  C/C++ source lines

# Copyright (C) 2000 by Kai Schtz <schuetz.kai@gmx.de>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


# $Id: showasm,v 1.10 2000/01/21 23:14:35 kais Exp $

use File::Basename;
use Getopt::Long;

$linenumbermode = 0;
$colormode = 1;
$numoccmode = 1;

while($ARGV[0] =~ /^-/) {
  if ($ARGV[0] eq '--help') {
    #  print "Usage:  showasm [options] < cfile.s\n";
    print "Usage:  showasm [options] cfile.s\n";
    print "        showasm [options] cfile.{c,C,cc,cpp,...} [compileroptions]".
      "\n";
    print "Show assembler source mixed with the corresponding C/C++ source " .
      "lines.\n";
    print "When piping through 'less' append '-r' to less because of colors.".
      "\n";
    print "\n";
#    print "  -C, --no-color     Don't user ANSI/VT100 sequences to color "\
#    "output.\n";
    print "  -N, --line-numbers Display line numbers for c/c++ sourcefile " .
      "lines.\n";
    print "  -O, --num-occ      number multiple occurences of source lines.\n";
    print "      --help         display this help and exit\n";
    print "      --version      output version information and exit\n";
    exit;
  }

  if ($ARGV[0] eq '--version') {
    print "showasm 1.0\n";
    exit;
  }

#  if ($ARGV[0] =~ /^(-C|--no-color)$/) {
#    $colormode = 0;
#  }

  if ($ARGV[0] =~ /^(-N|--line-numbers)$/) {
    $linenumbermode = 1;
  }

  if ($ARGV[0] =~ /^(-O|--num-occ)$/) {
    $numoccmode = 1;
  }

  shift;
}

# open files, call the compiler with appropriate arguments if c/c++
# source given:

if (@ARGV) {
  if ($ARGV[0] =~ /\.(c{1,2}|cpp|c\+\+)$/i) { # .c, .cc, .C, .CC, .cpp e.t.c.
    $cfilename = $ARGV[0];  
    $asmfilename = basename($cfilename);
    # substitute extension with .s
    $asmfilename =~ s/\.(c{1,2}|cpp|c\+\+)$/.s/i;
    $asmfilename = "/tmp/showasm-$$-" . $asmfilename;
    print STDERR "Compiling...\n";
    if ($ARGV[0] =~ /\.C$/i) {
      shift;
      system "gcc -gstabs -S @ARGV $cfilename -o $asmfilename";
    } else {
      shift;
      system "g++ -gstabs -S @ARGV $cfilename -o $asmfilename";
    }
    @ARGV = () ;
    open(asmfile, $asmfilename)
      or die "showasm: can't open $asmfilename: $!\n";
    unlink $asmfilename;	# this is a temporary file, so delete it
				# immediately (this method prevents temp files
				# lying around when aborting with a signal)

  }else{			# asm-filename given
    open(asmfile, $ARGV[0]) or die "showasm: can't open $ARGV[0]: $!\n";
    shift;
  }
} else {
  die "missing file argument. Use --help for usage info.\n";
				# perl hackers: how to read from stdin instead?
#  asmfile = STDIN;
#  shift
}

if (@ARGV != 0) {
  print STDERR "warning: too many parameters. ignoring @ARGV !\n";
}

# FIXME: For now colors can only be switched off by uncommenting the
# three lines below

if ($colormode) {
  use Term::ANSIColor;
}else{
#  sub colored {			# perl hackers: this doen's work.
#  return $_[0];			# How can I achieve the effect ?
#  }
}

$ignorelabel="";
$srcfilename="";

while (<asmfile>) {
  if (/\.stabs/) {
    if (!/\.stabs\s+\"(.*)\",\s*(\w+),\s*(\d+),\s*(\d+),\s*(\S+)/) {
      die "can parse this .stabs line (aborting):\n$_";
    }else{
      $string = $1;
      $type = $2;
      $other = $3;
      $desc = $4;
      $value = $5;
      if (($type == 100)||($type == 132)) {	# new C source-file
	$srcfilename = $string;
	if ($srcfilename =~ /.+[^\/]$/) { # does not end with a '/'
	  open(srcfile, $srcfilename)
	    or die "showasm: can't open source file $srcfilename: $!\n";
	  print "#" x 80, "\n";
	  print "$srcfilename\n";
	  print "#" x 80, "\n";
				# read the whole sourcefile into "csource"
	  @csource = <srcfile>;
	  @occ = ();
	}
      }
    }
  } elsif (/\.stabn/) {
    if (!/\.stabn\s+(\d+),\s*(\d+),\s*(\d+),\s*(.+)$/) {
      die "can parse this .stabn line (aborting):\n$_";
    }else{
      $type = $1;
      $other = $2;
      $desc = $3;
      $value = $4;
      if ($type == 68) {	# C source-line reference
	$linenum = $desc;
	while($linenum <= @csource) { # output up to end of expression
	  $cline = $csource[$linenum - 1];
	  if ($cline !~ /^\s*$/) { # suppress empty lines
	    $line = "";
	    if ($linenumbermode) {
	      $line = $linenum . ":" . $line;
	    }
	    if ($numoccmode) {
	      $occ[$linenum]++;
	      $line = $line . $occ[$linenum] . ":";
	    }
	    $line .= $cline;
	    print colored($line, 'bold');
	    # to decide if this is the end of an expression first delete
	    # everything that would irritate the decision:
	    $line =~ s/\/\*.+\*\///g; # delete /* */ comments
	    $line =~ s/\/\/.+$//g; # delete // comments
	    $line =~ s/\".+\"//g;	# delete "" strings
	    $line =~ s/'.+'//g;	# delete '' character constants
	    if ($line =~ /[;{]/) { # ';' or '{' end an expression
	      last;
	    }
	  }
	  $linenum++;
	}
      }
    }
  } elsif (/\.stabd/) {	# ignore these
  } elsif (/\.stabx/) {	# ignore these
  } else {
    if (/^\./) {		# label
      if (/^\.LM\d+:$/) {	# don't print src-linenumber labels...
#	push @buffer, colored($_, 'white on_blue');
      } else {			# ...but print normal labels
	print colored($_, 'blue');
      }
    } elsif (/^\s+\./) {	# assembler directive
      print colored($_, 'red');      
    } else {			# normal assembler source line
      print $_;
    }
  }
}
