/* * Copyright (c) 2026, Apollo Telephone Laboratories. * Provided under the BSD-3 clause. */ #include #include #include #include #include "defconf/lexer.h" #include "defconf/ptrbox.h" #include "defconf/trace.h" /* Constants */ #define MAX_KW_LEN 32 #define MAX_NUM_LEN 20 /* * 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) { trace_fatal(state, "token exceeds maximum length\n"); return NULL; } c = lexer_nom(state, false); if (!isalnum(c) && c != '_') { lexer_putback(state, c); buf[bufind] = '\0'; return ptrbox_strdup(&state->ptrbox, buf); } buf[bufind++] = c; } return NULL; } /* * Scan a number * * @state: Defconf state * @lc: Last character provided * @result: Result is written here */ static int lexer_scan_num(struct defconf_state *state, int lc, struct token *result) { char c, buf[MAX_NUM_LEN + 1]; size_t bufind = 0; if (state == NULL || result == NULL) { return -1; } if (!isdigit(lc)) { return -1; } buf[bufind++] = lc; for (;;) { if (bufind >= sizeof(buf) - 1) { trace_fatal(state, "digits exceed maximum length\n"); return -1; } c = lexer_nom(state, false); if (!isdigit(c)) { lexer_putback(state, c); buf[bufind] = '\0'; /* Set the token */ result->type = TT_INTLIT; result->v = atoi(buf); return 0; } buf[bufind++] = c; } return -1; } /* * 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; } /* * Skips an entire line, this is useful for comments * * @state: Defconf state */ static void lexer_skip_line(struct defconf_state *state) { int c; if (state == NULL) { return; } for (;;) { c = lexer_nom(state, false); if (c == EOF) return; if (c == '\n') return; } } int lexer_scan(struct defconf_state *state, struct token *result) { int c; char *ident; int error; if (state == NULL || result == NULL) { return -1; } if ((c = lexer_nom(state, true)) == EOF) { return -1; } /* Check for single characters */ switch (c) { case '/': /* Is this a comment? */ if ((c = lexer_nom(state, false)) == '/') { lexer_skip_line(state); result->type = TT_COMMENT; return 0; } break; } /* Scan for an identifier */ if ((ident = lexer_scan_ident(state, c)) != NULL) { if (lexer_check_kw(ident, result) == 0) { return 0; } result->type = TT_IDENT; result->s = ident; return 0; } /* Is this a digit? */ if (isdigit(c)) { error = lexer_scan_num(state, c, result); if (error < 0) { return -1; } return 0; } trace_fatal(state, "unexpected token '%c'\n", c); return -1; }