NetBSD/gnu/usr.bin/gawk/node.c

464 lines
8.8 KiB
C

/*
* node.c -- routines for node management
*/
/*
* Copyright (C) 1986, 1988, 1989, 1991, 1992, 1993 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 2 of the License, 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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "awk.h"
extern double strtod();
AWKNUM
r_force_number(n)
register NODE *n;
{
register char *cp;
register char *cpend;
char save;
char *ptr;
unsigned int newflags;
#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
/* all the conditionals are an attempt to avoid the expensive strtod */
n->numbr = 0.0;
n->flags |= NUM;
if (n->stlen == 0)
return 0.0;
cp = n->stptr;
if (isalpha(*cp))
return 0.0;
cpend = cp + n->stlen;
while (cp < cpend && isspace(*cp))
cp++;
if (cp == cpend || isalpha(*cp))
return 0.0;
if (n->flags & MAYBE_NUM) {
newflags = NUMBER;
n->flags &= ~MAYBE_NUM;
} else
newflags = 0;
if (cpend - cp == 1) {
if (isdigit(*cp)) {
n->numbr = (AWKNUM)(*cp - '0');
n->flags |= newflags;
}
return n->numbr;
}
errno = 0;
save = *cpend;
*cpend = '\0';
n->numbr = (AWKNUM) strtod((const char *)cp, &ptr);
/* POSIX says trailing space is OK for NUMBER */
while (isspace(*ptr))
ptr++;
*cpend = save;
/* the >= should be ==, but for SunOS 3.5 strtod() */
if (errno == 0 && ptr >= cpend)
n->flags |= newflags;
else
errno = 0;
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 const char *values[] = {
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
};
#define NVAL (sizeof(values)/sizeof(values[0]))
NODE *
r_force_string(s)
register NODE *s;
{
char buf[128];
register char *sp = buf;
double val;
#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
/* not an integral value, or out of range */
if ((val = double_to_int(s->numbr)) != s->numbr
|| val < LONG_MIN || val > LONG_MAX) {
#ifdef GFMT_WORKAROUND
NODE *dummy, *r;
unsigned short oflags;
extern NODE *format_tree P((const char *, int, NODE *));
extern NODE **fmt_list; /* declared in eval.c */
/* create dummy node for a sole use of format_tree */
getnode(dummy);
dummy->lnode = s;
dummy->rnode = NULL;
oflags = s->flags;
s->flags |= PERM; /* prevent from freeing by format_tree() */
r = format_tree(CONVFMT, fmt_list[CONVFMTidx]->stlen, dummy);
s->flags = oflags;
s->stfmt = (char)CONVFMTidx;
s->stlen = r->stlen;
s->stptr = r->stptr;
freenode(r); /* Do not free_temp(r)! We want */
freenode(dummy); /* to keep s->stptr == r->stpr. */
goto no_malloc;
#else
/*
* no need for a "replacement" formatting by gawk,
* just use sprintf
*/
sprintf(sp, CONVFMT, s->numbr);
s->stlen = strlen(sp);
s->stfmt = (char)CONVFMTidx;
#endif /* GFMT_WORKAROUND */
} else {
/* integral value */
/* force conversion to long only once */
register long num = (long) val;
if (num < NVAL && num >= 0) {
sp = (char *) values[num];
s->stlen = 1;
} else {
(void) sprintf(sp, "%ld", num);
s->stlen = strlen(sp);
}
s->stfmt = -1;
}
emalloc(s->stptr, char *, s->stlen + 2, "force_string");
memcpy(s->stptr, sp, s->stlen+1);
no_malloc:
s->stref = 1;
s->flags |= STR;
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;
}
getnode(r);
*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 + 2, "dupnode");
memcpy(r->stptr, n->stptr, r->stlen);
r->stptr[r->stlen] = '\0';
}
return r;
}
/* this allocates a node with defined numbr */
NODE *
mk_number(x, flags)
AWKNUM x;
unsigned int flags;
{
register NODE *r;
getnode(r);
r->type = Node_val;
r->numbr = x;
r->flags = flags;
#ifdef DEBUG
r->stref = 1;
r->stptr = 0;
r->stlen = 0;
#endif
return r;
}
/*
* Make a string node.
*/
NODE *
make_str_node(s, len, flags)
char *s;
size_t len;
int flags;
{
register NODE *r;
getnode(r);
r->type = Node_val;
r->flags = (STRING|STR|MALLOC);
if (flags & ALREADY_MALLOCED)
r->stptr = s;
else {
emalloc(r->stptr, char *, len + 2, s);
memcpy(r->stptr, s, len);
}
r->stptr[len] = '\0';
if (flags & SCAN) { /* scan for escape sequences */
char *pf;
register char *ptm;
register int c;
register char *end;
end = &(r->stptr[len]);
for (pf = ptm = r->stptr; pf < end;) {
c = *pf++;
if (c == '\\') {
c = parse_escape(&pf);
if (c < 0) {
if (do_lint)
warning("backslash at end of string");
c = '\\';
}
*ptm++ = c;
} else
*ptm++ = c;
}
len = ptm - 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->stfmt = -1;
return r;
}
NODE *
tmp_string(s, len)
char *s;
size_t len;
{
register NODE *r;
r = make_string(s, len);
r->flags |= TEMP;
return r;
}
#define NODECHUNK 100
NODE *nextfree = NULL;
NODE *
more_nodes()
{
register NODE *np;
/* get more nodes and initialize list */
emalloc(nextfree, NODE *, NODECHUNK * sizeof(NODE), "newnode");
for (np = nextfree; np < &nextfree[NODECHUNK - 1]; np++) {
np->flags = 0;
np->nextp = np + 1;
}
np->nextp = NULL;
np = nextfree;
nextfree = nextfree->nextp;
return np;
}
#ifdef DEBUG
void
freenode(it)
NODE *it;
{
#ifdef MPROF
it->stref = 0;
free((char *) it);
#else /* not MPROF */
/* add it to head of freelist */
it->nextp = nextfree;
nextfree = it;
#endif /* not MPROF */
}
#endif /* DEBUG */
void
unref(tmp)
register NODE *tmp;
{
if (tmp == NULL)
return;
if (tmp->flags & PERM)
return;
if (tmp->flags & (MALLOC|TEMP)) {
tmp->flags &= ~TEMP;
if (tmp->flags & STR) {
if (tmp->stref > 1) {
if (tmp->stref != 255)
tmp->stref--;
return;
}
free(tmp->stptr);
}
freenode(tmp);
}
}
/*
* Parse a C escape sequence. STRING_PTR points to a variable containing a
* pointer to the string to parse. That pointer is updated past the
* characters we use. The value of the escape sequence is returned.
*
* A negative value means the sequence \ newline was seen, which is supposed to
* be equivalent to nothing at all.
*
* If \ is followed by a null character, we return a negative value and leave
* the string pointer pointing at the null character.
*
* If \ is followed by 000, we return 0 and leave the string pointer after the
* zeros. A value of 0 does not mean end of string.
*
* Posix doesn't allow \x.
*/
int
parse_escape(string_ptr)
char **string_ptr;
{
register int c = *(*string_ptr)++;
register int i;
register int count;
switch (c) {
case 'a':
return BELL;
case 'b':
return '\b';
case 'f':
return '\f';
case 'n':
return '\n';
case 'r':
return '\r';
case 't':
return '\t';
case 'v':
return '\v';
case '\n':
return -2;
case 0:
(*string_ptr)--;
return -1;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
i = c - '0';
count = 0;
while (++count < 3) {
if ((c = *(*string_ptr)++) >= '0' && c <= '7') {
i *= 8;
i += c - '0';
} else {
(*string_ptr)--;
break;
}
}
return i;
case 'x':
if (do_lint) {
static int didwarn;
if (! didwarn) {
didwarn = 1;
warning("Posix does not allow \"\\x\" escapes");
}
}
if (do_posix)
return ('x');
i = 0;
while (1) {
if (isxdigit((c = *(*string_ptr)++))) {
i *= 16;
if (isdigit(c))
i += c - '0';
else if (isupper(c))
i += c - 'A' + 10;
else
i += c - 'a' + 10;
} else {
(*string_ptr)--;
break;
}
}
return i;
default:
return c;
}
}