Files
2026-08-20 20:28:40 +01:00

671 lines
16 KiB
C

/*
* Simple ASCII file parsing object.
* Used as a base for the CGATS.5 and IT8.7 family file I/O class
*/
/*
* Version 2.05
*
* Author: Graeme W. Gill
* Date: 20/12/95
*
* Copyright 1996, 2002 Graeme W. Gill
* All rights reserved.
*
* This material is licensed with an "MIT" free use license:-
* see the License4.txt file in this directory for licensing details.
*/
/*
TTBD:
Should skip UTF-8 BOM (0xef 0xbb 0xbf)
*/
#define _PARS_C_ /* Turn on implimentation code */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <time.h>
#ifdef __sun
#include <unistd.h>
#endif
#ifdef _MSC_VER
#define vsnprintf _vsnprintf
#define snprintf _snprintf
#endif
#ifdef STANDALONE_TEST
extern void error(const char *fmt, ...), warning(const char *fmt, ...);
#endif
#include "pars.h"
#undef DEBUG /* Print each token returned */
static void del_parse(parse *p);
static int read_line(parse *p);
static void reset_del(parse *p);
static void add_del(struct _parse *p, char *t,
char *nr, char *c, char *q);
static char *get_token(parse *p);
/* Open the file, allocate and initialize the parse structure */
/* Return pointer to parse structure. Return NULL on error */
parse *
new_parse_al(
cgatsAlloc *al, /* Allocator object */
cgatsFile *fp /* File to read from */
) {
parse *p;
if ((p = (parse *) al->calloc(al, sizeof(parse), 1)) == NULL) {
return NULL;
}
p->al = al; /* Heap allocator */
p->fp = fp;
p->b = NULL; /* Init line buffer */
p->bs = 0;
p->bo = 0;
p->tb = NULL; /* Init token buffer */
p->tbs = 0;
p->to = 0;
p->line = 0;
p->token = 0;
p->ltflag = 0;
p->q = 0;
p->e.c = 0;
p->e.m[0] = '\000';
/* Reset the parsing delimiters */
reset_del(p);
/* Set default pointers to methods */
p->del = del_parse;
p->read_line = read_line;
p->reset_del = reset_del;
p->add_del = add_del;
p->get_token = get_token;
return p;
}
/* new_parse() with default malloc allocator is in parsstd.c */
#ifndef SEPARATE_STD
#define COMBINED_STD
#include "parsstd.c"
#undef COMBINED_STD
#endif /* SEPARATE_STD */
/* --------------------------------------------- */
/* size_t versions of saturating arithmatic */
#ifndef SIZE_MAX
# define SIZE_MAX ((size_t)(-1))
#endif
/* a * b */
static size_t ssat_mul(size_t a, size_t b) {
if (a == 0 || b == 0)
return 0;
if (a > (SIZE_MAX/b))
return SIZE_MAX;
else
return a * b;
}
/* --------------------------------------------- */
/* Memory image cgatsFile compatible class */
/* Buffer is assumed to have been allocated by the given allocator, */
/* and will be expanded on write. */
/* Get the size of the file (Only valid for memory file). */
static size_t cgatsFileMem_get_size(cgatsFile *pp) {
cgatsFileMem *p = (cgatsFileMem *)pp;
return p->end - p->start;
}
/* Set current position to offset. Return 0 on success, nz on failure. */
static int cgatsFileMem_seek(
cgatsFile *pp,
unsigned int offset
) {
cgatsFileMem *p = (cgatsFileMem *)pp;
unsigned char *np;
np = p->start + offset;
if (np < p->start || np >= p->end)
return 1;
p->cur = np;
return 0;
}
/* Read count items of size length. Return number of items successfully read. */
static size_t cgatsFileMem_read(
cgatsFile *pp,
void *buffer,
size_t size,
size_t count
) {
cgatsFileMem *p = (cgatsFileMem *)pp;
size_t len;
len = ssat_mul(size, count);
if (len > (size_t)(p->end - p->cur)) { /* Too much */
if (size > 0)
count = (p->end - p->cur)/size;
else
count = 0;
}
len = size * count;
if (len > 0)
memmove(buffer, p->cur, len);
p->cur += len;
return count;
}
/* Read a character */
static int cgatsFileMem_getch(
cgatsFile *pp
) {
cgatsFileMem *p = (cgatsFileMem *)pp;
int c;
if (p->cur < p->start || p->cur >= p->end)
return EOF;
c = (int)*p->cur;
p->cur++;
return c;
}
/* Expand the memory buffer file to hold up to pointer ep */
/* Don't expand if realloc fails */
static void cgatsFileMem_filemem_resize(cgatsFileMem *p, unsigned char *ep) {
size_t na, co, ce;
unsigned char *nstart;
/* No need to realloc */
if (ep <= p->aend) {
return;
}
co = p->cur - p->start; /* Current offset */
ce = p->end - p->start; /* Current end */
na = ep - p->start; /* new allocatd size */
/* Round new allocation up */
if (na <= 1024)
na += 1024;
else
na += 4096;
if ((nstart = p->al->realloc(p->al, p->start, na)) != NULL) {
p->start = nstart;
p->cur = nstart + co;
p->end = nstart + ce;
p->aend = nstart + na;
}
}
/* write count items of size length. Return number of items successfully written. */
static size_t cgatsFileMem_write(
cgatsFile *pp,
void *buffer,
size_t size,
size_t count
) {
cgatsFileMem *p = (cgatsFileMem *)pp;
size_t len;
len = ssat_mul(size, count);
if (len > (size_t)(p->end - p->cur)) /* Try and expand buffer */
cgatsFileMem_filemem_resize(p, p->start + len);
if (len > (size_t)(p->end - p->cur)) {
if (size > 0)
count = (p->end - p->cur)/size;
else
count = 0;
}
len = size * count;
if (len > 0)
memmove(p->cur, buffer, len);
p->cur += len;
if (p->end < p->cur)
p->end = p->cur;
return count;
}
/* do a printf */
static int cgatsFileMem_printf(
cgatsFile *pp,
const char *format,
...
) {
int rv;
va_list args;
cgatsFileMem *p = (cgatsFileMem *)pp;
int alen, len;
va_start(args, format);
rv = 1;
alen = 100; /* Initial allocation for printf */
cgatsFileMem_filemem_resize(p, p->cur + alen);
/* We have to use the available printf functions to resize the buffer if needed. */
for (;rv != 0;) {
/* vsnprintf() either returns -1 if it doesn't fit, or */
/* returns the size-1 needed in order to fit. */
len = vsnprintf((char *)p->cur, (p->aend - p->cur), format, args);
if (len > -1 && ((p->cur + len +1) <= p->aend)) /* Fitted in current allocation */
break;
if (len > -1) /* vsnprintf returned needed size-1 */
alen = len+2; /* (In case vsnprintf returned 1 less than it needs) */
else
alen *= 2; /* We just have to guess */
/* Attempt to resize */
cgatsFileMem_filemem_resize(p, p->cur + alen);
/* If resize failed */
if ((p->aend - p->cur) < alen) {
rv = 0;
break;
}
}
if (rv != 0) {
/* Figure out where end of printf is */
len = strlen((char *)p->cur); /* Length excluding nul */
p->cur += len;
if (p->cur > p->end)
p->end = p->cur;
rv = len;
}
va_end(args);
return rv;
}
/* flush all write data out to secondary storage. Return nz on failure. */
static int cgatsFileMem_flush(
cgatsFile *pp
) {
return 0;
}
/* Return the memory buffer. Error if not cgatsFileMem */
static int cgatsFileMem_get_buf(
cgatsFile *pp,
unsigned char **buf,
size_t *len
) {
cgatsFileMem *p = (cgatsFileMem *)pp;
if (buf != NULL)
*buf = p->start;
if (len != NULL)
*len = p->end - p->start;
return 0;
}
/* return the filename */
static char *cgatsFileMem_fname(
cgatsFile *pp
) {
// cgatsFileMem *p = (cgatsFileMem *)pp;
/* Memory doesn't have a name */
return "**Mem**";
}
/* we're done with the file object, return nz on failure */
static int cgatsFileMem_delete(
cgatsFile *pp
) {
cgatsFileMem *p = (cgatsFileMem *)pp;
cgatsAlloc *al = p->al;
int del_al = p->del_al;
if (p->del_buf) /* Free the memory buffer */
al->free(al, p->start);
al->free(al, p); /* Free object */
if (del_al) /* We are responsible for deleting allocator */
al->del(al);
return 0;
}
/* Create a memory image file access class with allocator */
/* Buffer is used as is. */
cgatsFile *new_cgatsFileMem_a(
void *base, /* Pointer to base of memory buffer */
size_t length, /* Number of bytes in buffer */
cgatsAlloc *al /* heap allocator */
) {
cgatsFileMem *p;
if ((p = (cgatsFileMem *) al->calloc(al, 1, sizeof(cgatsFileMem))) == NULL) {
return NULL;
}
p->al = al; /* Heap allocator */
p->get_size = cgatsFileMem_get_size;
p->seek = cgatsFileMem_seek;
p->read = cgatsFileMem_read;
p->getch = cgatsFileMem_getch;
p->write = cgatsFileMem_write;
p->gprintf = cgatsFileMem_printf;
p->flush = cgatsFileMem_flush;
p->get_buf = cgatsFileMem_get_buf;
p->fname = cgatsFileMem_fname;
p->del = cgatsFileMem_delete;
p->start = (unsigned char *)base;
p->cur = p->start;
p->aend = p->end = p->start + length;
return (cgatsFile *)p;
}
/* Create a memory image file access class with given allocator */
/* and delete base when cgatsFile is deleted. */
cgatsFile *new_cgatsFileMem_ad(void *base, size_t length, cgatsAlloc *al) {
cgatsFile *fp;
if ((fp = new_cgatsFileMem_a(base, length, al)) != NULL) {
((cgatsFileMem *)fp)->del_buf = 1;
}
return fp;
}
/* --------------------------------------------- */
/* Free up the structure (doesn't close the file) */
static void
del_parse(parse *p) {
cgatsAlloc *al = p->al;
int del_al = p->del_al;
if (p->b != NULL)
al->free(al, p->b);
if (p->tb != NULL)
al->free(al, p->tb);
al->free(al, p);
if (del_al) /* We are responsible for deleting allocator */
al->del(al);
}
/* Read the next line from the file into the line buffer. */
/* Return 0 if the read fails due to reaching EOF before */
/* putting anything in the buffer. */
/* Return -1 if there was some other sort of failure, */
/* and the error message in parse will be valid. */
static int
read_line(parse *p) {
int c;
p->bo = 0; /* Reset pointer to the start of the line buffer */
p->q = 0; /* Reset quoted flag */
p->e.c = 0; /* Reset error status */
p->e.m[0] = '\000';
do {
if ((c = p->fp->getch(p->fp)) == EOF) {
if (p->bo == 0) { /* If there is nothing in the buffer */
p->line = 0;
#ifdef DEBUG
printf("pars: read_line() got EOF\n");
#endif
return 0;
}
c = 0; /* Finish the line */
}
if (p->ltflag == 1) { /* Finished last line on '\r' */
p->ltflag = 0;
if (c == '\n') {
if (p->q == 0)
continue; /* Ignore the following '\n' */
else
p->line--; /* Undo double increment due to \n after \r */
}
/* else fall through and use character */
} else if (p->ltflag == 2) { /* Finished last line on comment character */
/* Suck up chars till the start of the next line */
if (c == '\r')
p->ltflag = 1;
else if (c == '\n')
p->ltflag = 0;
continue; /* Ignore characters untill we get to the end of the line */
}
if (c == '\r') {
p->line++; /* Increment line number */
p->ltflag = 1; /* Remember to allow 1 of '\n' before next line */
if (p->q == 0)
c = 0; /* Finish the line */
} else if (p->q == 0 && (p->delf[c] & PARS_COMM) != 0) { /* Hit a comment */
p->line++; /* Increment line number */
p->ltflag = 2; /* Remember to flush all chars up to end of line */
c = 0; /* Finish the line */
} else if (c == '\n') {
p->line++; /* Increment line number */
if (p->q == 0)
c = 0; /* Finish the the line */
}
/* Deal with starting/stopping a quoted section */
if ((p->delf[c] & PARS_QUOTE) != 0) {
if (p->q == 0) /* We weren't in a quoted section */
p->q = c; /* Start of quoted section */
else if (c == p->q) /* If matching quote */
p->q = 0; /* End quoted section */
}
/* Can put the character in the buffer now */
if (p->bo == p->bs) { /* Run out of buffer space */
p->bs = (p->bs + 100) * 2; /* Expand line buffer size */
if ((p->b = (char *) p->al->realloc(p->al, p->b, p->bs)) == NULL) {
sprintf(p->e.m,"parse.read_line(), realloc failed!");
return (p->e.c = -1);
}
}
p->b[p->bo++] = c; /* Stash character away */
} while (c != 0); /* Null means we've done the end of the line */
p->to = 0; /* Reset token pointer to the start of the line buffer */
p->q = 0; /* Reset quoted flag */
#ifdef DEBUG
printf("pars: read_line() got buffer '%s'\n",p->b);
#endif
return 1;
}
/* Reset the delimiter character set */
static void
reset_del(parse *p) {
int i;
for (i = 0; i < 256; i++)
p->delf[i] = 0;
p->delf[0] = PARS_TERM;
}
/* Add to the parsing characters */
static void
add_del(
parse *p, /* Parse structure */
char *t, /* Terminators */
char *nr, /* Not Read */
char *c, /* Comment start */
char *q) /* Quote characters */
{
int i;
if (t != NULL)
for (i = 0; t[i] != '\000'; i++)
p->delf[(int)t[i]] |= PARS_TERM;
if (nr != NULL)
for (i = 0; nr[i] != '\000'; i++)
p->delf[(int)nr[i]] |= PARS_SKIP;
if (c != NULL)
for (i = 0; c[i] != '\000'; i++)
p->delf[(int)c[i]] |= PARS_COMM;
if (q != NULL)
for (i = 0; q[i] != '\000'; i++)
p->delf[(int)q[i]] |= PARS_QUOTE;
}
/* Using the current token delimiter table and the current line, */
/* parse it from the current location and return a pointer to the */
/* null terminated token. Return NULL if there is no token found */
/* set the parse err and e.c to non-zero if there was some other error */
static char *
get_token(parse *p) {
int tbo = 0; /* Token buffer offset */
int term = 0; /* flag to trigger token termination */
char c;
p->e.c = 0; /* Reset error status */
p->e.m[0] = '\000';
if (p->b == NULL) {
#ifdef DEBUG
printf("pars: read_token() NULL buffe\n");
#endif
return NULL;
}
p->token++; /* Increment token number */
p->q = 0;
do {
if (term)
c = '\000'; /* end token */
else if ((c = p->b[p->to++]) == '\000') /* Fetch next token */
p->to--; /* Safety - don't pass end */
/* Deal with starting/stopping a quoted section */
if ((p->delf[c] & PARS_QUOTE) != 0) {
if (p->q == 0) /* We weren't in a quoted section */
p->q = c; /* Start of quoted section */
else if (c == p->q) /* If matching quote */
p->q = 0; /* End quoted section */
}
if (tbo == p->tbs) { /* Run out of buffer space */
p->tbs = (p->tbs + 100) * 2; /* Expand token buffer size */
if ((p->tb = (char *) p->al->realloc(p->al, p->tb, p->tbs)) == NULL) {
sprintf(p->e.m,"parse.get_token(), realloc failed!");
p->e.c = -1;
return NULL;
}
}
if ((p->q != 0 && (p->q != c || (p->delf[c] & PARS_SKIP) == 0))
/* If quoted, store if trigger quite is not being skipped */
|| (!(tbo == 0 && (p->delf[c] & PARS_TERM) != 0 && (p->delf[c] & PARS_SKIP) != 0)
/* Skip initial non-reader terminators */
&& (p->delf[c] & PARS_SKIP) == 0)) /* Skip non-readers */
p->tb[tbo++] = c; /* Stash character away in token */
if (p->q == 0 /* If not quoted and if token is non-empty and we have a terminator */
&& tbo != 0 && (p->delf[c] & PARS_TERM) != 0)
term = 1; /* Finish token off next time around */
} while (c != '\000'); /* Null means we've done the end of the token */
p->q = 0;
if (tbo <= 1) {
p->token = 0;
#ifdef DEBUG
printf("pars: read_token() got nothing useful\n");
#endif
return NULL; /* Haven't read anything useful */
}
#ifdef DEBUG
printf("pars: read_token() returning '%s'\n",p->tb);
#endif
return p->tb;
}
/* ========================================================== */
/* Test code */
#ifdef STANDALONE_TEST
int
main() {
int rc;
parse *pp;
cgatsFile *fp;
if ((fp = new_cgatsFileStd_name("test.txt", "r")) == NULL)
error("Failed to open file 'test.txt'");
if ((pp = new_parse(fp)) == NULL)
error("Failed to create parse object with file 'test.txt'");
/* Setup our token parsing charaters */
pp->add_del(pp, " ,\t"," ,\t", "#", "\"");
for (;;) {
char *tp;
if ((rc = pp->read_line(pp)) == -1)
error("%s",pp->e.m);
if (rc == 0)
break;
printf("Line %d = '%s'\n",pp->line,pp->b);
do {
tp = pp->get_token(pp);
if (pp->e.c != 0)
error("%s",pp->e.m);
if (tp != NULL)
printf("Token %d = '%s'\n",pp->token,tp);
} while (tp != NULL);
}
printf("End of File\n");
pp->del(pp); /* Clean up */
fp->del(fp); /* Close the file */
return 0;
}
/* Basic printf type error() and warning() routines for standalone test */
void
error(const char *fmt, ...) {
va_list args;
fprintf(stderr,"chart: Error - ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
exit (-1);
}
void
warning(const char *fmt, ...) {
va_list args;
fprintf(stderr,"chart: Warning - ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
#endif /* STANDALONE_TEST */
/* ---------------------------------------------------------- */