#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[]) { SandConfig config = { .print_handler = print_handler, }; SandState state = sand_create_state(config); 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); } sand_destroy_state(&state); return 0; }