#!/bin/sh -f
# Generate a source code listing for C or C++ code with assembler code. The
# listing is always written to stdout.
# Author: Igor Metz <metz@iam.unibe.ch>

# Revision 1.4  94/08/26  13:58:27  coxs <coxs@dg-rtp.dg.com>
# lister now guesses how to should be configured. Added elf and coff support.
#
# Revision 1.3  89/12/18  13:58:27  metz
# lister must now be configured before it can be used. This is done in the
# /bin/sh part of the code.
# 
# 
# Revision 1.2  89/08/16  17:35:02  metz
# Support for SPARC added.
# 
# Revision 1.1  89/08/16  16:49:22  metz
# Initial revision
# 

# Requires: gawk (may be it works also with nawk)

# usage:  lister filename [compiler-options]

# Method:
# compile the source with -g option to assembler code, then merge the
# generated assembler code with the source code. Compiler options
# can be supplied on the command line (for example -O)

# To install lister, assign one of the supported values to the variable MYSYS:
# mc68020  for Motorola 68020 (Sun-3, ..)
# mc68030  for Motorola 68030 (Sun-3, ..)
# sparc    for SPARC (SUN-4, ..)
# i386     for i386 (Sun i386, ...)
# i386-gnu-linux for i386 (GNU/Linux, ...)

# Guess what kind of objects we are creating and thus what type of assembler
# symbols to look for

ex /tmp/$$.c <<END >/dev/null
a
main (){}
.
w
q
END
WD=`pwd`
cd /tmp
gcc -c $$.c
case "`file $$.o`" in 
*ELF*) MYSYS=elf ;;
*COFF*|*BCS*) MYSYS=coff ;;
*mc68k*|*M68000*) MYSYS=mc68030 ;;
*SPARC*) MYSYS=sparc ;;
*386*) MYSYS=i386 ;;
esac
rm $$.c $$.o
cd $WD

# uncomment the line you need if the above guesses incorrectly:
# MYSYS=mc68020
# MYSYS=mc68030
# MYSYS=sparc
# MYSYS=i386
# MYSYS=i386-gnu-linux
# MYSYS=`mach`  # this will work on Suns with SunOS > 4.0.0
# MYSYS=elf
# MYSYS=coff

WHOAMI=$0
if [ $# -gt 0 ] ; then
FILENAME=$1
shift
fi

exec gawk -v whoami=$WHOAMI -vsys=$MYSYS -voptions="$*" '
# commandline arguments:
#  ARGV[0] = "gawk"
#  ARGV[1] = processid
#  ARGV[2] = filename
BEGIN {
  if (ARGC != 3) {
    usage()
    exit 1
  }

  # Declaration of global variables
  c_filename = ""
  asm_filename = ""
  cmdline = ""
  asm_code = ""
  c_code = ""
  c_lineno = 0
  oldlineno = 0
  newlineno = 0
  ignore_stabd = 0
  num_of_fields = 0

  # check processor architecture and set sourcecode line_hint accordingly
  if (sys == "sparc" || sys == "i386") {
    line_hint = "^[ \t]*\.stabn.*"
    line_field = 3;
    line_delimiter = ",";
    line_offset = 0;
  }
  else if (sys == "mc68020" || sys == "mc68030" || sys == "i386-gnu-linux") {
    line_hint = "^[ \t]*\.stabd.*"
    line_field = 3;
    line_delimiter = ",";
    line_offset = 0;
  }
  else if (sys == "elf") {
    line_hint = "section.*\.line"
    line_field = 3;
    line_delimiter = "\t";
    line_offset = 0;
  }
  else if (sys == "coff") {
    line_hint = "^[ \t]*ln"
    line_field = 3;
    line_delimiter = "\t";
  }
  else {
    error("Processor type " sys " is not supported yet, sorry")
  }

  parse_cmdline()

  printf("compiling %s to asm code\n", c_filename ) > "/dev/stderr"

  if (system(cmdline) != 0 ) {
    error("Compilation of " c_filename " failed")
  }

  printf("generating listing\n") > "/dev/stderr"


  while ( getline asm_code < asm_filename > 0 ) {
    if ( (ignore_stabd==0) && (asm_code ~ line_hint)) {
      while ( sys == "elf" && (asm_code !~ "word" && asm_code !~ "byte") && 
        getline asm_code < asm_filename > 0);
      # source line hint found. Split the line into fields separated by commas.
      # num_of_fields is 4 for sparc, 3 for m68k
      num_of_fields = split(asm_code, fields, line_delimiter)
      newlineno = fields[line_field] + line_offset;

      if (newlineno > oldlineno) {
        while ( newlineno > c_lineno && getline c_code < c_filename > 0) {
	  c_lineno++
	  printf("%4d %s\n", c_lineno, c_code)
	}
	oldlineno = newlineno
      }
    }
    else if ( asm_code ~ ".*Ltext[ \t]*$" ) {
      # filename hint found
      if ( match(asm_code, c_filename)) {
        ignore_stabd = 0
      }
      else {
        ignore_stabd = 1
      }
    }
    else if ( sys == "elf" && asm_code ~ "section.*\.debug" ) {
      while ( asm_code !~ "^[ \t]*[.]*previous" &&
	      asm_code !~ "\.popsection" && 
              getline asm_code < asm_filename > 0 );
      if ( ! (getline asm_code < asm_filename > 0)) break;
    }
    else if ( sys == "coff" && asm_code ~ "^[ \t]*sdef" ) {
      if ( asm_code ~ "\.bf" ) {
         while ( asm_code !~ "^[ \t]*line" && 
                 getline asm_code < asm_filename > 0 ) {
           num_of_fields = split(asm_code, fields, "\t")
           line_offset = fields[line_field] - 1;
         }
      }
      while ( asm_code !~ "^[ \t]*endef" && 
              getline asm_code < asm_filename > 0 ) {
      }
      if ( ! (getline asm_code < asm_filename > 0)) break;
    }
    printf("\t\t\t%s\n", asm_code)
  }

  # general cleanup
  system("/bin/rm " asm_filename)
}

function usage() {
    printf("usage: %s filename compiler-options\n", whoami) > "/dev/stderr"
}

function error(s) {
    printf("error: %s\n", s) > "/dev/stderr"
    exit 1
}

function parse_cmdline(    i) {
  # construct filenames to use
  asm_filename = "/tmp/lister" ARGV[1] ".s"
  ARGV[1] = ""
  c_filename = ARGV[2]
  ARGV[2] = ""

  # construct commandline to use
  if ( match(c_filename, ".C") || match(c_filename, ".cc") ) {
    cmdline = "g++"
  }
  else if (match(c_filename, ".c") || match(c_filename, ".i")) {
    cmdline = "gcc"
  }
  else {
    error("unknown extension for file " c_filename)
  }

  cmdline = cmdline " -g -S -o " asm_filename

  # now we append the compiler options specified by the user
  cmdline = cmdline " " options

  # last but not least: the name of the file to compile
  cmdline = cmdline " " c_filename
}

' $$ $FILENAME