#include "lint.h"
#include "y-tab.h"
#include "lnode.h"
#include "config.h"

#define NUM_CHUNK		100	/* Number of chunks to allocate */

extern char *xalloc();
extern int current_line;
int tot_alloc_lnode;

struct lnode_var_def *prog_status;

void free_lnode PROT((struct lnode *, int)),
	free_lnode_string PROT((struct lnode *));
void free_sub_part PROT((struct lnode *, int));
void free();

static int tot_lnode_single;
struct lnode_single *alloc_lnode_single(type)
    int type;
{
    struct lnode_single *p;
    p = (struct lnode_single *)xalloc(sizeof (struct lnode_single));
    p->type = type;
    p->line = current_line + L_SINGLE;
    tot_lnode_single++;
    return p;
}

static struct lnode_number *lnode_number_list;
static int tot_lnode_number;
struct lnode_number *alloc_lnode_number(type, number)
    int type, number;
{
    struct lnode_number *p;
    if (!lnode_number_list) {
	int i;
	lnode_number_list = (struct lnode_number *)xalloc(NUM_CHUNK *
					sizeof (struct lnode_number));
	for (p = lnode_number_list, i=0; i<NUM_CHUNK - 1; p++, i++)
	    p->number = (int)(p+1);
	p->number = 0;
	tot_lnode_number += NUM_CHUNK;
    }
    p = lnode_number_list;
    lnode_number_list = (struct lnode_number *)lnode_number_list->number;
    p->type = type;
    p->number = number;
    p->line = current_line + L_NUMBER;
    return p;
}

/*
 * Strings are allocated from the shared string area.
 * Be carfule not to free() such an area through free().
 */
static struct lnode_name *lnode_name_list;
static int tot_lnode_name;
struct lnode_name *alloc_lnode_name(type, name)
    char *name;
    int type;
{
    struct lnode_name *p;
    char *spn;
    if (!lnode_name_list) {
	int i;
	lnode_name_list = (struct lnode_name *)xalloc(NUM_CHUNK *
						sizeof (struct lnode_name));
	for (p = lnode_name_list, i=0; i<NUM_CHUNK - 1; p++, i++)
	    p->name = (char *)(p+1);
	p->name = 0;
	tot_lnode_name += NUM_CHUNK;
    }
    p = lnode_name_list;
    lnode_name_list = (struct lnode_name *)lnode_name_list->name;
    p->type = type;
    p->name = make_shared_string(name);
    free(name);
    p->line = current_line + L_NAME;
    return p;
}

static int tot_lnode_variable;
struct lnode_variable *alloc_lnode_variable(type, number)
    int type, number;
{
    struct lnode_variable *p;
    p = (struct lnode_variable *)xalloc(sizeof (struct lnode_variable));
    p->type = type;
    p->number = number;
    p->line = current_line + L_VARIABLE;
    /* p->is_static = 0; */
    tot_lnode_variable++;
    return p;
}

static struct lnode_1 *lnode_1_list;
static int tot_lnode_1;
struct lnode_1 *alloc_lnode_1(type, expr)
    struct lnode *expr;
    int type;
{
    struct lnode_1 *p;
    if (!lnode_1_list) {
	int i;
	lnode_1_list = (struct lnode_1 *)xalloc(NUM_CHUNK *
						sizeof (struct lnode_1));
	for (p = lnode_1_list, i=0; i<NUM_CHUNK - 1; p++, i++)
	    p->expr = (struct lnode *)(p+1);
	p->expr = 0;
	tot_lnode_1 += NUM_CHUNK;
    }
    p = lnode_1_list;
    lnode_1_list = (struct lnode_1 *)lnode_1_list->expr;
    p->type = type;
    p->expr = expr;
    p->line = current_line + L_1;
    return p;
}

static struct lnode_2 *lnode_2_list;
static int tot_lnode_2;
struct lnode_2 *alloc_lnode_2(type, expr1, expr2)
    struct lnode *expr1, *expr2;
    int type;
{
    struct lnode_2 *p;
    if (!lnode_2_list) {
	int i;
	lnode_2_list = (struct lnode_2 *)xalloc(NUM_CHUNK *
						sizeof (struct lnode_2));
	for (p = lnode_2_list, i=0; i<NUM_CHUNK - 1; p++, i++)
	    p->expr1 = (struct lnode *)(p+1);
	p->expr1 = 0;
	tot_lnode_2 += NUM_CHUNK;
    }
    p = lnode_2_list;
    lnode_2_list = (struct lnode_2 *)lnode_2_list->expr1;
    p->type = type;
    p->line = current_line + L_2;
    p->expr1 = expr1;
    p->expr2 = expr2;
    return p;
}

static int tot_lnode_3;
struct lnode_3 *alloc_lnode_3(type, expr1, expr2, expr3)
    struct lnode *expr1, *expr2, *expr3;
    int type;
{
    struct lnode_3 *p;
    p = (struct lnode_3 *)xalloc(sizeof (struct lnode_3));
    p->type = type;
    p->line = current_line + L_3;
    p->expr1 = expr1;
    p->expr2 = expr2;
    p->expr3 = expr3;
    tot_lnode_3++;
    return p;
}

static int tot_lnode_def;
struct lnode_def *alloc_lnode_def(type, name, block, num_var)
    struct lnode *block;
    char *name;
    int type, num_var;
{
    struct lnode_def *p;
    char *spn;
    p = (struct lnode_def *)xalloc(sizeof (struct lnode_def));
    p->type = type;
    p->line = current_line + L_DEF;
    p->name = make_shared_string(name);
    free(name);
    p->length = strlen(p->name);
    p->num_var = num_var;
    p->block = block;
    p->next = 0;
    p->num_ref = 0;
    p->is_static = 0;
    p->num_arg = 0;
    tot_lnode_def++;
    return p;
}

static int tot_var_def;
void alloc_lnode_var_def(type, name, num_var)
    char *name;
    int type, num_var;
{
    extern int static_variable_flag;
    struct lnode_var_def *p;

    p = (struct lnode_var_def *)xalloc(sizeof (struct lnode_var_def));
    p->type = type;
    p->line = current_line + L_VAR_DEF;
    p->name = make_shared_string(name);
    free(name);
    p->num_var = num_var;
    p->next = prog_status;
    p->is_static = static_variable_flag;
    prog_status = p;
    tot_var_def++;
}

static int tot_funcall;
struct lnode_funcall *alloc_lnode_funcall(type, name, arg)
    struct lnode *arg;
    char *name;
    int type;
{
    struct lnode_funcall *p;
    char *spn;

    p = (struct lnode_funcall *)xalloc(sizeof (struct lnode_funcall));
    p->type = type;
    p->line = current_line + L_FUNCALL;
    p->name = make_shared_string(name);
    free(name);
    p->expr = arg;
    tot_funcall++;
    return p;
}

/*
 * Statements in a block (list of statements) are stored with cons
 * node (lnode_2). This is very expensive, as one lnode_2 is required as
 * overhead for every statement. This function will take such a list
 * and move all statements in a block into one single memory vector, storing
 * them consecutively.
 */
int tot_lnode_block;
struct lnode_block *alloc_lnode_block(p)
    struct lnode *p;
{
    int size, num;
    char *block, *block2;
    struct lnode *l, *next;
    struct lnode_block *ret;

    if (p == 0)
	return 0;
    for (num = 0, size = 0, l=p; l; l = l->a2) {
	size += lnode_size[l->a1->line >> L_SHIFT];
	num++;
    }
    if (num == 1) {
	/* Only one statement. No need to make a block ! */
	struct lnode *stat;

	stat = p->a1;
	free_lnode(p, 1);
	return (struct lnode_block *)stat;
    }
    block = xalloc(sizeof (int) + size + 100);
    block2 = block;
    for (l=p; l; l = next) {
	int s;

	s = lnode_size[l->a1->line >> L_SHIFT];
	memcpy(block2, (char *)l->a1, s);
	free_lnode(l->a1, 0);	/* Node has been copied. */
	l->a1 = 0;
	block2 += s;
	next = l->a2;
	free_lnode(l, 1);
    }
    ret = (struct lnode_block *)xalloc(sizeof (struct lnode_block));
    ret->type = F_BLOCK;
    ret->line = current_line + L_BLOCK;
    ret->block = block;
    ret->num_nodes = num;
    tot_lnode_block++;
    return ret;
}

void print_lnode_status(tot_before_this)
    int tot_before_this;
{
    int tot =
	tot_lnode_number * sizeof (struct lnode_number) +
	tot_lnode_name * sizeof (struct lnode_name) +
	tot_lnode_1 * sizeof (struct lnode_1) +
	tot_lnode_2 * sizeof (struct lnode_2) +
	tot_lnode_3 * sizeof (struct lnode_3) +
	tot_var_def * sizeof (struct lnode_var_def) +
	tot_funcall * sizeof (struct lnode_funcall) +
	tot_lnode_def * sizeof (struct lnode_def) +
	tot_lnode_block * sizeof (struct lnode_block) +
	tot_before_this;
    add_message("lnode:\n");
    add_message("   single:     %5d %6d\n", tot_lnode_single,
		tot_lnode_single * sizeof (struct lnode_single));
    add_message("   numbers:    %5d %6d\n", tot_lnode_number,
		tot_lnode_number * sizeof (struct lnode_number));
    add_message("   names:      %5d %6d\n", tot_lnode_name,
		tot_lnode_name * sizeof (struct lnode_name));
    add_message("   1 size expr:%5d %6d\n", tot_lnode_1,
		tot_lnode_1 * sizeof (struct lnode_1));
    add_message("   2 size expr:%5d %6d\n", tot_lnode_2,
		tot_lnode_2 * sizeof (struct lnode_2));
    add_message("   3 size expr:%5d %6d\n", tot_lnode_3,
		tot_lnode_3 * sizeof (struct lnode_3));
    add_message("   functions:  %5d %6d\n", tot_lnode_def,
		tot_lnode_def * sizeof (struct lnode_def));
    add_message("   glob vars:  %5d %6d\n", tot_var_def,
		tot_var_def * sizeof (struct lnode_var_def));
    add_message("   fun calls:  %5d %6d\n", tot_funcall,
		tot_funcall * sizeof (struct lnode_funcall));
    add_message("   blocks:     %5d %6d\n", tot_lnode_block,
		tot_lnode_block * sizeof (struct lnode_block));
    add_message("   Total bytes       %6d\n", tot);
}

int lnode_size[(L_MAX >> L_SHIFT) + 1];

/*
 * Setup fast access to the size of an lnode.
 */

void compute_lnode_size()
{
    lnode_size[L_SINGLE >> L_SHIFT] = sizeof (struct lnode_single);
    lnode_size[L_NUMBER >> L_SHIFT] = sizeof (struct lnode_number);
    lnode_size[L_NAME >> L_SHIFT] = sizeof (struct lnode_name);
    lnode_size[L_VARIABLE >> L_SHIFT] = sizeof (struct lnode_variable);
    lnode_size[L_1 >> L_SHIFT] = sizeof (struct lnode_1);
    lnode_size[L_2 >> L_SHIFT] = sizeof (struct lnode_2);
    lnode_size[L_3 >> L_SHIFT] = sizeof (struct lnode_3);
    lnode_size[L_DEF >> L_SHIFT] = sizeof (struct lnode_def);
    lnode_size[L_VARIABLE >> L_SHIFT] = sizeof (struct lnode_var_def);
    lnode_size[L_FUNCALL >> L_SHIFT] = sizeof (struct lnode_funcall);
    lnode_size[L_BLOCK >> L_SHIFT] = sizeof (struct lnode_block);
}

void free_lnode(p, update_count)
    struct lnode *p;
    int update_count;
{
    switch(p->line & L_MASK) {
    case L_SINGLE:
	if (update_count)
	    tot_lnode_single--;
	break;
    case L_NUMBER:
	((struct lnode_number *)p)->number = (int)lnode_number_list;
	lnode_number_list = (struct lnode_number *)p;
	return;
    case L_NAME:
	((struct lnode_name *)p)->name = (char *)lnode_name_list;
	lnode_name_list = (struct lnode_name *)p;
	return;
    case L_VARIABLE:
	if (update_count)
	    tot_lnode_variable--;
	break;
    case L_1:
	((struct lnode_1 *)p)->expr = (struct lnode *)lnode_1_list;
	lnode_1_list = (struct lnode_1 *)p;
	return;
    case L_2:
	((struct lnode_2 *)p)->expr1 = (struct lnode *)lnode_2_list;
	lnode_2_list = (struct lnode_2 *)p;
	return;
    case L_3:
	if (update_count)
	    tot_lnode_3--;
	break;
    case L_DEF:
	if (update_count)
	    tot_lnode_def--;
	break;
    case L_VAR_DEF:
	if (update_count)
	    tot_var_def--;
	break;
    case L_FUNCALL:
	if (update_count)
	    tot_funcall--;
	break;
    case L_BLOCK:
	if (update_count)
	    tot_lnode_block--;
	break;
    default:
	fatal("Bad type in lnode_size(): 0x%x\n", p->line & L_MASK);
    }
    free((char *)p);
}

void add_prog_ref(p)
    struct lnode_def *p;
{
    p->num_ref++;
}

/*
 * The program is stored at one of two places. Either a tree pointed
 * to by ob->prog (first argument), or a consecutive area pointed
 * to by 'block'. These two different trees has to be free'd in different
 * ways. It is load_ob_from_swap() that loads a prog into the ob->block
 * instead of ob->prog. ob->block will also contain all variable definitions.
 */
int free_prog(p, block, size)
    struct lnode_def *p;
    int size;
    char *block;
{
    extern int total_num_prog_blocks, total_prog_block_size;
    p->num_ref--;
    if (p->num_ref > 0)
	return 0;
    if (block) {
	/* free_lnode_string((struct lnode *)block); */
	free(block);
	total_prog_block_size -= size;
	total_num_prog_blocks -= 1;
    } else {
	free_sub_part((struct lnode *)p, 1);
    }
    return 1;
}

/*
 * Free the space of one node, and all linkes from it.
 * When a L_BLOCK is freed, don't call free_lnode() with the nodes
 * in the top of the block, because these are allocated in one big
 * chunk.
 */
void free_sub_part(p, do_free)
    struct lnode *p;
    int do_free;
{
    extern int tot_alloc_strings;
    if (p == 0)
	return;
    switch(p->line & L_MASK) {
    case L_SINGLE:
	break;
    case L_NUMBER:
	break;
    case L_NAME:
	free_string(((struct lnode_name *)p)->name);
	break;
    case L_VARIABLE:
	break;
    case L_1:
	free_sub_part(((struct lnode_1 *)p)->expr, 1);
	((struct lnode_1 *)p)->expr = 0;
	break;
    case L_2:
	free_sub_part(((struct lnode_2 *)p)->expr1, 1);
	((struct lnode_2 *)p)->expr1 = 0;
	free_sub_part(((struct lnode_2 *)p)->expr2, 1);
	((struct lnode_2 *)p)->expr2 = 0;
	break;
    case L_3:
	free_sub_part(((struct lnode_3 *)p)->expr1, 1);
	((struct lnode_3 *)p)->expr1 = 0;
	free_sub_part(((struct lnode_3 *)p)->expr2, 1);
	((struct lnode_3 *)p)->expr2 = 0;
	free_sub_part(((struct lnode_3 *)p)->expr3, 1);
	((struct lnode_3 *)p)->expr3 = 0;
	break;
    case L_DEF:
    {
	struct lnode_def *dp = (struct lnode_def *)p;
	free_string(dp->name);
	dp->name = 0;
	free_sub_part(dp->block, 1);
	dp->block = 0;
	if (dp->next) {
	    free_sub_part((struct lnode *)dp->next, 1);
	    dp->next = 0;
	}
	break;
    }
    case L_VAR_DEF:
    {
	struct lnode_var_def *lp;
	lp = (struct lnode_var_def *)p;
	if (lp->next)
	    free_sub_part((struct lnode *)lp->next, 1);
	free_string(lp->name);
	lp->name = 0;
	break;
    }
    case L_FUNCALL:
	free_string(((struct lnode_funcall *)p)->name);
	((struct lnode_funcall *)p)->name = 0;
	free_sub_part(((struct lnode_funcall *)p)->expr, 1);
	((struct lnode_funcall *)p)->expr = 0;
	break;
    case L_BLOCK:
    {
	struct lnode_block *lb = (struct lnode_block *)p;
	char *block = lb->block;
	int i;
	
	for (i=0; i < lb->num_nodes; i++) {
	    free_sub_part((struct lnode *)block, 0);
	    block += lnode_size[((struct lnode *)block)->line >> L_SHIFT];
	}
	free(lb->block);
	break;
    }
    default:
	fatal("Bad type in free_sub_part(): 0x%x\n", p->line & L_MASK);
    }
    if (do_free)
	free_lnode(p, 1);
}

/*
 * Free shared strings used by this lnode.
 * This is only done for programs that has been swapped out, and
 * thus are allocated in one big block. Then, only strings has to
 * be manually freed.
 */
void free_lnode_string(p)
    struct lnode *p;
{
    if (p == 0)
	return;
    switch(p->line & L_MASK) {
    case L_CONSTANT:
	break;
    case L_SINGLE:
	break;
    case L_NUMBER:
	break;
    case L_NAME:
	free_string(((struct lnode_name *)p)->name);
	break;
    case L_VARIABLE:
	break;
    case L_1:
	free_lnode_string(((struct lnode_1 *)p)->expr);
	break;
    case L_2:
	free_lnode_string(((struct lnode_2 *)p)->expr1);
	free_lnode_string(((struct lnode_2 *)p)->expr2);
	break;
    case L_3:
	free_lnode_string(((struct lnode_3 *)p)->expr1);
	free_lnode_string(((struct lnode_3 *)p)->expr2);
	free_lnode_string(((struct lnode_3 *)p)->expr3);
	break;
    case L_DEF:
    {
	struct lnode_def *dp = (struct lnode_def *)p;
	free_string(dp->name);
	free_lnode_string(dp->block);
	if (dp->next) {
	    free_lnode_string((struct lnode *)dp->next);
	}
	break;
    }
    case L_VAR_DEF:
    {
	struct lnode_var_def *lp;
	lp = (struct lnode_var_def *)p;
	if (lp->next)
	    free_lnode_string((struct lnode *)lp->next);
	free_string(lp->name);
	break;
    }
    case L_FUNCALL:
	free_string(((struct lnode_funcall *)p)->name);
	free_lnode_string(((struct lnode_funcall *)p)->expr);
	break;
    case L_BLOCK:
    {
	struct lnode_block *lb = (struct lnode_block *)p;
	char *block = lb->block;
	int i;
	
	for (i=0; i < lb->num_nodes; i++) {
	    free_lnode_string((struct lnode *)block);
	    block += lnode_size[((struct lnode *)block)->line >> L_SHIFT];
	}
	break;
    }
    default:
	fatal("Bad type in free_lnode_string(): 0x%x\n", p->line & L_MASK);
    }
}

#define NUM_LNODE_NUMBER_CONST		20
static struct lnode_number lnode_number_const[NUM_LNODE_NUMBER_CONST];
static struct lnode_number lnode_number_const_id[NUM_LNODE_NUMBER_CONST];
static struct lnode_number lnode_number_const_loc[NUM_LNODE_NUMBER_CONST];
static int num_number_match;

static int lnode_func_list[] =
{
    F_ENABLE_COMMANDS, F_THIS_OBJECT, F_THIS_PLAYER, F_TIME, F_USERS,
    F_RETURN,
};
#define NUM_FUNCS	(sizeof lnode_func_list / sizeof lnode_func_list[0])
static struct lnode_1 funcalls[NUM_FUNCS];
static int num_funcall_match;

init_constant_lnodes() {
    int i, num;

    for (i=0; i < NUM_FUNCS; i++) {
	funcalls[i].line = L_CONSTANT;
	funcalls[i].type = lnode_func_list[i];
	funcalls[i].expr = 0;
    }
    for (i=0; i < NUM_LNODE_NUMBER_CONST; i++) {
	lnode_number_const[i].line = L_CONSTANT;
	lnode_number_const[i].type = F_NUMBER;
	lnode_number_const[i].number = i;
	lnode_number_const_id[i].line = L_CONSTANT;
	lnode_number_const_id[i].type = F_IDENTIFIER;
	lnode_number_const_id[i].number = i;
	lnode_number_const_loc[i].line = L_CONSTANT;
	lnode_number_const_loc[i].type = F_LOCAL_NAME;
	lnode_number_const_loc[i].number = i;
    }
}

struct lnode_single lnode_const0 = { F_CONST0, L_CONSTANT };
struct lnode_single lnode_const1 = { F_CONST1, L_CONSTANT };
int num_lnode_single;

int match_and_replace_lnode(match, destp)
    struct lnode *match, **destp;
{
    struct lnode_number *p = (struct lnode_number *)match;
    struct lnode_1 *p1 = (struct lnode_1 *)match;

    if (match->type == F_CONST0) {
	*destp = (struct lnode *)&lnode_const0;
	num_lnode_single++;
	return 1;
    }
    if (match->type == F_CONST1) {
	*destp = (struct lnode *)&lnode_const1;
	num_lnode_single++;
	return 1;
    }
    if ((p1->line & L_MASK) == L_1 && p1->expr == 0) {
	int i;

	for (i=0; i < NUM_FUNCS; i++) {
	    if (p1->type != lnode_func_list[i])
		continue;
	    *destp = (struct lnode *)&funcalls[i];
	    num_funcall_match++;
	    return 1;
	}
	return 0;
    }
    if ((p->line & L_MASK) == L_NUMBER && p->type == F_NUMBER) {
	if (p->number < 0 || p->number >= NUM_LNODE_NUMBER_CONST)
	    return 0;
	*destp = (struct lnode *)&lnode_number_const[p->number];
	num_number_match++;
	return 1;
    }
    if ((p->line & L_MASK) == L_NUMBER && p->type == F_IDENTIFIER) {
	if (p->number < 0 || p->number >= NUM_LNODE_NUMBER_CONST)
	    return 0;
	*destp = (struct lnode *)&lnode_number_const_id[p->number];
	num_number_match++;
	return 1;
    }
    if ((p->line & L_MASK) == L_NUMBER && p->type == F_LOCAL_NAME) {
	if (p->number < 0 || p->number >= NUM_LNODE_NUMBER_CONST)
	    return 0;
	*destp = (struct lnode *)&lnode_number_const_loc[p->number];
	num_number_match++;
	return 1;
    }
    return 0;
}

void status_lnodes_matched() {
    add_message("Saved lnode_numbers: %6d (%7d bytes)\n", num_number_match,
		num_number_match * sizeof (struct lnode_number) -
		sizeof lnode_number_const_loc -
		sizeof lnode_number_const_id -
		sizeof lnode_number_const);
    add_message("Saved lnode_1      : %6d (%7d bytes)\n", num_funcall_match,
		sizeof (struct lnode_1) * (num_funcall_match - NUM_FUNCS));
    add_message("Saved lnode_single : %6d (%7d bytes)\n", num_lnode_single,
		sizeof (struct lnode_single) * (num_lnode_single - 2));
}