summaryrefslogtreecommitdiff
path: root/core/lexer.c
diff options
context:
space:
mode:
authorChloe M. <chloe@aptel.org>2026-08-23 04:49:51 +0000
committerChloe M. <chloe@aptel.org>2026-08-23 04:49:51 +0000
commit78b925a4d86b57e58e6322b7cad7bc47098618fd (patch)
treee66da7a598bb4be1ca869f72600112dda34a4067 /core/lexer.c
initial commit
There is some work to be done on top of this such as creating a way to manage memory to be freed later without needing to manually manage every pointer. Signed-off-by: Chloe M. <chloe@aptel.org>
Diffstat (limited to 'core/lexer.c')
-rw-r--r--core/lexer.c191
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;
+}