#include "evalint.h"
#include "variables.h"
#include <string.h>

/* Is the adr field in a variable valid ? */
#define CLEAN(v) ((v)->adr && (v)->key == cache_time)

long cache_time; /* This is incremented every time a variable is freed or created */
/* Variables are refered to by name, but this involves searching through a
  list at each reference, hence caching the address to speed things up.
  But these addresses may become invalid every time a varible is created/
  deleted, hence this counter incremented at every such instance. Of course,
  after ~2 billion creations/deletions, there is a faint possibility of
  error, but I think I can live with it :-) */

context *current;

void set_context(new)
context *new;
{
    current = new;
    cache_time++;
}

/* General variable creation routine */
vbl *_create_var(name)
char *name;
{
    register vbl *var;

    if (var = ALLOC(vbl))
    {
	var->var_Node.ln_Name = ALLOCMEM(strlen(name) + 1);
	if (var->var_Node.ln_Name)
	{
	    strcpy(var->var_Node.ln_Name, name);
	    var->val = NULL;
	    var->flags = NULL;
	    AddHead(current, &var->var_Node);
	    cache_time++; /* Invalidate addresses */
	}
	else
	{
	    FREE(vbl, var);
	    var = NULL;
	}
    }
    return(var);
}

int create_var(v)
variable *v;
{
    vbl *new = _create_var(v->name);

    v->adr = new;
    v->key = cache_time;

    return(0 != new);
}

int create_var_name(name)
char *name;
{
    return(0 != _create_var(name));
}

void free_var(v)
variable *v;
{
    register vbl *var;

    /* How to find the address of a varible : */
    if (!CLEAN(v)) var = find_var(v->name);
    else var = v->adr;

    if (var)
    {
	Remove(&var->var_Node);
	FREEMEM(strlen(var->var_Node.ln_Name) + 1, var->var_Node.ln_Name);
	FREE(vbl, var);
	cache_time++;
    }
}

void free_var_name(name)
char *name;
{
    register vbl *var;

    if (var = find_var(name))
    {
	Remove(&var->var_Node);
	FREEMEM(strlen(var->var_Node.ln_Name) + 1, var->var_Node.ln_Name);
	FREE(vbl, var);
	cache_time++;
    }
}

/* Return value of var */
value get_var(v)
register variable *v;
{
    if (CLEAN(v)) return(v->adr->val);
    else
    {
	v->adr = find_var(v->name);
	v->key = cache_time;
	if (v->adr) return(v->adr->val);
	else return(NULL); /* No such var */
    }
}

value get_var_name(name)
char *name;
{
    vbl *v = find_var(name);

    if (v) return(v->val);
    else return(NULL);
}

/* set_var attempts to create the variable if it doesn't exist */
int set_var(v, val)
register variable *v;
value val;
{
    if (!CLEAN(v))
    {
	v->adr = find_var(v->name);
	v->key = cache_time;
	if (!v->adr)
	    if (!(v->adr = _create_var(v->name))) return(FALSE);
    }
    v->adr->val = val;
    return(TRUE);
}

int set_var_name(name, val)
char *name;
value val;
{
    register vbl *v = find_var(name);

    if (!v) if (!(v = _create_var(name))) return(FALSE);
    v->val = val;
}

void free_var_list(list)
var_list *list;
{
    register struct Node *scan, *node;

    scan = list->lh_Head;
    while (scan->ln_Succ)
    {
	node = scan;
	scan = scan->ln_Succ;
	Remove(node);
	FREEMEM(strlen(node->ln_Name) + 1, node->ln_Name);
	FREE(struct Node, node);
    }
}

/* Scan an expression to find all the variables it uses */
/* It can be called multiple times to find all the variables in multiple expressions */
var_list *make_var_list(expr, list)
register value expr;
var_list *list;
{
    struct Node *node;
    char *str;

    switch (expr->type)
    {
	case var:
	    if (!FindName(list, expr->vr.name)) /* Not already in list */
	    {
		int l = strlen(expr->vr.name) + 1;

		if ((node = ALLOC(struct Node)) && (str = ALLOCMEM(l)))
		{
		    node->ln_Name = strcpy(str, expr->vr.name);
		    AddTail(list, node);
		}
		else
		{
		    if (node) FREE(struct Node, node);
		    if (str) FREEMEM(l, str);
		    free_vars(list);
		    return(NULL);
		}
	    }
	    return(list);
	case op:
	    if (make_var_list(expr->opr.arg1, list))
		return(make_var_list(expr->opr.arg2, list));
	    else
		return(NULL);
	case fonc:
	    return(make_var_list(expr->fn.arg, list));
	case cste:
	    return(list);
    }
}

/* Create all the vars in a list, if a variable already exists, its value is
  occulted by the new variable (till this one is freed) */
int create_vars(list)
var_list *list;
{
    register struct Node *scan;

    for (scan = list->lh_Head; scan->ln_Succ; scan = scan->ln_Succ)
	if (!_create_var(scan->ln_Name)) /* free vars */
	{
	    while (scan = scan->ln_Pred, scan->ln_Pred)
		free_var_name(scan->ln_Name);

	    return(FALSE);
	}
    return(TRUE);
}

void free_vars(list)
var_list *list;
{
    register struct Node *scan;

    for (scan = list->lh_Head; scan->ln_Succ; scan = scan->ln_Succ)
	free_var_name(scan->ln_Name);
}

