/* first.c - Phase I, input data preprocessing */

/* Written 1995-1997 by Werner Almesberger, EPFL-LRC */


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

#include "common.h"
#include "qgen.h"
#include "file.h"


int field = 0;
int group = 0;
int offset = 0;
int varlen_fields = 0;

static unsigned char mask = 0;
static int seq = 0;


#define ALLOC(field) ({ \
    if (mask & beg_mask) \
	die("bit collision (%s: 0x%02X+0x%02X)",walk->id,mask,beg_mask); \
    field->pos += offset; \
    if (next <= 8) { \
	mask |= end_mask; \
	walk->jump = 0; \
    } \
    else { \
	mask = end_mask; \
	walk->jump = (next-1) >> 3; \
    } \
    if (walk->flush && walk->size) { \
	if (!mask) die("nothing to flush (%s)",walk->id); \
	walk->jump++; \
	mask = 0; \
    } })


static void process(FIELD *start,int defines)
{
    FIELD *walk;
    TAG *scan;
    unsigned char beg_mask,end_mask,orig_mask;
    int tmp,shift,next;

    for (walk = start; walk; walk = walk->next) {
	walk->field = field; /* record it even if this isn't a real field (we
				need to know the first field number later in
				second.c and this way we don't have to search
				for it) */
	if (walk->structure) {
	    int start_field,start_group;

	    start_field = field;
	    start_group = group;
	    process(walk->my_block,walk->structure->instances > 0);
	    if (walk->structure->instances > 1) {
		walk->structure->instances = -1;
		walk->structure->first_field = start_field;
		walk->structure->fields = field-start_field;
		walk->structure->groups = group-start_group;
	    }
	    continue;
	}
	walk->seq = seq++; /* number of fields, always increases */
	if (*walk->id != '_') {
	    if (defines) to_h("#define QF_%s %d\n",walk->id,field);
	    to_test("    \"%s\",\n",walk->id);
	    field++;
	}
	if (mask && walk->brk)
	    die("mask 0x%02x at break (%s)\n",mask,walk->id);
	/* compute position and adjust masks */
	tmp = walk->size > 7 ? 0xff : (1 << walk->size)-1;
	beg_mask = tmp << walk->pos;
	tmp = 0xff & ~((1 << (8-(walk->size & 7)))-1);
	if (walk->size && !tmp) tmp = 0xff;
	next = walk->pos+walk->size;
	shift = 8-(next & 7);
	if (shift == 8) shift = 0;
	end_mask = tmp >> shift;
	if (debug)
	    printf("mask 0x%02X, beg 0x%02X, end 0x%02X, offset %d, flush %d, "
	      "%s\n",mask,beg_mask,end_mask,offset,walk->flush,walk->id);
	/* handle values */
	if (!walk->value) {
	    if (walk->var_len == -2) walk->var_len = varlen_fields++;
	    ALLOC(walk);
	    offset += walk->jump*8;
	}
	else switch (walk->value->type) {
		case vt_id:
		    ALLOC(walk);
		    to_c("    q_put(q_initial,%d,%d,%s); /* %s */\n",walk->pos,
		      walk->size,walk->value->id,walk->id);
		    offset += walk->jump*8;
		    break;
		case vt_case:
		    if (*walk->id != '_') {
			ALLOC(walk);
			offset += walk->jump*8;
#if 0 /* bad idea */
			for (scan = walk->value->tags; scan; scan = scan->next)
			    if (scan->deflt) {
				to_c("    q_put(q_initial,%d,%d,%s); "
				  "/* %s */\n",walk->pos,walk->size,
				  scan->value,walk->id);
			    }
#endif
		    }
		    /* fall through */
#if 0
		    if (*walk->id != '_') {
			ALLOC(walk);
			offset += walk->jump*8;
			orig_mask = mask;
			for (scan = walk->value->tags; scan; scan = scan->next)
			  {
			    if (scan->id && defines)
				to_h("#define QG_%s %d\n",scan->id,-group-1);
			    scan->group = group++;
			    scan->pos = walk->pos;
			    mask = orig_mask;
			    to_c("    q_put(q_initial,%d,%d,%s); /* %s */\n",
			      walk->pos,walk->size,scan->value,walk->id);
			    process(scan->block,defines);
			    if (mask) offset += 8;
			}
			break;
		    }
#endif
		    /* fall through */
		case vt_multi:
		    orig_mask = mask;
		    for (scan = walk->value->tags; scan; scan = scan->next) {
			if (scan->id && defines)
			    to_h("#define QG_%s %d\n",scan->id,-group-1);
			scan->group = group++;
			scan->pos = walk->pos;
			if (debug)
			    printf("| %s/%s (walk->flush = %d)\n",walk->id,
			      scan->value,walk->flush);
			if (*walk->id != '_') mask = orig_mask;
			else {
			    ALLOC(scan);
			    if (debug)
				printf("| jump = %d (mask 0x%02x)\n",
				  walk->jump,mask);
			    offset += walk->jump*8;
			    to_c("    q_put(q_initial,%d,%d,%s); /* %s */\n",
			      scan->pos,walk->size,scan->value,walk->id);
			}
			process(scan->block,defines);
			if (*walk->id != '_' && mask) die("EF129");
		    }
		    break;
		case vt_length:
		    ALLOC(walk);
		    offset += walk->jump*8;
		    process(walk->value->block,defines);
		    break;
		default:
		    abort();
	    }
    }
}


void first(FIELD *def)
{
    def->group = group++;
    to_h("/*\n * Identifiers to access numbers of fields and of named\n");
    to_h(" * groups (unnamed groups don't need that). Field numbers are\n");
    to_h(" * zero-based. Group numbers are negative and -1-based.\n */\n\n");
    to_c("static unsigned char q_initial[Q_DATA_BYTES];\n\n");
    to_c("/*\n * Initialization of constant data. Could also do this in\n");
    to_c(" * the translator and output the resulting byte stream.\n */\n\n");
    to_c("static void q_init_global(void)\n{\n");
    to_c("    memset(q_initial,0,sizeof(q_initial));\n");
    to_test("static const char *fields[] = {\n");
    process(def,1);
    to_test("    NULL\n};\n\n");
    to_c("}\n\n");
    if (mask) offset += 8;
    to_h("\n/*\n * Sizes of various tables which are allocated at run-time.\n");
    to_h(" */\n");
    to_h("\n#define Q_DATA_BYTES %d\n",offset/8);
    to_h("#define Q_GROUPS %d\n",group);
    to_h("#define Q_FIELDS %d\n",field);
    to_h("#define Q_VARLEN_FIELDS %d\n\n",varlen_fields);
}
