// I appear to have written essentially the same program twice.  Probably couldn't
// find the earlier one when I needed it.  Not sure which came first or which gives
// better output.  This one is recode-gcc6809-v1.c and is in this ~/src/C-PROGS directory
// on my home linux.

// The other one is now named recode-gcc6809-v2.c and has been copied to this directory.
// It was originally named stabs.c when I found it on my gtoal.com server.

// The primary difference appears to be that v1 outputs a file that looks like a compiler
// listing, showing the generated machine code, and v2 looks more like an assembler source
// that could be assembled by as6809 with comments showing the original C source...

// A minor secondary difference is in the way the C statements follow or precede the
// corresponding assembly code in some instances.  Which is a very insignificant difference.

/*
     This generates a listing file of your C program with the generated assembly language
     and binary (in hex) next to each source line.  This is a facility that's usually
     available on most compilers but was lacking on the 6809 C compiler.  We make it
     work by running an extra compilation of the source with "-g" added, which adds
     stabs directives to the assembly code and resulting listing.  The code here
     merges that listing with the original C source.  It's not perfect because of
     the way that GCC displays 'inline' procedures.  It may be possible to shuffle
     the files around even more to remedy this, but I don't plan to for the moment -
     this seems good enough.  You can always "#define inline" to remove inlining to
     some extent, to get a better view of what the compiler is doing, though of course
     it will no longer exactly represent your compiled program.

     This is very hacky code.  It could be cleaned up a lot.  Read it in conjunction
     with the Makefile (and check README-SOURCE-MERGING.txt if you plan to extend
     this in any way.)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>

static int debug = 0;

// TO DO: remove the noise associated with embedded asm() statements...
//        parameterise the program with argv.

char *getsourceline(FILE *f) {
  static char p[1024], *s;
  s = p;
  for (;;) {
    int c = fgetc(f);
    if (c == EOF) {
      *s = '\0';
      if (s == p) return NULL;
      return strdup(p);
    }
    if (c == '\r') continue;
    if (c == '\n') {
      *s = '\0';
      return strdup(p);
    }
    *s++ = c;
  }
}

void output(FILE *f, char *s) {
  while (*s != '\0') {
    int c = *s++;
    if (c == ' ' || c == '\t' || c == '\n' || ((c > 32) && (c < 127))) fputc(c, f);
  }
}

int main(int argc, char **argv) {
#define HUGE 20000
  FILE *stabs;
  FILE *source;
  char fname[512], SOURCE[512], ASSEM[512], *fp;
  typedef struct {
    char *source;
    char *assem;
  } combined;
  combined prog[HUGE];
  int source_line = 0;
  int capture = 0;
  char *ProgName = argv[0]; // for now
  
  if (argc == 2) {
    char *s;
    strcpy(SOURCE, argv[1]);
    strcpy(ASSEM, argv[1]);

    s = strrchr(ASSEM, '.');
    if (s && strcmp(s, ".c")==0) {
      strcpy(s, ".asm");
    } else {
      fprintf(stderr, "%s: argument should be a .c file, not \"%s\"\n", ProgName, SOURCE);
      exit(EXIT_FAILURE);
    }

    stabs = fopen(ASSEM, "r");
    if (stabs == NULL) {
      s = strrchr(ASSEM, '.'); strcpy(s, ".s");
      stabs = fopen(ASSEM, "r");
      if (stabs == NULL) {
        fprintf(stderr, "%s: could not open \"%s\" - %s\n", ProgName, ASSEM, strerror(errno));
        exit(EXIT_FAILURE);
      }
    }
    source = fopen(SOURCE, "r");
    if (source == NULL) {
      fprintf(stderr, "%s: could not open \"%s\" - %s\n", ProgName, SOURCE, strerror(errno));
      exit(EXIT_FAILURE);
    }
  } else {
    fprintf(stderr, "syntax: %s file.c  (requires an accompanying file.s or file.asm)\n", ProgName);
    exit(EXIT_FAILURE);
  }
  
  stabs = fopen("clamp.s", "r"); // temporary
  if (stabs == NULL) {
    fprintf(stderr, "%s: cannot open \"%s\" - %s\n", "clamp.s", ProgName, strerror(errno));
    exit(EXIT_FAILURE);
  }

  source = fopen(SOURCE, "r");
  if (source == NULL) {
    fprintf(stderr, "%s: cannot open \"%s\" - %s\n", ProgName, SOURCE, strerror(errno));
    exit(EXIT_FAILURE);
  }

  fprintf(stdout, "\n                              GNU C (GCC) version 4.3.6\n\nSource: %s\n\n", SOURCE);
  prog[source_line].source = NULL;
  prog[source_line].assem = NULL;
  for (;;) {
    source_line += 1;
    prog[source_line].source = getsourceline(source);
    prog[source_line].assem = NULL;
    if (prog[source_line].source == NULL) {
      source_line -= 1;
      break;
    }
  }
  
  if (debug) fprintf(stderr, "%d lines read.\n", source_line);

  // Now that we have the source code, annotate it with the asm!
  
  for (;;) {
    char *oline = getsourceline(stabs), *line;
    line = oline;
    if (line == NULL) break;
/*
                           3131         .area   .data
   0002                    3132 _workBuffer:
   0002 00 00 00 00 00 00  3133         .word   0,0,0,0,0,0,0,0
        00 00 00 00 00 00
        00 00 00 00
                           3134         .area   .bss
                           3135         .globl  _screenPoints
   0001                    3136 _screenPoints:  .blkb   30
 */
    if (strlen(line) >= strlen("   0002 00 00 00 00 00 00  3133 ") /*&& line[8] != ' '*/) {
      // remove the .asm file line number from continued lines
      line += strlen("   0002 00 00 00 00 00 00  3133 ");
      line[-2] = ' ';
      line[-3] = ' ';
      line[-4] = ' ';
      line[-5] = ' ';
      line[-6] = ' ';
    }
    if (strncmp(oline, "\fASxxxx Assembler", strlen("\fASxxxx Assembler"))== 0) {
      capture = 0;
    } else if (strncmp(line, ";\t.stabs", strlen(";\t.stabs")) == 0) {
      if (debug) fprintf(stderr, "STABS: %s\n", line);
      // .stabs  "source/main.c",132,
      char *s = line+strlen(";\t.stabs");
      while (*s == ' ' || *s == '\t') s+=1;
      if (*s == '"') {
        fp = fname;
        s += 1;
        while (*s != '"') {
          *fp++ = *s++;
        }
        *fp = '\0';
        s += 1;
        if (strncmp(s, ",132,", strlen(",132,")) == 0) {
          if (debug) fprintf(stderr, "File [[%s]] directive found: %s\n", fname, line);
          capture = (strcmp(fname, SOURCE) == 0);
          if (capture) {
            if (debug) fprintf(stderr, "(capturing asm)\n");
          } else {
            if (debug) fprintf(stderr, "(skipping asm)\n");
          }
        } else {
          if (debug) fprintf(stderr, "? Not wanted stabs: %s\n", line);
        }
      } else {
        if (debug) fprintf(stderr, "* Malformed stabs directive: %s at [[%s]]\n", line, s);
      }
    } else if (strncmp(line, ";\t.stabd", strlen(";\t.stabd")) == 0) {
      // .stabd  68,0,1342
      if (debug) fprintf(stderr, "STABD: %s\n", line);
      char *s = line+strlen(";\t.stabd");
      while (*s == ' ' || *s == '\t') s+=1;
      if (strncmp(s, "68,0,", strlen("68,0,")) == 0) {
        s += strlen("68,0,");
        if (debug) fprintf(stderr, "Assembly code for line %s follows.\n", s);
        /*if (capture)*/ capture = atoi(s);
      } else {
        if (debug) fprintf(stderr, "* Malformed stabd directive: %s at [[%s]]\n", line, s);
      }
    } else if (strncmp(line, ";\t.stabn", strlen(";\t.stabn")) == 0) {
      //fprintf(stderr, "Ignore %s\n", line);
    } else if (strncmp(line, ";\t.stab", strlen(";\t.stab")) == 0) {
      if (debug) fprintf(stderr, "? Warning: unknown stab directive %s\n", line);
    } else if (strstr(line, "\t.area\t")) {
      char *s = strstr(line, "\t.area\t");
      if (strncmp(s, "\t.area\ttext", strlen("\t.area\ttext")) == 0) {
        capture = 1;
      } else {
        capture = 0; // suppress .bss, .data etc
      }
    } else if (line[0] == ';') {
      if (debug) fprintf(stderr, "COMMENT REMOVED: %s\n", line);
    } else {
  /*
     5C1E                   23192 L41:
                          23193 ;       .stabd  68,0,8468
   5C1E 6D 62         [ 7]23194         tst     2,s     ; XJoy.126
   5C20 2C B7         [ 3]23195         bge     L42     ;
   5C22 C6 81         [ 2]23196         ldb     #-127   ; tmp38,
   5C24 E0 62         [ 5]23197         subb    2,s     ; tmp37, XJoy.126
   5C26 E1 E4         [ 4]23198         cmpb    ,s      ;cmpqi:(R)      ; tmp37, crosshair_x.19
   5C28 2E B6         [ 3]23199         bgt     L43     ;
   5C2A 20 AD         [ 3]23200         bra     L42     ;
                          23201 ;       .stabs  "follow_crosshair:F15",36,0,0,_follow_crosshair
   5C2C                   23202 Lscope3:
                          23203         .area   .bss
   00CA                   23204 _rseed: .blkb   2
                          23205         .area   .text
   5C2C                   23206 _pickrandom:
                          23207 ;       .stabd  68,0,8505
   5C2C                   23208 LFBB4:
   5C2C 34 60         [ 7]23209         pshs    y,u     ;
   5C2E 32 7C         [ 5]23210         leas    -4,s    ;,,
   5C30 31 84         [ 4]23211         leay    ,x      ; low, low
   5C32                   23212 LBB105:
   5C32                   23213 LBB106:
   5C32                   23214 LBB107:
   5C32                   23215 LBB108:
                          23216 ;       .stabd  68,0,8495
   5C32 BE 00 CA      [ 6]23217         ldx     _rseed  ;, rseed
 */
      if (capture) {
        char *temp_ptr, *s;
        int newlen;
        if (prog[capture].assem == NULL) {
          prog[capture].assem = strdup("");
        }
        newlen = strlen(prog[capture].assem)+strlen(oline)+2;
        temp_ptr = (char *)realloc(prog[capture].assem, newlen);
        if (temp_ptr == NULL) {
          fprintf(stderr, "Realloc %d failed.\n", newlen); exit(EXIT_FAILURE);
        }
        prog[capture].assem = temp_ptr;
        s = prog[capture].assem + strlen(prog[capture].assem);
        sprintf(s, "%s\n", oline);
        if (debug) fprintf(stderr, "ADDED: %s\n", oline);

      }
    }
    free(oline);
  }
  
  for (int i = 1; i <= source_line; i++) {
    fprintf(stdout, "                          %5d ", i);
    output(stdout, prog[i].source);
    fprintf(stdout, "\n");
    if (prog[i].assem != NULL) {
      output(stdout, prog[i].assem);
    }
  }
  
  exit(EXIT_SUCCESS);
}
