#include #include #include #include #ifndef CONFIG_BUFFER_BLOCK #define CONFIG_BUFFER_BLOCK 32 #endif struct ConfigDirective { Array *values; HashMap *children; }; struct ConfigParseResult { unsigned int ok : 1; union { size_t lineNumber; HashMap *confMap; } data; }; typedef enum ConfigToken { TOKEN_UNKNOWN, TOKEN_NAME, TOKEN_MACRO_ASSIGNMENT, TOKEN_VALUE, TOKEN_SEMICOLON, TOKEN_BLOCK_OPEN, TOKEN_BLOCK_CLOSE, TOKEN_MACRO, TOKEN_EOF } ConfigToken; typedef struct ConfigParserState { FILE *stream; unsigned int line; char *token; size_t tokenSize; size_t tokenLen; ConfigToken tokenType; HashMap *macroMap; } ConfigParserState; unsigned int ConfigParseResultOk(ConfigParseResult *result) { return result ? result->ok : 0; } size_t ConfigParseResultLineNumber(ConfigParseResult *result) { return result && !result->ok ? result->data.lineNumber : 0; } HashMap * ConfigParseResultGet(ConfigParseResult *result) { return result && result->ok ? result->data.confMap : NULL; } void ConfigParseResultFree(ConfigParseResult *result) { /* * Note that if the parse was valid, the hash map * needs to be freed separately. */ free(result); } Array * ConfigValuesGet(ConfigDirective *directive) { return directive ? directive->values : NULL; } HashMap * ConfigChildrenGet(ConfigDirective *directive) { return directive ? directive->children : NULL; } /* * Takes a void pointer because it is only used with * HashMapIterate(), which requires a pointer to a function * that takes a void pointer. */ static void ConfigDirectiveFree(void *ptr) { ConfigDirective *directive = ptr; size_t i; if (!directive) { return; } for (i = 0; i < ArraySize(directive->values); i++) { free(ArrayGet(directive->values, i)); } ArrayFree(directive->values); ConfigFree(directive->children); free(directive); } void ConfigFree(HashMap *conf) { HashMapIterate(conf, ConfigDirectiveFree); HashMapFree(conf); } static ConfigParserState * ConfigParserStateCreate(FILE * stream) { ConfigParserState *state = malloc(sizeof(ConfigParserState)); if (!state) { return NULL; } state->macroMap = HashMapCreate(); if (!state->macroMap) { free(state); return NULL; } state->stream = stream; state->line = 1; state->token = NULL; state->tokenSize = 0; state->tokenLen = 0; state->tokenType = TOKEN_UNKNOWN; return state; } static void ConfigParserStateFree(ConfigParserState *state) { if (!state) { return; } free(state->token); HashMapIterate(state->macroMap, free); HashMapFree(state->macroMap); free(state); } static int ConfigIsNameChar(int c) { return isdigit(c) || isalpha(c) || (c == '-' || c == '_'); } static char ConfigConsumeWhitespace(ConfigParserState *state) { int c; while (isspace(c = fgetc(state->stream))) { if (c == '\n') { state->line++; } } return c; } static void ConfigConsumeLine(ConfigParserState *state) { while (fgetc(state->stream) != '\n'); state->line++; } static void ConfigTokenSeek(ConfigParserState *state) { int c; /* If we already hit EOF, don't do anything */ if (state->tokenType == TOKEN_EOF) { return; } while ((c = ConfigConsumeWhitespace(state)) == '#') { ConfigConsumeLine(state); } /* * After all whitespace and comments are consumed, identify the * token by looking at the next character */ if (feof(state->stream)) { state->tokenType = TOKEN_EOF; return; } if (ConfigIsNameChar(c)) { state->tokenLen = 0; /* Read the key/macro into state->token */ if (!state->token) { state->tokenSize = CONFIG_BUFFER_BLOCK; state->token = malloc(CONFIG_BUFFER_BLOCK); } state->token[state->tokenLen] = c; state->tokenLen++; while (ConfigIsNameChar((c = fgetc(state->stream)))) { state->token[state->tokenLen] = c; state->tokenLen++; if (state->tokenLen >= state->tokenSize) { state->tokenSize += CONFIG_BUFFER_BLOCK; state->token = realloc(state->token, state->tokenSize); } } state->token[state->tokenLen] = '\0'; state->tokenLen++; if (!isspace(c)) { state->tokenType = TOKEN_UNKNOWN; } else { state->tokenType = TOKEN_NAME; if (c == '\n') { state->line++; } } } else { switch (c) { case '=': state->tokenType = TOKEN_MACRO_ASSIGNMENT; break; case '"': state->tokenLen = 0; state->tokenType = TOKEN_VALUE; /* read the value into state->curtok */ while ((c = fgetc(state->stream)) != '"') { if (c == '\n') { state->line++; } /* * End of the stream reached without finding * a closing quote */ if (feof(state->stream)) { state->tokenType = TOKEN_EOF; break; } state->token[state->tokenLen] = c; state->tokenLen++; if (state->tokenLen >= state->tokenSize) { state->tokenSize += CONFIG_BUFFER_BLOCK; state->token = realloc(state->token, state->tokenSize); } } state->token[state->tokenLen] = '\0'; state->tokenLen++; break; case ';': state->tokenType = TOKEN_SEMICOLON; break; case '{': state->tokenType = TOKEN_BLOCK_OPEN; break; case '}': state->tokenType = TOKEN_BLOCK_CLOSE; break; case '$': state->tokenLen = 0; /* read the macro name into state->curtok */ while (ConfigIsNameChar(c = fgetc(state->stream))) { state->token[state->tokenLen] = c; state->tokenLen++; if (state->tokenLen >= state->tokenSize) { state->tokenSize += CONFIG_BUFFER_BLOCK; state->token = realloc(state->token, state->tokenSize); } } state->token[state->tokenLen] = '\0'; state->tokenLen++; state->tokenType = TOKEN_MACRO; ungetc(c, state->stream); break; default: state->tokenType = TOKEN_UNKNOWN; break; } } /* Resize curtok to only use the bytes it needs */ if (state->tokenLen) { state->tokenSize = state->tokenLen; state->token = realloc(state->token, state->tokenSize); } } static int ConfigExpect(ConfigParserState *state, ConfigToken tokenType) { return state->tokenType == tokenType; } static HashMap * ConfigParseBlock(ConfigParserState *state, int level) { HashMap *block = HashMapCreate(); ConfigTokenSeek(state); while (ConfigExpect(state, TOKEN_NAME)) { char *name = malloc(state->tokenLen + 1); strcpy(name, state->token); ConfigTokenSeek(state); if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) { ConfigDirective *directive; directive = malloc(sizeof(ConfigDirective)); directive->children = NULL; directive->values = ArrayCreate(); while (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO)) { char *dval; char *dvalCpy; if (ConfigExpect(state, TOKEN_VALUE)) { dval = state->token; } else if (ConfigExpect(state, TOKEN_MACRO)) { dval = HashMapGet(state->macroMap, state->token); if (!dval) { goto error; } } else { dval = NULL; /* Should never happen */ } /* dval is a pointer which is overwritten with the next token. */ dvalCpy = malloc(strlen(dval) + 1); strcpy(dvalCpy, dval); ArrayAdd(directive->values, dvalCpy); ConfigTokenSeek(state); } if (ConfigExpect(state, TOKEN_BLOCK_OPEN)) { /* token_seek(state); */ directive->children = ConfigParseBlock(state, level + 1); if (!directive->children) { goto error; } } /* * Append this directive to the current block, * overwriting a directive at this level with the same name. * * Note that if a value already exists with this name, it is * returned by HashMapSet() and then immediately passed to * ConfigDirectiveFree(). If the value does not exist, then * NULL is sent to ConfigDirectiveFree(), making it a no-op. */ ConfigDirectiveFree(HashMapSet(block, name, directive)); } else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT)) { ConfigTokenSeek(state); if (ConfigExpect(state, TOKEN_VALUE)) { char * valueCopy = malloc(strlen(state->token) + 1); strcpy(valueCopy, state->token); free(HashMapSet(state->macroMap, name, state->token)); ConfigTokenSeek(state); } else { goto error; } } else { goto error; } if (!ConfigExpect(state, TOKEN_SEMICOLON)) { goto error; } ConfigTokenSeek(state); } if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF)) { ConfigTokenSeek(state); return block; } else { goto error; } error: /* Only free the very top level, because this will recurse */ if (!level) { ConfigFree(block); } return NULL; } ConfigParseResult * ConfigParse(FILE * stream) { ConfigParseResult *result; HashMap *conf; ConfigParserState *state; result = malloc(sizeof(ConfigParseResult)); state = ConfigParserStateCreate(stream); conf = ConfigParseBlock(state, 0); if (!conf) { result->ok = 0; result->data.lineNumber = state->line; } else { result->ok = 1; result->data.confMap = conf; } ConfigParserStateFree(state); return result; }