From bdbb94b63785d03d12b2225222ee34108d896668 Mon Sep 17 00:00:00 2001 From: Linnnus Date: Thu, 10 Apr 2025 05:21:21 +0000 Subject: feat(cli): More CLI stuff, idk --- src/cli/main.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 4 deletions(-) (limited to 'src/cli/main.c') 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 #include #include +#include +#include +#include + +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), "", 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 \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; } -- cgit v1.2.3