/*program: ax.  assemble and execute CSC258 assembly language programs*/
/* written 2003 September 2.  author: E.Hehner. */
/* modified 2004 March 7 by Hehner */
/*         to fix UNIX input problem, endian problem, and allow tabs */
/* modified 2005 January 21 by Hehner to make padding uniform */
/*         and to get rid of message on normal return */
/* modified 2005 November 14 by Hehner to fix OUT instruction */
/* modified 2005 December 13 by Hehner to add CFI instruction */
/* modified 2007 June 22 by Hehner to change CFI and CIF instructions */
/*         so that they load AC, and to give better trace output */
/* modified 2007 June 24 by Hehner to allow absolute addresses */
/* modified 2007 December 19 by Hehner to add A format, */
/*         remove most quotes, and allow blank lines */

#define MaxLineLen 100
#define MaxIdLen 20
#define RamSize 5000
#define LabTabSize 500
  
#include <signal.h>     /*UNIX*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
FILE *fpin, *fopen();
char line[MaxLineLen+2];

/*op-codes*/
#define numops 22
char ops[numops+1][4] = {"LDA", "STA", "ADD", "SUB", "MUL",
  "DIV", "MOD", "FLA", "FLS", "FLM", "FLD", "CIF", "CFI", "AND",
  "IOR", "XOR", "BUN", "BZE", "BSA", "BIN", "INP", "OUT"};

/*hardware*/
union {unsigned int u; signed int i; float f;} RAM[RamSize], AC;
unsigned char IRop;  unsigned int IRadr;  /* IR in two parts */
unsigned int PC;
unsigned char E;

void assemble (char show) /*initializes RAM and PC*/
{ struct {char id[MaxIdLen+1]; unsigned char def; unsigned int adr;}
    labeltable[LabTabSize]
  = {{"main", 0, RamSize}, {"opsys", 1, RamSize}};
  int lastlabel = 1;  int thislabel;
  int currentRAMadr = 0;  int aRAMadr, bRAMadr;
  int h, i, j, k; /*positions in line*/
  int thisop;
  unsigned int uu;  char cc;  /*used for reading into*/

  while (fgets(line, MaxLineLen+2, fpin)!=NULL)
  { if (currentRAMadr>=RamSize)
      {printf("assemble: memory size too small\n"); exit(0);}
    i=0; label: while(line[i]==' ' || line[i]=='\t') i++;
    if(line[i]=='\r' || line[i]=='\n') continue; /*restart the loop*/
    j=i; while(line[j]!=' ' && line[j]!='\t' && line[j]!=':'
               && line[j]!='\'' && line[j]!='\r' && line[j]!='\n') j++;
    k=j; while(line[k]==' ' || line[k]=='\t') k++;
    if(j>i && line[k]==':') /*we have a label*/
    { if(j-i>MaxIdLen)
        {printf("assemble: identifier too long\n%s\n", line); exit(0);}
      h=0; while(i<j) {labeltable[lastlabel+1].id[h] = line[i]; h++; i++;}
      labeltable[lastlabel+1].id[h] = '\0';
      thislabel = 0;
      while(strcmp(labeltable[thislabel].id, labeltable[lastlabel+1].id)!=0)
        thislabel++;
      if(thislabel<=lastlabel) /*it was already there*/
      { if(labeltable[thislabel].def)
          {printf("assemble: duplicate label\n%s\n", line); exit(0);}
        /*fixup forward addresses*/
        aRAMadr = labeltable[thislabel].adr;
        while(aRAMadr!=RamSize)
        { bRAMadr = RAM[aRAMadr].u & 0x00FFFFFF;
          RAM[aRAMadr].u = (RAM[aRAMadr].u & 0xFF000000) | currentRAMadr;
          aRAMadr = bRAMadr;
        } /*end while*/
      } else lastlabel = thislabel;
      labeltable[thislabel].def = 1;
      labeltable[thislabel].adr = currentRAMadr;
      i = k+1; goto label;
    } /*end of label processing*/

    if(i==j) {printf("assemble: bad format\n%s\n", line); exit(0);}
    /*now i<j<=k
      and (line[i] is not blank nor tab nor colon nor quote nor return nor newline)
      and (line[j] is blank or tab or colon or quote or return or newline)
      and (line[k] is not blank nor tab nor colon) */
    /*process instruction or data*/
    if(j-i==3) /*op-code*/
    { h=0; while(i<j) {ops[numops][h] = line[i];  h++;  i++;}
      ops[numops][h] = '\0';
      thisop = 0; while(strcmp(ops[thisop], ops[numops])!=0) thisop++;
      if(thisop==numops)
        {printf("assemble: bad format\n%s\n", line); exit(0);}
      RAM[currentRAMadr].u = thisop * 0x01000000;
      /*find address starting at line[k]*/
      if('0'<=line[k] && line[k]<='9') /*we have an absolute address*/
      { aRAMadr = line[k] - '0';  k++;
        while('0'<=line[k] && line[k]<='9')
        { aRAMadr = aRAMadr*10 + line[k] - '0';  k++; }
        RAM[currentRAMadr].u += aRAMadr; /*no check for too big address*/
      } else
      { i=j=k;
        while(line[j]!=' ' && line[j]!='\t' && line[j]!=':'
              && line[j]!='\'' && line[j]!='\r' && line[j]!='\n') j++;
        if(i==j){printf("assemble: bad format\n%s\n", line); exit(0);}
        /*we have an identifier address*/
        if(j-i>MaxIdLen)
          {printf("assemble: identifier too long\n%s\n", line); exit(0);}
        h=0; while(i<j) {labeltable[lastlabel+1].id[h] = line[i]; h++; i++;}
        labeltable[lastlabel+1].id[h] = '\0';
        thislabel = 0;
        while(strcmp(labeltable[thislabel].id, labeltable[lastlabel+1].id)!=0)
          thislabel++;
        if(thislabel<=lastlabel) /*it was already there*/
          if(labeltable[thislabel].def)
            RAM[currentRAMadr].u += labeltable[thislabel].adr;
          else /*it's another forward reference*/
          { RAM[currentRAMadr].u += labeltable[thislabel].adr;
            labeltable[thislabel].adr = currentRAMadr;
          }
        else /*it's a new forward reference*/
        { labeltable[thislabel].def = 0;
          labeltable[thislabel].adr = currentRAMadr;
          RAM[currentRAMadr].u += RamSize;
          lastlabel = thislabel;
        }
      } currentRAMadr++;
    } else if(j-i==1) /*data or W*/
    { if(line[i]=='I')
      { if(sscanf(&line[k], "%d", &RAM[currentRAMadr].i)!=1)
          {printf("assemble: bad format\n%s\n", line); exit(0);}
        currentRAMadr++;
      } else if(line[i]=='F')
      { if(sscanf(&line[k], "%e", &RAM[currentRAMadr].f)!=1)
          {printf("assemble: bad format\n%s\n", line); exit(0);}
        currentRAMadr++;
      } else if(line[i]=='C' && line[k]=='\'')
      { uu = 0;  i = j = k+1;  nextchar:
        if(line[j]=='\'') {RAM[currentRAMadr].u = uu; currentRAMadr++;}
        else if (j-i<4) {uu = uu*256 + line[j]; j++; goto nextchar;}
        else {printf("assemble: bad format\n%s\n", line); exit(0);}
      } else if(line[i]=='B' && (line[k]=='0' || line[k]=='1'))
      { uu = 0;  i = j = k;  nextbit:
        if (j-i<32 && line[j]=='0') {uu = uu*2;  j++;  goto nextbit;}
        else if (j-i<32 && line[j]=='1') {uu = uu*2 + 1;  j++;  goto nextbit;}
        else {RAM[currentRAMadr].u = uu; currentRAMadr++;}
      } else if(line[i]=='H' && (   (line[k]>='0' && line[k]<='9')
                                 || (line[k]>='A' && line[k]<='F')))
      { if(sscanf(&line[k], "%X", &RAM[currentRAMadr].u)!=1)
          {printf("assemble: bad format\n%s\n", line); exit(0);}
        currentRAMadr++;
      } else if(line[i]=='A')
      { /*find address starting at line[k]*/
        if('0'<=line[k] && line[k]<='9') /*we have an absolute address*/
        { aRAMadr = line[k] - '0';  k++;
          while('0'<=line[k] && line[k]<='9')
          { aRAMadr = aRAMadr*10 + line[k] - '0';  k++; }
          RAM[currentRAMadr].u = aRAMadr; /*no check for too big address*/
        } else
        { i=j=k;
          while(line[j]!=' ' && line[j]!='\t' && line[j]!=':'
                && line[j]!='\'' && line[j]!='\r' && line[j]!='\n') j++;
          if(i==j){printf("assemble: bad format\n%s\n", line); exit(0);}
          /*we have an identifier address*/
          if(j-i>MaxIdLen)
            {printf("assemble: identifier too long\n%s\n", line); exit(0);}
          h=0; while(i<j) {labeltable[lastlabel+1].id[h] = line[i]; h++; i++;}
          labeltable[lastlabel+1].id[h] = '\0';
          thislabel = 0;
          while(strcmp(labeltable[thislabel].id, labeltable[lastlabel+1].id)!=0)
            thislabel++;
          if(thislabel<=lastlabel) /*it was already there*/
            if(labeltable[thislabel].def)
              RAM[currentRAMadr].u = labeltable[thislabel].adr;
            else /*it's another forward reference*/
            { RAM[currentRAMadr].u = labeltable[thislabel].adr;
              labeltable[thislabel].adr = currentRAMadr;
            }
          else /*it's a new forward reference*/
          { labeltable[thislabel].def = 0;
            labeltable[thislabel].adr = currentRAMadr;
            RAM[currentRAMadr].u = RamSize;
            lastlabel = thislabel;
          }
        } currentRAMadr++;
      } else if(line[i]=='W')
      { if(sscanf(&line[k], "%d", &uu)!=1)
          {printf("assemble: bad format\n%s\n", line); exit(0);}
        currentRAMadr += uu;
      } else {printf("assemble: bad format\n%s\n", line); exit(0);}
    } else {printf("assemble: bad format\n%s\n", line); exit(0);}
  } /*end while*/

  /*check that all labels are defined*/
  for(thislabel=0;  thislabel<=lastlabel;  thislabel++)
    if(!labeltable[thislabel].def)
    { printf("assemble: unresolved address\n%s\n", labeltable[thislabel].id);
      exit(0);
    }
  PC = labeltable[0].adr; /*set PC to main*/
  if(show=='y')
    for(aRAMadr=0; aRAMadr<currentRAMadr; aRAMadr++)
      printf("%d: %08X\n", aRAMadr, RAM[aRAMadr].u);
} /*end assemble*/

void execute (char trace) /*executes instructions in RAM starting at the
                            address in PC and ending normally with a branch
                            to RamSize (where the operating system should be)
                            or abnormally with an illegal op-code
                            or an address past the end of memory*/
/* arithmetic overflow fails to make E=1 */
{ char cc;  /*used for reading into*/
  while (1)
  { if(PC==RamSize) return;
    if(PC>RamSize)
      {printf("execute: instruction address past end of memory\n"); return;}
    IRop = RAM[PC].u / 0x01000000;  IRadr = RAM[PC].u & 0x00FFFFFF;
    if(trace=='y')
    { printf("To execute %d: %s %d press return. ", PC, ops[IRop], IRadr);
      while ((cc = getchar ( )) != '\n' && cc!= '\r');
    }
    if(IRadr>=RamSize && (IRop<15 || IRop==18))
      {printf("execute: data address past end of memory\n"); return;}
    switch(IRop)
    { case 0:  /*LDA*/ AC = RAM[IRadr]; PC++;  break;
      case 1:  /*STA*/ RAM[IRadr] = AC;  PC++;  break;
      case 2:  /*ADD*/ E = 0;  AC.i += RAM[IRadr].i;  PC++;  break;
      case 3:  /*SUB*/ E = 0;  AC.i -= RAM[IRadr].i;  PC++;  break;
      case 4:  /*MUL*/ E = 0;  AC.i *= RAM[IRadr].i;  PC++;  break;
      case 5:  /*DIV*/ if (RAM[IRadr].i==0) E = 1;
                       else{E=0; AC.i /= RAM[IRadr].i;}
                       PC++;  break;
      case 6:  /*MOD*/ if (RAM[IRadr].i==0) E = 1;
                       else{E=0; AC.i %= RAM[IRadr].i;}
                       PC++;  break;
      case 7:  /*FLA*/ E = 0;  AC.f += RAM[IRadr].f;  PC++;  break;
      case 8:  /*FLS*/ E = 0;  AC.f -= RAM[IRadr].f;  PC++;  break;
      case 9:  /*FLM*/ E = 0;  AC.f *= RAM[IRadr].f;  PC++;  break;
      case 10: /*FLD*/ if (RAM[IRadr].f==0.0) E = 1;
                       else{E=0; AC.f /= RAM[IRadr].f;}
                       PC++;  break;
      case 11: /*CIF*/ AC.f = (float) RAM[IRadr].i;  PC++;  break;
      case 12: /*CFI*/ if (RAM[IRadr].i >= 0.0) AC.i = (int) (RAM[IRadr].f + 0.5);
                       else AC.i = (int) (RAM[IRadr].f - 0.5);
                       PC++;  break;
      case 13: /*AND*/ AC.u &= RAM[IRadr].u;  E = (AC.u!=0);  PC++;  break;
      case 14: /*IOR*/ AC.u |= RAM[IRadr].u;  E = (AC.u!=0);  PC++;  break;
      case 15: /*XOR*/ AC.u ^= RAM[IRadr].u;  E = (AC.u!=0);  PC++;  break;
      case 16: /*BUN*/ PC = IRadr;  break;
      case 17: /*BZE*/ if (E) PC++; else PC = IRadr;  break;
      case 18: /*BSA*/ RAM[IRadr].u = PC+1;  PC = IRadr + 1;  break;
      case 19: /*BIN*/ PC = RAM[IRadr].u & 0x00FFFFFF;  break;
      case 20: /*INP*/ AC.u = getchar ( );  PC++;  break;/*waits; no branch*/
      case 21: /*OUT*/ putchar (AC.u % 256);  PC++;  break; /* no branch */
      default: {printf("execute: illegal op-code\n"); return;}
    } /*end switch*/
    if(trace=='y')
      printf(" Now AC = (hex)%08X and RAM[%d] = (hex)%08X\n", AC.u, IRadr, RAM[IRadr].u);
  } /*end while*/
} /*end execute*/

void finish (int i) {system("stty -cbreak echo"); exit(0);} /*UNIX*/

int main (int count, char *argument[])
{ char show, trace;
  if (count!=2) {printf("%s\n", "usage: ax file"); exit(0);}
  if ((fpin = fopen(argument[1], "r")) == NULL)
  {printf("%s\n", "can't open file"); exit(0);}
  QS: printf("Show memory (in hexadecimal)? (y/n): "); show = getchar();
      while(getchar()!='\n');
      if (show!='y' && show!='n') { printf("What? "); goto QS; }
  assemble (show);
  QT: printf("Trace execution? (y/n): "); trace = getchar();
      while(getchar()!='\n');
      if (trace!='y' && trace!='n') { printf("What? "); goto QT; }
  signal(SIGINT, finish);        /*UNIX*/
  system("stty cbreak -echo");   /*UNIX*/
  execute (trace);
  system("stty -cbreak echo");   /*UNIX*/
  exit(0);
} /*end main*/
