User:Marinus/Word!CPU interpreter

From Esolang
Jump to navigation Jump to search

This is my Word!CPU interpreter (emulator?).

/*
 * wordcpu.c
 * by Marinus Oosters
 *
 * usage:
 * wordcpu -p program [-m file] [-i file] [-I file] [-o file] [-O file]
 *
 * -p : initialize program memory
 * -m : initialize data memory (default: zeroes)
 * -i : input  1 (default: stdin)
 * -o : output 1 (default: stdout)
 * -I : input  2 (default: stderr)
 * -O : output 2 (default: stderr)
 * -d : debug on
 *
 * NOTE: I'm assuming the 8 registers and the 8 'pointers' are the same thing.
 * The specs seem to mean this but it's not explicitly stated. The pointers wrap,
 * the instruction pointer does NOT: jumping out of memory space ends the program.
 *
 * ALSO NOTE: I've tried to keep endianness out of it as much as possible,
 * but this program has only been tested on a low-endian system.
 *
 * I added a Block D command, E, which ends the program/halts the CPU.
 *
 * 1110nnnn nnnnnnnn
 * 
 * This is public domain software. Share and enjoy!
 * 
 *
 */


#include <stdio.h>
#include <getopt.h>

#define ERRSTR "IP: %4x REGS: %2x %2x %2x %2x %2x %2x %2x %2x\n"\
               "DATA MEM: %2x %2x %2x %2x %2x %2x %2x %2x\n"\
               "PGM MEM: %2x %2x\n\n"
#define ERRVAR ip, reg[0],reg[1],reg[2],reg[3],reg[4],reg[5],reg[6],reg[7],\
               dmem[reg[0]],dmem[reg[1]],dmem[reg[2]],dmem[reg[3]],\
               dmem[reg[4]],dmem[reg[5]],dmem[reg[6]],dmem[reg[7]],\
               pmem[ip], pmem[ip+1]

// memory
unsigned char dmem[0x2000], *dpt = dmem;
unsigned char pmem[0x2000], *ppt = pmem;

// registers & pointers
unsigned int reg[8] = {0,0,0,0,0,0,0,0};
int ip = 0, dp = 0;
int debug=0;

usage(){
   printf(
          "usage: wordcpu -p program [-d] [-m file] [-i file] [-I file] [-o file] [-O file]\n"
          "\n"
          " -p : initialize program memory\n"
          " -m : initialize data memory (default: zeroes)\n"
          " -i : input  1 (default: stdin)\n"
          " -o : output 1 (default: stdout)\n"
          " -I : input  2 (default: stderr)\n"
          " -O : output 2 (default: stderr)\n"
          " -d : debug on\n"
          );
   exit(0);
}

die(char*x){
   fprintf(stderr,x);
   exit(1);
}


unsigned char* lhb(int *b, char lh) {
   int foo=40;
   if ( ((char*)&foo)[0] != 40) lh = !lh;
   lh = !!lh;
   return ((unsigned char*)b)+lh;
}

/* split commands into respective fields */
/* all commands are two bytes long, and divided in four blocks:

    byte 1    byte 2
A:  000VVVVV  VVVVVVV0 (the last 0 is actually part of the V)
B:  CCCCXPPP  VVVVVVVV
C:  1111XPPP  CCCCYRRR
D:  CCCCYRRR  VVVVVVVV

   where:
   V = address or value
   C = command
   X = switch between data and program memory (0=program, 1=data)
   P = pointer
   Y = switch between high and low bits (0=low, 1=high)
   R = register

   blocks:
   0 = A; 1 = B; 2 = C; 3 = D

   Return code:
   0 = success
   1 = block error
*/


char getcmd (unsigned char byte1, unsigned char byte2,
             unsigned char *block, unsigned int *v,
             unsigned char *x, unsigned char *p,
             unsigned char *y, unsigned char *r,
             unsigned char *c) {
   
   // determine block
   switch(byte1>>4) {
      case 0: case 1: *block = 0; break;
      case 2: case 3: case 4: case 5: case 6: case 7: case 8: *block = 1; break;
      case 15: *block = 2; break;
      case 9: case 10: case 11: case 14: *block = 3; break;
      default: return 1;
   }
   
   switch(*block) {
      case 0: *v = (((int)byte1)<<8) + (int)byte2; *x=*p=*y=*r=0; break;
      case 1: *c = (byte1>>4);
              *x = ((byte1%16)>>3);
              *p = (byte1%8);
              *v = byte2;
              *y = *r = 0;
              break;
      case 2: *x = ((byte1%16)>>3);
              *p = (byte1%8);
              *c = (byte2>>4);
              *y = ((byte2%16)>>3);
              *r = (byte2%8);
              *v = 0;
              break;
      case 3: *c = (byte1>>4);
              *y = ((byte1%16)>>3);
              *r = (byte1%8);
              *v = byte2;
              *x = *p = 0;
              break;
      default: return 1;
   
   }
   return 0;
} 

int main(int argc, char **argv) {
   
   int foo=0;
   char p,m,i,o,I,O;
   p=m=i=o=I=O=0;
   FILE *pp,*mm,*ii,*oo,*II,*OO;
   
   while((foo=getopt(argc,argv,"p:m:i:o:I:O:d"))!=-1){
      switch(foo) {
         case 'p':
            p=1;if(!(pp=fopen(optarg,"r")))die("Cannot open program file\n");
            break;
         case 'm':
            m=1;if(!(mm=fopen(optarg,"r")))die("Cannot open memory file\n");
            break;
         case 'i':
            i=1;if(!(ii=fopen(optarg,"r")))die("Cannot open file for input 1\n");
            break;
         case 'o':
            o=1;if(!(oo=fopen(optarg,"w")))die("Cannot open file for output 1\n");
            break;
         case 'I':
            I=1;if(!(II=fopen(optarg,"r")))die("Cannot open file for input 2\n");
            break;
         case 'O':
            O=1;if(!(OO=fopen(optarg,"W")))die("Cannot open file for output 2\n");
            break;
         case 'd':
            debug=1;
            break;
      }
   }
   
   // zero memory
   bzero(dmem,0x2000);
   bzero(pmem,0x2000);
   
   // read program file
   if (!p) usage();
   while(!feof(pp))*(ppt++)=fgetc(pp);ppt=pmem;
   // read memory file
   if (m) while (!feof(mm))*(dpt++)=fgetc(mm);dpt=dmem;
   
   i||(ii=stdin);
   o||(oo=stdout);
   I||(II=stderr);
   O||(OO=stderr);
   
   // run
   char block,c,x,y,p_,r;
   int v;
   ip = dp = foo = 0;
   
   while(ip < 0x2000 && ip>-1) {
      // get command:
      if (getcmd(pmem[ip],pmem[ip+1],&block,&v,&x,&p_,&y,&r,&c)) {
         fprintf(stderr, "ERROR: INVALID CMD: cannot determine block.\n" ERRSTR, ERRVAR);
         return 0; 
      }
      // 'debug'
      if (debug) {
         fprintf(stderr, "CMD: %2x %2x :: ip=%4x block=%1x v=%2x x=%2x p=%2x y=%2x r=%2x c=%2x\n"
                         "REG: %2x %2x %2x %2x %2x %2x %2x %2x\n"
                         "DAT: %2x %2x %2x %2x %2x %2x %2x %2x\n"
                         "PGM: %2x %2x %2x %2x %2x %2x %2x %2x\n",
                 pmem[ip]%256,pmem[ip+1]%256,ip,block,v,x,p_,y,r,c,
                 reg[0],reg[1],reg[2],reg[3],reg[4],reg[5],reg[6],reg[7],
                 dmem[reg[0]],dmem[reg[1]],dmem[reg[2]],dmem[reg[3]],dmem[reg[4]],
                 dmem[reg[5]],dmem[reg[6]],dmem[reg[7]],
                 pmem[reg[0]],pmem[reg[1]],pmem[reg[2]],pmem[reg[3]],pmem[reg[4]],
                 pmem[reg[5]],pmem[reg[6]],pmem[reg[7]]);
      }
      switch(block) {
         case 0: /*A*/ ip = v-2; break; // the 2 will be added back at the end.
         case 1: /*B*/
            switch(c) {
               case 2: (x?pmem:dmem)[reg[p_]] += v; break;
               case 3: reg[p_] -= v; reg[p_] %= 0x2000; break;
               case 4: reg[p_] += v; reg[p_] %= 0x2000; break;
               case 5: (x?pmem:dmem)[reg[p_]] = ~((x?pmem:dmem)[reg[p_]] & (char)v); break;
               case 6: if ((x?pmem:dmem)[reg[p_]]) ip -= v*2 + 2;break; /* 2 is added at the end of the loop */
               case 7: if (!(x?pmem:dmem)[reg[p_]]) ip += v*2 - (!!v)*2;break; /* 2 is added at the end of the loop */
               case 8:
                  switch(v) {
                     case 0: (x?pmem:dmem)[reg[p_]] = fgetc(ii); break;
                     case 1: (x?pmem:dmem)[reg[p_]] = fgetc(II); break;
                     case 2: fputc((x?pmem:dmem)[reg[p_]],oo); break;
                     case 3: fputc((x?pmem:dmem)[reg[p_]],OO); break;
                     default:
                        fprintf(stderr, "ERROR: INVALID CMD: I/O mode %2x unknown.\n" ERRSTR,
                                v, ERRVAR);
                        return 0;
                  }
                  break;
               default:
                  fprintf(stderr, "ERROR: INVALID CMD: No cmd %2x in block %c.\n" ERRSTR,
                          c, (block==0)?'A':(block==1)?'B':(block==2)?'C':(block==3)?'D':'!',
                          ERRVAR);
                  return 0;
            }
            break;
         case 2: /*C*/
            switch(c) {
               case 0: (x?pmem:dmem)[reg[p_]] = *(lhb(&reg[r],y)); break;
               case 1: *(lhb(&reg[r],y)) = (x?pmem:dmem)[reg[p_]]; break;
               case 2: foo=(x?pmem:dmem)[reg[p_]];(x?pmem:dmem)[reg[p_]]=*(lhb(&reg[r],y));*(lhb(&reg[r],y))=foo;break;
               case 3: *(lhb(&reg[r],y)) = ~(*(lhb(&reg[r],y)) & (x?pmem:dmem)[reg[p_]]); break;
               case 4: (x?pmem:dmem)[reg[p_]] = ~((x?pmem:dmem)[reg[p_]] & *(lhb(&reg[r],y))); break;
               default:
                  fprintf(stderr, "ERROR: INVALID CMD: No cmd %2x in block %c.\n" ERRSTR,
                          c, (block==0)?'A':(block==1)?'B':(block==2)?'C':(block==3)?'D':'!',
                          ERRVAR);
                  return 0;
            }
            break;
         case 3: /*D*/
            switch(c){
               case 9: *(lhb(&reg[r],y)) = v; break;
               case 10: if (!*(lhb(&reg[r],y))) ip += v*2 + (!!v)*2; break;
               case 11: if (*(lhb(&reg[r],y))) ip -= v*2 - 2; break;
               case 14: return 0;
               default:
                  fprintf(stderr, "ERROR: INVALID CMD: No cmd %2x in block %c.\n" ERRSTR,
                          c, (block==0)?'A':(block==1)?'B':(block==2)?'C':(block==3)?'D':'!',
                          ERRVAR);
                  return 0;
            }
      }
      ip += 2;
   }
   return 0;
}