/*  fd2lib  by Volker Barthelmann                         */
/*  rework 09/96 by Johnny Teveßen <j.tevessen@line.org>  */

/*  "T:"s removed: Not very portable!
*/

#define NDEBUG

#ifdef _DCC
#  define CTYPE_NEAR
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>

const char VersTag[] = "\0$VER: fd2lib 1.3 (15.9.96)";  /* AmigaOS version string. Doesn't hurt... */

#define MAXLINELEN       1000
#define BUFFEREDLEN     16384   /* 1024 is standard */

#define NUMREGS        16

static const char *regnames[NUMREGS] =
{
  "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
  "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"
};

enum
{
  A0=0, A1, A2, A3, A4, A5, A6, A7,
  D0  , D1, D2, D3, D4, D5, D6, D7
};

#define SMALLCODE        1
#define SMALLDATA        2
#define FASTCALL         4
#define NEWNOTATE        8
#define VARGSLOGIC      16
#define DEBUG           32

#ifndef   TRUE
   typedef short BOOL;
#  define TRUE  1
#  define FALSE 0
#endif

#ifndef   NULL
#  define NULL ((void *)0L)
#endif

#ifdef __GNUC__
#  define gnuspec(x) x
#else
#  define gnuspec(x)
#endif

#define NORETURN gnuspec(__attribute__ ((noreturn)))

static const char *varargs[] =
{
#include "vargs.h"
  NULL, NULL
};

static void ExitFailure(const char *, const char *) NORETURN;

static void
ExitFailure(const char *cause, const char *insertme)
{
  fprintf(stderr, cause, insertme);

  exit(EXIT_FAILURE);
}

static void
check(const char *ptr)
{
  if(!*ptr)
  {
    ExitFailure("Unexpected EOL\n", NULL);
  }
}

static void warnhim(int linenr, const char *format, ...) gnuspec(__attribute__ ((format (printf, 2, 3))));

static void
warnhim(int linenr, const char *format, ...)
{
  char linebuf[250];
  va_list vl;

  va_start(vl,format);
  vsprintf(linebuf, format, vl);
  va_end(vl);

  fprintf(stderr, "Warning line %d: %s\n", linenr, linebuf);
}

static FILE *
OpenLVO(const char *name, const char *outdir, const char *outform)
{
  FILE *lvos;
  char lvoname[MAXLINELEN];

  strcpy(lvoname, outdir);

  if(name)
  {
    char *k = (char *)name, *p;
    int lvonamlen;

    if((p = strrchr(k, '/')) != NULL) k = p + 1;
    if((p = strrchr(k, ':')) != NULL) k = p + 1;

    strcat(lvoname, k);

    lvonamlen = strlen(lvoname);

    if((lvonamlen > 7) && !strcmp(lvoname+lvonamlen-7, "_lib.fd"))
    {
      lvoname[lvonamlen-7] = '\0';
    }
    else if((lvonamlen > 3) && !strcmp(lvoname+lvonamlen-3, ".fd"))
    {
      lvoname[lvonamlen-3] = '\0';
    }

    strcat(lvoname, "_lvo.s");
  }
  else
  {
    strcat(lvoname, "fd_lvo.s");
  }

  printf(outform, lvoname, lvoname, lvoname);

  lvos = fopen(lvoname, "w");

  return(lvos);
}

static void
ProcessFD(const char *name, int mode, const char *outdir, const char *outform)
{
    FILE *fd, *lvos;
    FILE *out;
    int offset = 0, i, j, count, savecount;
    char function[80], tmpfuncnam[80], ff[MAXLINELEN+8], base[50];
    char line[MAXLINELEN];
    register char *p;
    char *functionp;
    int reg[NUMREGS], loops, linenr = 0;
    BOOL public = -1;           /* Why FALSE? */

    /*  "-1" is the initial value for "public". It means:
    **  no statement yet.
    */

    *function = *base = *line = '\0';

    if(name) fd = fopen(name, "r");
    else     fd = stdin;

    if(!fd) ExitFailure("Could not open `%s'\n", name);

    setvbuf(fd, NULL, _IOFBF, BUFFEREDLEN);             /* maybe _IOLBF? */

    if(!(lvos = OpenLVO(name, outdir, outform)))
      ExitFailure("Could not create lvo file\n", NULL);

    setvbuf(lvos, NULL, _IOFBF, BUFFEREDLEN);

    for(;;)
    {
        char *k;

        if(!fgets(line, MAXLINELEN-1, fd)) ExitFailure("Unexpected EOF\n", NULL);

        linenr ++;

        if((*line == '*') || !*line) continue;

        switch(*line + line[2])
        {
          case ('#'+'b'):
            if(!strncmp(line, "##base", 6))
            {
                if(*base) warnhim(linenr, "##base detected more than once!");

                p = line+6; while(isspace(*p)) p++;
                k = base  ; while(isgraph(*p)) *k++ = *p++;

                *k = '\0';

                if(mode & DEBUG) printf("Base set to `%s'\n", base);
                continue;
            }

            if(!strncmp(line, "##bias", 6))
            {
                p = line+6; while(isspace(*p)) p++;
                sscanf(p, "%i", &offset);

                if(mode & DEBUG) printf("Bias set to -%d\n", offset);
                continue;
            }
            break;

          case ('#'+'p'):
            if(!strncmp(line, "##public", 8))
            {
                if(public == TRUE)  warnhim(linenr, "##public after ##public detected!");
                else                public = TRUE;

                if(mode & DEBUG) printf("Turned on public mode at bias -%d\n", offset);
                continue;
            }

            if(!strncmp(line, "##private", 9))
            {
                if(public == FALSE) warnhim(linenr, "##private after ##private detected!");
                else                public = FALSE;

                if(mode & DEBUG) printf("Turned off public mode at bias -%d\n", offset);
                continue;
            }
            break;

          case ('#'+'e'):
            if(!strncmp(line, "##end", 5)) return;
            break;
        }

        if(*line == '#')
        {
          warnhim(linenr, "Unknown directive: `%s'!", line);
          continue;
        }

        if(!public)
        {
          offset += 6;
          continue;
        }

        if(public == -1)
        {
          warnhim(linenr, "Neither ##public nor ##private specified yet. Assuming ##public.");
          public = TRUE;
        }

        functionp = function;

        for(loops=0; loops<=1; loops++)
        {
            char *p = line; char *k = functionp;

            while(isspace(*p)) p++;

            if(!loops)
            {
                while((*p != '(') && *p) *k++ = *p++;

                check(p);
                *k = '\0';

                fprintf(lvos, "_LVO%s\tequ\t-%d\n"
                              "\txdef\t_LVO%s\n",
                              functionp, offset, functionp
                       );
            }
            else
            {
                while((*p != '(') && *p) p++;
                check(p);
            }

            if(mode & DEBUG) printf("function=%s, loops=%d\n", functionp, loops);

            /* Open function stub source file */

            sprintf(ff, "%s%s.s", outdir, functionp);

            printf(outform, ff, ff, ff);

            out = fopen(ff, "w");
            if(!out) ExitFailure("Could not create <%s>\n", functionp);
            setvbuf(out, NULL, _IOFBF, BUFFEREDLEN);

            /* Write assembler headers */

            if(mode & SMALLDATA) fprintf(out, "\tnear\t%s,-2\n", regnames[A4]);
            if(mode & SMALLCODE) fputs  (     "\tnear\tcode\n" , out);

            if(*base) fprintf(out, "\txref\t%s\n", base);

            fprintf(out, "\txdef\t_%s\n"
                         "\tsection\t\"CODE\",code\n"
                         "\n"
                         "_%s:\n",
                         functionp, functionp
                   );

            /* Set all registers to 'unused' */

            for(i=0; i<NUMREGS; i++) reg[i] = 0;

            /* Skip argument names */

            while((*p!=')') && *p) p++;

            check(p);
            p++;

            /* Search for beginning of register list */

            while((*p!='(') && *p) p++;

            check(p);
            p++;

            /* Scan register list */

            count = savecount = 0;

            while((*p!=')') && *p)
            {
                /* Check whether register description is valid */

                if((!((*p=='a') || (*p=='A') || (*p=='d') || (*p=='D'))) || !((p[1]>='0') && (p[1]<='7')))
                    ExitFailure("Bad register description\n", NULL);

                /* Convert description to internal enum format */
                /* Corrected: 'A' was not recognized           */

                if((*p=='a') || (*p=='A')) j = p[1] -  '0';
                else                       j = p[1] - ('0'-8);

                /* Mark register as used and save its argument counter */

                reg[j] = ++count;

                /* Increase counter if register has to be saved */

                if(!(j==A0 || j==A1 || j==A6 || j==D0 || j==D1))
                  savecount ++;

                /* Search for next register */

                p += 2;
                while(isspace(*p) && *p)     p++;

                if((*p == '/') || (*p==',')) p++;
                else if(*p != ')') ExitFailure("Parse error - ')' expected\n", NULL);

                while(isspace(*p) && *p)     p++;

                check(p);
            }

            if(savecount)
            {
                /* 'savecount' registers have to be saved */

                /* Always save library register */

                if((mode & FASTCALL) || (savecount == 1))
                {
                  /*  Store registers sequential. NEW: Changed that it'll also be
                  **  used if only two (including library register) registers have
                  **  to be stored. That's faster than the original method.
                  */

                  fprintf(out, "\tmove.l\t%s,-(%s)\n", regnames[A6], regnames[A7]);
                }
                else
                {
                  /*  It would be better to set A6 to the end of the list ...
                  */

                  fprintf(out, "\tmovem.l\t%s", regnames[A6]);
                }

                for(i=2; i<NUMREGS; i++)        /* Can start at 2, because 0 and 1 are ignored */
                {
                    if(reg[i] != 0)
                    {
                      switch(i)
                      {
                        case D0:
                        case D1:
                          break;

                        case A6:
                          warnhim(linenr, "Register conflict in `%s' (using libbasereg)!", functionp);
                          break;

                        default:
                          if((mode & FASTCALL) || (savecount == 1))
                          {
                            fprintf(out, "\tmove.l\t%s,-(%s)\n", regnames[i], regnames[A7]);
                          }
                          else
                          {
                            fprintf(out, "/%s", regnames[i]);
                          }
                          break;
                      }
                    }
                }

                if(!((mode & FASTCALL) || (savecount == 1))) fprintf(out, ",-(%s)\n", regnames[A7]);
            }
            else
            {
               /*  No registers have to be saved, so just save the register
               **  where the library base will be stored
               */

               fprintf(out, "\tmove.l\t%s,-(%s)\n", regnames[A6], regnames[A7]);
            }

            /* Load A6 with base NOW, so there's no conflict with SMALLDATA and A4 anymore ... */

            if(*base != '\0')
            {
                if(mode & SMALLDATA)
                {
                  fprintf(out, (mode & NEWNOTATE) ? "\tmove.l\t(%s,%s),%s\n"
                                                  : "\tmove.l\t%s(%s),%s\n",
                               base, regnames[A4], regnames[A6]
                         );
                }
                else
                {
                  fprintf(out, "\tmove.l\t%s,%s\n", base, regnames[A6]);
                }
            }
            else ExitFailure("No base defined!\n", NULL);

            for(j=1; j<=count; j++)     /* Arguments */
            {
                for(i=0; i<NUMREGS; i++) /* Registers */
                {
                    if(reg[i] == j)     /* Argument in THIS register? */
                    {
                        int saveoffset = (savecount+j+1)<<2;

                        if((i>=D7) || (reg[i+1] != j+1) || ((loops==1) && (j>=count-1)))
                        {
                            if(!loops || (j<count))
                            {
                                fprintf(out, (mode & NEWNOTATE) ? "\tmove.l\t(%d,%s),%s\n"
                                                                : "\tmove.l\t%d(%s),%s\n",
                                              saveoffset, regnames[A7], regnames[i]);
                            }
                            else
                            {
                              if(i <= 7)
                              {
                                /*  adress register */

                                fprintf(out, (mode & NEWNOTATE) ? "\tlea\t(%d,%s),%s\n"
                                                                : "\tlea\t%d(%s),%s\n",
                                             saveoffset, regnames[A7], regnames[i]
                                       );
                              }
                              else
                              {
                                /*  data register  */

                                /*  Changed: moveq.l will be forced if possible
                                **  (there are still assemblers that do not do this
                                **  automatically).
                                */

                                fprintf(out, "\tmove%s\t#%d,%s\n" \
                                             "\tadd.l\t%s,%s\n",
                                             (saveoffset <= 127) ? "q" : ".l",
                                             saveoffset, regnames[i], regnames[A7], regnames[i]
                                       );
                              }
                            }
                        }
                        else
                        {
                            /* Here no fastcall - slower. :-( */

                            fprintf(out, (mode & NEWNOTATE) ? "\tmovem.l\t(%d,%s),%s"
                                                            : "\tmovem.l\t%d(%s),%s",
                                         saveoffset, regnames[A7], regnames[i]
                                   );

                            while((i<D7) && (reg[i+1] == j+1) && (!loops || (j<count-1)))
                            {
                                i++; j++;

                                fprintf(out, "/%s", regnames[i]);
                            }

                            fputc('\n', out);
                        }
                    } /* if reg[i]==j */
                } /* for i */
            } /* for j */

            /* Now place the real function call */

            fprintf(out, (mode & NEWNOTATE) ? "\tjsr\t(-%d,%s)\n"
                                            : "\tjsr\t-%d(%s)\n",
                         offset, regnames[A6]
                   );

            /* Start restoring registers ... libbasereg first. */

            if(!((mode & FASTCALL) || (savecount == 1)))
            {
              if(!savecount) fprintf(out, "\tmove.l\t(%s)+,%s" , regnames[A7], regnames[A6]);
              else           fprintf(out, "\tmovem.l\t(%s)+,%s", regnames[A7], regnames[A6]);
            }

            /* Now the others */

            for(i=2; i<NUMREGS; i++)
            {
                if((mode & FASTCALL) || (savecount == 1))
                {
                  j = (NUMREGS+1) - i;          /* swap direction; okay for != 16 regs? */
                }
                else
                {
                  j = i;
                }

                if(reg[j])
                {
                  switch(j)
                  {
                    case A6:
                    case D0:
                    case D1:
                      break;

                    default:
                      if((mode & FASTCALL) || (savecount == 1))
                      {
                        fprintf(out, "\tmove.l\t(%s)+,%s\n", regnames[A7], regnames[j]);
                      }
                      else
                      {
                        fprintf(out, "/%s", regnames[j]);
                      }
                      break;
                  }
                }
            }

            /* If fastcall was used, a6 was put first. So we pop it last. */

            if((mode & FASTCALL) || (savecount == 1))
            {
              fprintf(out, "\tmove.l\t(%s)+,%s", regnames[A7], regnames[A6]);
            }

            /* Return from subroutine */

            fputs("\n" \
                  "\trts\n" \
                  "\n" \
                  "\tend\n",
                  out
                 );

            fclose(out);

            if(loops != 0) break;

            p = (char *) *varargs;
            loops = 3;

            if(mode & DEBUG) printf("Searching function `%s' in vargs table...\n", functionp);

            if(p != NULL)
            {
              if(mode & VARGSLOGIC)
              {
                int fnlen = strlen(functionp);

                if((fnlen > 7) && (!strcmp(functionp + fnlen - 7, "TagList")))
                {
                  /*  xxxTagList function found. Make xxxTags of it
                  */

                  strncpy(tmpfuncnam, functionp, fnlen - 4);
                  strcpy (tmpfuncnam + fnlen - 4,    "s"  );

                  functionp = tmpfuncnam;
                  loops     = 0;
                }
                else if((fnlen > 1) && ((functionp[fnlen-1] == 'A') && (functionp[fnlen-2] >= 'a') && (functionp[fnlen-2] <= 'z')))
                {
                  /*  Not that smart recognition... But you probably
                  **  don't want to have a function CreateDA() varargs,
                  **  want you?
                  **
                  **  Recognized are functions that end with 'A' and that
                  **  have a lowercase letter before that.
                  */

                  strcpy(tmpfuncnam, functionp);
                  tmpfuncnam[fnlen-1] = '\0';

                  functionp = tmpfuncnam;
                  loops     = 0;
                }

                if(!loops)
                {
                  if(mode & DEBUG) puts("Found via internal logic!");
                }
              }

              if(loops) for(i=0; p != NULL; i+=2)
              {
                if(!strcmp(p, functionp))
                {
                    if(mode & DEBUG) puts("Found!");

                    functionp = (char *) varargs[i - 1];        /* [i+1] */
                    loops = 0;
                    break;
                }

                /*i += 2;*/
                p = (char *) varargs[i];
              }
            }
        }

        offset += 6;
    } /* for(;;) */

    if(name) fclose(fd);

    fputs("\n\tend\n", lvos);

    fclose(lvos);
}

/*  Append '/' to path if needed
*/

static void
fillpath(char *dirpath)
{
  int sl = strlen(dirpath);

  switch(dirpath[sl-1])
  {
    case ':':   /* ':' should be AMIGA-only! This will be commented out. */
    case '/':
      break;

    default:
      strcpy(dirpath+sl, "/");
      break;
  }
}

/*  Show program usage
*/

static void Usage(const char *) NORETURN;

static void
Usage(const char *myname)
{
  printf("fd2lib 1.3  (c) 9/96 by Volker Barthelmann / Johnny Teveßen\n"
         "\n"
         "  -- Caution: Needs ~5000 byte stack! --\n"
         "\n"
         "Usage : %s [-sc] [-sd] [-40] [-on] [-nv] [-o <dir>] [-of <format>]\n"
         "           [-d] [-?|--help] [files/pattern]\n"
         "\n"
         "  -sc : Use small code model (else large code model)\n"
         "  -sd : Use small data model (else large data model)\n"
         "  -40 : Use fast call model for 68040\'s (no 'movem's)\n"
         "  -on : Use old motorola assembler notation\n"
         "  -nv : No varargs logic - ...A and ...TagList will not be detected\n"
         "  -o  : Specify directory to store source files in\n"
         "  -of : C printf style output format to generate compiling\n"
         "        script. Three `%%s' are replaced with output file name\n"
         "  -d  : Turn on debugging/verbose mode\n"
         "  -?  : Show help/version and quit\n"
         "files : FD files to convert, defaults to stdin\n"
         "\n"
         "Commandline is parsed left-to-right. Specifying\n"
         "\"alib_lib.fd -sd blib_lib.fd\" will result in alib\n"
         "getting large data model.\n",
         myname
        );

  exit(0);
}

/*  Remember: ixemul.library does command line expansion, eg.:
**
**  redrose# fd2lib -sc -sd /fd/a*_lib.fd
**
**  will become:
**
**  redrose# fd2lib -sc -sd /fd/amigaguide_lib.fd /fd/asl_lib.fd ...
**
*/

int
main(int argc, char **argv)
{
    int erg       = 0 /*EXIT_FAILURE*/;
    int mode      = NEWNOTATE | VARGSLOGIC;
    int filesdone = 0;

    char outdir[80] = "", outform[250] = "";

    if(argc > 1)
    {
      int i;

      for(i=1; i<argc; i++)
      {
        if(argv[i][0] == '-')
        {
          /* Parse option */

               if( !strcmp(argv[i], "-sc")) mode |=  SMALLCODE;
          else if( !strcmp(argv[i], "-sd")) mode |=  SMALLDATA;
          else if( !strcmp(argv[i], "-40")) mode |=  FASTCALL;
          else if( !strcmp(argv[i], "-on")) mode &= ~NEWNOTATE;
          else if( !strcmp(argv[i], "-nv")) mode &= ~VARGSLOGIC;
          else if( !strcmp(argv[i], "-o" ))
          {
            if(i < (argc-1))
            {
              i ++;

              if(strlen(argv[i]) < sizeof(outdir))
              {
                strcpy  (outdir, argv[i]);
                fillpath(outdir);
              }
              else
              {
                fprintf(stderr, "Path too long. Maximum is %lu characters. Ignored.\n", (unsigned long)sizeof(outdir));
              }
            }
            else
            {
              fputs("No path specified after `-o'!\n", stderr);
            }
          }
          else if( !strcmp(argv[i], "-of"))
          {
            if(i < (argc-1))
            {
              i ++;

              if(strlen(argv[i]) < sizeof(outform))
              {
                strcpy(outform, argv[i]);
                strcat(outform, "\n"   );
              }
              else
              {
                fprintf(stderr, "Format too long. Maximum is %lu characters. Ignored.\n", (unsigned long)sizeof(outform));
              }
            }
            else
            {
              fputs("No format specified after `-of'!\n", stderr);
            }
          }
          else if( !strcmp(argv[i], "-d" )) mode |=  DEBUG;
          else if((!strcmp(argv[i], "-?" )) ||
                  (!strcmp(argv[i], "--help"))) Usage(*argv);
          else
          {
            fprintf(stderr, "Unknown option `%s'\n\n", argv[i]);
            Usage(*argv);
          }
        }
        else
        {
          /* Process file */

          if(argv[i][0] == '?') Usage(*argv);
          else
          {
            ProcessFD(argv[i], mode, outdir, outform);
            filesdone ++;
          }
        }
      }
    }

    if(!filesdone) ProcessFD(NULL, mode, outdir, outform);

    return(erg);
}
