summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/build_debug.sh4
-rwxr-xr-xscripts/build_release.sh4
-rw-r--r--src/cli/main.c161
-rw-r--r--src/core/interpret.c19
-rw-r--r--src/core/interpret.h20
-rw-r--r--src/core/state.c11
-rw-r--r--src/core/state.h25
7 files changed, 236 insertions, 8 deletions
diff --git a/scripts/build_debug.sh b/scripts/build_debug.sh
index fcdf16a..bf5f42b 100755
--- a/scripts/build_debug.sh
+++ b/scripts/build_debug.sh
@@ -1,5 +1,5 @@
#!/bin/sh
set -e -u -o pipefail
mkdir -p bin/debug
-clang -std=c23 -ggdb -O0 -Wall -Wextra -fsanitize=undefined,address -o bin/debug/sand src/{cli,core}/*.c
-clang -std=c23 -ggdb -O0 -Wall -Wextra -fsanitize=undefined,address -o bin/debug/unit src/{unit,core}/*.c
+clang -std=c23 -D_POSIX_C_SOURCE=200809L -ggdb -O0 -Wall -Wextra -fsanitize=undefined,address -o bin/debug/sand src/{cli,core}/*.c
+clang -std=c23 -D_POSIX_C_SOURCE=200809L -ggdb -O0 -Wall -Wextra -fsanitize=undefined,address -o bin/debug/unit src/{unit,core}/*.c
diff --git a/scripts/build_release.sh b/scripts/build_release.sh
index 6f42aed..cb1ee8b 100755
--- a/scripts/build_release.sh
+++ b/scripts/build_release.sh
@@ -1,5 +1,5 @@
#!/bin/sh
set -e -u -o pipefail
mkdir -p bin/release
-clang -std=c23 -O3 -Wall -Wextra -o bin/release/sand src/{cli,core}/*.c
-clang -std=c23 -O3 -Wall -Wextra -o bin/release/unit src/{unit,core}/*.c
+clang -std=c23 -D_POSIX_C_SOURCE=200809L -O3 -Wall -Wextra -o bin/release/sand src/{cli,core}/*.c
+clang -std=c23 -D_POSIX_C_SOURCE=200809L -O3 -Wall -Wextra -o bin/release/unit src/{unit,core}/*.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 <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;
}
diff --git a/src/core/interpret.c b/src/core/interpret.c
new file mode 100644
index 0000000..bbb238c
--- /dev/null
+++ b/src/core/interpret.c
@@ -0,0 +1,19 @@
+#include "interpret.h"
+#include "tokenizer.h"
+
+#include <stdbool.h>
+
+SandInterpretResult sand_interpret(SandState *S, const char *filename, const char *source, size_t source_length) {
+ SandTokenizer tokenizer = sand_create_tokenizer(source, source_length, filename);
+
+ while (true) {
+ SandToken token = sand_get_next_token(&tokenizer);
+ sand_print_location(stdout, &token.location);
+ printf(": %s \"%.*s\"\n", sand_token_kind_to_string(token.kind), (int)token.content_length, token.content);
+ if (token.kind == SAND_TOKEN_EOF) {
+ break;
+ }
+ }
+
+ return SAND_INTERPRET_OK;
+}
diff --git a/src/core/interpret.h b/src/core/interpret.h
new file mode 100644
index 0000000..8bdc92f
--- /dev/null
+++ b/src/core/interpret.h
@@ -0,0 +1,20 @@
+#ifndef SAND_INTERPRET_H
+#define SAND_INTERPRET_H
+
+// This module defines a single function which composes all the stuff the
+// interpreter does. It should be bascially the only function that an embedder
+// needs to know.
+
+#include "state.h"
+
+#include <stddef.h>
+
+typedef enum {
+ SAND_INTERPRET_OK,
+ SAND_INTERPRET_COMPILE_ERROR,
+ SAND_INTERPRET_RUNTIME_ERROR,
+} SandInterpretResult;
+
+SandInterpretResult sand_interpret(SandState *, const char *filename, const char *source, size_t source_length);
+
+#endif
diff --git a/src/core/state.c b/src/core/state.c
new file mode 100644
index 0000000..91bd9e4
--- /dev/null
+++ b/src/core/state.c
@@ -0,0 +1,11 @@
+#include "state.h"
+
+SandState sand_create_state(SandConfig config) {
+ return (SandState) {
+ .config = config,
+ };
+}
+
+void sand_destroy_state(SandState *S) {
+ (void) S;
+}
diff --git a/src/core/state.h b/src/core/state.h
new file mode 100644
index 0000000..406a79c
--- /dev/null
+++ b/src/core/state.h
@@ -0,0 +1,25 @@
+#ifndef SAND_STATE_H
+#define SAND_STATE_H
+
+// This module defines the evaluator state. This is the "world" in Sand.
+// Multiple evaluator states can coexist as they are totally separate.
+
+#include <stddef.h>
+
+typedef void (* SandPrintHandler)(const char *message, size_t length);
+
+typedef struct {
+ SandPrintHandler print_handler;
+} SandConfig;
+
+// This data structure should be treated as entirely opaque by consuming code.
+typedef struct {
+ SandConfig config;
+} SandState;
+
+
+SandState sand_create_state(SandConfig config);
+
+void sand_destroy_state(SandState *);
+
+#endif