#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "file_buffer.h"
#include "cmpp.h"
#define N_BYTE_FILEBUF_INIT_DFLT 16384
static int fb_fill(FILEBUF *p_fb);
static int fbget_quoted_unescaped_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj);
static size_t fb_make_space_at_end(FILEBUF *p_fb);
static int fbget_quoted_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj);
static int fbget_quoted_escaped_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj);
static int fbget_unquoted_string(FILEBUF *p_fb,
unsigned int n_type_wanted, FBTYPE *p_type_wanted,
FBTYPE *p_type_found, FBOBJ *p_fbobj);
static int fb_return_obj(FILEBUF *p_fb,
unsigned int n_type_wanted, FBTYPE *p_type_wanted,
FBTYPE *p_type_found, FBOBJ *p_fbobj);
static int fb_return_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj);
static int fb_skip_to_eol(FILEBUF *p_fb);
static int fb_skip_whitespace(FILEBUF *p_fb);
*
* Parameters
* filename: Name of file to be opened for reading
* n_byte_buf_init: Intial buffer size. May be 0 for default size
*
* Return values
* NULL: Error occurred. The value of errno will provide more details
* Otherwise an initialized structure
*/
FILEBUF *fbopen(const char *filename, size_t n_byte_buf_init)
{
int xrc = 0;
FILEBUF *p_fb = (FILEBUF *) NULL;
if (n_byte_buf_init == 0) {
n_byte_buf_init = N_BYTE_FILEBUF_INIT_DFLT;
}
if ((p_fb = (FILEBUF *) malloc(sizeof *p_fb)) == (FILEBUF *) NULL) {
xrc = -1;
goto EXITPOINT;
}
p_fb->is_eof = false;
p_fb->f_skip_to_eol = false;
p_fb->fp = (FILE *) NULL;
p_fb->p_buf = (char *) NULL;
if ((p_fb->p_buf = (char *) malloc(n_byte_buf_init)) == (char *) NULL) {
xrc = -1;
goto EXITPOINT;
}
p_fb->n_byte_buf_alloc = n_byte_buf_init;
p_fb->p_data_end = p_fb->p_data_cur = p_fb->p_buf;
p_fb->p_buf_end = p_fb->p_buf + n_byte_buf_init;
* handle all EOL chars, so any translations by the OS are almost
* pure overhead. Also, not converting ensures that if fread returns
* fewer than the requested number of chars, that read was to the
* end of the file (if not an error). Otherwise an additional read
* getting a size of 0 would be required. */
if ((p_fb->fp = fopen(filename, "rb")) == (FILE *) NULL) {
xrc = -1;
goto EXITPOINT;
}
EXITPOINT:
if (xrc != 0) {
if (p_fb != (FILEBUF *) NULL) {
const int errno_save = errno;
* changes it */
(void) fbclose(p_fb);
errno = errno_save;
p_fb = (FILEBUF *) NULL;
}
}
return p_fb;
}
*
* Parameter
* p_fb: The address of the FILEBUF to free. This argument may be NULL.
*
* Return values
* 0: OK
* EOF: Error closing file. Details can be found using errno.
*/
int fbclose(FILEBUF *p_fb)
{
if (p_fb == (FILEBUF *) NULL) {
return 0;
}
int xrc = 0;
{
void *p;
if ((p = p_fb->p_buf) != NULL) {
free(p);
}
}
{
FILE *fp;
if ((fp = p_fb->fp) != (FILE *) NULL) {
xrc = fclose(fp);
}
}
free(p_fb);
return xrc;
}
* type.
*
* Parameters
* p_fb: FILEBUF pointer initialized using fbopen()
* n_type_wanted: number of desired type conversions for data from highest
* priority to lowest.
* p_type_wanted: Desired type conversions for data from highest priority
* to lowest.
* p_type_found: Address to receive the type of the data obtained
* p_fbobj: Address of an FBOBJ structure to receive the data
*
* Return codes
* +1: EOF reached
* 0: Normal return
* -1: Error. Use errno for further details.
*
* Remarks
* Type BUF_TYPE_STRING is always implicitly added to the list of wanted
* types as the final choice, which any data will satisfy
*
* A string may be double-quoted. In this case the quotes are not supplied
* to the caller as part of the data. Double-quoting ensures that a string
* will not be converted to any other type. Within double quotes, a double
* qoute and a backslash are escaped by a backslash, and a final unescaped
* double quote is impilcitly added if EOF is reached when scanning for a
* closing quote.
*
* A "*" or a "#" not within a quoted expression begins a comment that
* extends to the end of the line.
*
* When called p_fb has data from the last get or it is the first call.
*
* Return Codes
* +1: EOF
* 0: Normal
* -1: Error
*/
int fbget(FILEBUF *p_fb,
unsigned int n_type_wanted, FBTYPE *p_type_wanted,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
if (p_fb->is_eof && p_fb->p_data_cur == p_fb->p_data_end) {
return +1;
}
p_fb->p_obj_start = (char *) NULL;
* the last call to fbget */
if (p_fb->f_skip_to_eol) {
const int rc = fb_skip_to_eol(p_fb);
if (rc != 0) {
return rc;
}
}
{
const int rc = fb_skip_whitespace(p_fb);
if (rc != 0) {
return rc;
}
}
if (*p_fb->p_data_cur == '"') {
return fbget_quoted_string(p_fb, p_type_found, p_fbobj);
}
return fbget_unquoted_string(p_fb, n_type_wanted, p_type_wanted,
p_type_found, p_fbobj);
}
* to the quote starting the quoted string. On return it points to the first
* character after the current item or equals p_fb->p_data_end if the
* current item extens to the end of the current data string. */
static int fbget_quoted_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
if (++p_fb->p_data_cur == p_fb->p_data_end) {
const int rc = fb_fill(p_fb);
if (rc != 0) {
if (rc < 0) {
return -1;
}
* closing quote, so add an implicit closing quote, i.e., end
* the string to form "".
*
* Since the object was started at the beginning of the buffer
* and the buffer has at leat 1 byte a NULL to create the
* string "" can be written here */
*(p_fb->p_obj_end = p_fb->p_obj_start = p_fb->p_buf) = '\0';
return fb_return_string(p_fb, p_type_found, p_fbobj);
}
}
p_fb->p_obj_start = p_fb->p_data_cur;
* is found to be true */
return fbget_quoted_unescaped_string(p_fb, p_type_found, p_fbobj);
}
* entry. If an escape is found, processing continues as an escaped string */
int fbget_quoted_unescaped_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
char *p_data_cur = p_fb->p_data_cur;
char *p_data_end = p_fb->p_data_end;
for ( ; ; ) {
for ( ; p_data_cur != p_data_end; ++p_data_cur) {
const char ch_cur = *p_data_cur;
if (ch_cur == '"') {
*(p_fb->p_obj_end = p_data_cur) = '\0';
p_fb->p_data_cur = p_data_cur + 1;
return fb_return_string(p_fb, p_type_found, p_fbobj);
}
if (ch_cur == '\\') {
* left by the escape character */
p_fb->p_data_cur = p_data_cur;
return fbget_quoted_escaped_string(p_fb,
p_type_found, p_fbobj);
}
}
p_fb->p_data_cur = p_data_cur;
const int rc = fb_fill(p_fb);
if (rc != 0) {
if (rc < 0) {
return -1;
}
* closing quote, i.e., end the string. Since fb_fill()
* did not return -1, there is at least 1 byte at the end of
* the buffer where the read would have gone. */
*(p_fb->p_obj_end = p_fb->p_data_cur) = '\0';
return fb_return_string(p_fb, p_type_found, p_fbobj);
}
p_data_cur = p_fb->p_data_cur;
p_data_end = p_fb->p_data_end;
}
}
* entry */
static int fbget_quoted_escaped_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
char *p_data_src = p_fb->p_data_cur;
char *p_data_dst = p_data_src;
char *p_data_end = p_fb->p_data_end;
bool f_escape_in_progress = false;
for ( ; ; ) {
for ( ; p_data_src != p_data_end; ++p_data_src) {
const char ch_cur = *p_data_src;
if (f_escape_in_progress) {
f_escape_in_progress = false;
*p_data_dst++ = ch_cur;
}
else {
if (ch_cur == '"') {
p_fb->p_data_cur = p_data_src + 1;
*(p_fb->p_obj_end = p_data_dst) = '\0';
return fb_return_string(p_fb, p_type_found, p_fbobj);
}
if (ch_cur == '\\') {
f_escape_in_progress = true;
}
else {
*p_data_dst++ = ch_cur;
}
}
}
p_fb->p_data_end = p_fb->p_data_cur = p_data_dst;
* avoid the moves */
if (!f_escape_in_progress) {
return fbget_quoted_unescaped_string(p_fb, p_type_found,
p_fbobj);
}
const int rc = fb_fill(p_fb);
if (rc != 0) {
if (rc < 0) {
return -1;
}
* closing quote, i.e., end the string. Since fb_fill()
* did not return -1, there is at least 1 byte at the end of
* the buffer where the read would have gone. */
*(p_fb->p_obj_end = p_fb->p_data_cur) = '\0';
return fb_return_string(p_fb, p_type_found, p_fbobj);
}
p_data_dst = p_data_src = p_fb->p_data_cur;
p_data_end = p_fb->p_data_end;
}
}
static int fbget_unquoted_string(FILEBUF *p_fb,
unsigned int n_type_wanted, FBTYPE *p_type_wanted,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
p_fb->p_obj_start = p_fb->p_data_cur;
static const signed char p_map[1 << CHAR_BIT] = {
[(unsigned char ) ' '] = (signed char) +1,
[(unsigned char ) '\t'] = (signed char) +1,
[(unsigned char ) '\n'] = (signed char) +1,
[(unsigned char ) '\r'] = (signed char) +1,
[(unsigned char ) '\v'] = (signed char) +1,
[(unsigned char ) '\f'] = (signed char) +1,
[(unsigned char ) '*'] = (signed char) -1,
[(unsigned char ) '#'] = (signed char) -1
};
char *p_data_cur = p_fb->p_data_cur;
char *p_data_end = p_fb->p_data_end;
for ( ; ; ) {
for ( ; p_data_cur != p_data_end; ++p_data_cur) {
const char ch_cur = *p_data_cur;
const signed char map_cur = p_map[(unsigned char) ch_cur];
if (map_cur != 0) {
*(p_fb->p_obj_end = p_data_cur) = '\0';
p_fb->p_data_cur = p_data_cur + 1;
p_fb->f_skip_to_eol = map_cur < 0;
return fb_return_obj(p_fb, n_type_wanted, p_type_wanted,
p_type_found, p_fbobj);
}
}
p_fb->p_data_cur = p_data_cur;
const int rc = fb_fill(p_fb);
if (rc != 0) {
if (rc < 0) {
return -1;
}
* closing quote, i.e., end the string. Since fb_fill()
* did not return -1, there is at least 1 byte at the end of
* the buffer where the read would have gone. */
*(p_fb->p_obj_end = p_fb->p_data_cur) = '\0';
return fb_return_obj(p_fb, n_type_wanted, p_type_wanted,
p_type_found, p_fbobj);
}
p_data_cur = p_fb->p_data_cur;
p_data_end = p_fb->p_data_end;
}
}
* [start current object, current position) to the beginning of the buffer.
*
* If there is no space left at the end of the buffer after the move (so
* that the move did not occur and data extends to the end), the buffer is
* doubled in size. In either case, the end of the buffer is filled with
* data read from the file. */
static int fb_fill(FILEBUF *p_fb)
{
if (p_fb->is_eof) {
return +1;
}
* enlarge the buffer if still no space. Returned value is bytes
* available at the end of the buffer at p_data_end */
const size_t n_byte_read = fb_make_space_at_end(p_fb);
if (n_byte_read == 0) {
return -1;
}
const size_t n_got = fread(p_fb->p_data_end, 1, n_byte_read, p_fb->fp);
if (n_got < n_byte_read) {
if (ferror(p_fb->fp)) {
return -1;
}
p_fb->is_eof = true;
if (n_got == 0) {
return +1;
}
}
p_fb->p_data_end += n_got;
return 0;
}
* to the front of the buffer and enlarging if there is still no room
*
* Return value: Number of spaces that are free at the end of p_buf on
* return. If 0, more space could not be obtained.
*/
static size_t fb_make_space_at_end(FILEBUF *p_fb)
{
const char * const p_obj_start = p_fb->p_obj_start;
const char * const p_src = p_obj_start == (char *) NULL ?
p_fb->p_data_cur : p_obj_start;
char * const p_dst = p_fb->p_buf;
if (p_dst != p_src) {
const size_t n = (size_t)(p_fb->p_data_end - p_src);
if (n > 0) {
(void) memmove(p_dst, p_src, n);
}
ptrdiff_t delta = p_src - p_dst;
p_fb->p_data_cur -= delta;
p_fb->p_data_end -= delta;
if (p_obj_start != (char *) NULL) {
p_fb->p_obj_start -= delta;
}
}
else {
if (p_fb->p_buf_end - p_fb->p_data_end == 0) {
const size_t n_alloc_orig = p_fb->n_byte_buf_alloc;
* force many reallocs and to have strings end at "hard"
* locations such as right before where a terminating null
* should be added to a string */
const size_t n_added = n_alloc_orig;
const size_t n_byte_buf_alloc_new = n_alloc_orig + n_added;
void * const p = realloc(p_fb->p_buf, n_byte_buf_alloc_new);
if (p == NULL) {
return 0;
}
ptrdiff_t delta = (char *) p - p_fb->p_buf;
p_fb->p_buf = (char *) p;
p_fb->p_buf_end = (char *) p + n_byte_buf_alloc_new;
p_fb->n_byte_buf_alloc = n_byte_buf_alloc_new;
p_fb->p_data_cur += delta;
p_fb->p_data_end += delta;
if (p_obj_start != (char *) NULL) {
p_fb->p_obj_start += delta;
}
}
}
return (size_t)(p_fb->p_buf_end - p_fb->p_data_end);
}
static int fb_skip_whitespace(FILEBUF *p_fb)
{
static const signed char p_map[1 << CHAR_BIT] = {
[(unsigned char ) ' '] = (signed char) +1,
[(unsigned char ) '\t'] = (signed char) +1,
[(unsigned char ) '\n'] = (signed char) +1,
[(unsigned char ) '\r'] = (signed char) +1,
[(unsigned char ) '\v'] = (signed char) +1,
[(unsigned char ) '\f'] = (signed char) +1,
[(unsigned char ) '*'] = (signed char) -1,
[(unsigned char ) '#'] = (signed char) -1
};
char *p_data_cur = p_fb->p_data_cur;
char *p_data_end = p_fb->p_data_end;
for ( ; ; ) {
for ( ; p_data_cur != p_data_end; ++p_data_cur) {
const char ch_cur = *p_data_cur;
const signed char map_cur = p_map[(unsigned char) ch_cur];
if (map_cur == 0) {
p_fb->p_data_cur = p_data_cur;
return 0;
}
if (map_cur == -1) {
p_fb->p_data_cur = p_data_cur + 1;
const int rc = fb_skip_to_eol(p_fb);
if (rc != 0) {
return rc;
}
* the character after the comment, which is a \n or \r.
* These characters are whitespace that will be skipped,
* so incrementing past it in the ++p_data_cur of the for()
* only skips a character that will be skipped anyhow.
* (A long comment to say that
* p_data_cur = p_fb->p_data_cur - 1 is not necessary.) */
p_data_cur = p_fb->p_data_cur;
p_data_end = p_fb->p_data_end;
}
}
p_fb->p_data_cur = p_data_cur;
const int rc = fb_fill(p_fb);
if (rc != 0) {
return rc;
}
p_data_cur = p_fb->p_data_cur;
p_data_end = p_fb->p_data_end;
}
}
static int fb_skip_to_eol(FILEBUF *p_fb)
{
char *p_data_cur = p_fb->p_data_cur;
char *p_data_end = p_fb->p_data_end;
for ( ; ; ) {
for ( ; p_data_cur != p_data_end; ++p_data_cur) {
const char ch_cur = *p_data_cur;
if (ch_cur == '\n' || ch_cur == '\r') {
p_fb->p_data_cur = p_data_cur;
return 0;
}
}
p_fb->p_data_cur = p_data_cur;
const int rc = fb_fill(p_fb);
if (rc != 0) {
return rc;
}
p_data_cur = p_fb->p_data_cur;
p_data_end = p_fb->p_data_end;
}
}
static int fb_return_obj(FILEBUF *p_fb,
unsigned int n_type_wanted, FBTYPE *p_type_wanted,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
const char * const p_obj_start = p_fb->p_obj_start;
const char * const p_obj_end = p_fb->p_obj_end;
* errno in this case. Aside from that, it can only be returned
* as a string anyhow. */
if (p_obj_start != p_obj_end) {
unsigned int i;
for (i = 0; i < n_type_wanted; ++i) {
FBTYPE type_cur = p_type_wanted[i];
errno = 0;
if (type_cur == BUF_TYPE_ULONG) {
char *p_end;
unsigned long val = strtoul(p_obj_start, &p_end, 10);
* for the end of the string rather than a NULL handles the
* case of an embedded NULL which the latter test would
* not */
if (errno == 0 && p_end == p_obj_end) {
*p_type_found = BUF_TYPE_ULONG;
p_fbobj->ulong_value = val;
return 0;
}
}
else if (type_cur == BUF_TYPE_LONG) {
char *p_end;
long val = strtol(p_obj_start, &p_end, 10);
if (errno == 0 && p_end == p_obj_end) {
*p_type_found = BUF_TYPE_LONG;
p_fbobj->long_value = val;
return 0;
}
}
else if (type_cur == BUF_TYPE_DOUBLE) {
char *p_end;
double val = strtod(p_obj_start, &p_end);
if (errno == 0 && p_end == p_obj_end) {
*p_type_found = BUF_TYPE_DOUBLE;
p_fbobj->dbl_value = val;
return 0;
}
}
else if (type_cur == BUF_TYPE_STRING) {
break;
}
else {
print_error("Unknown output data type %d is ignored.",
(int) type_cur);
}
}
}
* a string */
return fb_return_string(p_fb, p_type_found, p_fbobj);
}
static int fb_return_string(FILEBUF *p_fb,
FBTYPE *p_type_found, FBOBJ *p_fbobj)
{
const char *p_data_start =
p_fbobj->str_value.sz = p_fb->p_obj_start;
p_fbobj->str_value.n_char = (size_t)(p_fb->p_obj_end - p_data_start);
*p_type_found = BUF_TYPE_STRING;
return 0;
}