/*  This file (main.c) is part of Spencer's Assembler.

	Spencer's Assembler is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Copyright 2006, Spencer Putt*/

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "defines.h"
#include "parser.h"
#include "directive.h"


#include "TASM80.h"


char inNameBackup[512];
char outNameBackup[512];

BOOL doWabbit = TRUE;

// globals
command_info cmd_inf[200];
cmd_struct command_set[2048];
unsigned short reusables[256];
int total_reusables = 0;
unsigned char command_cache[16384*2];
unsigned int cache_index = 0;

unsigned int total_commands, total_mnemonics;
unsigned int program_counter;
FILE* current_file, *tabFile, *tabBackup, *output_file;

int line_number;
int list_bytes;
int (*tfputc)(int, FILE*);
int (*ingetc)(FILE*);

BOOL pass_one;
char *current_file_name;
char current_extention[4];
char linebuf[512];
int cchar = 0;
expansion_struct *expansion_buf = NULL;

int tabfputc(int, FILE*);
void skip_until(char str[][64], unsigned char);
void assemble_line(FILE*, char *);
void do_assemble_line(FILE*, char *);
char* get_word(char*, char*);
char* get_level_word(char*, char*, char);
int fgetw(FILE *,char *);
char* upcase(char *);
char* pack_string(char *);
char* reduce_string(char *);
void do_pass(FILE*, FILE*);
void post_error(const char *, ...);
void tabinsert(const char * str, ...);
int string_ingetc(FILE*);
int file_ingetc(FILE*);
int search_defines(char* str);
int insert_define(char* name, char* define);

int wabbit(char*, char*);

int main(int argc, char *argv[]) {
    BOOL made_swap,export_labels = FALSE,do_all_passes = FALSE,
            do_stat_file = FALSE,export_defines = FALSE,do_label_file = FALSE;
    char buffer[256],word_buf[64],filename[100],*ptr,tempchar;
    FILE *inFile,*outFile,*labFile = NULL;
    int i,temp,arg_i,op_i;
    char *spasm_define;
    
    if (argc == 1) {
		for(i=strlen(argv[0]);i>0;i--) {
			if (argv[0][i] == '\\') {
				i++;
				break;
			}
		}
        printf("\
Spencer's Assembler + Wabbitsign, April 2007\n\n\
Usage: %s <input> [output.EXT] [flags]\n\n\
 <input>      : z80-assembly source file\n\
 [output.EXT] : extension determines output format.\n\n\
    The following extensions generate their respective file formats.\n\
    8XK, 8XP, 86S, 86P, 85S, 85P, 83P, 82P, 73P\n\
      e.g. \"%s test.asm test.8xp\" generates a TI-83+ program file.\n\n\
 [flags]      : -T Generate Table (listing) file <input>.lst\n\
\t\t-L Generate Label file <input>.lab\n\
\t\t-S Generate code Statistics file.\n\
\t\t-D Generate label file from Defines <input>.lab\n\
\n",argv[0]+i,argv[0]+i);
        return 0;
    }
    puts("Spencer's Assembler + Wabbitsign, April 2007");

    if (!(tabFile = fopen("TASM80.TAB","r"))) {
		tabFile = fopen("TASM80.TAB","wt");
		for(i =0;tasm80inc[i] != 0;i++) {
			fputc(tasm80inc[i],tabFile);
		}
		fclose(tabFile);
		tabFile = fopen("TASM80.TAB","r");
	    total_mnemonics = total_commands = 0;
	   	ingetc = &file_ingetc;
    	while (get_line(tabFile, buffer)) {
        	if (next_char(buffer) && *next_char(buffer)>='A' && *next_char(buffer)<='Z') {
	            if (add_instruction(buffer) != -1) total_commands++;
    	    }
	    }
    	fclose(tabFile);
		remove("TASM80.TAB");
    } else {
	    total_mnemonics = total_commands = 0;
	   	ingetc = &file_ingetc;
    	while (get_line(tabFile, buffer)) {
        	if (next_char(buffer) && *next_char(buffer)>='A' && *next_char(buffer)<='Z') {
	            if (add_instruction(buffer) != -1) total_commands++;
    	    }
	    }
    	fclose(tabFile);
	}

    if (!(inFile = fopen(argv[1],"rb"))) {
        puts("Couldn't open input file.");
        return 1;
    }


    for (i = 0; argv[1][i] && argv[1][i] != '.'; i++);
    // save this file's extension
    strcpy(current_extention,argv[1]+i+1);
    argv[1][i+1] = 0;
    // save the base name
    strcpy(word_buf,argv[1]);
    argv[1][i+1] = current_extention[0];
    if (argc > 2 && argv[2][0] != '-') {
		doWabbit = TRUE;
		tmpnam(inNameBackup);
        strcpy(filename,inNameBackup);
		strcpy(outNameBackup,argv[2]);
        i = 3;
    } else {
		doWabbit = FALSE;
        strcat(strcpy(filename,word_buf),"bin");        
        i = 2;
    }
    
    outFile = (FILE*) 1;
    do_all_passes = TRUE;
    tfputc = &fputc;
    tabFile = NULL;
    for (op_i = 2; op_i < argc; op_i++) upcase(argv[op_i]);
    for (; i < argc; i++) {
        // handle options
        switch (argv[i][1]) {
            case 'T':
                do_label_file = TRUE;
                break;
            case 'D':
                puts("Treating defines as labels.");
                export_defines = TRUE;
                goto open_label_file;
            case 'L':
                export_labels = TRUE;
            open_label_file:
                do_all_passes = FALSE;
                strcat(strcpy(buffer,word_buf),"lab");
                labFile = fopen(buffer,"w");
                outFile = NULL;
                break;
            case 'S':
                do_stat_file = TRUE;
                break;
        }
    }
    
    if (outFile) {
        if (!(outFile = fopen(filename,"wb+"))) {
            printf("Failed to open output file \"%s\".",buffer);
            return 1;
        }
    }
    
    if (do_all_passes && do_label_file) {
        strcat(strcpy(buffer,word_buf),"lst");
        tabFile = fopen(buffer,"wb");
        tfputc = &tabfputc;
        fprintf(tabFile,"Table file generated by Spencer's Assembler\n");
    }
    
    output_file = outFile;
    strcpy(buffer,argv[1]);
    current_file_name = buffer;    
    current_file = inFile;
    tabBackup = tabFile;
    
    //puts("Beginning assembly ... ");
    
    // Define a constant define "SPASM"

    puts("Pass 1 ...");
    program_counter = 0;
    pass_one = TRUE;
    
    define_array[0].define = NULL;
    define_array[0].name = (char*) malloc(6);
    strcpy(define_array[0].name,"SPASM");
    total_defines = 1;
    
    do_pass(inFile,outFile);
    
    puts("Done.");
    if (do_all_passes) {
        for (i = 0; i < total_defines; i++) {
            free(define_array[i].name);
            free(define_array[i].define);
        }
        define_array[0].define = NULL;
        define_array[0].name = (char*) malloc(6);
        strcpy(define_array[0].name,"SPASM");
        total_defines = 1;  
        puts("Pass 2 ...");
        program_counter = 0;
        total_reusables = 0;
        cache_index = 0;
        pass_one = FALSE;
        do_pass(inFile,outFile);
        puts("Done.");
    }
    //puts("Startin to check arguments...");
    if (export_defines && total_defines) {
        //puts("Starting to export defines...");
        for (i = 0; i < total_defines; i ++) {
        	    if (define_array[i].define) strcpy(buffer,define_array[i].define);
            else buffer[0] = 0;
            printf("#define %s %s\n",define_array[i].name,buffer);
            //printf("Malloc'd to: %p %p\n",define_array[i].name,define_array[i].define);
            if (buffer[0]) temp = parse_string(buffer);
            else temp = 1;
            printf("Value: %0.4X\n",temp);
            if (!current_parse_error) {
                fprintf(labFile,"%s = $%0.4X\n",define_array[i].name,temp);
            } else {
                printf("Had trouble with \"%s\".\n",define_array[i].name);
            }
            flush_parse_errors();
        }
    }
    if (export_labels && total_labels) {
        //puts("Starting to export labels...");       
        for (i = 0; i < total_labels; i++) {
            fprintf(labFile,"%s = $%0.4x",label_array[i].name,label_array[i].value);
            //if (i < total_labels-1 && label_array[i+1].name[0] == '_') break;
            fprintf(labFile,"\n");
        }
    }
    if (do_stat_file) {
        FILE *statsFile = fopen("stats.txt","w");
    
        for (i = 0; i < total_mnemonics; i++) {
            fprintf(statsFile,"****** %s group *******\n",cmd_inf[i].mnemonic);
            arg_i = cmd_inf[i].ref;
            for (op_i = 0; op_i < cmd_inf[i].members; op_i++,arg_i++) {
                if (command_set[arg_i].weight) {
                    fprintf(statsFile,"%s %6s\t%d\n",cmd_inf[i].mnemonic,
                        command_set[arg_i].args,command_set[arg_i].weight);
               }
            }
        }
    }

    fclose(inFile);
    if (outFile) fclose(outFile);
    if (tabFile) fclose(tabFile);
    if (labFile) fclose(labFile);
    if ( doWabbit == TRUE ) {
	    wabbit(inNameBackup,outNameBackup);
	    remove(inNameBackup);
	}
    //system("PAUSE");
    return 0;
}

int add_instruction(char *tstr) {
    char word_buf[64],mnemonic[16];
    int inf_index,cmd_index;
    static const char addinstr_error[] = "Missing data at %s step of new instruction";
    static const char com_types[][8] = {"NOP","ZBIT","R1","ZIX"};
    cmd_struct c_command;
    
    /* inst word */
    if (!next_char(tstr)) {
        post_error(addinstr_error,"instr");
        return -1;
    }
    tstr = get_word( upcase(tstr), mnemonic);
    /* args word */
    if (!tstr) {
        post_error(addinstr_error,"args");
        return -1;
    }
    tstr = get_word(tstr, word_buf);
    strcpy(c_command.args = malloc(strlen(word_buf)+1), word_buf);
    
    /* opcode data */
    if (!tstr) {
        post_error(addinstr_error,"opcode");
        return -1;
    } else {
        unsigned int result;
        tstr = get_word(tstr, word_buf);
        result = conv_hex(word_buf);
        *((unsigned int*) c_command.opcode) = result;
    
        /* nbytes word */
        if (!tstr) {
            post_error(addinstr_error,"nbytes");
            return -1;
        }
        tstr = get_word(tstr, word_buf);
        result = parse_string(word_buf);
        c_command.size = result;
    }
    
    if (!tstr) {
        post_error(addinstr_error,"rule");
        return -1;
    } else {
        int i;
        tstr = get_word(tstr, word_buf);
        for (i = 0; i < 4 && strcmp(com_types[i],word_buf); i++);
        if (i == 4) i = 0;
        c_command.type = i;
    }
    
    if (tstr) {
        int result;
        
        tstr = get_word(tstr, word_buf);
        result = parse_string(word_buf);
        if (result > 1) return -1;
    }
    
    if (tstr) tstr = get_word(tstr, word_buf);
    if (tstr) {
        unsigned int result;
        get_word(tstr, word_buf);
        result = conv_hex(word_buf);
        c_command.mask[0] = result & 0xFF;
        c_command.mask[1] = result >> 8;
    } else {
        c_command.mask[0] = 0;
        c_command.mask[1] = 0;
    }
    c_command.weight = 0;
    for (inf_index = 0; inf_index < total_mnemonics && strcmp(mnemonic, cmd_inf[inf_index].mnemonic); inf_index++);
    if (inf_index == total_mnemonics) {
        total_mnemonics++;
        strcpy(cmd_inf[inf_index].mnemonic = malloc(strlen(mnemonic)+1), mnemonic);
        cmd_inf[inf_index].members = 1;
        cmd_inf[inf_index].ref = total_commands;
        cmd_index = total_commands;
        cmd_inf[inf_index].weight = 0;
    } else {
        int i;
        cmd_index = cmd_inf[inf_index].ref + cmd_inf[inf_index].members++;
        for (i = total_commands; i > cmd_index; i--) command_set[i] = command_set[i-1];
        for (i = inf_index + 1; i < total_mnemonics; i++) cmd_inf[i].ref++;
    }
    command_set[cmd_index] = c_command;
    
    return cmd_index;
}
    

void assemble_file(char *filename, FILE *outFile) {
    FILE *file,*backup;
    int line_backup;
    char *str;
    int (*mode_backup)(FILE*);

    if (file = fopen(filename,"rb")) {
        str = (char*) malloc(strlen(filename)+1);
        strcpy(str,filename);
        
        filename = current_file_name;
        current_file_name = str;
        
        backup = current_file;
        current_file = file;
        
        mode_backup = ingetc;
        ingetc = &file_ingetc;
        
        line_backup = line_number;
        do_pass(file,outFile);
        line_number = line_backup;
        
        ingetc = mode_backup;
        
        current_file = backup;
        current_file_name = filename;
        
        free(str);
        fclose(file);
    } else {
        printf("Couldn't find file %s.\n", filename);
    }
}

int string_ingetc(FILE* file) {
    //printf("Getting char\n");
    if (!expansion_buf->expansion[expansion_buf->index]) {
        return EOF;
    }
    return (unsigned int) expansion_buf->expansion[expansion_buf->index++];
}

int file_ingetc(FILE* file) {
	int inchar = fgetc(file);
	if (inchar==EOF) return EOF;
	return linebuf[cchar++] = inchar;
}

int infeof(FILE* file) {
    if (ingetc==&file_ingetc) return feof(file);
    //printf("Feof test: %s\n",expansion_buf->expansion);
    if (expansion_buf->expansion[expansion_buf->index]) return 0;
    
    return 1;
}
    

/* Gets a line from the file, from current fp*/
int get_line(FILE *file, char* buffer) {
    BOOL in_string = FALSE,in_quote = FALSE,unbreakable = FALSE;
    int i,this_char,prev_char = 0;
    static const char unbreak_prefix[3] = "DEF";
    char *backup = buffer;

    //printf((ingetc==&file_ingetc)?"File get mode\n":"String get mode\n");

    if (infeof(file)) return 0;

    //printf("Running ingetc\n");
    this_char = ingetc(file);
    
    while (this_char !='\r' && this_char != '\n' && this_char!=EOF &&
            !((this_char == '\\' || this_char == ';') && !unbreakable && !in_string && !in_quote)) {
        if (prev_char != '\\') {
            if (this_char=='"') in_string = !in_string;
            else if (this_char=='\'' && !in_string) in_quote = !in_quote;
        }
        //printf("Writing char to buffer ...\n");
        *buffer++ = this_char;

        if (this_char=='#' && ingetc == &file_ingetc) {
            for (i = 0; i < sizeof(unbreak_prefix) &&
                (this_char = ingetc(file)) != EOF &&
                toupper(this_char) == unbreak_prefix[i]; i++)
					*buffer++ = this_char;
            if (i == sizeof(unbreak_prefix)) unbreakable = TRUE;
            else *buffer++ = this_char;
        }
        prev_char = this_char;
        this_char = ingetc(file);
    }
    *buffer = 0;
    //printf("String I got: %s\n",backup);
    if (this_char==';') {
        while ((this_char=ingetc(file))!=EOF && this_char != '\n');
        if (ingetc==&file_ingetc) {
			line_number++;
			linebuf[cchar] = 0; cchar = 0;
		}
        return 1; 
    } else if (this_char!='\\') {
		if (this_char == '\r') ingetc(file);       //now gets the \n, next char is new line
        if (ingetc == &file_ingetc) {
            linebuf[cchar] = 0;
            cchar = 0;
            //if (this_char != EOF) {
            line_number++;
            //}
		}
        return 1;
    }
    //puts("Probably hit a line split");
    return 1;
}


void do_pass(FILE *file, FILE *outFile) {
    char buffer[512],ptr[512],word_buf[512];
    int i,is_eof;
	unsigned int line_backup;
    BOOL no_string = TRUE;
    
    if (!pass_one && tabFile) fprintf(tabFile,"%5d %04x: ",1,program_counter);
    list_bytes = 0;
    line_number = 0;
	line_backup = 0;
    fseek(file,0L,SEEK_SET);
    while (get_line(file,buffer)) {

        if (*buffer) do_assemble_line(outFile,buffer);
        if (!pass_one) show_parse_errors();
        else flush_parse_errors();
        
        is_eof = infeof(file);
        if ((!pass_one && tabFile) && ((line_number != line_backup) || (is_eof && ingetc == &file_ingetc))) {
            if (line_number <= line_backup+1) {
			
                if (next_char(linebuf)) {
                    for (i = 0; i < 4-list_bytes; i++) {
                        fprintf(tabFile,"-  ");
                    }
    				//printf("Linebuf: %s\n",linebuf);
                    fprintf(tabFile, linebuf);
                } else fputc('\n',tabFile);
            }
        
            line_backup = line_number;
            if (!is_eof) {
    			fprintf(tabFile,"%5d %04x: ",line_number+1,program_counter);
                no_string = FALSE;
                list_bytes = 0;
                
                get_word(linebuf,word_buf);
                upcase(word_buf);
                if (!strcmp(word_buf,"#INCLUDE")) {
                    no_string = TRUE;
                    fputs(linebuf, tabFile);
                    fputc('\n', tabFile);
                }
            }
        }
        
    }
}   


/* Line expansions allocated RAM for a piece of code, and are chained in
such a way that lines of code can be nested (a la macros or includes)

To assemble a line, create a line expansion and pass a pointer to a buffer
containing your target piece of code.

Step 1:
    line_expansion(your_code, LE_CREATE);
Step 2:
//For a single line of code:
    assemble_line(output_file, get_line(NULL, line_buffer) );
//For possible multiple lines (or lines with line splits):
    while (get_line(NULL, line_buffer)) assemble_line(output_file, line_buffer);
Step 4:
    line_expansion(NULL, LE_DESTROY); */

void line_expansion(char *ptr, int mode) {
    expansion_struct *temp_exp;
    static const char exp_error[]="Couldn't allocate expansion memory.";
        
    if (mode == LE_CREATE) {
        temp_exp = (expansion_struct*) malloc(sizeof(expansion_struct));
        if (!temp_exp) {
            return post_error(exp_error);
        }
        temp_exp->exp_backup = expansion_buf;
        expansion_buf = temp_exp;
        expansion_buf->index = 0;
        expansion_buf->expansion = (char*) malloc(strlen(ptr) + 1);
        if (!expansion_buf->expansion) {
            return post_error(exp_error);
        }
        strcpy(expansion_buf->expansion, ptr);
        
        expansion_buf->input_backup = ingetc;
        ingetc = &string_ingetc;
    } else {
        temp_exp = expansion_buf;
        expansion_buf = expansion_buf->exp_backup;
        ingetc = temp_exp->input_backup;
        free(temp_exp->expansion);
        free(temp_exp);
    }
}

void assemble_line(FILE *outFile, char *ptr) {
    char buffer[512];

    line_expansion(ptr, LE_CREATE);

    while (get_line(current_file,buffer)) do_assemble_line(outFile,buffer);

    line_expansion(NULL, LE_DESTROY);
}

void do_assemble_line(FILE *outFile, char *buffer) {
    char *ptr,word_buf[512],arg_buf[2][64],tempchar,parse_buf[512],*sarg_ptr;
    int i,temp,arg_i,op_i,ci,ai,arg_count,arg_index,cmd_count,tArg;
    int parindicator;
    //cmd_struct tcmd;
    command_info tcmdinf;
    label_struct temp_label;
    
    //printf("p%d*%x*:%s\n",!pass_one,program_counter,buffer);
    
    if (!(ptr = get_word(buffer,word_buf))) return;
    ptr = next_char(ptr);
    //printf("word1: '%s'\nword2: '%s'\n",word_buf,ptr);
    
    if (word_buf[0]=='#') buffer = next_char(buffer);
    else if (word_buf[0] == '.') {
        ptr = next_char(buffer);
        handle_directive(outFile, word_buf, ptr);
        //printf("Done with handle_directive\n");
        return;
    } else if (ptr && ptr[0] == '.') {
        return handle_directive(outFile, word_buf, ptr);
    }
    
    /* Is the first character whitespace? */
    if (buffer[0] == ' ' || buffer[0] == '\t') {
        /* Word_buf = command mnemonic
           Ptr = rest of the arguments */
        upcase(word_buf);

        for (cmd_count = 0; cmd_count < total_mnemonics &&
            strcmp(word_buf,cmd_inf[cmd_count].mnemonic); cmd_count++);
        if (cmd_count < total_mnemonics) {

            BOOL matches;
            
            op_i = cmd_inf[cmd_count].ref;
            if (!pass_one) op_i += command_cache[cache_index];
            
            if (!ptr) {
                arg_count = 0;

                if (command_set[op_i].args[0] == '"' && command_set[op_i].args[1] == '"')
                    matches = TRUE;
                else matches = FALSE;
            } else {
                /* We know there are arguments */
                pack_string(ptr);
                //printf("%s is the mnemonic, supposedly at %d:%d out of %d %s\n",word_buf,op_i,cmd_inf[cmd_count].ref,total_commands,cmd_inf[cmd_count].mnemonic);
                temp = op_i + cmd_inf[cmd_count].members;
                matches = FALSE;
                /* Go through the argument lists of the found command */
                for (; op_i < temp && !matches; op_i++) {
                    ci = ai = parindicator = arg_count = arg_index = 0;
                    matches = TRUE;
                    //printf("Compare this: %s %s\n",ptr,command_set[op_i].args);
                    while (matches && command_set[op_i].args[ci]) {
                        if (command_set[op_i].args[ci] == ptr[ai] && ptr[ai]!='*') {
                            ai++; ci++;
                            continue;
                        }
                        if (command_set[op_i].args[ci] != '*') {
                            matches = FALSE;
                            break;
                        }
                        if (command_set[op_i].args[ci+1] == ptr[ai] && !parindicator) {
                            if (ptr[ai]) ai++;
                            ci++;
                            if (command_set[op_i].args[ci]) {
                                ci++;
                                if (!arg_index && ptr[ai]!=')') arg_buf[arg_count][arg_index++] = '0';
                            }
                            arg_buf[arg_count++][arg_index] = 0;
                            arg_index = 0;
                        } else {
                            if (ptr[ai] == '(') parindicator++;
                            else if (ptr[ai] == ')') parindicator--;
                            //printf("arg_buf: %s, ptr: %s\n, %p",arg_buf[arg_count]+arg_index,ptr+ai,ptr);
                            arg_buf[arg_count][arg_index++] = ptr[ai++];
                        }
                    }
                    if (ptr[ai]) matches = FALSE;
                }
                op_i--;
                if (arg_index) {
                    arg_buf[arg_count][arg_index++] = ptr[ai];
                    arg_buf[arg_count][arg_index] = 0;
                    arg_index = 0; arg_count++;
                }
            }
            /* Save the argument position the cache */
            if (pass_one) {
                if (matches) command_cache[cache_index] = op_i - cmd_inf[cmd_count].ref;
                else command_cache[cache_index] = 0;
            }
            cache_index++;
            //printf("Arg issues testing point:\nptr: %s\narg_count: %d, \'%s\'\narg_index: %d\nmatches: %d\n",ptr,arg_count,arg_buf[0],arg_index, matches);
            if (!pass_one && matches) {
                //printf("Handling command %s %s\n",cmd_inf[cmd_count].mnemonic,command_set[op_i].args);
                if (!arg_count) {
                    //printf("Argsless op size %d\n",command_set[op_i].size);
                    for(i = 0; i < command_set[op_i].size; i++) {
                        tfputc(command_set[op_i].opcode[i],outFile);
                    }
                } else {
                    int out_i = 0, temp_arg, overflow;
                    char outbuf[8];
                    i = 0;
                    while (command_set[op_i].opcode[i]) {
                        outbuf[out_i++] = command_set[op_i].opcode[i++];
                    }
                    //printf("put in %d bytes of opcode\n",i);

                    if (arg_count == 1) {
                        //puts("starting parsing");
                        //printf("Parsing: %s\n",arg_buf[0]);
                        if (arg_buf[0][0]) temp_arg = parse_string(arg_buf[0]);
                        else {
                            post_error("%s is missing an argument relative to to %s %s",next_char(buffer),cmd_inf[cmd_count].mnemonic,command_set[op_i].args);
                            temp_arg = 0;
                        }
                        if (command_set[op_i].type == 3) {
                            unsigned int arg_sig = temp_arg & ~0x7F;
                            if ((temp_arg < 0 && arg_sig == (unsigned int) (-1 & ~0x7F)) || 
                                (temp_arg >= 0 && arg_sig == 0)) {
                                temp_arg &= (unsigned int) 0xFF;
                                goto normal_command;
                            } else {
                                post_error("IX/IY offset %s exceeded relative range.",arg_buf[0]);
                            }
                        } else if (command_set[op_i].type == 0) {
                        normal_command:
                            outbuf[out_i++] = temp_arg | command_set[op_i].mask[0];
                            if (i != command_set[op_i].size - 1) {
                                temp_arg >>= 8;
                                outbuf[out_i++] = temp_arg | command_set[op_i].mask[1];
                            }
                            overflow = temp_arg & 0xFF00;
                            if (!(overflow == 0xFF00 || !overflow)) post_error("Overflow error on argument %s.", arg_buf[0]);
                            
                        } else if (command_set[op_i].type == 1) {
                            /* Bit, set, reset operations */
                            if (temp_arg > 7) post_error("Value %d is outside of possible bit range 0-7.",temp_arg);
                            temp_arg = (temp_arg & 0x07) << 3;
                            outbuf[out_i-1] = temp_arg | command_set[op_i].opcode[i-1];
                        } else {
                            /* Relative jumps */
                            int jump_length;

                            temp_arg -= (program_counter+2);
                            jump_length = abs(temp_arg-1);
                            if (jump_length > 128) {
                                post_error("Distance to %s exceeded relative jump range by %d bytes.",arg_buf[0],jump_length-128);
                                tArg = 0;
                            }
                            outbuf[out_i++] = temp_arg;
                        }
                    } else {
                        /* The two argument commands are ix,iy commands */
                        if (command_set[op_i].type != 0 && command_set[op_i].type != 3) {
                            /* Special case bit, set, res opcodes with ix */
                            outbuf[out_i++] = parse_string(arg_buf[1]) | command_set[op_i].mask[0];
                            outbuf[out_i++] = ((parse_string(arg_buf[0]) & 0x07)  << 3) | command_set[op_i].mask[1];
                        } else {
                            outbuf[out_i++] = parse_string(arg_buf[0]);
                            outbuf[out_i++] = parse_string(arg_buf[1]);
                        }                                 
                    }
                    /* Output the written bytes */
                    for (i = 0; i < out_i; i++) tfputc(outbuf[i],outFile);
                    command_set[op_i].weight++;
                    cmd_inf[cmd_count].weight++;
                    if (cmd_count) {
                        for (arg_i = cmd_count-1; arg_i>=0 && cmd_inf[arg_i].weight < cmd_inf[cmd_count].weight; arg_i--);
                        arg_i++;
                        if (arg_i != cmd_count) {
                            tcmdinf = cmd_inf[arg_i];
                            cmd_inf[arg_i] = cmd_inf[cmd_count];
                            cmd_inf[cmd_count] = tcmdinf;
                        }
                    }
                }
            } else if (!matches) {
                if (ptr && ptr[ai] && !(ptr[ai]==' ' || ptr[ai]=='\t')) post_error("Invalid argument structure %s.",ptr);
                else post_error("Missing required arguments.");
                return;
            }
            program_counter+=command_set[op_i].size;
        } else {
            //printf("Command error: '%s' '%s'\n",word_buf,ptr);
            if (*(get_level_word(buffer,word_buf,'(') - 1) == '(') {
                /* We found a parenthesis.  This can mean a few things.
                    a) It's an argumented macro.
                    b) It's a misspelled or invalid assembly command that uses indirection e.g. dl a,(hl)
                    Determine which it is by searching for its define. */
                if (resolve_macro(outFile, buffer, NULL, TRUE)) {
                    if (ptr) {
                        get_word(buffer,word_buf);
                        post_error("Assembly command %s not found.",word_buf);
                    } else {
                        post_error("Macro %s not found.",pack_string(word_buf));
                    }
                }
            } else {
                int m_loc;
                get_word(buffer, word_buf);
                m_loc = search_defines(word_buf);
                if (m_loc != -1) {
                    strcpy(buffer + 1, define_array[m_loc].define);
                    if (ptr) strcat(buffer, ptr - 1);
                    return assemble_line(outFile, buffer);
                } else {
                    get_word(buffer,word_buf);
                    post_error("Assembly command %s not found.",word_buf);
                }
            }
        }
    } else {
        if (buffer[0] == '#') {
            handle_preop(outFile, word_buf, ptr);     
        } else {
            /* at this point, there was a character in column 0
            and the test for preop has already failed.
            possibilities:
                   3 place equate x = y
                   macro'd directive x equ y
                   label alone label:/label
                   label with code label: xor a
                   macro with args macro(x,y)*/
            if (ptr) { /*If this has a second word, it is either equate,
                            lable with code, macro'd directive, or arg'd macro*/
                if (*ptr == '=') {
                    //puts("Handling 3-place Equate");
                    /* Pass one only task */
                    if (pass_one) {
                        /* Handle 3 place equate */
                        ptr = next_char(ptr+1);
                        if (ptr) {
                            int temp_arg;
                            /* Parse argument */
                            temp_arg = parse_string(ptr);
                            temp_label.name = (char*) malloc(strlen(word_buf)+1);
                            strcpy(temp_label.name, word_buf);
                            temp_label.value = temp_arg;
                            insert_label(&temp_label);
                        } else {
                            post_error("Equate lacks arguments", word_buf);
                        }
                    }
                } else {
                    int len_name = strlen(word_buf);
                    if (buffer[len_name - 1] == ':') { //safe, wordbuf = buffer
                        //puts("Label with code");
                        /* with colon, gaurenteed label and code */
                        buffer[len_name - 1] = '\\'; //safe, wordbuf = buffer
                        return assemble_line(outFile, buffer);
                    } else {
                        int last_char = ptr - buffer + strlen(ptr) - 1;
                        int first_paren = 0;
                        /* Find the situations like 'macro(x, y)' or 'macro (x, y)'*/
                        while (buffer[last_char] == ' ' || buffer[last_char] == '\t') last_char--;
                    
                        while (buffer[first_paren] && buffer[first_paren] != '(') first_paren++;
 
                        if (buffer[last_char] == ')' && buffer[first_paren]) {
                            /* with parenthesis, means macro */
                            strcpy(buffer + len_name, ptr);
                            return assemble_line(outFile, buffer);
                        } else {
                            /* this is a label with code or macro'd directive. */
                            char middle_buf[64];

                            ptr = get_word(ptr, middle_buf);
                            upcase(middle_buf);
                            temp = search_defines(middle_buf);
                            if (temp == -1) {
                                /* if it wasn't a define, it's label and code */
                                do_assemble_line(outFile, word_buf);
                                return do_assemble_line(outFile,buffer+len_name);
                            } else {
                                /* plug in the define, and assemble the new line */
                                strcpy(word_buf,ptr);
                                buffer[len_name+1] = 0;
                                strcat(buffer,define_array[temp].define);
                                strcat(buffer,word_buf);
                                return assemble_line(outFile, buffer);
                            }
                        }
                    }
                }
            } else {
                int len_name = strlen(word_buf);
                //printf("Label handling %s\n",word_buf);
                if (word_buf[len_name-1] == ':') word_buf[len_name-1] = 0;
                else if (word_buf[len_name-1] == ')') {
                    /* Something like this: "macro()" */
                    handle_parse_ec(resolve_macro(outFile, buffer, NULL, TRUE));
                }
                if (word_buf[0]=='_' && word_buf[1]==0) {
                    /* Increment reusable counter on pass one and two */
                    //printf("New reusables %d at %x\n",total_reusables,program_counter);
                    if (pass_one) reusables[total_reusables] = program_counter;
                    total_reusables++;
                }
                else
                if (pass_one) {
                    /* simple label */
                    temp_label.name = (char*) malloc(len_name+1);
                    strcpy(temp_label.name, word_buf);
                    temp_label.value = program_counter;
                    insert_label(&temp_label);
                }
            }
        }
    }
}

void skip_until(char str[][64], unsigned char amount) {
    char word_buf[64],buffer[256],*ptr,backup;
    int i,ifindicator = 0,prev_line;

    
    if (!pass_one && tabFile && ingetc == &file_ingetc) 
        fprintf(tabFile,"-  -  -  -  %s",linebuf);

    prev_line = line_number;
    while (get_line(current_file, buffer)) {
        if (!next_char(buffer)) continue;
        get_word(buffer,word_buf);
        upcase(word_buf);
        //printf("Skip line fetched: %s\n",word_buf);

        if (word_buf[0]=='#') {
            //printf("%d: %s\n",line_number,buffer);
            if (word_buf[1] == 'I' && word_buf[2] == 'F') {
                ifindicator++;
                //printf("Nesting once %d\n",ifindicator);
            }
        }
        /* Deal with nested ifs */
        if (ifindicator) {
            if (!strcmp(word_buf,"#ENDIF")) {
                ifindicator--;
                //printf("unnested once %d\n",ifindicator);
                continue;
            }
        } else {
            for (i = 0; i < amount; i++) {
                if (!strcmp(word_buf,str[i])) {
                    if (prev_line != line_number)
                        if (!pass_one && tabFile && ingetc == &file_ingetc) 
                            fprintf(tabFile,"%5d %04x: -  -  -  -  %s",
                                line_number,
                                program_counter,
                                linebuf);
                    return;
                }
            }
            if (!pass_one && tabFile && ingetc == &file_ingetc) 
                fprintf(tabFile,"%5d %04x~             %s\n",
                line_number,program_counter,buffer);
        }
        prev_line = line_number;
    }
}

char* upcase(char *str) {
    int i;
    BOOL within_string,within_quote = FALSE;
    
    /* Make tests for '\' later safe */
    if (str[0] == '"') {
        i = 0;  within_string = TRUE;
    } else if (str[0] == '\'') {
        i = 0;  within_quote = TRUE;
    } else {
        i = -1; within_string = FALSE;
    }
    
    while (str[++i]) {
        if (str[i] >= 'a' && str[i] <= 'z' 
            && !(within_quote || within_string)) 
            str[i] += 'A'-'a';
        else if (str[i] == '"' && str[i-1]!='\\') within_string = !within_string;
        else if (str[i] == '\'' && str[i-1]!='\\' && !within_string) within_quote = !within_quote;
    }
    return str;
}

char* pack_string(char *str) {
    int i,si = 0;
    
    for (i = 0; str[i]; i++) if (str[i] != ' ' && str[i] != '\t') str[si++] = str[i];
    str[si] = 0;
    return upcase(str);
}


char* get_word(char* str, char* buffer) {
    int i;
    BOOL in_string = FALSE, escape = FALSE;
    if (!(str = next_char(str))) return NULL;
    for (i = 0; *str; str++) {
        if ((*str == ' ' || *str == '\t') && !in_string) break;
        else if (*str == '"' && !escape) in_string = !in_string;
        else if (*str == '\\') escape = TRUE;
        else escape = FALSE;
        
        buffer[i++] = *str;
    }
    buffer[i] = 0;
    if (!*str && !i) return NULL;
    return str;
}

/* Slow function to simplify coding of macros and directives with argument lists
 .db blank, blank, macro(blank,blank), blank
 macro( macro( blank, blank), blank) */
 
 /* Returns pointer to the character after the word, or NULL if no word */
char* get_level_word(char* input, char* output, char stop) {
    int parindicator, i;
    BOOL in_string = FALSE, in_quote = FALSE;
    
    if (!input) return NULL;

    parindicator = 0;
    while (*input && !(*input == stop && !parindicator && !in_string && !in_quote)) {
        if (!in_string && !in_quote) {
            if (*input == '(') parindicator++;
            else if (*input == ')') parindicator--;
        } else if (*input == '\\') {
            *output++ = *input++;
            *output++ = *input++;
            continue;
        }
        if (parindicator >= 0) {
            if (*input == '"') in_string = !in_string;
            else if (*input == '\'' && !in_string) in_quote = !in_quote;
            *output++ = *input;
        }
        input++;
    }
    *output = 0;
    if (*input) return input + 1;
    else return input;
}
        
/* Removes surrounding quotation marks and reduces control characters */
char* reduce_string(char* input) {
    char *output = input;
    int i = 0;
    if (input[0] == '"') i = 1;
    
    for (; input[i] && input[i] != '"'; i++, output++) {
        if (input[i] == '\\') {
            char pbuf[16] = "'\\x'";
            pbuf[2] = input[++i];
            *output = parse_string(pbuf);
        } else {
            *output = input[i];
        }
    }
    *output = 0;
    return input;
}

int tabfputc(int outchar, FILE* file) {
    fputc(outchar, file);
    if (list_bytes == 4) {
        list_bytes = 0;
        fprintf(tabFile,"\n            ");
    }
    fprintf(tabFile,"%02X ", (unsigned char) outchar);
    list_bytes++;
    return outchar;
}

void post_error(const char * str, ...) {
    va_list arg_list;
    int arg1,arg2,arg3;
    
    va_start(arg_list,str);
    arg1 = va_arg(arg_list,int);
    arg2 = va_arg(arg_list,int);
    arg3 = va_arg(arg_list,int);
    printf("%s line %d: ",current_file_name,line_number);
    printf(str,arg1,arg2,arg3);
    putchar('\n');
    if (tabFile && !pass_one) {
        fprintf(tabFile,"Error: ");
        fprintf(tabFile,str,arg1,arg2,arg3);
        fprintf(tabFile,"\n%5d %04x: ",line_number,program_counter);
    }
}

void tabinsert(const char * str, ...) {
    va_list arg_list;
    int arg1;
    
    va_start(arg_list,str);
    arg1 = va_arg(arg_list,int);
    
    fprintf(tabFile,str,arg1);
    fprintf(tabFile,"\n            ",line_number,program_counter);
}
