/*
   parser - Parse assembly file and insert debugging information

   Copyright (C) 2003-2004 Kevin Kofler
   Portions Copyright (C) 2002-2003 Romain Liévin
   Portions Copyright (C) 2000-2003 Sebastian Reichelt

   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, 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.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>

#include "parser.h"

#define EXIT_FAILURE 1


char *output_file=NULL;
unsigned char **Strings=NULL;
unsigned char **LineContents=NULL;
int Count=0;
int LineContentsCount=0;

int
main (int argc, char **argv)
{
  int i;
  FILE *infile, *outfile;
  char *p;
  char *source_file;
  unsigned char **buffer;

  /* GNU stuff */
  program_name = argv[0];
  i = decode_switches (argc, argv);

  if ((i>=argc) || strcmp(file_extension(argv[i]),".s")) usage(EXIT_FAILURE);
  if ((i+1>=argc)) usage(EXIT_FAILURE);
  if (!output_file) output_file=argv[i];
  source_file=argv[i+1];

  infile=fopen(argv[i],"rb");
  if (!infile) {
    fprintf(stderr,"Unable to open the assembly file <%s>\n",argv[i]);
    exit(-1);
  }

read_in_source_file:
  // Get the file size
  fseek(infile,0,SEEK_END);
  i=ftell(infile);
  fseek(infile,0,SEEK_SET);

  // Allocate a buffer for the entire file and read it into memory
  buffer=malloc(sizeof(unsigned char *));
  if (!buffer) outofmem();
  *buffer=malloc(i+1);
  if (!*buffer) outofmem();
  fread(*buffer,1,i,infile);
  (*buffer)[i]=0;

  fclose(infile);

  // Now split the file into lines
  i=0;
  while((p=strchr(buffer[i],'\n'))) {
    buffer=realloc(buffer,((++i)+1)*sizeof(unsigned char *));
    if (!buffer) outofmem();
    buffer[i]=malloc(strlen(p+1)+1);
    if (!buffer[i]) outofmem();
    strcpy(buffer[i],p+1);
    *p=0;
    if (p[-1]=='\r') p[-1]=0;
    buffer[i-1]=realloc(buffer[i-1],strlen(buffer[i-1])+1);
  }

  if (!Strings) {
    Strings=buffer;
    Count=i+1;

    infile=fopen(source_file,"rb");
    if (!infile) {
      fprintf(stderr,"Unable to open the source file <%s>\n",source_file);
      exit(-1);
    }
    goto read_in_source_file;
  } else {LineContents=buffer; LineContentsCount=i+1;}

  ParseDebugSFile();

  outfile=fopen(output_file,"wt");
  if (!outfile) {
    fprintf(stderr,"Unable to open the output file <%s>\n",argv[i]);
    exit(-1);
  }
  for (i=0;i<Count;i++) {
    fputs(Strings[i],outfile);
    fputc('\n',outfile);
  }
  fclose(outfile);

  exit(0);
}

/* Set all the option flags according to the switches specified.
   Return the index of the first non-option argument.  */

static int
decode_switches (int argc, char **argv)
{
  int c;

  for (c=1;c<argc;c++) {
    if (!strcmp(argv[c],"-V")||!strcmp(argv[c],"--version")) {
       printf ("TIGCC parser 1.01\n");
       exit(0);
    } else if (!strcmp(argv[c],"-h")||!strcmp(argv[c],"--help")) {
       usage(0);
    } else if (!strcmp(argv[c],"-o")) {
       if (c+1<argc) output_file=argv[c+1]; else usage(EXIT_FAILURE);
    } break;
  }

  if (c>=argc) usage(EXIT_FAILURE);

  return c;
}

__attribute__((noreturn)) static void outofmem(void)
{
  fprintf(stderr,"Fatal error: out of memory\n");
  exit(-1);
}

__attribute__((noreturn)) static void
usage (int status)
{
  printf ("%s - parse assembly files and insert debugging information\n",
          program_name);
  printf ("Usage: %s [OPTION]... assemblyfile sourcefile\n", program_name);
  printf ("\
Options:\n\
  -h, --help                 display this help and exit\n\
  -V, --version              output version information and exit\n\
  -g source_file             handle debugging information\n\
  -o output_file             select output file (default: same as input)\n\
");
  exit (status);
}

/* Return the filename extension or NULL */
static const char *file_extension(const char *filename)
{
  int i;
  const char *p;

  for(i=strlen(filename); i > 0; i--)
    {
      if(filename[i] == '.') break;
    }
  p=filename+i;

  return p;
}

static char *Trim(char *p)
{
  char *q;
  while (*p==' ') memmove(p,p+1,strlen(p)); // delete leading spaces
  while (p[strlen(p)-1]==' ') p[strlen(p)-1]=0; // delete trailing spaces
  while ((q=strchr(p,'\t'))) memmove(q,q+1,strlen(q)); // delete all tabs
  return realloc(p,strlen(p)+1);
}

static char *GetLineContents(int StartLine, int EndLine)
{
// This is a straight C translation of Sebastian's GetLineContents Delphi source code.
  int I;
  char *Result=calloc(1,1);
  if (!Result) outofmem();
  for(I=StartLine-1;I<=EndLine-1;I++) {
    if (I>=0 && I<LineContentsCount) {
      Result=realloc(Result,strlen(Result)+strlen(LineContents[I])+3);
      if (!Result) outofmem();
      strcat(Result,LineContents[I]);
      strcat(Result,"\r\n");
    }
  }
  Result[strlen(Result)-2]=0;
  Result=realloc(Result,strlen(Result)+1);
  return Result;
}

static void ParseDebugSFile(void)
{
// This is a straight C translation of Sebastian's ParseDebugSFile Delphi source code.
  int I=0;
  int LastLine=-1;
  int RecontinueLine=-1;
  int LastDebugLine=-1;
  int CurDebugLine;

  while (I<=Count) {
    if (I<Count && !strncmp(Strings[I],"\t.def\t",6)) { // delete the line
      memmove(Strings+I,Strings+I+1,((--Count)-I)*sizeof(unsigned char *));
      realloc(Strings,Count*sizeof(unsigned char *));
      I--;
    } else if (I>=Count || !strncmp(Strings[I],"\t.ln\t",5)) {
      if (I<Count) {
        CurDebugLine=atoi(Strings[I]+5);
        if (!CurDebugLine) CurDebugLine=LastDebugLine;
        *(Strings[I])='#'; // \t.ln\t -> #L
        Strings[I][1]='L';
        memmove(Strings[I]+2,Strings[I]+5,strlen(Strings[I])-1);
        Strings[I][strlen(Strings[I])+1]=0; // append \t
        Strings[I][strlen(Strings[I])]='\t';
      } else CurDebugLine=LastDebugLine+1;

      if (LastLine>=0 && CurDebugLine>=0) {
        char *S=Strings[LastLine],*P;
        #define append(s1,s2) ({const char *q=(s2);s1=realloc(s1,strlen(s1)+strlen(q)+1);if (!s1) outofmem();strcat(s1,q);})
        if (CurDebugLine<LastDebugLine) {
          append(S,Trim(GetLineContents(LastDebugLine,LastDebugLine)));
          append(S," ...");
          RecontinueLine=LastDebugLine;
        } else {
          if (RecontinueLine>=0 && CurDebugLine>=RecontinueLine) {
            append(S,Trim(GetLineContents(LastDebugLine,LastDebugLine)));
            append(S," ...");
            RecontinueLine=-1;
          } else append(S,Trim(GetLineContents(LastDebugLine,CurDebugLine-1)));
        }
        #undef append
        do {
          P=strchr(S,'\n');
          if (!P) P=strchr(S,'\r');
          if (P) *P=' ';
        } while (P);
        Strings[LastLine]=S;
      }
      LastLine=I;
      LastDebugLine=CurDebugLine;
    }
    I++;
  }
}
