/*
 * picasm.c
 *
 * Copyright 1995-2005 Timo Rossi, <trossi@iki.fi>
 * See the file LICENSE for license terms.
 *
 * http://www.iki.fi/trossi/pic/
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>

#include "picasm.h"
#include "util.h"
#include "token.h"
#include "symtab.h"

int warnlevel;

static int total_line_count;
static int errors, warnings; /* error & warning counts */

unsigned short list_flags;

static FILE *list_fp;
int listing_on;
static int list_loc;
static pic_instr_t *list_ptr;
long list_val;
static long list_len;

int default_radix;

int cond_nest_count;
int unique_id_count;

/* the current source file/macro */
struct inc_file *current_file;

/* Line buffer & pointer to it */
char *line_buf_ptr;
char line_buffer[256];

static struct patch *global_patch_list;
struct patch **local_patch_list_ptr;

char pic_name[256];

instrset_t pic_instr_set; /* set the PIC_NONE before PIC type is defined */

int prog_mem_size = PROGMEM_MAX;
static int data_eeprom_size = EEPROM_MAX;
static int reg_file_limit = 0x100;

short code_generated;

static pic_instr_t prog_mem[PROGMEM_MAX];
static pic_instr_t data_eeprom[EEPROM_MAX];
static pic_instr_t pic_id[4];
pic_instr_t config_fuses;

org_mode_t O_Mode;

int prog_location;         /* current address for program code */
int reg_location;   /* current address for register file */
int edata_location; /* current address for data EEPROM */
int org_val;

int local_level;

static void
err_out(char *str)
{
    fputs(str, stderr);
    fputc('\n', stderr);
}

/* Error handling */
/*
 * Show line number/line with error message
 */
static void
err_line_ref(void)
{
    struct inc_file *inc;
    char outbuf[128];
    int len;

    if(current_file != NULL)
    {
	inc = current_file;
	if(inc->type != INC_FILE)
	{
	    p_snprintf(outbuf, sizeof outbuf, "(Macro %-.99s line %d)",
		       inc->v.m.sym->name, inc->linenum);
	    err_out(outbuf);
	    while(inc != NULL && inc->type != INC_FILE)
		inc = inc->next;
	}
	p_snprintf(outbuf, sizeof outbuf, "%-.99s:%d:",
		   inc->v.f.fname, inc->linenum);
	err_out(outbuf);
	len = strlen(line_buffer);
	if(len > 100)
	    len = 100;
	strncpy(outbuf, line_buffer, len);
	outbuf[len] = '\0';
	err_out(outbuf);
    }
}

/*
 * Warning message
 */
void
warning(char *fmt, ...)
{
    char outbuf[128];
    va_list args;
    static const char warning_msg[] = "Warning: ";

    err_line_ref();
    strcpy(outbuf, warning_msg);
    va_start(args, fmt);
    p_vsnprintf(outbuf + (sizeof warning_msg - 1),
		sizeof outbuf - (sizeof warning_msg - 1), fmt, args);
    va_end(args);
    err_out(outbuf);
    if(list_fp != NULL)
	fputs(outbuf, list_fp);
    warnings++;
}

/*
 * skip the end of line, in case of an error
 */
static void
error_lineskip(void)
{
    write_listing_line(0);
    skip_eol();
    get_token();
}

/*
 * Error message
 * call error_lineskip() if lskip is non-zero
 *
 */
void
error(int lskip, char *fmt, ...)
{
    va_list args;
    char outbuf[128];
    static const char error_msg[] = "Error: ";

    err_line_ref();
    strcpy(outbuf, error_msg);
    va_start(args, fmt);
    p_vsnprintf(outbuf + (sizeof error_msg - 1),
		sizeof outbuf - (sizeof error_msg - 1),
		fmt, args);
    va_end(args);
    err_out(outbuf);
    if(list_fp != NULL)
    {
	fputs(outbuf, list_fp);
	fputc('\n', list_fp);
    }
    if(++errors >= MAX_ERRORS)
	fatal_error("too many errors, aborting");
	
    if(lskip)
	error_lineskip();
}

/*
 * Fatal error message
 */
void
fatal_error(char *fmt, ...)
{
    va_list args;
    char outbuf[128];
    static char const fatal_err_msg[] = "Fatal error: ";

    err_line_ref();
    strcpy(outbuf, fatal_err_msg);
    va_start(args, fmt);
    p_vsnprintf(outbuf + (sizeof fatal_err_msg - 1),
	     sizeof outbuf - (sizeof fatal_err_msg - 1),
	     fmt, args);
    va_end(args);
    err_out(outbuf);
    exit(EXIT_FAILURE);
}

/* memory allocation */
void *
mem_alloc(int size)
{
    void *p;

    if((p = malloc(size)) == NULL)
	fatal_error("Out of memory");

    return p;
}

/*
 * initialize the assembler
 */
static void
init_assembler(void)
{
    pic_instr_t *cp;
    int n;

    init_symtab();   /* initialize symbol table  */
    init_includes(); /* initialize include paths */

    /* initialize program memory to invalid values */
    for(n = PROGMEM_MAX, cp = prog_mem; n-- > 0; *cp++ = INVALID_INSTR)
	;

    /* initialize data memory to invalid values */
    for(n = EEPROM_MAX, cp = data_eeprom; n-- > 0; *cp++ = INVALID_DATA)
	;

    /* initialize jump  patch list */
    global_patch_list = NULL;

    prog_location = -1;
    reg_location = -1;
    edata_location = -1;

    org_val = -1;

    current_file = NULL;

    config_fuses = INVALID_CONFIG;
    pic_id[0] = INVALID_ID;

    errors = warnings = 0;

    list_flags = 0;
    list_len = 0;
    listing_on = 1;

    cond_nest_count = 0;
    unique_id_count = 1;
    total_line_count = 0;
    code_generated = 0;

    local_level = 0;

    ifskip_mode = 0;

    default_radix = 10;
}

/*
 * generate program code
 */
void gen_code(int val)
{
    if(pic_instr_set == PIC_NONE)
	fatal_error("PIC device type not set");

    if(O_Mode == O_NONE)
    {
	O_Mode = O_PROGRAM;

	if(org_val < 0)
	{
	    error(0, "ORG value not set");
	    prog_location = 0;
	    return;
	}

	prog_location = org_val;
    }
    else if(O_Mode != O_PROGRAM)
    {
	error(0, "ORG mode conflict");
	O_Mode = O_PROGRAM;
	return;
    }

    if(prog_location >= prog_mem_size)
	fatal_error("Code address out of range");

    if(prog_mem[prog_location] != INVALID_INSTR)
	warning("Overlapping code at 0x%x", prog_location);

    if((list_flags & LIST_PROG) == 0)
    {
	list_loc = prog_location;
	list_flags = LIST_LOC | LIST_PROG;
    }
    list_len++;

    prog_mem[prog_location++] = val;

    code_generated = 1;
}

/*
 * Generate data for data EEPROM
 */
void
gen_edata(int val)
{
    if(O_Mode == O_NONE)
    {
	O_Mode = O_EDATA;

	if(org_val < 0)
	{
	    error(0, "ORG value not set");
	    edata_location = 0;
	    return;
	}

	edata_location = org_val;
    }
    else if(O_Mode != O_EDATA)
    {
	error(0, "ORG mode conflict");
	O_Mode = O_EDATA;
	return;
    }

    if(edata_location >= data_eeprom_size)
	fatal_error("Data EEPROM address out of range");

    if(data_eeprom[edata_location] < 0x100)
	warning("Overlapping EEPROM data at 0x%x", edata_location);

    if((list_flags & LIST_LOC) == 0)
    {
	list_loc = edata_location;
	list_flags = LIST_LOC | LIST_EDATA;
    }
    list_len++;

    data_eeprom[edata_location++] = val;

    code_generated = 1;
}

/*
 * Write one line of Intel-hex file
 */
static void
write_hex_record(FILE *fp,
		 int reclen, /* length (in words) */
		 int loc, /* address */
		 pic_instr_t *data, /* pointer to word data */
		 int format) /* IHX8M or IHX16 */
{
    unsigned int check = 0;

    switch(format)
    {
    case IHX8M:
	fprintf(fp, ":%02X%04X00", 2*reclen, 2*loc);
	check += ((2*loc) & 0xff) + (((2*loc) >> 8) & 0xff) + 2*reclen;
	break;

    case IHX16:
	fprintf(fp, ":%02X%04X00", reclen, loc);
	check += (loc & 0xff) + ((loc >> 8) & 0xff) + reclen;
	break;
    }

    while(reclen--)
    {
	switch(format)
	{
	case IHX8M:
	    fprintf(fp, "%02X%02X",
		    (int)(*data & 0xff), (int)((*data >> 8) & 0xff));
	    break;

	case IHX16:
	    fprintf(fp, "%02X%02X",
		    (int)((*data >> 8) & 0xff), (int)(*data & 0xff));
	    break;
	}
	check += (*data & 0xff) + ((*data >> 8) & 0xff);
	data++;
    }

    /* write checksum */
    fprintf(fp, "%02X" HEX_EOL, (0x100 - (check & 0xff)) & 0xff);
}

/*
 * Write output file in ihx8m or ihx16-format
 *
 */
static void
write_output(char *fname, int format)
{
    int loc, reclen;
    FILE *fp;

    if((fp = fopen(fname, "w")) == NULL)
	fatal_error("Can't create file '%s': %s", fname, strerror(errno));

    /* program */
    for(loc = 0;;)
    {
	while(loc < prog_mem_size && prog_mem[loc] == INVALID_INSTR)
	    loc++;

	if(loc >= prog_mem_size)
	    break;
      
	reclen = 0;
	while(reclen < 8 && loc < prog_mem_size
	      && prog_mem[loc] != INVALID_INSTR)
	{
	    loc++;
	    reclen++;
	}
	write_hex_record(fp, reclen, loc-reclen, &prog_mem[loc-reclen], format);
    }

    /* PIC ID */
    if(pic_id[0] != INVALID_ID)
    {
	switch(pic_instr_set)
	{
	case PIC12BIT:
	    write_hex_record(fp, 4, prog_mem_size, pic_id, format);
	    break;

	case PIC14BIT:
	    write_hex_record(fp, 4, 0x2000, pic_id, format);
	    break;

	default:
	    break;
	}
    }

    /* config fuses */
    if(config_fuses != INVALID_CONFIG)
    {
	switch(pic_instr_set)
	{
	case PIC12BIT:
	    write_hex_record(fp, 1, 0xfff, &config_fuses, format);
	    break;
	case PIC14BIT:
	    write_hex_record(fp, 1, 0x2007, &config_fuses, format);
	    break;
	case PIC16BIT_NOMUL:
	case PIC16BIT:
	    /* XXX Is this correct? */
	    write_hex_record(fp, 1, 0xfe00, &config_fuses, format);
	    break;

	case PIC_NONE:
	    fatal_error("internal error");
	    break;
	}
    }

    if(data_eeprom_size > 0)
    { /* data EEPROM */
	for(loc = 0;;)
	{
	    while(loc < data_eeprom_size && data_eeprom[loc] >= 0x100)
		loc++;
        
	    if(loc >= data_eeprom_size)
		break;
        
	    reclen = 0;
	    while(reclen < 8 && loc < data_eeprom_size
		  && data_eeprom[loc] < 0x100)
	    {
		loc++;
		reclen++;
	    }
	    write_hex_record(fp, reclen, 0x2100+loc-reclen,
			     &data_eeprom[loc-reclen], format);
	}
    }

    fputs(":00000001FF" HEX_EOL , fp); /* end record */
    fclose(fp);
}

/*
 * Write one line to listing file (if listing is enabled)
 */
void
write_listing_line(int cond_flag)
{
    int i;

    if(list_fp != NULL && listing_on)
    {
	fprintf(list_fp, "%04d%c%c",
		++total_line_count,
		(current_file != NULL && current_file->type == INC_MACRO ?
		 '+' : ' '),
		(cond_flag ? '!' : ' '));

	if(line_buffer[0] != '\0')
	{
	    if(list_flags & LIST_VAL)
	    {
		fprintf(list_fp, "%08lX  ", list_val);
	    }
	    else
	    {
		if(list_flags & LIST_LOC)
		    fprintf(list_fp, "%04X", list_loc);
		else
		    fputs("    ", list_fp);

		if((list_flags & (LIST_PROG|LIST_EDATA|LIST_PTR)) != 0)
		{
		    fputc(((list_flags & LIST_FORWARD) ? '?' : ' '), list_fp);
		    if(list_flags & LIST_PROG)
		    {
			fprintf(list_fp, "%04X ",
				(unsigned short)prog_mem[list_loc]);
		    }
		    else if(list_flags & LIST_EDATA)
		    {
			fprintf(list_fp, "%04X ",
				(unsigned short)data_eeprom[list_loc]);
		    }
		    else if(list_flags & LIST_PTR)
		    {
			fprintf(list_fp, "%04X ",
				(unsigned short)(*list_ptr++));
		    }
		}
		else
		{
		    fputs("      ", list_fp);
		}
	    }

	    fputs(line_buffer, list_fp);
	    fputc('\n', list_fp);

	    list_len--;
	    for(i = 0; i < list_len; i++)
	    {
		list_loc++;
		fprintf(list_fp, "%04d%c ",
			total_line_count,
			(current_file != NULL
			 && current_file->type == INC_MACRO ?
			 '+' : ' '));

		if(list_flags & LIST_LOC)
		    fprintf(list_fp, "%04X", list_loc);
		else
		    fputs("    ", list_fp);

		if(list_flags & LIST_PROG)
		{
		    fprintf(list_fp, " %04X\n",
			    (unsigned short)prog_mem[list_loc]);
		}
		else if(list_flags & LIST_EDATA)
		{
		    fprintf(list_fp, " %04X\n",
			    (unsigned short)
			    data_eeprom[list_loc]);
		}
		else if(list_flags & LIST_PTR)
		{
		    fprintf(list_fp, " %04X\n",
			    (unsigned short)(*list_ptr++));
		}
	    }
	}
	else
	{ /* if(line_buffer[0] != '\0' ... */
	    fputc('\n', list_fp);
	}

	if(listing_on < 0)
	    listing_on = 0;
    }
    list_flags = 0;
    list_len = 0;
}

/*
 * parse and handle OPT-directive
 * (this is special as it must be done at macro definition time
 *  if inside a macro)
 */
static int
handle_opt(void)
{
    if(token_type != TOK_IDENTIFIER)
    {
	error(1, "OPT syntax error");
	return FAIL;
    }
    /*
     * Note: when listing is turned off, 'listing_on' is set to -1
     * here and the listing routine sets it to zero after listing
     * the line containing the 'opt nol'.
     */
    if(strcasecmp(token_string, "nol") == 0
       || strcasecmp(token_string, "nolist") == 0)
    {
	listing_on = -1;
    }
    else if(strcasecmp(token_string, "l") == 0
	    || strcasecmp(token_string, "list") == 0)
    {
	listing_on = 1;
    }
    else
    {
	error(1, "OPT syntax error");
	return FAIL;
    }

    get_token();
    return OK;
}

/*
 * Define a macro
 *
 * the check for multiple definitions of the macro name
 * has been done before calling this function.
 */
static void
define_macro(char *name)
{
    struct symbol *sym;
    struct macro_line *ml;
    int t;

    if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
	error(0, "Extraneous characters after a valid source line");
    skip_eol();

    write_listing_line(0);

    sym = add_symbol(name, SYMTAB_GLOBAL);
    sym->type = SYM_MACRO;
    sym->v.text = NULL;
    ml = NULL;

    for(;;)
    {
	get_token(); /* read first token on next line */
	t = 0;
	if(token_type == TOK_IDENTIFIER)
	{
	    t = 1;
	    get_token();
	    if(token_type == TOK_COLON)
		get_token();
	}
	if(token_type == TOK_EOF || token_type == KW_END)
	    fatal_error("Macro definition not terminated");

	if(token_type == KW_MACRO)
	    fatal_error("Nested macro definitions not allowed");

	if(token_type == KW_ENDM) /* end macro definition */
	    break;

/* OPT must be handled inside macros at definition time */
	if(token_type == KW_OPT)
	{
	    get_token();
	    handle_opt();
	}
	else
	{
	    if(ml == NULL)
	    {
		ml = mem_alloc(sizeof(struct macro_line)
			       +strlen(line_buffer));
		sym->v.text = ml;
	    }
	    else
	    {
		ml->next = mem_alloc(sizeof(struct macro_line)
				     +strlen(line_buffer));
		ml = ml->next;
	    }
	    strcpy(ml->text, line_buffer);
	    ml->next = NULL;
	}

	write_listing_line(0);

	line_buf_ptr = NULL;
	tok_char = ' ';
    }
    if(t)
	error(0, "Label not allowed with ENDM");
    get_token();
}

/*
 * Skip subroutine used by the conditional assembly directives
 * return: -1=premature EOF, 0=ok, 1=label (not allowed)
 */
static int
if_else_skip(void)
{
    int t, ccount;

    ccount = 0;
    ifskip_mode++;
    do
    {
	skip_eol();
	get_token();
	write_listing_line(1);

	t = 0;
	if(token_type == TOK_IDENTIFIER)
	{
	    t = 1;
	    get_token();
	    if(token_type == TOK_COLON)
		get_token();
	}
	if(token_type == KW_IF)
	{
	    ccount++;
	}
	else if(token_type == KW_ENDIF)
	{
	    if(ccount <= 0)
		break;
	    ccount--;
	}
	else if(token_type == KW_ELSE && ccount <= 0)
	{
	    break;
	}
    } while(token_type != TOK_EOF && token_type != KW_END);

    ifskip_mode--;
    return (token_type == TOK_EOF || token_type == KW_END) ? -1 : t;
}

/*
 * Add a patch pointing to the current location
 */
void
add_patch(int tab, struct symbol *sym, patchtype_t type)
{
    struct patch **patch_list_ptr, *ptch;

    patch_list_ptr =
	(tab == SYMTAB_GLOBAL ? &global_patch_list : local_patch_list_ptr);

    if(O_Mode == O_NONE)
    {
	O_Mode = O_PROGRAM;
	if(org_val < 0)
	{
	    error(0, "ORG value not set");
	    prog_location = 0;
	    return;
	}
	prog_location = org_val;
    }

    ptch = mem_alloc(sizeof(struct patch));
    ptch->label = sym;
    ptch->type = type;
    ptch->location = prog_location;

    /* add a new patch to patch_list */
    ptch->next = *patch_list_ptr;
    *patch_list_ptr = ptch;
}  

/*
 * Apply the store patches and also free the patch list
 */
static void
apply_patches(struct patch *patch_list)
{
    struct patch *ptch, *p2;

    /*
     * fix forward jumps/calls
     */
    for(ptch = patch_list; ptch != NULL; ptch = p2)
    {
	p2 = ptch->next;

	if(ptch->label->type == SYM_FORWARD)
	{
	    error(0, "Undefined label '%s%s'",
		  (patch_list == global_patch_list ? "" : "="),
		  ptch->label->name);
	}
	else
	{
	    switch(ptch->type)
	    {
	    case PATCH8:
		if(pic_instr_set == PIC12BIT
		   && (ptch->label->v.value & 0x100) != 0
		   && (prog_mem[ptch->location] & 0xff00) == 0x900)
		    error(0, "CALL address in upper half of a page (label '%s%s')",
			  (patch_list == global_patch_list ? "" : "="),
			  ptch->label->name);

		prog_mem[ptch->location] =
		    (prog_mem[ptch->location] & 0xff00)
		    | (ptch->label->v.value & 0xff);
		break;

	    case PATCH9:
		prog_mem[ptch->location] =
		    (prog_mem[ptch->location] & 0xfe00)
		    | (ptch->label->v.value & 0x1ff);
		break;

	    case PATCH11:
		prog_mem[ptch->location] =
		    (prog_mem[ptch->location] & 0xf800)
		    | (ptch->label->v.value & 0x7ff);
		break;

	    case PATCH_UPPER_BYTE:
		prog_mem[ptch->location] =
		    (prog_mem[ptch->location] & 0xf800)
		    | ((ptch->label->v.value >> 8) & 0xff);
		break;
	    }
	}
	mem_free(ptch);
    }
}

/*
 * Generate code for an instruction with 8-bit literal data
 * allows forward references
 */
int
gen_byte_c(int instr_code)
{
    int symtype;
    long val;
    struct symbol *sym;

    if(token_type == KW_HIBYTE)
    {
	get_token();
	if(token_type != TOK_LEFTPAR)
	{
	    error(1, "'(' expected");
	    return FAIL;
	}
	get_token();
	if(token_type != TOK_IDENTIFIER && token_type != TOK_LOCAL_ID)
	{
	    error(1, "Identifier expected");
	    return FAIL;
	}
	symtype =
	    (token_type == TOK_IDENTIFIER ? SYMTAB_GLOBAL : SYMTAB_LOCAL);

	if(symtype == SYMTAB_LOCAL && local_level == 0)
	{
	    error(1, "Local symbol outside a LOCAL block");
	    return FAIL;
	}

	sym = lookup_symbol(token_string, symtype);
	if(sym != NULL && sym->type != SYM_FORWARD)
	{
	    if(sym->type != SYM_SET || sym->type != SYM_DEFINED)
	    {
		error(1, "Invalid symbol type");
		return FAIL;
	    }

	    gen_code(instr_code | ((sym->v.value >> 8) & 0xff));
	}
	else
	{
	    if(sym == NULL)
	    {
		sym = add_symbol(token_string, symtype);
		sym->type = SYM_FORWARD;
	    }

	    add_patch(symtype, sym, PATCH_UPPER_BYTE);
	    gen_code(instr_code);
	    list_flags |= LIST_FORWARD;
	}

	get_token();
	if(token_type != TOK_RIGHTPAR)
	{
	    error(1, "')' expected");
	    return FAIL;
	}
	get_token();
	return OK;
    }


    if(token_type == TOK_IDENTIFIER || token_type == TOK_LOCAL_ID)
    {
	symtype =
	    (token_type == TOK_IDENTIFIER ? SYMTAB_GLOBAL : SYMTAB_LOCAL);
	
	if(symtype == SYMTAB_LOCAL && local_level == 0)
	{
	    error(1, "Local symbol outside a LOCAL block");
	    return FAIL;
	}
	
	sym = lookup_symbol(token_string, symtype);
	if(sym == NULL || sym->type == SYM_FORWARD)
	{
	    if(sym == NULL)
	    {
		sym = add_symbol(token_string, symtype);
		sym->type = SYM_FORWARD;
	    }

	    add_patch(symtype, sym, PATCH8);
	    get_token();
	    gen_code(instr_code);
	    list_flags |= LIST_FORWARD;
	    return OK;
	}
    }

    if(get_expression(&val) != OK)
	return FAIL;

    if(val < -0x80 || val > 0xff)
    {
	error(0, "8-bit literal out of range");
	return FAIL;
    }

    gen_code(instr_code | (val & 0xff));
    return OK;
}

/*
 * check if the current token is a valid ORG mode specifier
 * and return the mode (or O_NONE if not valid mode specifier)
 */
static int
org_mode(void)
{
    if(token_type == KW_EDATA)
	return O_EDATA;

    if(token_type == TOK_IDENTIFIER)
    {
	if(strcasecmp(token_string, "code") == 0)
	    return O_PROGRAM;

	if(strcasecmp(token_string, "reg") == 0)
	    return O_REGFILE;
    }

    return O_NONE;
}

/*
 * Device type selection: automatically include a device-specific
 * header file (which uses the set_pic_type directive and defines
 * the __do_config directive).
 */
static void
do_device_include(char *devname)
{
    char incname[256];
    char *cp;
    int c;

    if(strncasecmp(devname, "PIC", 3) == 0)
	devname += 3;

    strcpy(incname, "_pic");
    cp = &incname[4];
    while(cp < &incname[sizeof(incname)-3])
    {
	c = (unsigned char)(*devname);
	if(c == '\0')
	    break;

	if(isupper(c))
	    c = tolower(c);

	*cp++ = c;
	devname++;
    }
    strcpy(cp, ".i");

    get_token();

    if(pic_instr_set != PIC_NONE)
    {
	/*
	 * This error message would be misleading when the device type
	 * has been set on the picasm command line, but because it can
	 * only be done once from the command line, we can not get
	 * this error condition in that case.
	 */
	error(1, "Duplicate DEVICE setting");
	return;
    }

    begin_include(incname, 1, 1);
}

/*
 * The assembler itself
 */
static void
assembler(char *fname, char *cmdline_pic_type)
{
    static char symname[256];
    struct symbol *sym;
    int op, t, symtype;
    long val;
    char *cp;

    sym = add_symbol("_PICASM", SYMTAB_GLOBAL);
    sym->type = SYM_DEFINED;
    sym->type2 = SYMT_CONSTANT;
    sym->v.value = I_VERSION;

    begin_include(fname, 0, 1);

    if(cmdline_pic_type != NULL)
	do_device_include(cmdline_pic_type);

    get_token();

    while(token_type != TOK_EOF)
    {
	sym = NULL;
	if(token_type == TOK_IDENTIFIER || token_type == TOK_LOCAL_ID)
	{
	    symtype =
		(token_type == TOK_IDENTIFIER ? SYMTAB_GLOBAL : SYMTAB_LOCAL);

	    if(symtype == SYMTAB_LOCAL && local_level == 0)
	    {
		error(1, "Local symbol outside a LOCAL block");
		continue;
	    }

	    t = (line_buf_off == 0);

	    p_strcpy(symname, token_string, sizeof symname);
	    sym = lookup_symbol(symname, symtype);
	    if(sym != NULL && sym->type == SYM_MACRO)
	    {
		/* skip whitespace */
		while(isspace(tok_char))
		    read_src_char();

		if(line_buf_ptr != NULL &&
		   strncasecmp(line_buf_ptr-1, "macro", 5) == 0 &&
		   line_buf_ptr[4] != '.' && line_buf_ptr[4] != '_' &&
		   !isalnum((unsigned char)line_buf_ptr[4]))
		{
		    error(1, "Multiple definition of macro '%s'", symname);
		    continue;
		}

		expand_macro(sym);
		continue;
	    }

	    get_token();
	    switch(token_type)
	    {
	    case KW_MACRO:
		get_token();
		if(sym != NULL)
		{
		    error(1, "Multiply defined symbol '%s%s'",
			  (symtype == SYMTAB_LOCAL ? "=" : ""),
			  sym->name);
		    continue;
		}
		define_macro(symname);
		goto line_end;

	    case TOK_EQUAL: /* '=' can be used instead of EQU */
	    case KW_EQU:
		if(sym != NULL)
		{
		    if(sym->type != SYM_FORWARD)
			error(0, "Multiply defined symbol '%s%s'",
			      (symtype == SYMTAB_LOCAL ? "=" : ""),
			      sym->name);
		}
		else
		{
		    sym = add_symbol(symname, symtype);
		}
		get_token();
		sym->type = SYM_DEFINED;
		sym->type2 = SYMT_CONSTANT;

		if(get_expression(&sym->v.value) != OK)
		    continue; /* error_lineskip() done in expr.c */

		list_val = sym->v.value;
		list_flags = LIST_VAL;
		goto line_end;

	    case KW_SET:
		if(sym != NULL && sym->type != SYM_SET)
		{
		    error(0, "Multiply defined symbol '%s%s'",
			  (symtype == SYMTAB_LOCAL ? "=" : ""),
			  sym->name);
		}
		else if(sym == NULL)
		{
		    sym = add_symbol(symname, symtype);
		}
		get_token();
		sym->type = SYM_SET;
		sym->type2 = SYMT_CONSTANT;

		if(get_expression(&sym->v.value) != OK)
		    continue;

		list_val = sym->v.value;
		list_flags = LIST_VAL;
		goto line_end;

	    case TOK_COLON:
		get_token();
		goto do_label;

	    default:
		if(t == 0)
		    warning("Label not in the beginning of a line");

	    do_label:
		switch(O_Mode)
		{
		case O_PROGRAM:
		    t = prog_location;
		    break;

		case O_REGFILE:
		    t = reg_location;
		    break;

		case O_EDATA:
		    t = edata_location;
		    break;

		case O_NONE:
		    t = org_val;
		    break;
		}

		if(t < 0)
		{
		    error(0, "ORG value not set");
		}
		else
		{
		    if(sym != NULL && sym->type != SYM_FORWARD)
		    {
			error(0, "Multiply defined symbol '%s%s'",
			      (symtype == SYMTAB_LOCAL ? "=" : ""),
			      sym->name);
		    }
		    if(sym == NULL)
		    {
			sym = add_symbol(symname, symtype);
		    }
		    sym->type = SYM_DEFINED;
		    switch(O_Mode)
		    {
		    case O_PROGRAM:
			sym->type2 = SYMT_LABEL;
			break;

		    case O_REGFILE:
			sym->type2 = SYMT_REGFILE;
			break;

		    default:
			break;
		    }
		    sym->v.value = t;
		    list_loc = t;
		    list_flags = LIST_LOC;
		}
		break;
	    }
	} /* if(token_type == TOK_IDENTIFIER... */

	/* if this line has a label, 'sym' points to it */

	if(token_type == TOK_NEWLINE)
	{
	    write_listing_line(0);
	    get_token();
	    continue;
	}

	if(token_type == TOK_IDENTIFIER &&
	   (sym = lookup_symbol(token_string, SYMTAB_GLOBAL))
	   != NULL && sym->type == SYM_MACRO)
	{
	    expand_macro(sym);
	    continue;
	}

	if(token_type == KW_END)
	    break;

	if(token_type == KW_ERROR)
	{
	    while(isspace((unsigned char)(*line_buf_ptr)))
		line_buf_ptr++;
	    error(1, "%s", line_buf_ptr);
	    continue;
	}
	  
	op = token_type;
	get_token();

	switch(op)
	{
	case KW_INCLUDE:
	    if(token_type != TOK_STRCONST)
	    {
		error(1, "Missing file name after INCLUDE");
		continue;
	    }

	    p_strcpy(symname, token_string, sizeof symname);
	    get_token();
	    if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
		error(0, "Extraneous characters after a valid source line");

	    begin_include(symname, 1, 0);

	    write_listing_line(0);
	    get_token();
	    continue;

	case TOK_EQUAL:
	case KW_SET:
	case KW_EQU:
	    if(sym == NULL)
		error(1, "SET/EQU without a label");
	    else
		error(1, "SET/EQU syntax error");
	    continue;

	case KW_MACRO:
	    error(1, "MACRO without a macro name");
	    continue;

	case KW_ENDM:
	    error(1, "ENDM not allowed outside a macro");
	    continue;

	case KW_EXITM:
	    if(current_file == NULL || current_file->type != INC_MACRO)
	    {
		error(1, "EXITM not allowed outside a macro");
		continue;
	    }
	    cond_nest_count = current_file->cond_nest_count;
	    end_include();
	    break;

	case KW_OPT:
	    if(handle_opt() != OK)
	    {
		error_lineskip();
		continue;
	    }
	    break;

	case KW_LOCAL:
	    add_local_symtab();
	    break;

	case KW_ENDLOCAL:
	    if(local_level == 0)
	    {
		error(1, "ENDLOCAL without LOCAL");
		continue;
	    }

	    apply_patches(*local_patch_list_ptr);
	    remove_local_symtab();
	    break;
	    
	case KW_IF:
	    if(sym != NULL)
		error(0, "Label not allowed with IF");

	    get_expression(&val); /*note: error status ignored*/

	    if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
		error(0, "Extraneous characters after a valid source line");

	    if(val == 0)
	    {
		write_listing_line(0);
		t=if_else_skip();
		if(t == -1)
		    fatal_error("Conditional not terminated");
		else if(t == 1)
		    error(0, "Label not allowed with %s",
			  (token_type == KW_ELSE ? "ELSE" : "ENDIF"));

		if(token_type == KW_ELSE)
		    cond_nest_count++;
		get_token();
		goto line_end2;
	    }
	    else
	    {
		cond_nest_count++;
	    }
	    break;

	case KW_ELSE:
	    if(sym != NULL)
		error(0, "Label not allowed with %s", "ELSE");

	    if(current_file == NULL
	       || cond_nest_count <= current_file->cond_nest_count)
		error(0, "ELSE without IF");

	    write_listing_line(0);
	    t = if_else_skip();
	    if(t == -1)
		fatal_error("Conditional not terminated");
	    else if(t == 1)
		error(0, "Label not allowed with %s",
		      (token_type == KW_ELSE ? "ELSE" : "ENDIF"));

	    if(token_type == KW_ELSE)
		error(0, "Multiple ELSE statements with one IF");

	    cond_nest_count--;

	    get_token();
	    goto line_end2;

	case KW_ENDIF:
	    if(sym != NULL)
		error(0, "Label not allowed with %s", "ENDIF");

	    if(current_file == NULL
	       || cond_nest_count <= current_file->cond_nest_count)
		error(0, "ENDIF without IF");

	    cond_nest_count--;
	    break;

	case KW_ORG:
	    if(pic_instr_set == PIC_NONE)
		fatal_error("PIC device type not set");

	    org_val = -1;
	    if((t = org_mode()) != O_NONE)
	    {
		get_token();
		O_Mode = (org_mode_t)t;
		switch(O_Mode)
		{
		case O_PROGRAM:
		case O_NONE: /* shut up GCC warning, cannot really happen here */
		    org_val = prog_location;
		    break;

		case O_REGFILE:
		    org_val = reg_location;
		    break;

		case O_EDATA:
		    org_val = edata_location;
		    break;
		}
		if(org_val < 0)
		{
		    error(0, "ORG value not set");
		    O_Mode = O_NONE;
		}
		break;
	    }

	    if(get_expression(&val) != OK)
		continue;

	    if(val < 0 || val >= prog_mem_size)
	    {
		error(1, "ORG value out of range");
		continue;
	    }

	    org_val = val;
	    O_Mode = O_NONE;

	    if(token_type == TOK_COMMA)
	    {
		get_token();
		if((t = org_mode()) == O_NONE)
		{
		    error(0, "Invalid ORG mode");
		}
		else
		{
		    O_Mode = (org_mode_t)t;
		    switch(O_Mode)
		    {
		    case O_PROGRAM:
		    case O_NONE: /* shut up GCC warning, cannot really happen here */
			prog_location = org_val;
			break;
		  
		    case O_REGFILE:
			reg_location = org_val;
			break;
		  
		    case O_EDATA:
			edata_location = org_val;
			break;
		    }
		}
	      
		get_token();
	    }
	    
	    list_loc = org_val;
	    list_flags = LIST_LOC;
	    break;

	case KW_DS:
	    if(get_expression(&val) != OK)
		continue;

	    if(O_Mode == O_NONE)
	    {
		O_Mode = O_REGFILE;
		if(org_val < 0)
		{
		    error(0, "ORG value not set");
		    reg_location = 0;
		}
		else
		{
		    reg_location = org_val;
		}
	    }

	    if(O_Mode != O_REGFILE)
	    {
		error(0, "ORG mode conflict");
	    }
	    else
	    {
		if(reg_location >= reg_file_limit)
		    fatal_error("Register file address out of range");
		list_loc = reg_location;
		list_flags = LIST_LOC;
		reg_location += val;
	    }

	    if(sym != NULL && sym->type2 == SYMT_UNKNOWN)
		sym->type2 = SYMT_REGFILE;
	    break;

	case KW_CBLOCK:
	    if(sym != NULL)
		error(0, "Label not allowed with CBLOCK");

	    if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
	    {
		if(get_expression(&val) != OK)
		{
		    error_lineskip();
		}
		else
		{
		    reg_location = val;
		    if(token_type != TOK_NEWLINE && token_type != TOK_EOF)
			error(0, "Extraneous characters after a valid source line");
		    skip_eol();
		}
	    }

	    if(reg_location < 0)
	    {
		error(0, "ORG/CBLOCK register address not set");
		reg_location = 0;
	    }

	    /* not really implemented .... */
	    while(token_type != KW_ENDC)
	    {
		list_loc = -1;
		while(token_type != TOK_NEWLINE)
		{
		    if(token_type == TOK_EOF)
		    {
			error(0, "Unexpected EOF inside a CBLOCK");
			break;
		    }

		    if(token_type != TOK_IDENTIFIER)
		    {
			error(1, "identifier expected");
			goto out;
		    }

		    if(reg_location >= reg_file_limit)
			fatal_error("Register file address out of range");

		    if(list_loc < 0)
			list_loc = reg_location;

		    sym = lookup_symbol(token_string, SYMTAB_GLOBAL);
		    if(sym != NULL)
		    {
			if(sym->type != SYM_FORWARD)
			{
			    error(0, "Symbol type conflict (%s)", token_string); 
			    sym = NULL;
			}
		    }
		    else
		    {
			sym = add_symbol(token_string, SYMTAB_GLOBAL);
		    }

		    if(sym != NULL)
		    {
			sym->type = SYM_DEFINED;
			sym->type2 = SYMT_REGFILE;
			sym->v.value = reg_location;
			reg_location++;
		    }

		    get_token();
		    if(token_type == TOK_COMMA)
			get_token();
		}
		list_flags = (list_loc >= 0 ? LIST_LOC : 0);
		write_listing_line(0);
		get_token(); /* skip the newline */
	    }
	    get_token(); /* skip KW_ENDC token */
	out:
	    break;

	case KW_EDATA:
	    if(pic_instr_set == PIC_NONE)
		fatal_error("PIC device type not set");

	    if(data_eeprom_size == 0)
	    {
		error(1, "PIC%s does not have data EEPROM", pic_name);
		continue;
	    }

	    for(;;)
	    {
		if(token_type == TOK_STRCONST)
		{
		    for(cp = token_string; *cp != '\0'; cp++)
			gen_edata((int)((unsigned char)(*cp)));
		    get_token();
		}
		else
		{
		    if(get_expression(&val) != OK)
			break;

		    if(val < 0 || val > 0xff)
		    {
			error(0, "Data EEPROM byte out of range");
		    }
		    else
		    {
			gen_edata(val);
		    }
		}
		if(token_type != TOK_COMMA)
		    break;

		get_token();
	    }
	    break;

	case KW_CONFIG:
	    if(pic_instr_set == PIC_NONE)
		fatal_error("PIC device type not set");

	    parse_config();

	    list_flags = LIST_PTR;
	    list_ptr = &config_fuses;
	    list_len = 1;
	    break;

	    /* Device type */
	case KW_DEVICE:
	    if(token_type != TOK_IDENTIFIER && token_type != TOK_STRCONST)
	    {
		error(1, "DEVICE requires a device type");
		continue;
	    }

	    do_device_include(token_string);
	    break;


	case KW_SET_PIC_TYPE:
	    if(pic_instr_set != PIC_NONE)
		fatal_error("PIC type already set");

	    if(token_type != TOK_STRCONST)
		fatal_error("Invalid set_pic_type parameters");

	    strncpy(pic_name, token_string, sizeof(pic_name));
	    pic_name[sizeof(pic_name)-1] = '\0';
	    get_token();
	    if(token_type != TOK_COMMA)
		fatal_error("Invalid set_pic_type parameters");

	    get_token();
	    if(token_type != TOK_STRCONST)
		fatal_error("Invalid set_pic_type parameters");

	    if(strcasecmp(token_string, "12-bit") == 0 ||
	       strcasecmp(token_string, "12bit") == 0)
	    {
		pic_instr_set = PIC12BIT;
	    }
	    else if(strcasecmp(token_string, "14-bit") == 0 ||
		    strcasecmp(token_string, "14bit") == 0)
	    {
		pic_instr_set = PIC14BIT;
	    }
	    else if(strcasecmp(token_string, "16-bit") == 0 ||
		    strcasecmp(token_string, "16bit") == 0)
	    {
		pic_instr_set = PIC16BIT;
	    }
	    else if(strcasecmp(token_string, "16-bit-no-mul") == 0 ||
		    strcasecmp(token_string, "16bit_nomul") == 0)
	    {
		pic_instr_set = PIC16BIT_NOMUL;
	    }
	    else
	    {
		fatal_error("Invalid set_pic_type parameters");
	    }

	    get_token();

	    cp = pic_name;
	    if(strncasecmp(cp, "PIC", 3) == 0)
		cp += 3;
	    p_snprintf(symname, sizeof symname, "__%s", cp);
	    sym = add_symbol(symname, SYMTAB_GLOBAL);
	    sym->type = SYM_DEFINED;
	    sym->type2 = SYMT_CONSTANT;
	    sym->v.value = 1;

	    sym = lookup_symbol("__progmem_size", SYMTAB_GLOBAL);
	    if(sym != NULL && (sym->type == SYM_SET || sym->type == SYM_DEFINED))
	    {
		if(sym->v.value < 0)
		    fatal_error("Invalid __progmem_size");

		prog_mem_size = sym->v.value;
	    }

	    sym = lookup_symbol("__data_eeprom_size", SYMTAB_GLOBAL);
	    if(sym != NULL && (sym->type == SYM_SET || sym->type == SYM_DEFINED))
	    {
		if(sym->v.value < 0)
		    fatal_error("Invalid __data_eeprom_size");

		data_eeprom_size = sym->v.value;
	    }

	    break;

	/* PIC ID */
	case KW_PICID:
	    if(pic_instr_set == PIC_NONE)
		fatal_error("PIC device type not set");

	    if(pic_id[0] != INVALID_ID)
	    {
		error(1, "Multiple ID definitions");
		continue;
	    }

	    /*
	     * should check if ID is really allowed for all PIC types.
	     * Microchip 1994 databook specfically says that ID is not
	     * implemented on PIC16C6x/74... but 1995/96 databook
	     * has different information about that...
	     *
	     * But seems that the 16-bit PICs don't have an id
	     *
	     */

	    if(pic_instr_set == PIC16BIT)
	    {
		error(1, "ID locations not supported on PIC%s", pic_name);
		continue;
	    }

	    for(t = 0;;)
	    {
		if(get_expression(&val) != OK)
		    continue;

		if(val < 0 || val > 0x3fff)
		{
		    error(0, "PIC ID value out of range");
		}
		else
		{
		    if(t >= 4)
		    {
			error(1, "PIC ID too long (max 4 bytes)");
			continue;
		    }
		    pic_id[t] = (pic_instr_t)val;
		}

		t++;

		if(token_type != TOK_COMMA)
		    break;

		get_token();
	    }

	    if(t > 0)
	    {
		list_flags = LIST_PTR;
		list_len = t;
		list_ptr = pic_id;

		while(t < 4)
		    pic_id[t++] = 0x3fff;
	    }
	    break;

	    /* mnemonics */

	default:
	    if(pic_instr_set == PIC_NONE)
		fatal_error("PIC device type not set");

	    switch(pic_instr_set)
	    {
	    case PIC12BIT:
		t = assemble_12bit_mnemonic(op);
		break;

	    case PIC14BIT:
	    default:
		t = assemble_14bit_mnemonic(op);
		break;

	    case PIC16BIT_NOMUL:
	    case PIC16BIT:
		t = assemble_16bit_mnemonic(op);
		break;
	    }
	    if(t != OK)
		continue;
	    break;
	}

line_end:
	write_listing_line(0);

line_end2:
	if(token_type == TOK_EOF)
	    continue;

	if(token_type != TOK_NEWLINE)
	{
	    error(0, "Extraneous characters after a valid source line");
	    {
#if 0
		fprintf(stderr, "token_type==%d, token_string='%s'\n",
			token_type, token_string);/*XXX*/
#endif
	    }
	    skip_eol();
	}
	get_token();
    } /* while(token_type != TOK_EOF) */

    /*
     * Close all open source files
     * (only really necessary if END has been used)
     */
    while(current_file != NULL)
	end_include();

    if(local_level > 0)
	error(0, "LOCAL not terminated with ENDLOCAL");

    apply_patches(global_patch_list);
}

static void
usage(void)
{
    fputs("Usage: picasm "
	  "[-o<objname>] [-l<listfile>] [-s] [-ihx8m/ihx16] [-i<incdir>]\n"
	  "              "
	  "[-pic<device>] [-d<sym>[=<value>]] [-w[n]] <filename>\n", stderr);
    exit(EXIT_FAILURE);
}

/*
 * main program
 */
int
main(int argc, char *argv[])
{
    static char in_filename[256], out_filename[256],
	list_filename[256], sym_filename[256];
    static int out_format = IHX8M;
    int listing = 0, symdump = 0;
    char *p;
    time_t ti;
    struct tm *tm;
    char *cmdline_pic_type = NULL;

    pic_instr_set = PIC_NONE;
    out_filename[0] = '\0';
    list_filename[0] = '\0';
    sym_filename[0] = '\0';
    warnlevel = 0;

    init_assembler();

    while(argc > 1 && argv[1][0] == '-')
    {
	switch(argv[1][1])
	{
	case 'o': /* output file name */
	    if(argv[1][2] != '\0')
	    {
		p_strcpy(out_filename, &argv[1][2], sizeof out_filename);
	    }
	    else
	    {
		if(argc < 3)
		{
		    fputs("-o option requires a file name\n", stderr);
		    exit(EXIT_FAILURE);
		}
		p_strcpy(out_filename, argv[2], sizeof out_filename);
		argc--;
		argv++;
	    }
	    break;

	case 'i': case 'I': /* output hex format (ihx8m/ihx16) */
	    if(strcasecmp(&argv[1][1], "ihx8m") == 0)
	    {
		out_format = IHX8M;
	    }
	    else if(strcasecmp(&argv[1][1], "ihx16") == 0)
	    {
		out_format = IHX16;
	    }
	    else
	    {
		if(argv[1][2] != '\0')
		{
		    add_include_path(&argv[1][2]);
		}
		else
		{
		    if(argc < 3)
			usage();
		    add_include_path(argv[2]);
		    argc--;
		    argv++;
		}
	    }
	    break;

	case 'd': /* define symbols */
	{
	    char symname[256];
	    long val = 1;
	    struct symbol *sym;

	    p = &argv[1][2];
	    if(!isalpha((unsigned char)(*p)) && *p != '\0')
	    {
	    badsym:
		fputs("Bad symbol name (-d option)\n", stderr);
		exit(EXIT_FAILURE);
	    }
	    while(*p != '\0' && *p != '=')
	    {
		if(!isalnum((unsigned char)(*p)) && *p != '_')
		    goto badsym;
		if(p >= &argv[1][2] + 255)
		{
		    fputs("Symbol too long (-d option)\n", stderr);
		    exit(EXIT_FAILURE);
		}
		p++;
	    }
	    if(p == &argv[1][2])
		goto badsym;
	    memcpy(symname, &argv[1][2], p - &argv[1][2]);
	    symname[p - &argv[1][2]] = '\0';
	    if(*p == '=')
	    {
		char *ep;

		val = strtol(p+1, &ep, 0);
		if(*ep != '\0')
		{
		    fputs("Invalid numeric value (-d option)\n", stderr);
		    exit(EXIT_FAILURE);
		}
	    }

	    if(lookup_symbol(symname, SYMTAB_GLOBAL) != NULL)
	    {
		fprintf(stderr, "Multiply defined symbol '%s'\n", symname);
		exit(EXIT_FAILURE);
	    }

	    sym = add_symbol(symname, SYMTAB_GLOBAL);
	    sym->type = SYM_DEFINED;
	    sym->type2 = SYMT_CONSTANT;
	    sym->v.value = val;
	}
	break;

	case 'p': case 'P': /* PIC type */
	    if(!((argv[1][2] == 'i' || argv[1][2] == 'I') &&
		 (argv[1][3] == 'c' || argv[1][3] == 'C')))
		usage();

	    cmdline_pic_type = &argv[1][1];
	    break;

	case 'l': /* listing/list filename */
	    listing = 1;
	    if(argv[1][2] != '\0')
		p_strcpy(list_filename, &argv[1][2], sizeof list_filename);
	    break;

	case 's':
	    symdump++;
	    break;

	case 'w': /* warning mode (gives some more warnings) */
	    if(argv[1][2] != '\0')
	    {
		warnlevel = atoi(&argv[1][2]);
	    }
	    else
	    {
		warnlevel = 1;
	    }
	    break;

	case 'v': /* version info */
	    fprintf(stderr,
		    "12/14/16-bit PIC assembler " VERSION
		    " -- Copyright 1995-2004 Timo Rossi\n");
	    break;
	
	case '-': /* end of option list */
	case '\0':
	    argc--;
	    argv++;
	    goto opt_done;

	default:
	    usage();
	}
	argc--;
	argv++;
    }

opt_done:
    if(argc != 2)
	usage();

#ifdef BUILTIN_INCLUDE1
    add_include_path(BUILTIN_INCLUDE1);
#endif
#ifdef BUILTIN_INCLUDE2
    add_include_path(BUILTIN_INCLUDE2);
#endif

    strncpy(in_filename, argv[1], sizeof(in_filename)-1);
    if(strchr(in_filename, '.') == NULL)
	p_strcat(in_filename, ".asm", sizeof in_filename);

    if(out_filename[0] == '\0')
    {
	p_strcpy(out_filename, in_filename, sizeof out_filename);
	if((p = strrchr(out_filename, '.')) != NULL)
	    *p = '\0';
    }
    if(strchr(out_filename, '.') == NULL)
	p_strcat(out_filename, ".hex", sizeof out_filename);

    list_fp = NULL;
    if(listing)
    {
	if(list_filename[0] == '\0')
	{
	    p_strcpy(list_filename, in_filename, sizeof list_filename);
	    if((p = strrchr(list_filename, '.')) != NULL)
		*p = '\0';
	    p_strcat(list_filename, ".lst", sizeof list_filename);
	}

	if((list_fp = fopen(list_filename, "w")) == NULL)
	    fatal_error("Can't create listing file '%s': %s",
			list_filename, strerror(errno));

	ti = time(NULL);
	tm = localtime(&ti);

	fprintf(list_fp, "** 12/14-bit PIC assembler " VERSION "\n");
	fprintf(list_fp, "** %s assembled %s\n",
		in_filename, asctime(tm));
    }

    assembler(in_filename, cmdline_pic_type);
    if(errors == 0)
    {
	if(pic_instr_set != PIC_NONE && code_generated)
	    write_output(out_filename, out_format);
	else
	    fputs("No code generated\n", stderr);
    }
    else
    {
	fprintf(stderr, "%d %s found\n",
		errors, errors == 1 ? "error" : "errors");
    }

    if(warnings != 0)
    {
	fprintf(stderr, "%d %s\n",
		warnings, warnings == 1 ? "warning" : "warnings");
    }

    if(list_fp)
    {
	if(symdump)
	    dump_symtab(list_fp, symdump);
	fclose(list_fp);
    }

    return errors ? EXIT_FAILURE : EXIT_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1