
/* Zusie 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>
#include "zmnem.h"

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

#define RAM_SIZE            32768 /* in bytes */

static byte *out;
static int offset; /* in bytes */

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++;
//printf("num %s = %d\n", *text, val);
   *text = t;
   return val;
}

static void expand_defines(const byte *t)
{
   define_t *d;
   int len;

   for (d=defines.next ; d ; d=d->next) {
      if (!strncmp(t, d->key, strlen(d->key)) && !isalpha(t[strlen(d->key)])) {
//         printf("match for %s\n", d->key);
         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 */
//         printf("now %s\n", t);
         return;
      }
   }
}

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

   expand_defines(t);

   if (*t == '(') {
      t++;
      expand_defines(t);
      val = get_number(&t);
      while (*t) {
         if (*t == ')') {
            t++;
            break;
         }
         while (isspace(*t))
            t++;
         op = *t++;
         while (isspace(*t))
            t++;
         expand_defines(t);
         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);

   printf("eq: %s [%s] => %d\n", *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);

   printf("'%s' => '%s'\n", key, value);

   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_label(const byte *text)
{
   char str[200];

   /* text will be a label name.
    * pretend this is a define by appending
    * the current offset as two bytes.
    */
  sprintf(str, "%s %d %d", text, offset%256, offset/256);
  printf("label is %s\n", str);

  do_define(str);
}

static void do_offset(const byte *text)
{
   int ofs = get_eq(&text);
   if (ofs >= 0 && ofs < RAM_SIZE)
      offset = ofs;
   printf("New offset is %d\n", offset);
}

static void do_line(const byte *text)
{
   zmnem_t *z;
   buf_t mnem;
   int i;
   byte payload[2];

   printf("Line: Offset is %d\n", offset);

   /* Get opcode */

   get_token(&text, mnem, 0);

   for (i=0, z=zmnem ; zmnem[i].mnemonic != NULL ; i++, z++) {
      if (!stricmp(zmnem[i].mnemonic, mnem))
         break;
   }

   if (!z->mnemonic)
      err("No such mnemonic!");

   /* Get payload if any */

   printf("Parsed opcode %d\n", z->opcode);

   for (i=0 ; i<z->payload ; i++) {
      payload[i] = (byte)get_eq(&text);
      printf("... %d = %d\n", i, payload[i]);
   }

   /* Emit it */

   out[offset++] = (byte )z->opcode;

   for (i=0 ; i<z->payload ; i++)
      out[offset++] = payload[i];
}

static void run_file(const char *filename, int pass)
{
   buf_t buf;
   byte *p;
   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 (!strncmp(p, "#include ", 9))
         run_file(p+9, pass);
      else if (!strncmp(p, "#define ", 8))
         do_define(p+8);
      else if (*p == '@')
         do_offset(p+1);
      else if (*p == ':')
         do_label(p+1);
      else if (*p == '%')
         ;
      else
         do_line(p);
   }

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

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

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

   out = calloc(1, RAM_SIZE);
   defines.next = NULL;

   // Parse labels to get their addresses
   offset = 0;
   run_file(argv[1], 0);

   // Actually parse opcodes
   offset = 0;
   run_file(argv[1], 1);

   write_ram(argv[2]);

   return 0;
}

