/*
 * cc -- unix-like compiler driver for amiga lattice c
 *
 * adapted for lattice version 5.0 and extensively modified
 *   by Miles Bader, from the original by Fred Fish (copyright
 *   follows).
 */

/************************************************************************
 *                                                                     *
 *                     Copyright (c) 1985, Fred Fish                   *
 *                         All Rights Reserved                         *
 *                                                                     *
 *     This software and/or documentation is released into the         *
 *     public domain for personal, non-commercial use only.            *
 *     Limited rights to use, modify, and redistribute are hereby      *
 *     granted for non-commercial purposes, provided that all          *
 *     copyright notices remain intact and all changes are clearly     *
 *     documented.  The author makes no warranty of any kind with      *
 *     respect to this product and explicitly disclaims any implied    *
 *     warranties of merchantability or fitness for any particular     *
 *     purpose.                                                        *
 *                                                                     *
 ************************************************************************
 */

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

#include <dos.h>
#include <proto/dos.h>
#include <ios1.h> /* magic */

#include "common.h"
#include "list.h"
#include "dstr.h"
#include "op.h"
#include "options.h"

#include "config.h"

static char *locate(char *file,struct list *list);     /* find a file */
static void cleanObjects(int mustExist);       /* Remove .o for link and go mode */
static int makeObjs();                 /* Reduce to list of object files */
static int parseCommandLine();         /* Deal with command line */
static int compile(struct op *op);     /* Translate from .c to .o */
static int assemble(struct op *op);    /* Translate from .s to .o */
static int link();                     /* Gather .o's into executable */
static int runCmd(struct dstr *cmd);

#ifdef amiga
#define CHECK_ABORT chkabort()
#else
#define CHECK_ABORT                            /* NULL expansion */
#endif /* amiga */
 
DBUG_GLOBALS

/*
 *     flags set by command line arguments
 */
static int debugLevel=DEFAULT_DEBUGLEVEL;      /* -g (debugging level) */

struct list *libDirs=NULL;
struct list *incDirs=NULL;
struct list *binDirs=NULL;
struct list *defines=NULL;
struct list *undefines=NULL;
struct list *libs=NULL;

struct list *pass1PassThrus=NULL;
struct list *pass2PassThrus=NULL;
struct list *optPassThrus=NULL;
struct list *linkPassThrus=NULL;

struct list *ops=NULL;

static char *outfile=DEFAULT_EXEC;     /* output file name from linker */
static char *tempDir=TEMPDIR;          /* where to keep quad files */
static char *sinkFile=SINKFILE;                /* where to put output from passes */

struct dstr *cmdLine=NULL;
 
static int abortmsg()
{
    if(options_IsSet(OPT_ECHO))
       fprintf(stderr,"cc: aborting\n");
    return 1; /* abort */
}

static int cleanup(status)
int status;
{
    if(cmdLine!=NULL)  dstr_Free(cmdLine);

    if(libDirs!=NULL)  list_Free(libDirs);
    if(incDirs!=NULL)  list_Free(incDirs);
    if(binDirs!=NULL)  list_Free(binDirs);
    if(defines!=NULL)  list_Free(defines);
    if(undefines!=NULL)        list_Free(undefines);
    if(libs!=NULL)     list_Free(libs);

    if(pass1PassThrus!=NULL)   list_Free(pass1PassThrus);
    if(pass2PassThrus!=NULL)   list_Free(pass2PassThrus);
    if(optPassThrus!=NULL)     list_Free(optPassThrus);
    if(linkPassThrus!=NULL)    list_Free(linkPassThrus);

    if(ops!=NULL)      list_Free(ops);

    return status;
}

static void init()
{
    int *opt;

    options_Init();
    for(opt=defaultOptions; *opt!=0; opt++)
       options_Set(*opt);

    cmdLine=dstr_Create(30);           /* global command buffer */

    libDirs=list_Create((void **)libDirsInit);
    incDirs=list_Create((void **)incDirsInit);
    binDirs=list_Create((void **)binDirsInit);
    defines=list_Create((void **)definesInit);
    undefines=list_Create((void **)undefinesInit);
    libs=list_Create((void **)libsInit);

    pass1PassThrus=list_Create(NULL);
    pass2PassThrus=list_Create(NULL);
    optPassThrus=list_Create(NULL);
    linkPassThrus=list_Create(NULL);

    ops=list_Create(NULL);
    list_SetFree(ops,op_Free);
}

void main(argc, argv)
int argc;
char *argv[];
{
    int status;

    DBUG_ENTER("main");

    onexit(cleanup);
    init();

    onbreak(abortmsg);

    if(parseCommandLine(argc,argv)==0)
       fatal("no files specified");

    cleanObjects(FALSE);       /* get rid of existing object files if any */

    status=makeObjs();
    if(status==0 && options_IsSet(OPT_LINK)){
       status=link();
       if(list_GetLen(ops)==1)
           cleanObjects(TRUE);
    }

    exit(status);
}

/*
 *     The following macro is used to allow optional whitespace between
 *     an option and it's argument.  Argp is left pointing at the option
 *     and argv and argc are adjusted accordingly if necessary.
 *
 *     Note that there is no check for missing option arguments.  In
 *     particular, -o -V will blindly take -V as the output file name.
 *
 */

#define XARG(argc,argv,argp) {if(*++argp=='\0'){argp=(*argv++);argc--;}}

static int parseCommandLine(argc,argv)
int argc;
char **argv;
{
    register char *argp;    

    DBUG_ENTER("parseCommandLine");

    argc--;
    argv++;
    while(argc-- > 0){
       CHECK_ABORT;

       argp=*argv++;

       if(*argp=='+')
           options_SetByName(argp+1);
       else if(*argp!='-')    
           list_Add(ops,(void *)op_Create(argp));
       else
           switch(*++argp){
               case 'c':
                   options_Clear(OPT_LINK);    /* don't link object modules */
                   break;
               case 'p':                       /* pass thru args to a pass */
                   switch(*++argp){
                       case '1':               /* pass 1 */
                           XARG(argc,argv,argp);                       
                           list_Add(pass1PassThrus,argp);
                           break;
                       case '2':               /* pass 2 */
                           XARG(argc,argv,argp);                       
                           list_Add(pass2PassThrus,argp);
                           break;
                       case 'o':               /* optimizer */
                           XARG(argc,argv,argp);                       
                           list_Add(optPassThrus,argp);
                           break;
                       case 'l':               /* linking */
                           XARG(argc,argv,argp);                       
                           list_Add(linkPassThrus,argp);
                           break;
                       default:
                           fatal("unknown pass '%c'; must be one of [12ol]",argp[-1]);
                   }
                   break;
               case 'D':
                   XARG(argc,argv,argp);
                   list_Add(defines,(void *)argp);
                   break;
               case 'E':
                   warning("-E unimplemented, converted to -P instead");
                   /* fall through */
               case 'P':
                   options_Clear(OPT_COMPILE);
                   options_Clear(OPT_ASSEMBLE);
                   options_Clear(OPT_LINK);
                   break;
               case 'g':
                   if(*++argp!='\0') /* optional debuggin level */
                       debugLevel=atoi(argp);
                   options_Set(OPT_DEBUG);
                   break;
               case 'I':
                   XARG(argc,argv,argp);
                   list_Add(incDirs,(void *)argp);
                   break;
               case 'l':
                   XARG(argc,argv,argp);
                   list_Add(libs,(void *)argp);
                   break;
               case 'L':
                   XARG(argc,argv,argp);
                   list_Add(libDirs,(void *)argp);
                   break;
               case 'B':
                   XARG(argc,argv,argp);
                   list_Add(binDirs,(void *)argp);
                   break;
               case 'O':
                   options_Set(OPT_OPTIMIZE);
                   break;
               case 'o':
                   XARG(argc,argv,argp);
                   outfile=argp;
                   break;
               case 't':               /* warning, non-standard */
                   XARG(argc,argv,argp);
                   tempDir=argp;
                   break;
               case 'S':
                   warning("-S option not yet implemented, ignored");
                   options_Clear(OPT_ASSEMBLE);
                   break;
               case 'U':
                   XARG(argc,argv,argp);
                   list_Add(undefines,(void *)argp);
                   break;
               default:
                   fatal("unknown option '%c'",*argp);
                   break;
           }
    }

    DBUG_RETURN(int,list_GetLen(ops));
}

/*
 *     For each operand, do compilation or assembly as necessary, to
 *     reduce to an object file in the current directory.
 */
static int makeObjs()
{
    int n;
    void **els;
    int numOps=list_GetLen(ops);
    int status=0;

    DBUG_ENTER("makeObjs");

    for(n=numOps,els=list_GetEls(ops); n>0; n--,els++){
       struct op *op=(struct op *)*els;

       CHECK_ABORT;

       if(numOps>1 && (op_IsC(op) || op_IsS(op)))
           printf("%s.%s:\n",op->rootname,op->suffix);

       if(op_IsC(op))
           status=compile(op);
       else if(op_IsS(op))
           status=assemble(op);
    }

    DBUG_RETURN(int,status);
}

/* handy little routine */
void addfList(ds,fmt,list)
struct dstr *ds;
char *fmt;
struct list *list;
{
    int n;
    void **els;

    for(n=list_GetLen(list),els=list_GetEls(list); n>0; n--,els++)
       dstr_Addf(ds,fmt,(char *)*els);
}

/*
 *     Note that commands to cc of the form "-l<name>" get interpreted
 *     to mean use a library called "name.lib" from the library
 *     directory.
 *      -lm is specially interpreted to be the appropriate math library.
 */
static int link()
{
    int n;
    void **els;

    DBUG_ENTER("link");

    dstr_Clear(cmdLine);

    dstr_Addf(cmdLine,"%s ",locate("blink",binDirs));

    if(options_IsSet(OPT_DETACH))
       dstr_Append(cmdLine,locate("cback.o",libDirs));
    else if(options_IsSet(OPT_RESIDENT)){
       if(options_IsSet(OPT_CATCH))
           dstr_Append(cmdLine,locate("catchres.o",libDirs));
       else
           dstr_Append(cmdLine,locate("cres.o",libDirs));
    }else if(options_IsSet(OPT_CATCH))
       dstr_Append(cmdLine,locate("catch.o",libDirs));
    else
       dstr_Append(cmdLine,locate("c.o",libDirs));

    for(n=list_GetLen(ops),els=list_GetEls(ops); n>0; n--,els++){
       struct op *op=(struct op *)*els;
       char *name=(op_IsO(op) ? op->rootname : op->basename);

       dstr_Addf(cmdLine,"+%s.o",name);
    }

    if(options_IsSet(OPT_TINYMAIN))
       dstr_Addf(cmdLine," define __main=__tinymain ");

    dstr_Addf(cmdLine," library ");
    for(n=list_GetLen(libs),els=list_GetEls(libs); n>0; n--,els++){
       char buffer[50];
       char *lib=(char *)*els;

       if(strcmp(lib,"m")==0){
           /* map -lm to appropiate library */

           strcpy(buffer,"lib:lcm");

           if(options_IsSet(OPT_FFP))
               strcat(buffer,"ffp");
           else if(options_IsSet(OPT_IEEE))
               strcat(buffer,"ieee");
           else if(options_IsSet(OPT_881))
               strcat(buffer,"881");

           /*
            * there are only r and s libraries for lcm.lib in the lattice
            * distribution; if there is a way to get the lcmieee, lcmffp,
            * & lcm881 libraries to work with reg-args & short-ints, I
            * couldn't find it.
            */
           if(options_IsSet(OPT_SHORTINTS))
               strcat(buffer,"s");
           if(options_IsSet(OPT_REGARGS))
               strcat(buffer,"r");

           strcat(buffer,".lib");
       }else if(strcmp(lib,"lc")==0){
           /* do the standard c library */

           strcpy(buffer,"lib:lc");

           if(options_IsSet(OPT_SHORTINTS))
               strcat(buffer,"s");
           if(options_IsSet(OPT_REGARGS))
               strcat(buffer,"r");
           if(options_IsSet(OPT_ABSDATA))
               strcat(buffer,"nb");

           strcat(buffer,".lib");

           if(options_IsSet(OPT_REGARGS)){
               /*
                * Now, do it AGAIN, but without registers, to catch any missing refs.
                * I don't think this is exactly the right thing to do, but there seem
                * to be internal functions missing from the register-fied libraries
                */

               strcat(buffer,"+lib:lc");

               if(options_IsSet(OPT_SHORTINTS))
                   strcat(buffer,"s");
               if(options_IsSet(OPT_ABSDATA))
                   strcat(buffer,"nb");

               strcat(buffer,".lib");
           }
       }else{
           sprintf(buffer,"%s.lib",lib);
           strcpy(buffer,locate(buffer,libDirs));
       }           

        if(n==1)
           dstr_Append(cmdLine,buffer);
       else
           dstr_Addf(cmdLine,"%s+",buffer);
    }

    if(outfile==NULL)
       /* there better be at least one source file! */
       outfile=((struct op *)list_GetEls(ops))->basename;

    dstr_Addf(cmdLine," to %s map nil:",outfile);

    addfList(cmdLine," %s",linkPassThrus);

    DBUG_RETURN(int,runCmd(cmdLine));
}

/*
 *     compile one operand from a C source program to an object module.
 */
static int compile(op)
struct op *op;
{
    int status=0;

    DBUG_ENTER("compile");

    if(options_IsSet(OPT_ASSEMBLE)){
       status=pass1(op);
       if(status==0 && options_IsSet(OPT_COMPILE)){
           CHECK_ABORT;
           if(options_IsSet(OPT_OPTIMIZE))
               status=optimize(op);
           if(status==0)
               status=pass2(op);
       }
    }

    DBUG_RETURN(int,status);
}

/*
 *     Note that because of brain-damage in the fact that -p to lc1 removes
 *     all predefined defs, we must add them so replacing -c with -P in the
 *     cc command line will result in the same set of predefined symbols.
 *     This is rather ugly and leaves a hole for future problems if we
 *     get out of sync with respect to what names the compiler predefines.
 */
static int pass1(op)
register struct op *op;
{
    char tmpFile[MAXPATH];
    int status;
    int n;
    void **els;

    DBUG_ENTER("pass1");

    dstr_Clear(cmdLine);

    dstr_Append(cmdLine,locate((options_IsSet(OPT_BIGLC1) ? "lc1b" : "lc1"),binDirs));

    if(options_IsSet(OPT_DEBUG)){
       dstr_Addf(cmdLine," -d%d",debugLevel);
       if(options_IsSet(OPT_OPTIMIZE)){
           warning("-g incompatible with -O, -O turned off");
           options_Clear(OPT_OPTIMIZE);
       }
    }

    if(options_IsSet(OPT_FFP))
       dstr_Addf(cmdLine," -ff");
    else if(options_IsSet(OPT_881))
       dstr_Addf(cmdLine," -f8");
    else if(options_IsSet(OPT_IEEE))
       dstr_Addf(cmdLine," -fi");
    else
       dstr_Addf(cmdLine," -fl");

    if(options_IsSet(OPT_SHORTINTS))
       dstr_Addf(cmdLine," -w");
    if(options_IsSet(OPT_LONGALIGN))
       dstr_Addf(cmdLine," -l");

    if(options_IsSet(OPT_020))
       dstr_Addf(cmdLine," -m2");
    else if(options_IsSet(OPT_030))
       dstr_Addf(cmdLine," -m3");

    if(options_IsSet(OPT_CATCH) && !options_IsSet(OPT_ABSDATA)){
       warning("+catch implies +abs-data");
       options_Set(OPT_ABSDATA);
    }

    dstr_Addf(cmdLine," -b%d",options_IsSet(OPT_ABSDATA) ? 0 : 1);
    dstr_Addf(cmdLine," -r%d",options_IsSet(OPT_ABSCODE) ? 0 : 1);

    if(options_IsSet(OPT_REGARGS))
       dstr_Addf(cmdLine,"r"); /* part of -r switch */

    {
       char buf[15], *p=buf;

       if(options_IsSet(OPT_ANSI))             *p++='a';
       if(options_IsSet(OPT_CPP))              *p++='+';
       if(options_IsSet(OPT_TRAD))             *p++='l', *p++='o';
       if(options_IsSet(OPT_REGARGS))          *p++='r';
       if(options_IsSet(OPT_PURESTRINGS))      *p++='s';
       
       if(p>buf){
           *p='\0';
           dstr_Addf(cmdLine," -c%s",buf);
       }
    }

    if(options_IsSet(OPT_COMPILE)){
       strcpy(tmpFile,tempDir);
       strcat(tmpFile,op->basename);
       strcat(tmpFile,".q");
       dstr_Addf(cmdLine," -o%s",tmpFile);
    }else{
       strcpy(tmpFile,op->basename);
       strcat(tmpFile,".pp");

       dstr_Addf(cmdLine," -o%s -p",tmpFile);
    }

    for(n=list_GetLen(undefines),els=list_GetEls(undefines); n>0; n--,els++){
       /*************************
       dstr_Addf(cmdLine," -u%s",(char *)*els);
       **************************/
       warning("-U%s ignored! (unimplemented)",(char *)*els);
    }

    addfList(cmdLine," -d%s",defines);
    addfList(cmdLine," %s",pass1PassThrus);

    for(n=list_GetLen(incDirs),els=list_GetEls(incDirs); n>0; n--,els++){
       char *id=(*els);
       int len=strlen(id);

       dstr_Addf(cmdLine," -i%s",*els);

       if(len>0 && id[len-1]!=':' && id[len-1]!='/')
           dstr_Addf(cmdLine,"/");
    }

    dstr_Addf(cmdLine," %s",op->rootname);

    status=runCmd(cmdLine);
    if(status==0 && options_IsSet(OPT_EXEC) && !readable(tmpFile))
       status=1;

    DBUG_RETURN(int,status);
}

/* run the optimizer */
static int optimize(op)
struct op *op;
{
    DBUG_ENTER("optimize");

    dstr_Clear(cmdLine);
    dstr_Addf(cmdLine,"%s %s%s",locate("go",binDirs),tempDir,op->basename);

    addfList(cmdLine," %s",optPassThrus);

    DBUG_RETURN(int,runCmd(cmdLine));
}

/*
 *     Run second pass of compiler on a single operand.
 */
static int pass2(op)
struct op *op;
{
    char objFile[MAXPATH];
    int status;

#ifdef DBUG
    fprintf(stderr,"Entering pass2...\n");
#endif

    DBUG_ENTER("pass2");

    dstr_Clear(cmdLine);

    dstr_Append(cmdLine,locate("lc2",binDirs));

    if(!options_IsSet(OPT_STACKCHECK))
       dstr_Addf(cmdLine," -v");

    strcpy(objFile,op->basename);
    strcat(objFile,".o");

    dstr_Addf(cmdLine," -o%s",objFile);

    dstr_Addf(cmdLine," %s%s",tempDir,op->basename);

    addfList(cmdLine," %s",pass2PassThrus);

    status=runCmd(cmdLine);
    if(status==0 && options_IsSet(OPT_EXEC) && !readable(objFile))
       status=1;

    DBUG_RETURN(int,status);
}

/*
 *     I have not yet had occasion to use the macro assembler,so this
 *     part is not yet implemented.  If anyone wants to send me the
 *     appropriate code, I will be glad to install it.
 */
static int assemble(op)
struct op *op;
{
    DBUG_ENTER("assemble");
    warning("assembly pass not yet implemented");
    DBUG_RETURN(int,1);
}

static void filteredOutputFrom(source,filter)
FILE *source;
char **filter;
{
    char buf[200];

    DBUG_ENTER("FilteredOutputFrom");

    while(!feof(source)){
       char **ignore;

       fgets(buf,sizeof(buf),source);

       for(ignore=filter; *ignore!=NULL; ignore++)
           if(strncmp(*ignore,buf,strlen(*ignore))==0)
               break;

       if(*ignore==NULL)
           fputs(buf,stdout);
    }

    DBUG_VOID_RETURN;
}

#ifdef AMIGA

static int execToFile(cmd,sink)
char *cmd;
FILE *sink;
{
    /* this is a dopey version that uses Execute, since I can't get fork to work */
    long fh;

    DBUG_ENTER("execToFile");

    if(sink==NULL)
       fh=0;
    else
       fh=chkufb(fileno(sink))->ufbfh;

    DBUG_RETURN(int,Execute(cmd,0L,fh)!=(-1));

#if 0
    char *argv[50],**argp=argv;
    struct ProcID child;
    static struct FORKENV env={0,0,0,0,0,NULL};
    int i;

    DBUG_ENTER("execToFile");

    if(cmd==NULL)
       DBUG_RETURN(int,1);

    /* this is sort of stupid, cause forkv will just put them back together,
     * but oh well...
     */
    *argp=strtok(cmd," \t");
    while(*argp!=NULL)
       *++argp=strtok(NULL," \t");

    if(sink==NULL)
        env.std_out=0;
    else{
       struct UFB *ufb=chkufb(fileno(sink));
       int i;

       printf("argv[%d]:",argp-argv);
       for(argp=argv;*argp!=NULL;argp++)
           printf(" %s",*argp);
       putchar('\n');

       printf("env: %d, %d, %d, %d, %d, %d\n",
               env.priority, env.stack, env.std_in, env.std_out,
               env.console, env.msgport);

       printf("ufb==0x%x, fh==0x%x, go? ",ufb,ufb->ufbfh);

       fflush(stdout);

       env.std_out=ufb->ufbfh; /* magic to get amigados handle from FILE */
    }

    if((i=forkv(argv[0],argv,&env,&child))==-1){
        poserr("forkv");
       DBUG_RETURN(int,1);
    }

    printf("Fork returns: %d\n",i);

    DBUG_RETURN(int,wait(&child));
#endif
}

#endif
 
static int runCmd(cmd)
struct dstr *cmd;
{
    int status;
    char *buf=dstr_GetBuf(cmd);
    
    DBUG_ENTER("RunCommand");

    DBUG_3("cmd","execute '%s'",buf);
    if(options_IsSet(OPT_ECHO)){
       puts(buf);
       fflush(stdout);
    }

    CHECK_ABORT;

    if(options_IsSet(OPT_EXEC)){
       FILE *sink;

       if(sinkFile!=NULL && options_IsSet(OPT_FILTER)){
           sink=fopen(sinkFile,"w+");
           if(sink==NULL)
               perror(sinkFile);
       }else
           sink=NULL;

       status=execToFile(buf,sink);
       DBUG_3("sys","subcommand returns status %d",status);

       if(sink!=NULL){
           rewind(sink);
           filteredOutputFrom(sink,filteredPrefixes);
           fclose(sink);
           unlink(sinkFile);
       }

       DBUG_RETURN(int,status);
    }else
       DBUG_RETURN(int,0);
}

/*
 *     Look through the list of paths pointed to by "vec" until we find
 *     a file with name given pointed to by "namep".  If none is found,
 *     the name pointed to by namep is returned.
 */
static char *locate(name,paths)
char *name;
struct list *paths;
{
    static char namebuf[MAXPATH];
    void **els;
    int n;
    
    DBUG_ENTER("locate");

    if(strchr(name,':')!=NULL || *name=='/')
       return name;    /* absolute */
       
    for(n=list_GetLen(paths),els=list_GetEls(paths); n>0; n--,els++){
       int len=strlen((char *)*els);

       strcpy(namebuf,(char *)*els);
       if(len>0 && namebuf[len-1]!=':' && namebuf[len-1]!='/')
           strcat(namebuf,"/");
       strcat(namebuf,name);

       DBUG_3("try","look for '%s'",namebuf);
       if(readable(namebuf)){
           name=namebuf;
           break;
       }
    }

    DBUG_RETURN(char *,name);
}

/*
 *     Check to see if the file exists and is readable.
 */
static int readable(name)
char *name;
{
    register int status=0;
    register long fildes;
    
    DBUG_ENTER("readable");

#ifdef unix
    fildes=open(name,O_RDONLY);
    if(fildes >= 0){
       (void) close(fid4s);
       status=1;
    }
#else
    fildes=Lock(name,ACCESS_READ);
    if(fildes != 0){
       UnLock(fildes);
       status=1;
    }
#endif

    DBUG_RETURN(int,status);
}

/*
 *     If an executable is made from a single C file, the normal behavior
 *     for the unix environment is to treat the .o file as an intermediate
 *     file and remove it, so we follow suit.
 */
static void cleanObjects(mustExist)
int mustExist;
{
    int n;
    void **els;

    DBUG_ENTER("cleanObjects");
       
    for(n=list_GetLen(ops),els=list_GetEls(ops); n>0; n--,els++){
       char buf[MAXPATH];
        struct op *op=(struct op *)*els;

       if(!op_IsO(op)){
           strcpy(buf,op->basename);
           strcat(buf,".o");
           if(unlink(buf)!=0 && mustExist)
               warning("can't delete '%s'",buf);
        }
    }

    DBUG_VOID_RETURN;
}
