/* app.c - 68000 assembly pre-processor
 *
 * written by Karl Lehenbauer (uunet!sugar!karl or karl@sugar.hackercorp.com)
 * original release Dec-08-1988
 *
 * PUBLIC DOMAIN -- no warranties of usefulness, suitabilitiy, correctness
 *
 * Jan-09-1990 Brett Bourbin (Selgus Limited) changed input/output file
 *             handling, incorrect text strings and added the dbra construct.
 *
 * April-28-1990  Release 2.0
 *  Rewrote filename-handling code to provide the -o option with backward
 *   compatibility. Updated the docs plus converted to ANSI C.
 */

#include <stdio.h>
#include <assert.h>

#include "app.pro"

#define YES 1
#define NO 0

#define MATCH 0

int current_nest_level = -1;
int master_label_number = 0;
unsigned int line_number = 0;

char *label;
char *opcode;
char *operands;
char *comment;

char *input_filename, *input_extension, *output_filename;

panic(char *s)
{
       fprintf(stderr,"app: %s at line %d\n",s,line_number);
       fclose(stdout);
       unlink(output_filename);
       exit(1);
}

/* given a pointer to a line, make the global character pointers "label",
 * "opcode", "operands" and "comment" point to the various fields within
 * the line, or NULL for any that aren't present.  Note that crackline
 * butchers the line up by writing null bytes into it.
 */
crackline(char *s)
{
       register char *p = s;

       label = NULL;
       opcode = NULL;
       operands = NULL;
       comment = NULL;

       /* suck up leading blanks */
       while ((*p == ' ') || (*p == '\t'))
               p++;

       /* if end of line, return -- it's an empty line */
       if (*p == '\0')
               return;

       /* if the first nonblank char is a semicolon, it's a comment */
       if (*p == ';')
       {
               comment = s;
               return;
       }

       /* if the very first char isn't blank (and we already know it's
          not a semicolon), it's a label
        */
       if ((*s != ' ') && (*s != '\t'))
       {
               label = s;
               p = s + 1;
               while (*p != ' ' && *p != '\t' && *p != '\0') p++;
               if ((*p == ' ') || (*p == '\t'))
               {
                       *p = '\0';
                       p++;
               }
               else
                       return;
       }
       else    /* there isn't a label, suck up spaces to next parm */
       {
               p = s;
               while ((*p == ' ' || *p == '\t')) p++;
               if (*p == '\0')
                       return;
       }

       /* if the next parm is a comment, assign and we're done */
       if (*p == ';')
       {
               comment = p;
               return;
       }

       /* we're at the opcode, assign it and terminate with \0 if spaces
        * follow, else we're done */
       opcode = p;
       while (*p != ' ' && *p != '\t' && *p != '\0') p++;
       if ((*p == ' ') || (*p == '\t'))
       {
               *p = '\0';
               p++;
       }
       else
               return;

       /* if the next parm is a comment, assign and we're done */
       if (*p == ';')
       {
               comment = p;
               return;
       }

       operands = p;
       while (*p != ' ' && *p != '\t' && *p != '\0') p++;
       if ((*p == ' ') || (*p == '\t'))
       {
               *p = '\0';
               p++;
       }
       else
               return;

       comment = p;

}

#ifdef DEBUG
dumpit(void)
{
       printf("label: %s, opcode %s, operands %s, comment %s\n",label,opcode,operands,comment);
}
#endif

char s[255], ssave[255];

#define IF_STATEMENT_TYPE 1Xdefine ELSE_STATEMENT_TYPE 2
#define DO_STATEMENT_TYPE 3

#define MAX_NESTING_LEVELS 32

struct nesting_context
{
       int construct_type;
       int label_number;
       int second_label_number;
};

struct nesting_context nesting_data[MAX_NESTING_LEVELS];

/* push - push a nesting construct context, executed on .if and .do */
push(int new_construct_type,int label,int second_label)
{
       struct nesting_context *np;

       if (++current_nest_level >= MAX_NESTING_LEVELS)
               panic("too many nesting levels");

       np = &nesting_data[current_nest_level];
       np->construct_type = new_construct_type;
       np->label_number = label;
       np->second_label_number = second_label;
}

/* pop - discard the top nesting context, checking for underflow
 *  called when conditionals have been successfully closed
 */
pop(void)
{
       if (current_nest_level >= 0)
               --current_nest_level;
       else
               panic("'endif', 'enddo' or 'dbra' without a matching 'if' or 'do'");
}

/* generate and return new label number */
newlabel(void)
{
       return(master_label_number++);
}

/* structure in support of reversing the sense of conditionals */
struct condition_code_struct
{
       char *condition;
       char *reverse_condition;
};

#define N_CONDITION_TYPES 14

struct condition_code_struct condition_code_array[N_CONDITION_TYPES] =
{
       {"ne", "eq"},
       {"eq", "ne   (freopen(input_filename,"r",stdin) == NULL)
       {
               perror(input_filename);
               exit(5);
       }

       /* create output filename if necessary, by overwriting input name */
       if (!output_filename)
       {
               strcpy(input_extension,".asm");
               output_filename = input_filename;
       }

       if (freopen(output_filename,"w",stdout) == NULL)
       {
               perror(output_filename);
               exit(5);
       }

       preprocess_file();
       return(0);
}

preprocess_file(void)
{
       struct nesting_context *np;
       int i;

       /* for all lines in the file */
       while (gets(s) != NULL)
       {
               line_number++;  /* count the line */

               /* if it's not promising, copy it to output and go on */
               if (!looks_promising(s))
               {
                       printf("%s\n",s);
                       goto more;
               }

               strcpy(ssave,s);
               crackline(s);

               if (strcmp(opcode,".if") == MATCH)
               {
                       printf("\tb%s\tL%d\n",reverse_sense_of(operands),i = newlabel());
                       push(IF_STATEMENT_TYPE,i,-1);
               }
               else if (strcmp(opcode,".else") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if (np->construct_type != IF_STATEMENT_TYPE)
                               panic("'else' without 'if'");

                       printf("\tbra\tL%d\n",i = newlabel());
                       /* print the label from the top context */
                       print_label(np->label_number);
                       np->label_number = i;
                       np->construct_type = ELSE_STATEMENT_TYPE;
               }
               else if (strcmp(opcode,".endif") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if ((np->construct_type != IF_STATEMENT_TYPE)
                         && (np->construct_type != ELSE_STATEMENT_TYPE))
                               panic("'endif' without 'if' or 'else'");

                       print_label(np->label_number);
                       pop();
               }
               else if (strcmp(opcode,".elseif") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if (np->construct_type != IF_STATEMENT_TYPE)
                               panic("'else' without 'if'");

                       printf("\tbra\tL%d\n",i = newlabel());
                       /* print the label from the top context */
                       print_label(np->label_number);
                       np->label_number = i;
                       printf("\tb%s\tL%d\n",reverse_sense_of(operands),i);
               }
               else if (strcmp(opcode,".do") == MATCH)
               {
                       print_label(i = newlabel());
                       push(DO_STATEMENT_TYPE,i,newlabel());
               }
               else if (strcmp(opcode,".while") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if (np->construct_type != DO_STATEMENT_TYPE)
                               panic("'while' without 'do'");

                       printf("\tb%s\tL%d\n",reverse_sense_of(operands),np->second_label_number);
               }
               else if (strcmp(opcode,".enddo") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if (np->construct_type != DO_STATEMENT_TYPE)
                               panic("'enddo' without 'do'");

                       printf("\tbra\tL%d\n",np->label_number);
                       print_label(np->second_label_number);
                       pop();

               }
               else if (strcmp(opcode,".dbra") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if (np->construct_type != DO_STATEMENT_TYPE)
                               panic("'dbra' without 'do'");

                       printf("\tdbra\t%s,L%d\n",operands,np->label_number);
                       print_label(np->second_label_number);
                       pop();

               }
               else if (strcmp(opcode,".until") == MATCH)
               {
                       np = &nesting_data[current_nest_level];

                       if (np->construct_type != DO_STATEMENT_TYPE)
                               panic("'until' without 'do'");

                       printf("\tb%s\tL%d\n",operands,np->second_label_number);
               }
               else
                       printf("%s\n",ssave);
       more: ;
       }
       if (current_nest_level >= 0)
               panic("didn't close all your control structures");
}

