/*
    Small instrcuton set computer

    Assembler (C) 2004 Jason Hunt justinsanemsu@hotmail.com
*/


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

#include "table.h"

using namespace std;


#define max_line    100
#define max_out    0x10000

#define max_symbol 1000
#define max_resolve 1000


typedef unsigned short WORD;

typedef struct
{
    char name[16];
    WORD val;
} symbol_tab_type;


typedef struct
{
    char name[16];
    WORD addr;          // address where the resolved symbol will be placed
    int absolute;     // true if if gets resolved to an absolute address, reletive otherwise
} resolve_tab_type;



/*
   Token decoding
*/


/* returns true if the string is a valid label */
int is_label(char *l)
{
    int i = 0;
    char t = l[i];

    // these are the valid identifiers
    while((t >= 'a' && t<= 'z') || (t >= '0' && t <= '9') || (t >= 'A' && t <= 'Z') || (t == '_'))
        t = l[i++];

    // make sure next char is : and this is the end of the string
    if (t == ':' && l[i] == 0)
        return(1);

    return(0);
}
/* end of islabel */


/* returns true if the string is an instruction */
int is_inst(char *s, inst_type &inst)
{
    for (int i = 0; i <num_inst; i++)
        if (!stricmp(s, inst_table[i].inst_name))
        {
            inst = inst_table[i];
            return(1);
        }

    return(0);
}
/* end of is_inst */


/* returns true if the next instruction is a register */
int inline is_reg(char *s)
{
    return((s[0] == 'R' || s[0] == 'r') &&
          ((s[1] >= '0' && s[1] <= '9') ||
           (s[1] >= 'a' && s[1] <= 'f') ||
           (s[1] >= 'A' && s[1] <= 'F')));
}

/* returns true if the next instruction is a register */
int is_hex(char *s)
{
    while (*s != 0)
    {
        if (! (((*s >= '0') && (*s <= '9')) ||
                ((*s >= 'A') && (*s <= 'F')) ||
                ((*s >= 'A') && (*s <= 'F'))) )  return(0);
        s++;
    }

    return(1);
}

/* end of is_reg */

/*
     end of token decoding
*/




/*
     Tokenizing
*/


/* return the register index */
int get_reg_val(char *token)
{
    if (token[1] >= '0' && token[1] <= '9')
        return(token[1] - '0');

    if (token[1] >= 'A' && token[1] <= 'F')
        return(token[1] - 'A' + 10);

    if (token[1] >= 'a' && token[1] <= 'f')
        return(token[1] - 'A' + 10);

    return(-1);
}
/* end of get_reg_val */


/* read a line from the file, return true if eof reached */
int read_line(FILE *f, char *line)
{
    int i = 0;

    while(!feof(f) && i < max_line)
    {
        char tmp = fgetc(f);

        if (feof(f))
        {
            line[i] = 0; // null term
            return(1);
        }

        if (tmp == '\n')
        {
            line[i] = 0; // null term
            return(0);
        }

        line[i++] = tmp;

        if (i >= max_line)
        {
            // eat the rest of the line
            while(!feof(f) && fgetc(f) != '\n');
        }
    }

    line[i] = 0;

    return(0);
}
/* end of read line */


/* get the next token */
int next_token(char *line, char *token, unsigned int &line_index)
{
    char t = ' ';

    // return if line is blank
    if (line[0] == 0)
    {
        token[0] = 0;
        return(1);
    }

    unsigned int i = 0; // token index

    // eat white space
    while ((t == ' '|| t == '\t') && t != 0)
        t = line[line_index++];

    while(i < max_line)
    {
        if  (t == ' ' || t == ',' || t == '\t' || t == '\0')      // end of token
        {
            break;
        } else
        if  (t == ':')
        {
            // make sure the : gets carried along
            token[i++] = t;
            break;
        } 

        token[i++] = t;
        t = line[line_index++];         // get next char from the line
    }

    // force I to be in range
    if (i >= max_line) i = max_line - 1;

    token[i] = 0; // term the line


    // if the token is a comment, eat the rest of the line
    if (!strcmp(token, "//"))
    {
        while (t != '\n' && t != '\0')
            t = line[line_index++];
        token[0] = 0; // term the line, make blank
    }

    return(t == 0 ? 1 : 0);
}
/* end of get_next_token */


/* get the next register */
int get_next_reg(char *line, unsigned int &line_index, int &done_line)
{
    char token[max_line];
    done_line = next_token(line, token, line_index); // get the next token, shold be a register

    if (is_reg(token))
        return(get_reg_val(token));
    else
         printf("No register where expected\n");

    return(-1);
}
/* end of get_nexT_reg */



/* add a word to the output */
int add_to_out(WORD val, WORD* out, unsigned int &out_size)
{
    out[out_size++] = val;

    if (out_size >= max_out)
    {
        printf("Output exceeds memoey size\n");
        return(1);
    }

    return(0);
}
/* end of add_word */


/*
     End of Tokenizing
*/




int add_symbol(char *symbol, WORD val, symbol_tab_type *sym_tab, unsigned int &num_symbol)
{
    strcpy(sym_tab[num_symbol].name, symbol);
    sym_tab[num_symbol++].val = val;

    if (num_symbol >= max_symbol)
    {
        printf("Too many symbols\n");
        return(1);
    }
    return(0);
}

int add_resolve(char *symbol, WORD addr, int absolute, resolve_tab_type *res_tab, unsigned int &num_resolve)
{
    strcpy(res_tab[num_resolve].name, symbol);
    res_tab[num_resolve].addr = addr;
    res_tab[num_resolve++].absolute = absolute;

    if (num_resolve >= max_resolve)
    {
        printf("Too many resolves\n");
        return(1);
    }
    return(0);
}



/* core parser */
int parse_line(char *line, WORD &PC, symbol_tab_type  *sym_tab, unsigned int &num_symbol,
                                     resolve_tab_type *res_tab, unsigned int &num_resolve,
                                     WORD *out, unsigned int &out_size)
{
    if (line[0] == 0) return(0);

    char token[max_line];

    int done_line = 0;
    unsigned int line_index = 0;

    printf("%s\n", line);

    while(!done_line)
    {
        done_line = next_token(line, token, line_index);

        // make sure token is not empty
        if (token[0] != 0)
        {
            inst_type the_inst;


            if (is_label(token))
            {
                printf("LABEL (%s)\n", token);

                if(add_symbol(token, PC, sym_tab, num_symbol)) return(1);
            } else
            if (is_inst(token, the_inst))
            {
                // display the address
                printf("%04X:  ", PC);

                WORD encoded_inst = the_inst.opcode << 8; // and the core instruction

                WORD imm = 0; // the immidiate value, it any

                // deal with first operand
                if (the_inst.op1_type == S) // next operand a register?
                    encoded_inst |= get_next_reg(line, line_index, done_line);

                if (the_inst.op1_type == D) // next operand a register?
                    encoded_inst |= get_next_reg(line, line_index, done_line) << 4;

                if (the_inst.op1_type == I || the_inst.op1_type == A) // next operand an immidiate value
                {
                    done_line = next_token(line, token, line_index);

                    if (is_hex(token))
                        sscanf(token, "%x", &imm);
                    else
                        add_resolve(token, PC+1, 1, res_tab, num_resolve);
                }

                if (the_inst.op1_type == L) // relative
                {
                    done_line = next_token(line, token, line_index);

                    add_resolve(token, PC+1, 0, res_tab, num_resolve);
                }


                // deal with second operand
                if (the_inst.op2_type == S) // next operand a register?
                    encoded_inst |= get_next_reg(line, line_index, done_line);

                if (the_inst.op2_type == D) // next operand a register?
                    encoded_inst |= get_next_reg(line, line_index, done_line) << 4;

                if (the_inst.op2_type == I || the_inst.op2_type == A) // next operand a register?
                {
                    done_line = next_token(line, token, line_index);
                    if (is_hex(token))
                        sscanf(token, "%x", &imm);
                    else
                        add_resolve(token, PC+1, 1, res_tab, num_resolve);
                }

                // display the encoded instruction
                printf(" %04X", encoded_inst);

                // add the instruction
                if (add_to_out(encoded_inst, out, out_size)) return(1);

                // add the immidiate value, if any
                if (the_inst.op1_type == I || the_inst.op2_type == I ||
                    the_inst.op1_type == A || the_inst.op2_type == A ||
                    the_inst.op1_type == L)
                {
                    printf(" %04X", imm);

                    if (add_to_out(imm, out, out_size)) return(1);
                    PC++; // skip over imm for next inst
                }

                PC++; // normal add to inc over inst

                printf("\n\n");
                return(0); // done with this line
            }
            else
            {
               printf("Unknown token: (%s)\n", token);
                return(1);

            }
        }
    }

    return(0);
}
/* end of core parser */


/* assembles the file */
int assemble_file(char *source_file, char *dest_file)
{
    FILE *s = fopen(source_file, "rt");
    if (s == NULL)
    {
        printf("Unable to open source file\n");
        return(1);
    }

    FILE *o = fopen(dest_file, "wb");
    if (o == NULL) return(1);


    symbol_tab_type sym_table[max_symbol];
    unsigned int num_symbol = 0;

    resolve_tab_type res_table[max_resolve];  //symbols that need to be resolved
    unsigned int num_resolve = 0;


    int done = 0;

    WORD PC = 0;  // assembly program counter

    WORD out[max_out];

    unsigned int out_size;

    while (!done)
    {
        char line[max_line];

        if (read_line(s, line))
            done = 1;

        if (parse_line(line, PC, sym_table, num_symbol, res_table, num_resolve, out, out_size))
            done = 1;
    }

    fclose(s);


    printf("Resolve table:\n");

    for (int i = 0; i < num_resolve; i++)
        printf("%-16s  %04X   A: %d\n", res_table[i].name, res_table[i].addr, res_table[i].absolute);

    printf("\n\n");

    printf("Symbol table:\n");

    for (int i = 0; i < num_symbol; i++)
        printf("%-16s  %04X\n", sym_table[i].name, sym_table[i].val);

    printf("\n\n");


    //   resolve all of the symbols
    printf("Resolving symbols\n");
    for (int r = 0; r < num_resolve; r++)
    {
        int i;
        for (i = 0; i < num_symbol; i++)
        {
            if (!strcmp(res_table[r].name, sym_table[i].name))
            {
                if (res_table[r].absolute)
                    out[res_table[r].addr] = sym_table[i].val;
                else
                    out[res_table[r].addr] = (signed short)sym_table[i].val - (signed short)res_table[r].addr + 1;
                break;
            }
        }
        if (i == num_symbol)
        {
            printf("Unable to find (%s)\n", res_table[r].name);
            return(1);
        }
    }

    printf("Output size: %04x\n", out_size);
    printf("Asselbly output:\n");

    // display and write output
    for (int i = 0; i < out_size; i++)
    {
        printf("%04x ", out[i]);
        if ((i+1) % 8 == 0) printf("\n");

        fputc(out[i] >> 8, o); // write MSB
        fputc(out[i] & 0xFF, o); // write LSB
    }

    printf("\n\n");

    fclose(s);
    fclose(o);

    return(0);
}
/* end of assemble */

/* main funciton */
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("Usage asm infile.asm outfile.o\n");
        system("pause");
        return(1);
    }

    printf("Assemling %s to %s\n", argv[1], argv[2]);

    if(assemble_file(argv[1], argv[2]))
    {
        printf("Fatal error\n");
        system("PAUSE");
        return(1);
    }

    system("PAUSE");
    return(0);
}
/* end of main */






