mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-27 08:49:16 +08:00
763 lines
16 KiB
C
763 lines
16 KiB
C
/*********************************************************************
|
|
* Copyright 2009, UCAR/Unidata
|
|
* See netcdf/COPYRIGHT file for copying and redistribution conditions.
|
|
*********************************************************************/
|
|
/* $Id: data.c,v 1.7 2010/05/24 19:59:56 dmh Exp $ */
|
|
/* $Header: /upc/share/CVS/netcdf-3/ncgen/data.c,v 1.7 2010/05/24 19:59:56 dmh Exp $ */
|
|
|
|
#include "includes.h"
|
|
#include "ncoffsets.h"
|
|
#include "dump.h"
|
|
|
|
#define XVSNPRINTF vsnprintf
|
|
/*
|
|
#define XVSNPRINTF lvsnprintf
|
|
extern int lvsnprintf(char*, size_t, const char*, va_list);
|
|
*/
|
|
|
|
#define DATALISTINIT 32
|
|
|
|
/* Track all known datalist*/
|
|
Datalist* alldatalists = NULL;
|
|
|
|
NCConstant nullconstant;
|
|
NCConstant fillconstant;
|
|
|
|
Datalist nildatalist; /* to support NIL keyword */
|
|
|
|
Bytebuffer* codebuffer;
|
|
Bytebuffer* codetmp;
|
|
Bytebuffer* stmt;
|
|
|
|
|
|
/* Forward */
|
|
|
|
/**************************************************/
|
|
/**************************************************/
|
|
|
|
/* return 1 if the next element in the datasrc is compound*/
|
|
int
|
|
issublist(Datasrc* datasrc) {return istype(datasrc,NC_COMPOUND);}
|
|
|
|
/* return 1 if the next element in the datasrc is a string*/
|
|
int
|
|
isstring(Datasrc* datasrc) {return istype(datasrc,NC_STRING);}
|
|
|
|
/* return 1 if the next element in the datasrc is a fill value*/
|
|
int
|
|
isfillvalue(Datasrc* datasrc)
|
|
{
|
|
return srcpeek(datasrc) == NULL || istype(datasrc,NC_FILLVALUE);
|
|
}
|
|
|
|
/* return 1 if the next element in the datasrc is nc_type*/
|
|
int
|
|
istype(Datasrc* datasrc , nc_type nctype)
|
|
{
|
|
NCConstant* ci = srcpeek(datasrc);
|
|
if(ci != NULL && ci->nctype == nctype) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
isstringable(nc_type nctype)
|
|
{
|
|
switch (nctype) {
|
|
case NC_CHAR: case NC_STRING:
|
|
case NC_BYTE: case NC_UBYTE:
|
|
case NC_FILLVALUE:
|
|
return 1;
|
|
default: break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
void
|
|
freedatasrc(Datasrc* src)
|
|
{
|
|
efree(src);
|
|
}
|
|
|
|
Datasrc*
|
|
allocdatasrc(void)
|
|
{
|
|
Datasrc* src;
|
|
src = ecalloc(sizeof(Datasrc));
|
|
src->data = NULL;
|
|
src->index = 0;
|
|
src->length = 0;
|
|
src->prev = NULL;
|
|
return src;
|
|
}
|
|
|
|
Datasrc*
|
|
datalist2src(Datalist* list)
|
|
{
|
|
Datasrc* src;
|
|
ASSERT(list != NULL);
|
|
src = allocdatasrc();
|
|
src->data = list->data;
|
|
src->index = 0;
|
|
src->length = list->length;
|
|
DUMPSRC(src,"#");
|
|
return src;
|
|
}
|
|
|
|
Datasrc*
|
|
const2src(NCConstant* con)
|
|
{
|
|
Datasrc* src;
|
|
ASSERT(con != NULL);
|
|
src = allocdatasrc();
|
|
src->data = con;
|
|
src->index = 0;
|
|
src->length = 1;
|
|
DUMPSRC(src,"#");
|
|
return src;
|
|
}
|
|
|
|
NCConstant
|
|
list2const(Datalist* list)
|
|
{
|
|
NCConstant con;
|
|
ASSERT(list != NULL);
|
|
con.nctype = NC_COMPOUND;
|
|
con.lineno = list->data[0].lineno;
|
|
con.value.compoundv = list;
|
|
con.filled = 0;
|
|
return con;
|
|
}
|
|
|
|
Datalist*
|
|
const2list(NCConstant* con)
|
|
{
|
|
Datalist* list;
|
|
ASSERT(con != NULL);
|
|
list = builddatalist(1);
|
|
if(list != NULL) {
|
|
dlappend(list,con);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
NCConstant*
|
|
srcpeek(Datasrc* ds)
|
|
{
|
|
if(ds == NULL) return NULL;
|
|
if(ds->index < ds->length)
|
|
return &ds->data[ds->index];
|
|
if(ds->spliced)
|
|
return srcpeek(ds->prev);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
srcreset(Datasrc* ds)
|
|
{
|
|
ds->index = 0;
|
|
}
|
|
|
|
NCConstant*
|
|
srcnext(Datasrc* ds)
|
|
{
|
|
DUMPSRC(ds,"!");
|
|
if(ds == NULL) return NULL;
|
|
if(ds->index < ds->length)
|
|
return &ds->data[ds->index++];
|
|
if(ds->spliced) {
|
|
srcpop(ds);
|
|
return srcnext(ds);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
srcmore(Datasrc* ds)
|
|
{
|
|
if(ds == NULL) return 0;
|
|
if(ds->index < ds->length) return 1;
|
|
if(ds->spliced) return srcmore(ds->prev);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
srcline(Datasrc* ds)
|
|
{
|
|
int index = ds->index;
|
|
int len = ds->length;
|
|
/* pick closest available entry*/
|
|
if(len == 0) return 0;
|
|
if(index >= len) index = len-1;
|
|
return ds->data[index].lineno;
|
|
}
|
|
|
|
void
|
|
srcpush(Datasrc* src)
|
|
{
|
|
NCConstant* con;
|
|
ASSERT(src != NULL);
|
|
con = srcnext(src);
|
|
ASSERT(con->nctype == NC_COMPOUND);
|
|
srcpushlist(src,con->value.compoundv);
|
|
}
|
|
|
|
void
|
|
srcpushlist(Datasrc* src, Datalist* dl)
|
|
{
|
|
Datasrc* newsrc;
|
|
ASSERT(src != NULL && dl != NULL);
|
|
newsrc = allocdatasrc();
|
|
*newsrc = *src;
|
|
src->prev = newsrc;
|
|
src->index = 0;
|
|
src->data = dl->data;
|
|
src->length = dl->length;
|
|
DUMPSRC(src,">!");
|
|
}
|
|
|
|
void
|
|
srcpop(Datasrc* src)
|
|
{
|
|
if(src != NULL) {
|
|
Datasrc* prev = src->prev;
|
|
*src = *prev;
|
|
freedatasrc(prev);
|
|
}
|
|
DUMPSRC(src,"<");
|
|
}
|
|
|
|
void
|
|
srcsplice(Datasrc* ds, Datalist* list)
|
|
{
|
|
srcpushlist(ds,list);
|
|
ds->spliced = 1;
|
|
}
|
|
|
|
void
|
|
srcsetfill(Datasrc* ds, Datalist* list)
|
|
{
|
|
if(ds->index >= ds->length) PANIC("srcsetfill: no space");
|
|
if(ds->data[ds->index].nctype != NC_FILLVALUE) PANIC("srcsetfill: not fill");
|
|
ds->data[ds->index].nctype = NC_COMPOUND;
|
|
ds->data[ds->index].value.compoundv = list;
|
|
}
|
|
|
|
|
|
/**************************************************/
|
|
#ifdef GENDEBUG
|
|
void
|
|
report(char* lead, Datalist* list)
|
|
{
|
|
extern void bufdump(Datalist*,Bytebuffer*);
|
|
Bytebuffer* buf = bbNew();
|
|
bufdump(list,buf);
|
|
fprintf(stderr,"\n%s::%s\n",lead,bbContents(buf));
|
|
fflush(stderr);
|
|
bbFree(buf);
|
|
}
|
|
|
|
void
|
|
report0(char* lead, Datasrc* src, int index)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/**************************************************/
|
|
|
|
/* Shallow constant cloning*/
|
|
NCConstant
|
|
cloneconstant(NCConstant* con)
|
|
{
|
|
NCConstant newcon = *con;
|
|
char* s;
|
|
switch (newcon.nctype) {
|
|
case NC_STRING:
|
|
s = (char*)ecalloc(newcon.value.stringv.len+1);
|
|
memcpy(s,newcon.value.stringv.stringv,newcon.value.stringv.len);
|
|
s[newcon.value.stringv.len] = '\0';
|
|
newcon.value.stringv.stringv = s;
|
|
break;
|
|
case NC_OPAQUE:
|
|
s = (char*)ecalloc(newcon.value.opaquev.len+1);
|
|
memcpy(s,newcon.value.opaquev.stringv,newcon.value.opaquev.len);
|
|
s[newcon.value.opaquev.len] = '\0';
|
|
newcon.value.opaquev.stringv = s;
|
|
break;
|
|
default: break;
|
|
}
|
|
return newcon;
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
Datalist*
|
|
datalistclone(Datalist* dl)
|
|
{
|
|
int i;
|
|
Datalist* clone = builddatalist(dl->length);
|
|
for(i=0;i<dl->length;i++) {
|
|
clone->data[i] = cloneconstant(dl->data+i);
|
|
}
|
|
return clone;
|
|
}
|
|
|
|
Datalist*
|
|
datalistconcat(Datalist* dl1, Datalist* dl2)
|
|
{
|
|
NCConstant* vector;
|
|
ASSERT(dl1 != NULL);
|
|
if(dl2 == NULL) return dl1;
|
|
vector = (NCConstant*)erealloc(dl1->data,sizeof(NCConstant)*(dl1->length+dl2->length));
|
|
if(vector == NULL) return NULL;
|
|
memcpy((void*)(vector+dl1->length),dl2->data,sizeof(NCConstant)*(dl2->length));
|
|
dl1->data = vector;
|
|
return dl1;
|
|
}
|
|
|
|
Datalist*
|
|
datalistappend(Datalist* dl, NCConstant* con)
|
|
{
|
|
NCConstant* vector;
|
|
ASSERT(dl != NULL);
|
|
if(con == NULL) return dl;
|
|
vector = (NCConstant*)erealloc(dl->data,sizeof(NCConstant)*(dl->length+1));
|
|
if(vector == NULL) return NULL;
|
|
vector[dl->length] = *con;
|
|
dl->length++;
|
|
dl->data = vector;
|
|
return dl;
|
|
}
|
|
|
|
Datalist*
|
|
datalistreplace(Datalist* dl, unsigned int index, NCConstant* con)
|
|
{
|
|
ASSERT(dl != NULL);
|
|
ASSERT(index < dl->length);
|
|
ASSERT(con != NULL);
|
|
dl->data[index] = *con;
|
|
return dl;
|
|
}
|
|
|
|
int
|
|
datalistline(Datalist* ds)
|
|
{
|
|
if(ds == NULL || ds->length == 0) return 0;
|
|
return ds->data[0].lineno;
|
|
}
|
|
|
|
|
|
/* Go thru a databuf of possibly nested constants
|
|
and insert commas as needed; ideally, this
|
|
operation should be idempotent so that
|
|
the caller need not worry about it having already
|
|
been applied. Also, handle situation where there may be missing
|
|
matching right braces.
|
|
*/
|
|
|
|
static char* commifyr(char* p, Bytebuffer* buf);
|
|
static char* wordstring(char* p, Bytebuffer* buf, int quote);
|
|
|
|
void
|
|
commify(Bytebuffer* buf)
|
|
{
|
|
char* list,*p;
|
|
|
|
if(bbLength(buf) == 0) return;
|
|
list = bbDup(buf);
|
|
p = list;
|
|
bbClear(buf);
|
|
commifyr(p,buf);
|
|
bbNull(buf);
|
|
efree(list);
|
|
}
|
|
|
|
/* Requires that the string be balanced
|
|
WRT to braces
|
|
*/
|
|
static char*
|
|
commifyr(char* p, Bytebuffer* buf)
|
|
{
|
|
int comma = 0;
|
|
int c;
|
|
while((c=*p++)) {
|
|
if(c == ' ') continue;
|
|
if(c == ',') continue;
|
|
else if(c == '}') {
|
|
break;
|
|
}
|
|
if(comma) bbCat(buf,", "); else comma=1;
|
|
if(c == '{') {
|
|
bbAppend(buf,'{');
|
|
p = commifyr(p,buf);
|
|
bbAppend(buf,'}');
|
|
} else if(c == '\'' || c == '\"') {
|
|
p = wordstring(p,buf,c);
|
|
} else {
|
|
bbAppend(buf,c);
|
|
p=word(p,buf);
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
char*
|
|
word(char* p, Bytebuffer* buf)
|
|
{
|
|
int c;
|
|
while((c=*p++)) {
|
|
if(c == '}' || c == ' ' || c == ',') break;
|
|
if(c == '\\') {
|
|
bbAppend(buf,c);
|
|
c=*p++;
|
|
if(!c) break;
|
|
}
|
|
bbAppend(buf,(char)c);
|
|
}
|
|
p--; /* leave terminator for parent */
|
|
return p;
|
|
}
|
|
|
|
static char*
|
|
wordstring(char* p, Bytebuffer* buf, int quote)
|
|
{
|
|
int c;
|
|
bbAppend(buf,quote);
|
|
while((c=*p++)) {
|
|
if(c == '\\') {
|
|
bbAppend(buf,c);
|
|
c = *p++;
|
|
if(c == '\0') return --p;
|
|
} else if(c == quote) {
|
|
bbAppend(buf,c);
|
|
return p;
|
|
}
|
|
bbAppend(buf,c);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
|
|
static const char zeros[] =
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
|
void
|
|
alignbuffer(NCConstant* prim, Bytebuffer* buf)
|
|
{
|
|
int alignment,pad,offset;
|
|
|
|
if(prim->nctype == NC_ECONST)
|
|
alignment = nctypealignment(prim->value.enumv->typ.typecode);
|
|
else if(usingclassic && prim->nctype == NC_STRING)
|
|
alignment = nctypealignment(NC_CHAR);
|
|
else if(prim->nctype == NC_CHAR)
|
|
alignment = nctypealignment(NC_CHAR);
|
|
else
|
|
alignment = nctypealignment(prim->nctype);
|
|
offset = bbLength(buf);
|
|
pad = getpadding(offset,alignment);
|
|
if(pad > 0) {
|
|
bbAppendn(buf,(void*)zeros,pad);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Following routines are in support of language-oriented output
|
|
*/
|
|
|
|
void
|
|
codedump(Bytebuffer* buf)
|
|
{
|
|
bbCatbuf(codebuffer,buf);
|
|
bbClear(buf);
|
|
}
|
|
|
|
void
|
|
codepartial(const char* txt)
|
|
{
|
|
bbCat(codebuffer,txt);
|
|
}
|
|
|
|
void
|
|
codeline(const char* line)
|
|
{
|
|
codepartial(line);
|
|
codepartial("\n");
|
|
}
|
|
|
|
void
|
|
codelined(int n, const char* txt)
|
|
{
|
|
bbindent(codebuffer,n);
|
|
bbCat(codebuffer,txt);
|
|
codepartial("\n");
|
|
}
|
|
|
|
void
|
|
codeflush(void)
|
|
{
|
|
if(bbLength(codebuffer) > 0) {
|
|
bbNull(codebuffer);
|
|
fputs(bbContents(codebuffer),stdout);
|
|
fflush(stdout);
|
|
bbClear(codebuffer);
|
|
}
|
|
}
|
|
|
|
void
|
|
bbindent(Bytebuffer* buf, const int n)
|
|
{
|
|
bbCat(buf,indented(n));
|
|
}
|
|
|
|
/* Provide an restrict snprintf that writes to an expandable buffer */
|
|
/* Simulates a simple snprintf because apparently
|
|
the IRIX one is broken wrt return value.
|
|
Supports only %u %d %f %s and %% specifiers
|
|
with optional leading hh or ll.
|
|
*/
|
|
|
|
static void
|
|
vbbprintf(Bytebuffer* buf, const char* fmt, va_list argv)
|
|
{
|
|
char tmp[128];
|
|
const char* p;
|
|
int c;
|
|
int hcount;
|
|
int lcount;
|
|
|
|
char* text;
|
|
|
|
for(p=fmt;(c=*p++);) {
|
|
hcount = 0; lcount = 0;
|
|
switch (c) {
|
|
case '%':
|
|
retry: switch ((c=*p++)) {
|
|
case '\0': bbAppend(buf,'%'); p--; break;
|
|
case '%': bbAppend(buf,c); break;
|
|
case 'h':
|
|
hcount++;
|
|
while((c=*p) && (c == 'h')) {hcount++; p++;}
|
|
if(hcount > 2) hcount = 2;
|
|
goto retry;
|
|
case 'l':
|
|
lcount++;
|
|
while((c=*p) && (c == 'l')) {
|
|
lcount++;
|
|
p++;
|
|
}
|
|
if(lcount > 2) lcount = 2;
|
|
goto retry;
|
|
case 'u':
|
|
if(hcount == 2) {
|
|
snprintf(tmp,sizeof(tmp),"%hhu",
|
|
(unsigned int)va_arg(argv,unsigned int));
|
|
} else if(hcount == 1) {
|
|
snprintf(tmp,sizeof(tmp),"%hu",
|
|
(unsigned int)va_arg(argv,unsigned int));
|
|
} else if(lcount == 2) {
|
|
snprintf(tmp,sizeof(tmp),"%llu",
|
|
(unsigned long long)va_arg(argv,unsigned long long));
|
|
} else if(lcount == 1) {
|
|
snprintf(tmp,sizeof(tmp),"%lu",
|
|
(unsigned long)va_arg(argv,unsigned long));
|
|
} else {
|
|
snprintf(tmp,sizeof(tmp),"%u",
|
|
(unsigned int)va_arg(argv,unsigned int));
|
|
}
|
|
bbCat(buf,tmp);
|
|
break;
|
|
case 'd':
|
|
if(hcount == 2) {
|
|
snprintf(tmp,sizeof(tmp),"%hhd",
|
|
(signed int)va_arg(argv,signed int));
|
|
} else if(hcount == 1) {
|
|
snprintf(tmp,sizeof(tmp),"%hd",
|
|
(signed int)va_arg(argv,signed int));
|
|
} else if(lcount == 2) {
|
|
snprintf(tmp,sizeof(tmp),"%lld",
|
|
(signed long long)va_arg(argv,signed long long));
|
|
} else if(lcount == 1) {
|
|
snprintf(tmp,sizeof(tmp),"%ld",
|
|
(signed long)va_arg(argv,signed long));
|
|
} else {
|
|
snprintf(tmp,sizeof(tmp),"%d",
|
|
(signed int)va_arg(argv,signed int));
|
|
}
|
|
bbCat(buf,tmp);
|
|
break;
|
|
case 'f':
|
|
if(lcount > 0) {
|
|
snprintf(tmp,sizeof(tmp),"((double)%.16g)",
|
|
(double)va_arg(argv,double));
|
|
} else {
|
|
snprintf(tmp,sizeof(tmp),"((float)%.8g)",
|
|
(double)va_arg(argv,double));
|
|
}
|
|
bbCat(buf,tmp);
|
|
break;
|
|
case 's':
|
|
text = va_arg(argv,char*);
|
|
bbCat(buf,text);
|
|
break;
|
|
case 'c':
|
|
c = va_arg(argv,int);
|
|
bbAppend(buf,(char)c);
|
|
break;
|
|
default:
|
|
PANIC1("vbbprintf: unknown specifier: %c",(char)c);
|
|
}
|
|
break;
|
|
default:
|
|
bbAppend(buf,c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
bbprintf(Bytebuffer* buf, const char *fmt, ...)
|
|
{
|
|
va_list argv;
|
|
va_start(argv,fmt);
|
|
vbbprintf(buf,fmt,argv);
|
|
va_end(argv);
|
|
}
|
|
|
|
void
|
|
bbprintf0(Bytebuffer* buf, const char *fmt, ...)
|
|
{
|
|
va_list argv;
|
|
va_start(argv,fmt);
|
|
bbClear(buf);
|
|
vbbprintf(buf,fmt,argv);
|
|
va_end(argv);
|
|
}
|
|
|
|
void
|
|
codeprintf(const char *fmt, ...)
|
|
{
|
|
va_list argv;
|
|
va_start(argv,fmt);
|
|
vbbprintf(codebuffer,fmt,argv);
|
|
va_end(argv);
|
|
}
|
|
|
|
NCConstant*
|
|
emptycompoundconst(int lineno, NCConstant* c)
|
|
{
|
|
ASSERT(c != NULL);
|
|
c->lineno = lineno;
|
|
c->nctype = NC_COMPOUND;
|
|
c->value.compoundv = builddatalist(0);
|
|
c->filled = 0;
|
|
return c;
|
|
}
|
|
|
|
NCConstant*
|
|
emptystringconst(int lineno, NCConstant* c)
|
|
{
|
|
ASSERT(c != NULL);
|
|
c->lineno = lineno;
|
|
c->nctype = NC_STRING;
|
|
c->value.stringv.len = 0;
|
|
c->value.stringv.stringv = NULL;
|
|
c->filled = 0;
|
|
return c;
|
|
}
|
|
|
|
#define INDENTMAX 256
|
|
static char* dent = NULL;
|
|
|
|
char*
|
|
indented(int n)
|
|
{
|
|
char* indentation;
|
|
if(dent == NULL) {
|
|
dent = (char*)ecalloc(INDENTMAX+1);
|
|
memset((void*)dent,' ',INDENTMAX);
|
|
dent[INDENTMAX] = '\0';
|
|
}
|
|
if(n*4 >= INDENTMAX) n = INDENTMAX/4;
|
|
indentation = dent+(INDENTMAX - 4*n);
|
|
return indentation;
|
|
}
|
|
|
|
void
|
|
dlextend(Datalist* dl)
|
|
{
|
|
size_t newalloc;
|
|
NCConstant* newdata = NULL;
|
|
newalloc = (dl->alloc > 0?2*dl->alloc:2);
|
|
newdata = (NCConstant*)ecalloc(newalloc*sizeof(NCConstant));
|
|
if(dl->length > 0)
|
|
memcpy(newdata,dl->data,sizeof(NCConstant)*dl->length);
|
|
dl->alloc = newalloc;
|
|
nullfree(dl->data);
|
|
dl->data = newdata;
|
|
}
|
|
|
|
|
|
Datalist*
|
|
builddatalist(int initial)
|
|
{
|
|
Datalist* ci;
|
|
if(initial <= 0) initial = DATALISTINIT;
|
|
initial++; /* for header*/
|
|
ci = (Datalist*)ecalloc(sizeof(Datalist));
|
|
memset((void*)ci,0,sizeof(Datalist)); /* only clear the hdr*/
|
|
ci->data = (NCConstant*)ecalloc(sizeof(NCConstant)*initial);
|
|
memset((void*)ci->data,0,sizeof(NCConstant)*initial);
|
|
ci->alloc = initial;
|
|
ci->length = 0;
|
|
return ci;
|
|
}
|
|
|
|
void
|
|
dlappend(Datalist* dl, NCConstant* constant)
|
|
{
|
|
if(dl->length >= dl->alloc)
|
|
dlextend(dl);
|
|
if(constant == NULL) constant = &nullconstant;
|
|
dl->data[dl->length++] = *constant;
|
|
}
|
|
|
|
NCConstant
|
|
builddatasublist(Datalist* dl)
|
|
{
|
|
|
|
NCConstant d;
|
|
d.nctype = NC_COMPOUND;
|
|
d.lineno = (dl->length > 0?dl->data[0].lineno:0);
|
|
d.value.compoundv = dl;
|
|
d.filled = 0;
|
|
return d;
|
|
|
|
}
|
|
|
|
/*! Function to free an allocated datalist.
|
|
|
|
This function is used to free an individual datalist
|
|
object. It is possible, hypothetically, that a
|
|
datalist will appear in the middle of a set of datalists,
|
|
in which case we'll need to determine that and shuffle around
|
|
'next' pointers. For the time being, this assumes that
|
|
we are freeing a datalist which was allocated locally
|
|
and must be discarded.
|
|
|
|
Using this function instead of an inline 'free' just in
|
|
case we ever want to extend it, we won't have to go back
|
|
and re-write a bunch of stuff. I hope.
|
|
|
|
@param dlist Pointer to datalist object being freed.
|
|
|
|
*/
|
|
void dlfree(Datalist **dlist) {
|
|
|
|
if(*dlist) free(*dlist);
|
|
dlist = NULL;
|
|
}
|
|
|
|
|