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_DIE
seems like a good bumper sticker\$\endgroup\$