
/* Zusie Microcode Assembler */
/* By Fredrik Andersson */

/* Yes, there are buffer overruns etc etc in this code. */
/* Don't bother telling me, I know and I don't care :) */

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

typedef unsigned char byte;
typedef byte buf_t[1024];

#define MC_SIZE            32768 /* in bytes */
#define BYTES_PER_MWORD    4

static byte *mc;
static int offset; /* in microwords */

static int debug = 0;

typedef struct define_t {
   struct define_t *next;
   byte *key, *value;
} define_t;

static define_t defines;

static void err(const char *msg)
{
   fprintf(stderr, "%s\n", msg);
   exit(1);
}

static void get_token(const byte **p, byte *out, int any)
{
   const byte *t = *p;
   *out = '\0';
   while (isspace(*t))
      t++;
   if (*t <= 32 || *t > 127)
      err("No token here!");
   while ((any && *t >= 32) || (!any && *t > 32 && *t <= 127))
      *out++ = *t++;
   while (isspace(*t))
      t++;
   *out = '\0';
   *p = t;
}

static unsigned long get_number(const byte **text)
{
   byte *t = *text;
   unsigned long radix = 10, val=0, x;

   while (isspace(*t))
      t++;

   if (*t == 'b') {
      radix = 2;
      t++;
   }
   else if (*t == 'h') {
      radix = 16;
      t++;
   }

   while ((radix == 2 && *t >= '0' && *t <= '1') ||
          (radix == 10 && *t >= '0' && *t <= '9') ||
          (radix == 16 && ((*t >= '0' && *t <= '9') ||
                           (*t >= 'a' && *t <= 'f') ||
                           (*t >= 'A' && *t <= 'F')))) {
      if (*t >= '0' && *t <= '9')
         x = *t - '0';
      else if (*t >= 'a' && *t <= 'f')
         x = *t - 'a' + 10;
      else if (*t >= 'A' && *t <= 'F')
         x = *t - 'A' + 10;
      else
         x = 0;
      val = radix*val + x;
      t++;
   }

   while (isspace(*t))
      t++;
   *text = t;
   return val;
}

static void expand_defines(byte *t, define_t *definelist, int remove_undefined)
{
   define_t *d;
   int len;
   int modified;
   byte *t0;

   t0 = t;

   if (!definelist)
      return;

   do {             // XXX loop not neded REMOVE REMOVE REMOVE
      modified = 0;
      t = t0;

      while (isspace(*t))
        t++;

      for (d=definelist->next ; d ; d=d->next) {
         if (!strncmp(t, d->key, strlen(d->key)) && (t[strlen(d->key)] == '\0' || !isalpha(t[strlen(d->key)]))) {
            if (debug)
               printf("expanding %s at %s\n", d->key, t);
            len = strlen(d->value);
            memmove(t, t+strlen(d->key), strlen(t)-strlen(d->key)+1);  /* remove key from input */
            memmove(t+len, t, strlen(t)+1); /* create space for define's value */
            memcpy(t, d->value, strlen(d->value)); /* insert the value */
            if (debug)
               printf("expansion %s gave %s\n", d->key, t);
            modified = 1;
         }
      }

      /* #<number> is always replaced with no token at all.
       * this indicates a positional parameter was referenced without
       * having been defined */

      if (remove_undefined && *t == '#' && *(t+1) >= '1' && *(t+1) <= '9') {
         if (debug)
            printf("no value for %c\n", t[1]);
         memmove(t, t+2, strlen(t)-1); /* move \0 too */
         if (debug)
            printf("string became [%s]\n", t);
         modified = 1;
      }
   } while (modified);
}

static int get_eq(const byte **text, define_t *params)
{
   unsigned long val, x;
   byte *t = *text;
   byte op;

   while (isspace(*t))
     t++;

   if (debug)
      printf("eq: parsing at [%s]\n", t);

   /* Skip whitespace and expand defines
    * until we have something that must be a number.
    */
again:
   expand_defines(t, &defines, 0);
   expand_defines(t, params, 1);
   if (isspace(*t)) {
      while (isspace(*t))
         t++;
      goto again;
   }

   /* just left-to-right operator precedence... */
   if (*t == '(') {
      t++;

      /* Skip whitespace and expand defines
       * until we have something that must be a number.
       */
again2:
      expand_defines(t, &defines, 0);
      expand_defines(t, params, 1);
      if (isspace(*t)) {
         while (isspace(*t))
            t++;
         goto again2;
      }

      /* Parse the number and process it as a L->R expression */

      val = get_number(&t);
      while (*t) {
         if (*t == ')') {
            t++;
            break;
         }
         while (isspace(*t))
            t++;
         op = *t++;
         while (isspace(*t))
            t++;
         expand_defines(t, &defines, 0);
         expand_defines(t, params, 1);
         x = get_number(&t);
         if (op == '+')
            val += x;
         else if (op == '-')
            val -= x;
         else if (op == '*')
            val *= x;
         else
            err("Bad eq-op!");
      }      
   }
   else
      val = get_number(&t);

   if (debug)
      printf("eq [%d]: %s [%s] => %u\n", offset, *text, t, val);
   *text = t;
   return val;
}

static void do_define(const byte *text)
{
   buf_t key, value;
   define_t *d;

   get_token(&text, key, 0);
   get_token(&text, value, 1);

   for (d=defines.next ; d ; d=d->next) {
      if (!strcmp(d->key, key)) {
         free(d->value);
         d->value = strdup(value);
         return;
      }
   }

   d = malloc(sizeof(define_t));
   d->key = strdup(key);
   d->value = strdup(value);
   d->next = defines.next;
   defines.next = d;
}

static void do_offset(const byte *text, define_t *params)
{
   int ofs = get_eq(&text, params);
   if (ofs >= 0 && ofs < MC_SIZE / BYTES_PER_MWORD)
      offset = ofs;
   if (debug)
      printf("New offset is %u\n", offset);
}

static void do_line(const byte *text, define_t *params)
{
   unsigned long microword = 0;

   while (*text) {
      while (isspace(*text))
         text++;
      microword |= get_eq(&text, params);    
   }

   if (debug)
      printf("line @ %d: %lx\n", offset, microword);

   mc[offset * BYTES_PER_MWORD + 3] = (microword & 0xff000000) >> 24;
   mc[offset * BYTES_PER_MWORD + 2] = (microword & 0x00ff0000) >> 16;
   mc[offset * BYTES_PER_MWORD + 1] = (microword & 0x0000ff00) >>  8;
   mc[offset * BYTES_PER_MWORD + 0] = (microword & 0x000000ff) >>  0;

   offset++;
}

static void parse_params(const char *text, define_t *inparams, define_t *params)
{
   define_t *d;
   buf_t pb;
   char pk[10];
   int i = 1;

   while (*text) {
      get_token(&text, pb, 0);
      expand_defines(pb, &defines, 0);
      expand_defines(pb, inparams, 1);
      d = malloc(sizeof(define_t));
      sprintf(pk, "#%d", i++);
      d->key = strdup(pk);
      d->value = strdup(pb);
      d->next = params->next;
      params->next = d;
      if (debug)
         printf("param %s=%s\n", pk, pb);
   }
}

static void free_defines(define_t *defines)
{
   define_t *d;
   while ( (d = defines->next) ) {
      defines->next = d->next;
      free(d->key);
      free(d->value);
      free(d);
   }
}

static void run_file(const char *filename, define_t *inparams)
{
   define_t params;
   buf_t buf;
   byte *p, *p2;
   FILE *fp;

   printf("Assembling %s...\n", filename);

   if (!(fp = fopen(filename, "rt")))
      err("Can't open input file!");

   while (fgets(buf, 1024, fp)) {
      for (p=buf ; isspace(*p) ; p++);
      if (!*p)
         continue;
      while (*p && p[strlen(p)-1] <= 0x20)
         p[strlen(p)-1] = '\0';

      if (debug)
         printf("assembling line \"%s\"\n", p);

      if (!strncmp(p, "#include ", 9)) {
         if ((p2 = strchr(p+9, ' '))) {
            *p2 = '\0';
            parse_params(p2+1, inparams, &params);
         }
         else
            params.next = NULL;
         run_file(p+9, &params);
         free_defines(&params);
      }
      else if (!strncmp(p, "#define ", 8))
         do_define(p+8);
      else if (*p == '@')
         do_offset(p+1, inparams);
      else if (*p == '%')
         ;
      else
         do_line(p, inparams);
   }

   fclose(fp);
   printf("Done %s...\n", filename);
}


void write_mc(const char *outfile)
{
   FILE *fp;
   if (!(fp = fopen(outfile, "wb")))
      err("Can't write output\n");
   fwrite(mc, MC_SIZE, 1, fp);
   fclose(fp);
   printf("%d bytes written to %s.\n", MC_SIZE, outfile);
}

int main(int argc, char **argv)
{
   if (argc < 3)
      return 1;

   if (argc == 4)
      debug = 1;

   mc = calloc(1, MC_SIZE);
   offset = 0;
   defines.next = NULL;

   run_file(argv[argc-2], NULL);
   write_mc(argv[argc-1]);

   return 0;
}
