/*
 * node.c -- routines for node management
 */

/* 
 * Copyright (C) 1986, 1988, 1989 the Free Software Foundation, Inc.
 * 
 * This file is part of GAWK, the GNU implementation of the
 * AWK Progamming Language.
 * 
 * GAWK is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 * 
 * GAWK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GAWK; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "awk.h"

extern double strtod();

/*
 * We can't dereference a variable until after we've given it its new value.
 * This variable points to the value we have to free up 
 */
NODE *deref;

AWKNUM
r_force_number(n)
NODE *n;
{
	char *ptr;

#ifdef DEBUG
	if (n == NULL)
		cant_happen();
	if (n->type != Node_val)
		cant_happen();
	if(n->flags == 0)
		cant_happen();
	if (n->flags & NUM)
		return n->numbr;
#endif
	if (n->stlen == 0)
		n->numbr = 0.0;
	else if (n->stlen == 1) {
		if (isdigit(n->stptr[0])) {
			n->numbr = n->stptr[0] - '0';
			n->flags |= NUMERIC;
		} else
			n->numbr = 0.0;
	} else {
		errno = 0;
		n->numbr = (AWKNUM) strtod(n->stptr, &ptr);
		/* the following >= should be ==, but for SunOS 3.5 strtod() */
		if (errno == 0 && ptr >= n->stptr + n->stlen)
			n->flags |= NUMERIC;
	}
	n->flags |= NUM;
	return n->numbr;
}

/*
 * the following lookup table is used as an optimization in force_string
 * (more complicated) variations on this theme didn't seem to pay off, but 
 * systematic testing might be in order at some point
 */
static char *values[] = {
	"0",
	"1",
	"2",
	"3",
	"4",
	"5",
	"6",
	"7",
	"8",
	"9",
};
#define	NVAL	(sizeof(values)/sizeof(values[0]))

NODE *
r_force_string(s)
NODE *s;
{
	char buf[128];
	char *fmt;
	long num;
	char *sp = buf;

#ifdef DEBUG
	if (s == NULL)
		cant_happen();
	if (s->type != Node_val)
		cant_happen();
	if (s->flags & STR)
		return s;
	if (!(s->flags & NUM))
		cant_happen();
	if (s->stref != 0)
		cant_happen();
#endif
	s->flags |= STR;
	/* should check validity of user supplied OFMT */
	fmt = OFMT_node->var_value->stptr;
	if ((num = s->numbr) == s->numbr) {
		/* integral value */
		if (num < NVAL && num >= 0) {
			sp = values[num];
			s->stlen = 1;
		} else {
			(void) sprintf(sp, "%ld", num);
			s->stlen = strlen(sp);
		}
	} else {
		(void) sprintf(sp, fmt, s->numbr);
		s->stlen = strlen(sp);
	}
	s->stref = 1;
	emalloc(s->stptr, char *, s->stlen + 1, "force_string");
	memcpy(s->stptr, sp, s->stlen+1);
	return s;
}

/*
 * Duplicate a node.  (For strings, "duplicate" means crank up the
 * reference count.)
 */
NODE *
dupnode(n)
NODE *n;
{
	register NODE *r;

	if (n->flags & TEMP) {
		n->flags &= ~TEMP;
		n->flags |= MALLOC;
		return n;
	}
	if ((n->flags & (MALLOC|STR)) == (MALLOC|STR)) {
		if (n->stref < 255)
			n->stref++;
		return n;
	}
	r = newnode(Node_illegal);
	*r = *n;
	r->flags &= ~(PERM|TEMP);
	r->flags |= MALLOC;
	if (n->type == Node_val && (n->flags & STR)) {
		r->stref = 1;
		emalloc(r->stptr, char *, r->stlen + 1, "dupnode");
		memcpy(r->stptr, n->stptr, r->stlen+1);
	}
	return r;
}

/* this allocates a node with defined numbr */
NODE *
make_number(x)
AWKNUM x;
{
	register NODE *r;

	r = newnode(Node_val);
	r->numbr = x;
	r->flags |= (NUM|NUMERIC);
	r->stref = 0;
	return r;
}

/*
 * This creates temporary nodes.  They go away quite quickly, so don't use
 * them for anything important 
 */
NODE *
tmp_number(x)
AWKNUM x;
{
	NODE *r;

	r = make_number(x);
	r->flags |= TEMP;
	return r;
}

/*
 * Make a string node.
 */

NODE *
make_str_node(s, len, scan)
char *s;
int len;
int scan;
{
	register NODE *r;
	char *pf;
	register char *pt;
	register int c;
	register char *end;

	r = newnode(Node_val);
	emalloc(r->stptr, char *, len + 1, s);
	memcpy(r->stptr, s, len);
	r->stptr[len] = '\0';
	end = &(r->stptr[len]);
	       
	if (scan) {	/* scan for escape sequences */
		for (pf = pt = r->stptr; pf < end;) {
			c = *pf++;
			if (c == '\\') {
				c = parse_escape(&pf);
				if (c < 0)
					cant_happen();
				*pt++ = c;
			} else
				*pt++ = c;
		}
		len = pt - r->stptr;
		erealloc(r->stptr, char *, len + 1, "make_str_node");
		r->stptr[len] = '\0';
		r->flags |= PERM;
	}
	r->stlen = len;
	r->stref = 1;
	r->flags |= (STR|MALLOC);

	return r;
}

/* Read the warning under tmp_number */
NODE *
tmp_string(s, len)
char *s;
int len;
{
	register NODE *r;

	r = make_string(s, len);
	r->flags |= TEMP;
	return r;
}


#define NODECHUNK	100

static NODE *nextfree = NULL;

NODE *
newnode(ty)
NODETYPE ty;
{
	NODE *it;
	NODE *np;

#ifdef MPROF
	emalloc(it, NODE *, sizeof(NODE), "newnode");
#else
	if (nextfree == NULL) {
		/* get more nodes and initialize list */
		emalloc(nextfree, NODE *, NODECHUNK * sizeof(NODE), "newnode");
		for (np = nextfree; np < &nextfree[NODECHUNK - 1]; np++)
			np->nextp = np + 1;
		np->nextp = NULL;
	}
	/* get head of freelist */
	it = nextfree;
	nextfree = nextfree->nextp;
#endif
	it->type = ty;
	it->flags = MALLOC;
#ifdef MEMDEBUG
	fprintf(stderr, "node: new: %0x\n", it);
#endif
	return it;
}

void
freenode(it)
NODE *it;
{
#ifdef DEBUG
	NODE *nf;
#endif
#ifdef MEMDEBUG
	fprintf(stderr, "node: free: %0x\n", it);
#endif
#ifdef MPROF
	free((char *) it);
#else
#ifdef DEBUG
	for (nf = nextfree; nf; nf = nf->nextp)
		if (nf == it)
			fatal("attempt to free free node");
#endif
	/* add it to head of freelist */
	it->nextp = nextfree;
	nextfree = it;
#endif
}

#ifdef DEBUG
pf()
{
	NODE *nf = nextfree;
	while (nf != NULL) {
		fprintf(stderr, "%0x ", nf);
		nf = nf->nextp;
	}
}
#endif

void
do_deref()
{
	if (deref == NULL)
		return;
	if (deref->flags & PERM) {
		deref = 0;
		return;
	}
	if ((deref->flags & MALLOC) || (deref->flags & TEMP)) {
		deref->flags &= ~TEMP;
		if (deref->flags & STR) {
			if (deref->stref > 1 && deref->stref != 255) {
				deref->stref--;
				deref = 0;
				return;
			}
			free(deref->stptr);
		}
		freenode(deref);
	}
	deref = 0;
}
