/* ".LST" file output for gplink
   Copyright (C) 2004, 2005
   Craig Franklin

This file is part of gputils.

gputils 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, or (at your option)
any later version.

gputils 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 gputils; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "stdhdr.h"

#include "libgputils.h"
#include "gplink.h"
#include "cod.h"

#ifdef STDC_HEADERS
#include <stdarg.h>
#endif

static gp_boolean list_enabled;
static gp_section_type *line_section;

static void
open_src(char *name, gp_symbol_type *symbol)
{
  struct list_context *new = malloc(sizeof(*new));

  assert(name != NULL);

  new->f = fopen(name, "rt");
  if(new->f) {
    new->name = strdup(name);
    new->missing_source = false;
  } else {
    new->missing_source = true;
  }

  new->symbol = symbol;
  new->line_number = 1;
  new->prev = state.lst.src;

  state.lst.src = new;

}

static void
close_src(void)
{
  struct list_context *old;
  
  assert(state.lst.src != NULL);
    
  old = state.lst.src;
  state.lst.src = state.lst.src->prev;
  free(old);

}

static void 
lst_line(const char *format, ...)
{
  va_list args;
  char buffer[BUFSIZ]; 

  if (state.lst.f == NULL)
    return;

  va_start(args, format);
  vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);

  fprintf(state.lst.f, "%s\n", buffer);

  return;
}

gp_linenum_type *
find_line_number(gp_symbol_type *symbol, int line_number)
{
  gp_section_type *section;
  gp_linenum_type *line = NULL;

  section = state.object->sections;

  /* FIXME: This too slow. */
  while (section != NULL) {
    line = section->line_numbers;
    while (line != NULL) {
      if ((line->symbol == symbol) && 
          (line->line_number == line_number)) {
        if (section != line_section) {
          /* switching sections, so update was_org with the new section
             address */
          state.lst.was_org = section->address >> state.byte_addr;
          line_section = section;
        }  
        return line;    
      }
      line = line->next;
    }
    section = section->next;
  }

  return NULL;
}

static void
write_src(int last_line)
{
  #define LINESIZ 520
  char linebuf[LINESIZ];
  char dasmbuf[LINESIZ];
  char *pc;
  gp_linenum_type *line = NULL;
  gp_boolean first_time;
  int org = 0;
  int data;
  int num_words;

  /* if the source file wasn't found, can't write it to the list file */
  if (state.lst.src->missing_source)
    return;

  while (1) {
    
    /* when last_line is 0 print all lines, else print to last_line */
    if ((last_line) && (state.lst.src->line_number > last_line))
      break;

    if (fgets(linebuf, LINESIZ, state.lst.src->f) == NULL)
      break;

    state.lst.was_org = org;

    if (list_enabled) {

      /* Eat the trailing newline */
      pc = strrchr(linebuf, '\n');
      if (pc)
        *pc = 0;

      first_time = true;

      line = find_line_number(state.lst.src->symbol,
                              state.lst.src->line_number);
      while (line) {
        /* print all instructions generated by this line of the source */

        if (line->line_number != state.lst.src->line_number)
          break;

        if (first_time == false) {
          /* only print the source line the first time */
          linebuf[0] = '\0';
        }
        state.cod.emitting = 1;
        org = line->address >> state.byte_addr;
        data = i_memory_get(line_section->data, org);
        assert(data & MEM_USED_MASK);
        num_words = gp_disassemble(line_section->data,
                                   org,
                                   state.class,
                                   dasmbuf,
                                   sizeof(dasmbuf));
        lst_line("%06lx   %04x     %-24s %s",
                 line->address,
                 data & 0xffff, 
                 dasmbuf,
                 linebuf);
        cod_lst_line(COD_NORMAL_LST_LINE);
        org++;
        if (num_words != 1) {
          state.lst.was_org = org;
          data = i_memory_get(line_section->data, org);
          assert(data & MEM_USED_MASK);
          lst_line("%06lx   %04x",
                   org << state.byte_addr,
                   data & 0xffff);
          cod_lst_line(COD_NORMAL_LST_LINE);
          org++;
          if (line->next) {
            /* skip the line number for the other half of this instruction */
            line = line->next;
          }
        }
        first_time = false;
        line = line->next;
      }
      
      if (first_time) {
        lst_line("%42s %s", " ", linebuf);
        state.cod.emitting = 0;
        cod_lst_line(COD_NORMAL_LST_LINE);
      }
    }
    
    state.lst.src->line_number++;
  }

}

/*
 * lst_init - initialize the lst file
 */

static void
lst_init(void)
{

  if (state.lstfile != named) {
    strncpy(state.lstfilename, state.basefilename, sizeof(state.lstfilename));
    strncat(state.lstfilename, ".lst", sizeof(state.lstfilename));
  }

  if ((gp_num_errors) || (state.lstfile == suppress)) {
    state.lst.f = NULL;
    state.lst.enabled = false;
    unlink(state.lstfilename);
  } else {
    state.lst.f = fopen(state.lstfilename, "wt");
    if (state.lst.f == NULL) {
      perror(state.lstfilename);
      exit(1);
    }
    state.lst.enabled = true;
  }

  if(!state.lst.enabled)
    return;

  state.lst.was_org = 0;
  state.cod.emitting = 0;

  lst_line("%s", GPLINK_VERSION_STRING);
  lst_line("%s", GPUTILS_COPYRIGHT_STRING);
  lst_line("Listing File Generated: %s", state.startdate);
  lst_line(" ");
  lst_line(" ");
  lst_line("Address  Value    Disassembly              Source");
  lst_line("-------  -----    -----------              ------");

}

void
write_lst(void)
{
  gp_symbol_type *symbol = state.object->symbols;
  gp_aux_type *aux;
  gp_boolean first_time = true;

  lst_init();

  if(!state.lst.enabled)
    return;

  list_enabled = true;
  state.lst.src = NULL;

  /* scan through the file symbols */
  while (symbol != NULL) {
    if (symbol->class == C_FILE) {
      /* open a new file */
      aux = symbol->aux_list;
      assert(aux != NULL);
      if (aux->_aux_symbol._aux_file.line_number) {
        /* it is an include file, so print the current file
           until the line number is reached */
        assert(state.lst.src != NULL);
        write_src(aux->_aux_symbol._aux_file.line_number);
      } else {
        /* it is not an include, so enable listing */
        list_enabled = true;
      }
      open_src(aux->_aux_symbol._aux_file.filename, symbol);
      if (first_time) {
        /* write the line numbers for the lst file header */
        cod_lst_line(COD_FIRST_LST_LINE);
        cod_lst_line(COD_NORMAL_LST_LINE);
        cod_lst_line(COD_NORMAL_LST_LINE);
        cod_lst_line(COD_NORMAL_LST_LINE);
        cod_lst_line(COD_NORMAL_LST_LINE);
        cod_lst_line(COD_NORMAL_LST_LINE);
        cod_lst_line(COD_NORMAL_LST_LINE);      
        first_time = false;
      }
    } else if (symbol->class == C_EOF) {
      /* print the rest of the current file then, close it */
      write_src(0);
      close_src();    
    } else if (symbol->class == C_LIST) {
      if (strcasecmp(symbol->name, ".list") == 0) {
        write_src(symbol->value);
        list_enabled = true;
      } else if (strcasecmp(symbol->name, ".nolist") == 0) {
        write_src(symbol->value);
        list_enabled = false;
      } else {
        assert(0);
      }
    }
    symbol = symbol->next;
  }

  fclose(state.lst.f);

}


syntax highlighted by Code2HTML, v. 0.9.1