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

/*  "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: fd2libPPC 0.1 (21.12.1997)";  /* 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  /* not available for PPC */
#define SMALLDATA        2
#define FASTCALL         4  /* not available for PPC */
#define NEWNOTATE        8  /* not available for PPC */
#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, "\t.set\tLVO%s,-%d\n"
                              "\t.global\tLVO%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 */

            fprintf(out,"\t.text\n");
            if(*base) fprintf(out, "\t.global\t%s\n", base+1);
            fprintf(out,"\t.global\tPPCCallOS\n");
            fprintf(out,"\t.global\t%s\n",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);
            }

            fprintf(out,"\t.align\t3\n%s:\n",functionp);
            if(!loops){
                fprintf(out,"\tstwu\t1,-96(1)\n\tmflr\t11\n\tstw\t11,100(1)\n");
            }else{
                int i;
                /* Hack the stack-frame for varargs. */
                /* Build stack-frame, but save LR in our own stack-frame, */
                /* because we have to overwrite the lower 8 bytes of the  */
                /* caller's frame. */
                fprintf(out,"\tstwu\t1,-128(1)\n\tmflr\t11\n\tstw\t11,100(1)\n");
                /* Save the caller's saved SP in our own stack-frame.     */
                fprintf(out,"\tlwz\t11,128(1)\n\tstw\t11,96(1)\n");
                /* Store r3-r8 at the top of our stack-frame and r9-r10   */
                /* at the low 8 bytes of the caller's frame. This way all */
                /* arguments will reside in one continuous area.          */
                for(i=3+count-1;i<=10;i++)
                    fprintf(out,"\tstw\t%d,%d(1)\n",i,104+4*(i-3));
            }
            for(i=0; i<NUMREGS; i++) /* Registers */
            {
                if(reg[i])
                {
                    if(!loops||reg[i]<count){
                        if(reg[i]<=8)
                            fprintf(out,"\tstw\t%d,",reg[i]+2);
                        else
                            fprintf(out,"\tlwz\t11,%d(1)\n\tstw\t11,",reg[i]*4+4-8*4);
                    }else{
                        fprintf(out,"\taddi\t11,1,%d\n\tstw\t11,",100+count*4);
                    }

                    if(i<=7)
                        fprintf(out,"%d(1)\n",68+4*i);
                    else
                        fprintf(out,"%d(1)\n",36+4*(i-8));


                }
            } /* for i */

            /* Now place the real function call */

            fprintf(out,"\tli\t11,-%d\n\tstw\t11,8(1)\n",offset);
            fprintf(out,"\tli\t11,1\n\tstw\t11,12(1)\n\tstw\t11,24(1)\n");
            fprintf(out,"\tlis\t11,%s@ha\n\tlwz\t11,%s@l(11)\n\tstw\t11,92(1)\n",base+1,base+1);
            fprintf(out,"\taddi\t3,1,8\n\tbl\tPPCCallOS\n");
            if(!loops){
                fprintf(out,"\tlwz\t11,100(1)\n\tmtlr\t11\n");
                fprintf(out,"\taddi\t1,1,96\n\tblr\n");
            }else{
                /* Varargs. Rebuild the caller's stack-frame. */
                fprintf(out,"\tlwz\t11,96(1)\n\tstw\t11,128(1)\n");
                fprintf(out,"\tlwz\t11,100(1)\n\tmtlr\t11\n");
                fprintf(out,"\taddi\t1,1,128\n\tblr\n");
            }
            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("fd2libPPC (c) by Volker Barthelmann / Johnny Teveßen\n"
         "\n"
         "  -- Caution: Needs ~5000 byte stack! --\n"
         "\n"
         "Usage : %s [-sd] [-nv] [-o <dir>] [-of <format>]\n"
         "           [-d] [-?|--help] [files/pattern]\n"
         "\n"
         "  -sd : Use small data model (else large data model)\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], "-sd")) mode |=  SMALLDATA;
          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);
}
