/* labels.c  label handling for the Netwide Assembler
 *
 * The Netwide Assembler is copyright (C) 1996 Simon Tatham and
 * Julian Hall. All rights reserved. The software is
 * redistributable under the licence given in the file "Licence"
 * distributed in the NASM archive.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "nasm.h"
#include "nasmlib.h"

/*
 * A local label is one that begins with exactly one period. Things
 * that begin with _two_ periods are NASM-specific things.
 *
 * If TASM compatibility is enabled, a local label can also begin with
 * @@, so @@local is a TASM compatible local label. Note that we only
 * check for the first @ symbol, although TASM requires both.
 */
#define islocal(l)                                              \
	(tasm_compatible_mode ?					\
	(((l)[0] == '.' || (l)[0] == '@') && (l)[1] != '.') :   \
	((l)[0] == '.' && (l)[1] != '.'))
#define islocalchar(c)						\
	(tasm_compatible_mode ?                                  \
	((c) == '.' || (c) == '@') :                            \
	((c) == '.'))

#define LABEL_BLOCK  32               /* no. of labels/block */
#define LBLK_SIZE    (LABEL_BLOCK*sizeof(union label))
#define LABEL_HASHES 37                       /* no. of hash table entries */

#define END_LIST -3                       /* don't clash with NO_SEG! */
#define END_BLOCK -2
#define BOGUS_VALUE -4

#define PERMTS_SIZE  4096               /* size of text blocks */
#if (PERMTS_SIZE > IDLEN_MAX)
#error "IPERMTS_SIZE must be less than or equal to IDLEN_MAX" 
#endif

/* values for label.defn.is_global */
#define DEFINED_BIT 1
#define GLOBAL_BIT 2
#define EXTERN_BIT 4

#define NOT_DEFINED_YET 0
#define TYPE_MASK 3
#define LOCAL_SYMBOL (DEFINED_BIT)
#define GLOBAL_PLACEHOLDER (GLOBAL_BIT)
#define GLOBAL_SYMBOL (DEFINED_BIT|GLOBAL_BIT)

union label {                               /* actual label structures */
    struct {
        long segment, offset;
        char *label, *special;
        int is_global, is_norm;
    } defn;
    struct {
        long movingon, dummy;
        union label *next;
    } admin;
};

struct permts {                               /* permanent text storage */
    struct permts *next;               /* for the linked list */
    int size, usage;                       /* size and used space in ... */
    char data[PERMTS_SIZE];               /* ... the data block itself */
};

extern int global_offset_changed;   /* defined in nasm.c */

static union label *ltab[LABEL_HASHES];/* using a hash table */
static union label *lfree[LABEL_HASHES];/* pointer into the above */
static struct permts *perm_head;      /* start of perm. text storage */
static struct permts *perm_tail;      /* end of perm. text storage */

static void init_block (union label *blk);
static char *perm_copy (char *string1, char *string2);

static char *prevlabel;

static int initialised = FALSE;

char lprefix[PREFIX_MAX] = {0};
char lpostfix[PREFIX_MAX] = {0};

/*
 * Internal routine: finds the `union label' corresponding to the
 * given label name. Creates a new one, if it isn't found, and if
 * `create' is TRUE.
 */
static union label *find_label (char *label, int create) 
{
    int hash = 0;
    char *p, *prev;
    int prevlen;
    union label *lptr;

    if (islocal(label))
        prev = prevlabel;
    else
        prev = "";
    prevlen = strlen(prev);
    p = prev;
    while (*p) hash += *p++;
    p = label;
    while (*p) hash += *p++;
    hash %= LABEL_HASHES;
    lptr = ltab[hash];
    while (lptr->admin.movingon != END_LIST) {
        if (lptr->admin.movingon == END_BLOCK) {
            lptr = lptr->admin.next;
            if (!lptr)
                break;
        }
        if (!strncmp(lptr->defn.label, prev, prevlen) &&
            !strcmp(lptr->defn.label+prevlen, label))
            return lptr;
        lptr++;
    }
    if (create) {
        if (lfree[hash]->admin.movingon == END_BLOCK) {
            /*
             * must allocate a new block
             */
            lfree[hash]->admin.next = (union label *) nasm_malloc (LBLK_SIZE);
            lfree[hash] = lfree[hash]->admin.next;
            init_block(lfree[hash]);
        }

        lfree[hash]->admin.movingon = BOGUS_VALUE;
        lfree[hash]->defn.label = perm_copy (prev, label);
        lfree[hash]->defn.special = NULL;
        lfree[hash]->defn.is_global = NOT_DEFINED_YET;
        return lfree[hash]++;
    } 
    else
        return NULL;
}

int lookup_label (char *label, long *segment, long *offset) 
{
    union label *lptr;

    if (!initialised)
        return 0;

    lptr = find_label (label, 0);
    if (lptr && (lptr->defn.is_global & DEFINED_BIT)) {
        *segment = lptr->defn.segment;
        *offset = lptr->defn.offset;
        return 1;
    } 
    else
        return 0;
}

int is_extern (char *label) 
{
    union label *lptr;

    if (!initialised)
        return 0;

    lptr = find_label (label, 0);
    if (lptr && (lptr->defn.is_global & EXTERN_BIT))
        return 1;
    else
        return 0;
}

void redefine_label (char *label, long segment, long offset, char *special,
                   int is_norm, int isextrn, struct ofmt *ofmt, efunc error) 
{
    union label *lptr;
    int exi;
    
    /* This routine possibly ought to check for phase errors.  Most assemblers
     * check for phase errors at this point.  I don't know whether phase errors
     * are even possible, nor whether they are checked somewhere else
     */

    (void) segment;  /* Don't warn that this parameter is unused */
    (void) special;  /* Don't warn that this parameter is unused */
    (void) is_norm;  /* Don't warn that this parameter is unused */
    (void) isextrn;  /* Don't warn that this parameter is unused */
    (void) ofmt;     /* Don't warn that this parameter is unused */

#ifdef DEBUG
#if DEBUG<3
    if (!strncmp(label, "debugdump", 9))
#endif
        error(ERR_DEBUG, "redefine_label (%s, %ld, %08lx, %s, %d, %d)",
                label, segment, offset, special, is_norm, isextrn);
#endif

    lptr = find_label (label, 1);
    if (!lptr)
        error (ERR_PANIC, "can't find label `%s' on pass two", label);
    
    if (!islocal(label)) {
	if (!islocalchar(*label) && lptr->defn.is_norm)
            prevlabel = lptr->defn.label;
    }

    global_offset_changed |= (lptr->defn.offset != offset);
    lptr->defn.offset = offset;
    
if (pass0 == 1) {
    exi = !!(lptr->defn.is_global & GLOBAL_BIT);
    if (exi)
    {
	char *xsymbol;
	int slen;
	slen = strlen(lprefix);
	slen += strlen(lptr->defn.label);
	slen += strlen(lpostfix);
	slen++; /* room for that null char */
	xsymbol = nasm_malloc(slen);
	sprintf(xsymbol,"%s%s%s",lprefix,lptr->defn.label,lpostfix);

	ofmt->symdef (xsymbol, segment, offset, exi, 
		special ? special : lptr->defn.special);
	ofmt->current_dfmt->debug_deflabel (xsymbol, segment, offset, exi,
		special ? special : lptr->defn.special);
/**	nasm_free(xsymbol);  ! outobj.c stores the pointer; ouch!!! **/
    }
    else
    {
	if ( (lptr->defn.is_global & (GLOBAL_BIT|EXTERN_BIT)) != EXTERN_BIT ) {
	ofmt->symdef (lptr->defn.label, segment, offset, exi,
		special ? special : lptr->defn.special);
	ofmt->current_dfmt->debug_deflabel (label, segment, offset, exi,
		special ? special : lptr->defn.special);
	}
    }
} /* if (pass0 == 1) */
}

void define_label (char *label, long segment, long offset, char *special,
                   int is_norm, int isextrn, struct ofmt *ofmt, efunc error) 
{
    union label *lptr;
    int exi;

#ifdef DEBUG
#if DEBUG<3
    if (!strncmp(label, "debugdump", 9))
#endif
        error(ERR_DEBUG, "define_label (%s, %ld, %08lx, %s, %d, %d)",
                label, segment, offset, special, is_norm, isextrn);
#endif
    lptr = find_label (label, 1);
    if (lptr->defn.is_global & DEFINED_BIT) {
        error(ERR_NONFATAL, "symbol `%s' redefined", label);
        return;
    }
    lptr->defn.is_global |= DEFINED_BIT;
    if (isextrn)
	lptr->defn.is_global |= EXTERN_BIT;

    if (!islocalchar(label[0]) && is_norm)    /* not local, but not special either */
	prevlabel = lptr->defn.label;
    else if (islocal(label) && !*prevlabel) {
	error(ERR_NONFATAL, "attempt to define a local label before any"
	      " non-local labels");
	}

    lptr->defn.segment = segment;
    lptr->defn.offset = offset;
    lptr->defn.is_norm = (!islocalchar(label[0]) && is_norm);

if (pass0 == 1 || (!is_norm && !isextrn && (segment&1))) {
    exi = !!(lptr->defn.is_global & GLOBAL_BIT);
    if (exi)
    {
	char *xsymbol;
	int slen;
	slen = strlen(lprefix);
	slen += strlen(lptr->defn.label);
	slen += strlen(lpostfix);
	slen++; /* room for that null char */
	xsymbol = nasm_malloc(slen);
	sprintf(xsymbol,"%s%s%s",lprefix,lptr->defn.label,lpostfix);

	ofmt->symdef (xsymbol, segment, offset, exi, 
		special ? special : lptr->defn.special);
	ofmt->current_dfmt->debug_deflabel (xsymbol, segment, offset, exi,
		special ? special : lptr->defn.special);
/**	nasm_free(xsymbol);  ! outobj.c stores the pointer; ouch!!! **/
    }
    else
    {
	if ( (lptr->defn.is_global & (GLOBAL_BIT|EXTERN_BIT)) != EXTERN_BIT ) {
	ofmt->symdef (lptr->defn.label, segment, offset, exi,
		special ? special : lptr->defn.special);
        ofmt->current_dfmt->debug_deflabel (label, segment, offset, exi,
		special ? special : lptr->defn.special);
	}
    }
} /* if (pass0 == 1) */
}

void define_common (char *label, long segment, long size, char *special,
                    struct ofmt *ofmt, efunc error) 
{
    union label *lptr;

    lptr = find_label (label, 1);
    if (lptr->defn.is_global & DEFINED_BIT) {
        error(ERR_NONFATAL, "symbol `%s' redefined", label);
        return;
    }
    lptr->defn.is_global |= DEFINED_BIT;

    if (!islocalchar(label[0]))	       /* not local, but not special either */
        prevlabel = lptr->defn.label;
    else
        error(ERR_NONFATAL, "attempt to define a local label as a "
              "common variable");

    lptr->defn.segment = segment;
    lptr->defn.offset = 0;

    ofmt->symdef (lptr->defn.label, segment, size, 2,
                  special ? special : lptr->defn.special);
    ofmt->current_dfmt->debug_deflabel(lptr->defn.label, segment, size, 2,
                  special ? special : lptr->defn.special);
}

void declare_as_global (char *label, char *special, efunc error) 
{
    union label *lptr;

    if (islocal(label)) {
        error(ERR_NONFATAL, "attempt to declare local symbol `%s' as"
              " global", label);
        return;
    }
    lptr = find_label (label, 1);
    switch (lptr->defn.is_global & TYPE_MASK) {
      case NOT_DEFINED_YET:
        lptr->defn.is_global = GLOBAL_PLACEHOLDER;
        lptr->defn.special = special ? perm_copy(special, "") : NULL;
        break;
      case GLOBAL_PLACEHOLDER:               /* already done: silently ignore */
      case GLOBAL_SYMBOL:
        break;
      case LOCAL_SYMBOL:
        if (!lptr->defn.is_global & EXTERN_BIT)
            error(ERR_NONFATAL, "symbol `%s': GLOBAL directive must"
                  " appear before symbol definition", label);
        break;
    }
}

int init_labels (void) 
{
    int i;

    for (i=0; i<LABEL_HASHES; i++) {
        ltab[i] = (union label *) nasm_malloc (LBLK_SIZE);
        if (!ltab[i])
            return -1;                       /* can't initialise, panic */
        init_block (ltab[i]);
        lfree[i] = ltab[i];
    }

    perm_head = 
        perm_tail = (struct permts *) nasm_malloc (sizeof(struct permts));

    if (!perm_head)
            return -1;

    perm_head->next = NULL;
    perm_head->size = PERMTS_SIZE;
    perm_head->usage = 0;

    prevlabel = "";

    initialised = TRUE;

    return 0;
}

void cleanup_labels (void) 
{
    int i;

    initialised = FALSE;

    for (i=0; i<LABEL_HASHES; i++) {
        union label *lptr, *lhold;

        lptr = lhold = ltab[i];

        while (lptr) {
            while (lptr->admin.movingon != END_BLOCK) lptr++;
            lptr = lptr->admin.next;
            nasm_free (lhold);
            lhold = lptr;
        }
    }

    while (perm_head) {
        perm_tail = perm_head;
        perm_head = perm_head->next;
        nasm_free (perm_tail);
    }
}

static void init_block (union label *blk) 
{
    int j;

    for (j=0; j<LABEL_BLOCK-1; j++)
            blk[j].admin.movingon = END_LIST;
    blk[LABEL_BLOCK-1].admin.movingon = END_BLOCK;
    blk[LABEL_BLOCK-1].admin.next = NULL;
}

static char *perm_copy (char *string1, char *string2) 
{
    char *p, *q;
    int len = strlen(string1)+strlen(string2)+1;

    if (perm_tail->size - perm_tail->usage < len) {
        perm_tail->next = (struct permts *)nasm_malloc(sizeof(struct permts));
        perm_tail = perm_tail->next;
        perm_tail->next = NULL;
        perm_tail->size = PERMTS_SIZE;
        perm_tail->usage = 0;
    }
    p = q = perm_tail->data + perm_tail->usage;
    while ( (*q = *string1++) ) q++;
    while ( (*q++ = *string2++) ) ;
    perm_tail->usage = q - perm_tail->data;

    return p;
}

/*
 * Notes regarding bug involving redefinition of external segments.
 *
 * Up to and including v0.97, the following code didn't work. From 0.97
 * developers release 2 onwards, it will generate an error.
 *
 * EXTERN extlabel
 * newlabel EQU extlabel + 1
 *
 * The results of allowing this code through are that two import records
 * are generated, one for 'extlabel' and one for 'newlabel'.
 *
 * The reason for this is an inadequacy in the defined interface between
 * the label manager and the output formats. The problem lies in how the
 * output format driver tells that a label is an external label for which
 * a label import record must be produced. Most (all except bin?) produce
 * the record if the segment number of the label is not one of the internal
 * segments that the output driver is producing.
 *
 * A simple fix to this would be to make the output formats keep track of
 * which symbols they've produced import records for, and make them not
 * produce import records for segments that are already defined.
 *
 * The best way, which is slightly harder but reduces duplication of code
 * and should therefore make the entire system smaller and more stable is
 * to change the interface between assembler, define_label(), and
 * the output module. The changes that are needed are:
 *
 * The semantics of the 'isextern' flag passed to define_label() need
 * examining. This information may or may not tell us what we need to
 * know (ie should we be generating an import record at this point for this
 * label). If these aren't the semantics, the semantics should be changed
 * to this.
 *
 * The output module interface needs changing, so that the `isextern' flag
 * is passed to the module, so that it can be easily tested for.
 */
