diff options
Diffstat (limited to 'core/lexer.c')
| -rw-r--r-- | core/lexer.c | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/core/lexer.c b/core/lexer.c new file mode 100644 index 0000000..2eb50a1 --- /dev/null +++ b/core/lexer.c @@ -0,0 +1,191 @@ +#include <ctype.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include "defconf/lexer.h" + +/* Constants */ +#define MAX_KW_LEN 32 + +/* + * Returns true if the given character is a whitespace character + * + * @c: Character to check + */ +static inline bool +lexer_is_ws(char c) +{ + switch (c) { + case '\t': + case ' ': + case '\f': + case '\r': + case '\n': + return true; + } + + return false; +} + +/* + * Place a character back in the putback buffer + * + * @state: Defconf state + * @c: Character to put back + */ +static inline void +lexer_putback(struct defconf_state *state, int c) +{ + if (state == NULL) { + return; + } + + state->lex_cache = c; +} + +/* + * Nom a single character from the input source file + * + * @state: Defconf state machine + * @skip_ws: If true, skip whitespace + * + * Returns the character on success, otherwise EOF on failure + */ +static int +lexer_nom(struct defconf_state *state, bool skip_ws) +{ + int c; + + if (state == NULL) { + return EOF; + } + + if ((c = state->lex_cache) != '\0') { + state->lex_cache = '\0'; + if (lexer_is_ws(c) && !skip_ws) + return c; + if (!lexer_is_ws(c)) + return c; + } + + while ((c = fgetc(state->in_fp)) != EOF) { + if (lexer_is_ws(c) && skip_ws) + continue; + + break; + } + + return c; +} + +/* + * Scan a keyword + * + * @state: Defconf state + * @lc: Last character provided + */ +static char * +lexer_scan_ident(struct defconf_state *state, int lc) +{ + char c, buf[MAX_KW_LEN]; + size_t bufind = 0; + + if (state == NULL) { + return NULL; + } + + if (!isalpha(lc)) { + return NULL; + } + + buf[bufind++] = lc; + for (;;) { + if (bufind >= sizeof(buf) - 1) { + printf("fatal: token exceeds maximum length\n"); + return NULL; + } + + c = lexer_nom(state, false); + if (!isalnum(c) && c != '_') { + lexer_putback(state, c); + buf[bufind] = '\0'; + return strdup(buf); + } + + buf[bufind++] = c; + } + + return NULL; +} + +/* + * Check if a keyword matches against known keywords + * + * @kw: Keyword to check + * @result: Result is written here + * + * Returns zero on success + */ +static int +lexer_check_kw(const char *kw, struct token *result) +{ + if (kw == NULL || result == NULL) { + return -1; + } + + switch (*kw) { + case 'd': + if (strcmp(kw, "define") == 0) { + result->type = TT_DEFINE; + return 0; + } + + break; + case 't': + if (strcmp(kw, "true") == 0) { + result->type = TT_TRUE; + return 0; + } + + break; + case 'f': + if (strcmp(kw, "false") == 0) { + result->type = TT_FALSE; + return 0; + } + + break; + } + + return -1; +} + +int +lexer_scan(struct defconf_state *state, struct token *result) +{ + int c; + char *ident; + + if (state == NULL || result == NULL) { + return -1; + } + + if ((c = lexer_nom(state, true)) == EOF) { + return -1; + } + + /* Scan for an identifier */ + if ((ident = lexer_scan_ident(state, c)) != NULL) { + if (lexer_check_kw(ident, result) == 0) { + free(ident); + return 0; + } + + result->type = TT_IDENT; + result->s = ident; + return 0; + } + + printf("fatal: unexpected token '%c'\n", c); + return -1; +} |
