From dbf4eb5d0e136727242d97e0a5573fe144dceca9 Mon Sep 17 00:00:00 2001 From: Emin Arslan Date: Mon, 4 May 2026 10:17:43 +0300 Subject: [PATCH] feat: abstract I/O and strings for freestanding compilation Co-authored-by: aider (openrouter/moonshotai/kimi-k2.6) --- Makefile | 23 ++++---- forth.h | 54 +++++++++++++------ forth_dict.c | 8 +-- forth_interp.c | 10 ++-- forth_words.c | 8 +-- main.c | 3 +- platform_freestanding.c | 114 ++++++++++++++++++++++++++++++++++++++++ platform_hosted.c | 38 ++++++++++++++ 8 files changed, 216 insertions(+), 42 deletions(-) create mode 100644 platform_freestanding.c create mode 100644 platform_hosted.c diff --git a/Makefile b/Makefile index c241ba1..441b735 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,22 @@ -CC = gcc -CFLAGS = -Wall -Wextra -g -std=c11 -D_POSIX_C_SOURCE=200809L -LDFLAGS = -SRCS = forth_core.c forth_dict.c forth_words.c forth_interp.c main.c -OBJS = $(SRCS:.c=.o) +CC = clang +CFLAGS = -Wall -Wextra -g -std=c11 +COMMON_SRCS = forth_core.c forth_dict.c forth_words.c forth_interp.c main.c + +HOSTED_SRCS = $(COMMON_SRCS) platform_hosted.c +FREESTANDING_SRCS = $(COMMON_SRCS) platform_freestanding.c + TARGET = forth +FREESTANDING_TARGET = forth_freestanding all: $(TARGET) -$(TARGET): $(OBJS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +$(TARGET): $(HOSTED_SRCS) forth.h + $(CC) $(CFLAGS) -D_POSIX_C_SOURCE=200809L -o $@ $(HOSTED_SRCS) -%.o: %.c forth.h - $(CC) $(CFLAGS) -c $< -o $@ +$(FREESTANDING_TARGET): $(FREESTANDING_SRCS) forth.h + $(CC) $(CFLAGS) -ffreestanding -nostdlib -fno-stack-protector -o $@ $(FREESTANDING_SRCS) clean: - rm -f $(OBJS) $(TARGET) + rm -f $(TARGET) $(FREESTANDING_TARGET) .PHONY: all clean diff --git a/forth.h b/forth.h index 7ba7183..0c41551 100644 --- a/forth.h +++ b/forth.h @@ -1,29 +1,46 @@ #ifndef FORTH_H #define FORTH_H -#include -#include -#include -#include #include -#include +#include +#include // Configuration (all hard limits removed) #define MAX_NAME_LEN 31 +#define FORTH_EOF (-1) -/* Portable panic hook. Override for bare-metal reset/handling. */ -#ifndef forth_panic -#define forth_panic() do { for (;;) ; } while (0) -#endif +/* Platform interface -- must be provided by the target platform layer. */ +void forth_putchar(char c); +int forth_getchar(void); +void forth_printf(const char* fmt, ...); +void forth_fflush(void); +void forth_panic(void); -/* Portable I/O hooks. Define FORTH_CUSTOM_IO before including this header - * to provide bare-metal UART (or other) implementations. */ -#ifndef FORTH_CUSTOM_IO -#define forth_putchar(c) putchar(c) -#define forth_getchar() getchar() -#define forth_printf(...) printf(__VA_ARGS__) -#define forth_fflush() fflush(stdout) -#endif +/* Platform string-to-number (base 10 is required). */ +int64_t forth_strtoll(const char* str, char** endptr, int base); + +/* Portable character classification. */ +static inline int forth_isspace(int c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +/* Portable string operations. */ +static inline size_t forth_strlen(const char* s) { + size_t n = 0; + while (s[n]) n++; + return n; +} + +static inline void forth_memcpy(void* dst, const void* src, size_t n) { + char* d = dst; + const char* s = src; + while (n--) *d++ = *s++; +} + +static inline int forth_strcmp(const char* a, const char* b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} /* Static memory sizes (override before including forth.h) */ #ifndef FORTH_DATA_STACK_SIZE @@ -105,6 +122,9 @@ extern Word* w_branch; extern Word* w_zbranch; extern Word* w_dot_quote_inner; +// Entry point +void forth_run(void); + // Core function prototypes void data_push(int64_t val); int64_t data_pop(void); diff --git a/forth_dict.c b/forth_dict.c index 22f3e40..20eea76 100644 --- a/forth_dict.c +++ b/forth_dict.c @@ -5,10 +5,10 @@ Word* add_primitive(const char* name, void (*code)(Word*), uint8_t flags) { w->prev = dict_head; dict_head = w; - size_t len = strlen(name); + size_t len = forth_strlen(name); if (len > MAX_NAME_LEN) len = MAX_NAME_LEN; w->flags = flags | (uint8_t)len; - memcpy(w->name, name, len); + forth_memcpy(w->name, name, len); w->name[len] = '\0'; w->code = code; w->body = NULL; @@ -18,14 +18,14 @@ Word* add_primitive(const char* name, void (*code)(Word*), uint8_t flags) { Word* lookup_word(const char* name) { for (Word* w = dict_head; w != NULL; w = w->prev) { if (w->flags & (1 << 6)) continue; - if (strcmp(w->name, name) == 0) return w; + if (forth_strcmp(w->name, name) == 0) return w; } return NULL; } Word* lookup_word_internal(const char* name) { for (Word* w = dict_head; w != NULL; w = w->prev) { - if (strcmp(w->name, name) == 0) return w; + if (forth_strcmp(w->name, name) == 0) return w; } return NULL; } diff --git a/forth_interp.c b/forth_interp.c index 0ca245d..6e3f98c 100644 --- a/forth_interp.c +++ b/forth_interp.c @@ -3,7 +3,7 @@ // Input tokenizer (unchanged) char* next_token(void) { if (input_ptr == NULL) return NULL; - while (*input_ptr != '\0' && isspace((unsigned char)*input_ptr)) { + while (*input_ptr != '\0' && forth_isspace((unsigned char)*input_ptr)) { input_ptr++; } if (*input_ptr == '\0') return NULL; @@ -63,10 +63,10 @@ void process_token(const char* token) { } } else { // Try to parse as number char* end; - long long v = strtoll(token, &end, 10); + int64_t v = forth_strtoll(token, &end, 10); if (end != token && *end == '\0') { if (state == 0) { - data_push((int64_t)v); + data_push(v); } else { // Compile lit + number if (!w_lit) { forth_printf("Fatal: lit word not found\n"); @@ -91,13 +91,13 @@ void outer_interpreter(void) { int c; while (i < sizeof(line_buf) - 1) { c = forth_getchar(); - if (c == EOF || c == '\n' || c == '\r') { + if (c == FORTH_EOF || c == '\n' || c == '\r') { break; } line_buf[i++] = (char)c; } line_buf[i] = '\0'; - if (i == 0 && c == EOF) { + if (i == 0 && c == FORTH_EOF) { break; } input_buf = line_buf; diff --git a/forth_words.c b/forth_words.c index b2760ca..6bb13ed 100644 --- a/forth_words.c +++ b/forth_words.c @@ -309,7 +309,7 @@ void do_zero_gt(Word* w) { void do_dot(Word* w) { (void)w; if (data_sp < 0) return; - forth_printf("%" PRId64 " ", data_pop()); + forth_printf("%lld ", (long long)data_pop()); forth_fflush(); } @@ -329,7 +329,7 @@ void do_emit(Word* w) { void do_key(Word* w) { (void)w; int c = forth_getchar(); - data_push(c == EOF ? -1 : c); + data_push(c == FORTH_EOF ? -1 : c); } void do_dot_quote(Word* w) { @@ -337,7 +337,7 @@ void do_dot_quote(Word* w) { if (state == 0) { // Interpret mode: print immediately if (input_ptr == NULL) { forth_printf("Missing string\n"); return; } - while (*input_ptr && isspace((unsigned char)*input_ptr)) input_ptr++; + while (*input_ptr && forth_isspace((unsigned char)*input_ptr)) input_ptr++; if (*input_ptr != '"') { forth_printf("Expected \" to start string\n"); return; } input_ptr++; char* start = input_ptr; @@ -349,7 +349,7 @@ void do_dot_quote(Word* w) { } else { // Compile mode: compile string for runtime if (input_ptr == NULL) { forth_printf("Missing string\n"); return; } - while (*input_ptr && isspace((unsigned char)*input_ptr)) input_ptr++; + while (*input_ptr && forth_isspace((unsigned char)*input_ptr)) input_ptr++; if (*input_ptr != '"') { forth_printf("Expected \" to start string\n"); return; } input_ptr++; char* start = input_ptr; diff --git a/main.c b/main.c index 1747670..719344f 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,6 @@ #include "forth.h" -int main(void) { +void forth_run(void) { here = user_mem; // Hidden words first @@ -104,5 +104,4 @@ int main(void) { // Start outer interpreter outer_interpreter(); - return 0; } diff --git a/platform_freestanding.c b/platform_freestanding.c new file mode 100644 index 0000000..770bf10 --- /dev/null +++ b/platform_freestanding.c @@ -0,0 +1,114 @@ +#include "forth.h" +#include + +static inline long syscall3(long n, long a1, long a2, long a3) { + long ret; + __asm__ volatile ("syscall" + : "=a"(ret) + : "a"(n), "D"(a1), "S"(a2), "d"(a3) + : "rcx", "r11", "memory"); + return ret; +} + +static inline long syscall1(long n, long a1) { + long ret; + __asm__ volatile ("syscall" + : "=a"(ret) + : "a"(n), "D"(a1) + : "rcx", "r11", "memory"); + return ret; +} + +void forth_putchar(char c) { + char ch = c; + syscall3(1, 1, (long)&ch, 1); +} + +void forth_fflush(void) { +} + +int forth_getchar(void) { + unsigned char c; + long n = syscall3(0, 0, (long)&c, 1); + if (n <= 0) return FORTH_EOF; + return (int)c; +} + +void forth_panic(void) { + syscall1(60, 1); + __builtin_unreachable(); +} + +int64_t forth_strtoll(const char* str, char** endptr, int base) { + const char* s = str; + int neg = 0; + int64_t val = 0; + while (forth_isspace(*s)) s++; + if (*s == '-') { neg = 1; s++; } + else if (*s == '+') { s++; } + while (*s >= '0' && *s <= '9') { + val = val * base + (*s - '0'); + s++; + } + if (endptr) *endptr = (char*)s; + return neg ? -val : val; +} + +static void print_str(const char* s) { + while (*s) forth_putchar(*s++); +} + +static void print_int(long long v) { + char buf[32]; + int i = 30; + int neg = 0; + if (v < 0) { neg = 1; v = -v; } + buf[31] = '\0'; + if (v == 0) { + buf[i--] = '0'; + } else { + while (v > 0 && i >= 0) { + buf[i--] = '0' + (v % 10); + v /= 10; + } + } + if (neg) buf[i--] = '-'; + print_str(&buf[i + 1]); +} + +void forth_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + while (*fmt) { + if (*fmt == '%' && *(fmt + 1)) { + fmt++; + if (*fmt == 's') { + print_str(va_arg(ap, const char*)); + } else if (*fmt == 'c') { + forth_putchar((char)va_arg(ap, int)); + } else if (*fmt == '%') { + forth_putchar('%'); + } else if (*fmt == 'l') { + if (*(fmt + 1) == 'l' && (*(fmt + 2) == 'd' || *(fmt + 2) == 'i' || *(fmt + 2) == 'u')) { + fmt += 2; + print_int(va_arg(ap, long long)); + } + } else if (*fmt == 'd' || *fmt == 'i' || *fmt == 'u') { + print_int(va_arg(ap, int)); + } else { + forth_putchar('%'); + forth_putchar(*fmt); + } + } else { + forth_putchar(*fmt); + } + fmt++; + } + va_end(ap); +} + +void _start(void) { + forth_run(); + syscall1(60, 0); + __builtin_unreachable(); +} diff --git a/platform_hosted.c b/platform_hosted.c new file mode 100644 index 0000000..222ef90 --- /dev/null +++ b/platform_hosted.c @@ -0,0 +1,38 @@ +#include "forth.h" +#include +#include +#include +#include +#include + +void forth_putchar(char c) { + putchar((unsigned char)c); +} + +int forth_getchar(void) { + return getchar(); +} + +void forth_fflush(void) { + fflush(stdout); +} + +void forth_panic(void) { + for (;;); +} + +int64_t forth_strtoll(const char* str, char** endptr, int base) { + return (int64_t)strtoll(str, endptr, base); +} + +void forth_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +int main(void) { + forth_run(); + return 0; +}