I implemented a key:value format file parser in C, or something that comes close to it as there is no such thing as dynamically creating structs in C.
Assumptions: we are creating a C program that needs some config options (e.g. the number K of threads in a concurrent server that handles requests using a pool of K worker threads), and we want to have them read from a text file. The file contains a pair key:value on each line. In our program, we know in advance the name of the parameters and their type.
fileparser.h
#ifndef FILE_PARSER_H
#define FILE_PARSER_H
typedef struct _parser Parser;
Parser* parseFile(char* filename, char* delim);
void getValueFor(Parser* p, char* key, char* dest, char* defaultVal);
int testError(Parser* p);
void destroyParser(Parser* p);
#endif
fileparser.c
#include "fileparser.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "../scerrhand.h"
#define MAX_FILENAME_LEN 100
#define MAX_DELIM_LEN 1
#define MAX_KEY_LEN 50
#define MAX_VAL_LEN 200
struct _pair {
char key[MAX_KEY_LEN + 1];
char val[MAX_VAL_LEN + 1];
struct _pair* nextPtr;
};
typedef struct _parser {
char filename[MAX_FILENAME_LEN + 1];
char delim[MAX_DELIM_LEN + 1];
struct _pair* hPtr;
int err;
} Parser;
static int cmpKeys(char* k1, char* k2) {
return strcmp(k1, k2);
}
static void _push(struct _pair** hPtr, struct _pair* newPair) {
newPair->nextPtr = *hPtr;
*hPtr = newPair;
}
static int _parse(Parser* p, FILE* fp) {
char buf[BUFSIZ + 1];
struct _pair* kvPair;
size_t i = 0;
while (i++, fgets(buf, BUFSIZ, fp)) {
char* savePtr, * token;
kvPair = calloc(sizeof(struct _pair), 1);
if (!kvPair) {
return -1; // out of memory
}
// get key
token = strtok_r(buf, p->delim, &savePtr);
strncpy(kvPair->key, token, strlen(token));
// remove any trailing spaces after the key name
kvPair->key[strcspn(kvPair->key, " ")] = '\0';
// get value
token = strtok_r(NULL, p->delim, &savePtr);
if (!token) { // delim missing: invalid syntax
return i;
}
strncpy(kvPair->val, token, strlen(token));
// remove trailing newline
kvPair->val[strcspn(kvPair->val, "\n")] = '\0';
_push(&p->hPtr, kvPair);
}
return 0;
}
Parser* parseFile(char* filename, char* delim) {
FILE* fp;
Parser* p = calloc(sizeof(Parser), 1);
if (!p) {
return NULL;
}
strncpy(p->filename, filename, MAX_FILENAME_LEN);
strncpy(p->delim, delim, MAX_DELIM_LEN);
SYSCALL_OR_DIE_NULL(fp = fopen(filename, "r"));
// parse file and capture error code
p->err = _parse(p, fp);
fclose(fp);
return p;
}
int testError(Parser* p) {
return p->err;
}
void getValueFor(Parser* p, char* key, char* dest, char* defaultVal) {
struct _pair*
currPtr = p->hPtr,
* prevPtr = NULL,
** target = NULL; // address of the node to remove after consuming its value
while (currPtr && cmpKeys(key, currPtr->key)) {
prevPtr = currPtr;
currPtr = currPtr->nextPtr;
}
// copy value if key is found; otherwise copy default value
strncpy(dest, currPtr ? currPtr->val : defaultVal, MAX_VAL_LEN);
// pop node out of list
if (currPtr) {
target = prevPtr ? &prevPtr->nextPtr : &p->hPtr;
*target = currPtr->nextPtr;
free(currPtr);
}
}
void destroyParser(Parser* p) {
struct _pair* tmp;
while (p->hPtr) {
tmp = p->hPtr;
p->hPtr = p->hPtr->nextPtr;
free(tmp);
}
free(p);
p = NULL;
}
scerrhand.h
#ifndef SC_ERR_HAND_H
#define SC_ERR_HAND_H
#include <stdlib.h>
// makes a system call and exits if return value is not zero
#define SYSCALL_OR_DIE_NZ(s) if(s) { puts("System call failed with nonzero status"); exit(EXIT_FAILURE); }
// makes a system call and exits if return value is -1
#define SYSCALL_OR_DIE_NEG_ONE(s) if((s) == -1) { puts("System call failed with status -1"); exit(EXIT_FAILURE); }
// makes a system call and exits if return value is NULL
#define SYSCALL_OR_DIE_NULL(s) if((s) == NULL) { puts("System call returned NULL"); exit(EXIT_FAILURE); }
#endif
I'd like some feedback on design, implementation, and whatnot.
SYSCALL_OR_DIEseems like a good bumper sticker \$\endgroup\$