summaryrefslogtreecommitdiff
path: root/src/cli/main.c
diff options
context:
space:
mode:
authorLinnnus <[email protected]>2025-04-10 05:21:21 +0000
committerLinnnus <[email protected]>2025-04-15 00:55:08 +0000
commitbdbb94b63785d03d12b2225222ee34108d896668 (patch)
treef4e7d779db6264ddcc15b9ef9cb0995f15dfadae /src/cli/main.c
parent7bd2461c849b4626653a4744427c904b87354bd7 (diff)
feat(cli): More CLI stuff, idk
Diffstat (limited to 'src/cli/main.c')
-rw-r--r--src/cli/main.c161
1 files changed, 157 insertions, 4 deletions
diff --git a/src/cli/main.c b/src/cli/main.c
index 4d96aa2..db42a14 100644
--- a/src/cli/main.c
+++ b/src/cli/main.c
@@ -1,13 +1,166 @@
#include "../core/state.h"
+#include "../core/interpret.h"
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+int read_file(const char *file_path, char **out_buffer, size_t *out_length) {
+ assert(out_buffer != NULL && *out_buffer == NULL);
+ assert(file_path != NULL);
+
+ const size_t CHUNK_SIZE = 1024;
+
+ FILE *fp = fopen(file_path, "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ char *buffer = NULL;
+ size_t allocated = 0;
+ size_t used = 0;
+ while (true) {
+ // Grow buffer, if needed.
+ if (used + CHUNK_SIZE > allocated) {
+ // Grow exponentially to guarantee O(log(n)) performance.
+ allocated = (allocated == 0) ? CHUNK_SIZE : allocated * 2;
+
+ // Overflow check. Some ANSI C compilers may optimize this away, though.
+ if (allocated <= used) {
+ free(buffer);
+ fclose(fp);
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ char *temp = realloc(buffer, allocated);
+ if (temp == NULL) {
+ int old_errno = errno;
+ free(buffer); // free() may not set errno
+ fclose(fp); // fclose() may set errno
+ errno = old_errno;
+ return -1;
+ }
+ buffer = temp;
+ }
+
+ size_t nread = fread(buffer + used, 1, CHUNK_SIZE, fp);
+ if (nread == 0) {
+ // End-of-file or errnor has occured.
+ // FIXME: Should we be checking (nread < CHUNK_SIZE)?
+ // https://stackoverflow.com/a/39322170
+ break;
+ }
+ used += nread;
+ }
+
+ if (ferror(fp)) {
+ int old_errno = errno;
+ free(buffer); // free() may not set errno
+ fclose(fp); // fclose() may set errno
+ errno = old_errno;
+ return -1;
+ }
+
+ // Reallocate to optimal size.
+ char *temp = realloc(buffer, used + 1);
+ if (temp == NULL) {
+ int old_errno = errno;
+ free(buffer); // free() may not set errno
+ fclose(fp); // fclose() may set errno
+ errno = old_errno;
+ return -1;
+ }
+ buffer = temp;
+
+ // Null-terminate the buffer. Note that buffers may still contain \0,
+ // so strlen(buffer) == length may not be true.
+ buffer[used] = '\0';
+
+ // Return buffer.
+ *out_buffer = buffer;
+ if (out_length != NULL) {
+ *out_length = used;
+ }
+ fclose(fp);
+ return 0;
+}
+
+static void run_file(SandState *S, const char *filename) {
+ char *source = NULL;
+ size_t source_length = 0;
+ if (read_file(filename, &source, &source_length) < 0) {
+ fprintf(stderr, "Error reading %s: %s\n", filename, strerror(errno));
+ exit(74);
+ }
+
+ SandInterpretResult result = sand_interpret(S, filename, source, source_length);
+ free(source); // At this point it should be okay to invalidate all references to the source code.
+
+ if (result == SAND_INTERPRET_COMPILE_ERROR) {
+ exit(65);
+ } else if (result == SAND_INTERPRET_RUNTIME_ERROR) {
+ exit(70);
+ }
+}
+
+static void repl(SandState *S) {
+ char *line = NULL;
+ size_t line_length = 0;
+
+ unsigned prompt_nr = 1;
+
+ while (true) {
+ printf("> ");
+ fflush(stdout);
+
+ // At this point the memory at `line` is reused, invalidating all existing references.
+ if (getline(&line, &line_length, stdin) < 0) {
+ fputc('\n', stdout);
+ break; // No more input - leave REPL loop.
+ }
+
+ // We need a dummy filename.
+ char filename[64];
+ snprintf(filename, sizeof(filename), "<repl %u>", prompt_nr);
+ prompt_nr += 1;
+
+ sand_interpret(S, filename, line, line_length);
+ }
+}
+
+static void print_help(FILE *stream, const char *argv0) {
+ fprintf(stream, "Usage: %s <filename>\n", argv0);
+ fprintf(stream, " %s --help\n", argv0);
+ fprintf(stream, " %s\n\n", argv0);
+ fprintf(stream, "When called without arguments, runs a REPL.\n");
+}
+
+static void print_handler(const char *message, size_t message_length) {
+ fprintf(stderr, message, message_length);
+}
int main(int argc, char *argv[]) {
- (void)argc;
- (void)argv;
+ SandConfig config = {
+ .print_handler = print_handler,
+ };
+ SandState state = sand_create_state(config);
- printf("TODO\n");
+ if (argc == 1) {
+ repl(&state);
+ } else if (argc == 2 && strcmp(argv[1], "--help") == 0) {
+ print_help(stdout, argv[0]);
+ exit(0);
+ } else if (argc == 2) {
+ run_file(&state, argv[1]);
+ } else {
+ print_help(stderr, argv[0]);
+ exit(64);
+ }
- return EXIT_SUCCESS;
+ sand_destroy_state(&state);
+ return 0;
}