OBFI

Brainfuck Interpreter
git clone http://git.omkov.net/OBFI
Log | Tree | Refs | README | LICENCE | Download

AuthorJakob Wakeling <[email protected]>
Date2023-12-28 03:23:49
Commit78da73c4c5daa6f6b9828bca71816985dc8c7162
Parent5a79c5977f1a7de29ed9f85fd8e5479c43c7c1d5

Clean up and move to C23, add a Makefile

Diffstat

M .gitignore | 6 +++---
D BuildUNIX.sh | 10 ----------
D BuildWindows.bat | 4 ----
M CMakeLists.txt | 8 +++++---
A Makefile | 13 +++++++++++++
M README.md | 12 ++----------
M src/obfi.c | 113 ++++++++++++++++++++++++++++++++++++++-----------------------------------------
D src/util/error.c | 41 -----------------------------------------
D src/util/error.h | 68 --------------------------------------------------------------------
A src/util/log.c | 14 ++++++++++++++
A src/util/log.h | 9 +++++++++
M src/util/optget.c | 11 ++++-------
M src/util/optget.h | 16 +++-------------
M src/util/util.h | 37 ++++++++++---------------------------

14 files changed, 117 insertions, 245 deletions

diff --git a/.gitignore b/.gitignore
index f563e5a..e4561ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 /.cache/
-/.vscode/
+/.vscode/*
 /bin/
 /build/
-/compile_commands.json
-/lib/
+
+!/.vscode/launch.json
diff --git a/BuildUNIX.sh b/BuildUNIX.sh
deleted file mode 100755
index d59505f..0000000
--- a/BuildUNIX.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env sh
-dir="$(dirname $(realpath "$0"))"
-
-cmake -S "${dir}" -B "${dir}/build" \
-	-DCMAKE_BUILD_TYPE:STRING=Release \
-	-DCMAKE_EXPORT_COMPILE_COMMANDS=1
-
-cmake --build "${dir}/build"
-
-mv -f "${dir}/build/compile_commands.json" "${dir}/compile_commands.json"
diff --git a/BuildWindows.bat b/BuildWindows.bat
deleted file mode 100644
index 592928c..0000000
--- a/BuildWindows.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-cmake -S "%~dp0\" -B "%~dp0\build" ^
-	-DCMAKE_BUILD_TYPE:STRING=Release
-
-cmake --build "%~dp0\build"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8462fd1..8fb9971 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,9 +1,11 @@
-cmake_minimum_required(VERSION 3.12)
+cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
 project(OBFI VERSION 1.0.4 LANGUAGES C)
 
+set(CMAKE_C_STANDARD 23)
+set(CMAKE_C_STANDARD_REQUIRED TRUE)
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
-add_compile_definitions(PROJECT_VERSION="${PROJECT_VERSION}")
 
-file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.c ${PROJECT_SOURCE_DIR}/src/**/*.c)
+file(GLOB_RECURSE SRC CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/*.c)
+add_compile_definitions(PROJECT_VERSION="${PROJECT_VERSION}")
 
 add_executable(obfi ${SRC})
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e994e06
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+.PHONY: all build test help
+all: help
+
+build: ## Build the project
+	@cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=1
+	@cmake --build ./build
+
+test: build ## Run unit tests
+	@(cd ./build && ctest)
+
+help: ## Display help information
+	@grep -E '^[a-zA-Z_-]+:.*?##.*$$' $(MAKEFILE_LIST) | \
+		awk 'BEGIN {FS = ":.*?## *"}; {printf "\033[36m%-6s\033[0m %s\n", $$1, $$2}'
diff --git a/README.md b/README.md
index 603df77..5653c46 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # OBFI
 
-**OBFI** is a minimal and performant Brainfuck interpreter.
+A minimal and performant Brainfuck interpreter.
 
 ## Implementation Details
 
@@ -20,15 +20,7 @@
 
 ## Usage
 
-### Dependencies
-
-- CMake >= 3.12, to build
-
-### Building
-
-To build **OBFI** on UNIX, run `BuildUNIX.sh`.
-
-- Binaries will be located in the `bin` directory.
+To build **OBFI**, from the project root, run `make build`.
 
 ## Meta
 
diff --git a/src/obfi.c b/src/obfi.c
index aa71c96..ed6cd2e 100644
--- a/src/obfi.c
+++ b/src/obfi.c
@@ -1,5 +1,3 @@
-// obfi.c
-// Main source file for OBFI
 // Copyright (C) 2020, Jakob Wakeling
 // MIT Licence
 
@@ -7,93 +5,91 @@
 	FIXME Segfault with large tape lengths and certain programs.
 */
 
-#include "util/error.h"
+#include "util/log.h"
 #include "util/optget.h"
 #include "util/util.h"
 
-#include <stddef.h>
-#include <stdint.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-static struct lop lops[] = {
-	{ "help",    ARG_NUL, 256 },
-	{ "version", ARG_NUL, 257 },
-	{ NULL, 0, 0 }
-};
-
-static size_t l = 30000;
-
 static inline int cmp(const char *s1, const char *s2);
-static inline int run(const char *b, const long *c);
+static inline int run(const char *b, const long *c, u64 len);
 
-static void hlp(void);
-static void ver(void);
+static const char *const help;
+static const char *const version;
 
-int main(int ac, char *av[]) { A0 = av[0];
-	struct opt opt = OPTGET_INIT; opt.str = "l:"; opt.lops = lops;
+int main(int ac, char *av[]) {
+	struct opt opt = OPTGET_INIT; opt.str = "l:"; opt.lops = (struct lop[]){
+		{ "help",    ARG_NUL, 256 },
+		{ "version", ARG_NUL, 256 },
+		{ NULL, 0, 0 }
+	};
+	
+	struct { u64 l; } args = { .l = 30000 };
+	
 	for (int o; (o = optget(&opt, av, 1)) != -1;) switch (o) {
 	case 'l': {
 		register char *s = opt.arg; register int c;
-		for (l = 0; *s >= '0' && *s <= '9'; ++s) { c = *s - '0';
-			if (l > (SIZE_MAX - c) / 10) { break; } l = l * 10 + c;
+		for (args.l = 0; *s >= '0' && *s <= '9'; ++s) { c = *s - '0';
+			if (args.l > (SIZE_MAX - c) / 10) { break; } args.l = args.l * 10 + c;
 		}
 
-		if (*s) { error(1, "%s: invalid tape length", opt.arg); } break;
-	}
-	case 256: { hlp(); return 0; }
-	case 257: { ver(); return 0; }
-	default: { return 1; }
+		if (*s) { log_fatal(-1, "%s: invalid tape length", opt.arg); } break;
+	} break;
+	case 256: { fputs(help, stdout);    } return 0;
+	case 257: { fputs(version, stdout); } return 0;
+	default: {} return -1;
 	}
 
-	if (opt.ind == ac) { error(1, "missing operand"); }
+	if (opt.ind == ac) { log_fatal(-1, "missing operand"); }
 
 	FILE *fi = fopen(av[opt.ind], "r");
-	if (!fi) { error(1, "%s: %s", av[opt.ind], serr()); }
+	if (!fi) { log_fatal(-1, "%s: %s", av[opt.ind], strerror(errno)); }
 
 	fseek(fi, 0, SEEK_END); size_t fl = ftell(fi); rewind(fi);
 
 	char *fb = malloc(fl + 1 * sizeof (*fb)); fb[fl] = 0;
-	if (!fb) { error(1, "%s", serr()); }
+	if (!fb) { log_fatal(-1, "%s", strerror(errno)); }
 
 	long *fc = malloc(fl * sizeof (*fc));
-	if (!fc) { error(1, "%s", serr()); }
+	if (!fc) { log_fatal(-1, "%s", strerror(errno)); }
 
 	fread(fb, 1, fl, fi); fclose(fi);
 
-	// Remove comments from instruction buffer
+	/* Remove comments from instruction buffer */
 	char *p = fb, *q = fb;
 	for (; *p; ++p) if (strchr("><+-.,[]", *p)) { *q++ = *p; } *q = 0;
 
 	register size_t i, j;
 	for (i = 0; fb[i]; ++i) {
-	switch (fb[i]) { // Generate initial operands
+	switch (fb[i]) { /* Generate initial operands */
 	case '<': case '-': { fc[i] = -1; break; }
 	default:            { fc[i] =  1; break; }
 	}
-	switch (fb[i]) { // Standardise instructions
+	switch (fb[i]) { /* Standardise instructions */
 	case '>': case '<': { fb[i] = '>'; break; }
 	case '+': case '-': { fb[i] = '+'; break; }
 	}
 	}
 
-	// Compress movement and additive instructions
+	/* Compress movement and additive instructions */
 	for (i = 0, j = 0; fb[i]; ++j) {
 		fb[j] = fb[i]; fc[j] = fc[i];
 		if (strchr(">+", fb[++i])) for (; fb[j] == fb[i]; fc[j] += fc[i++]);
 	} fb[j] = 0;
 
 	for (i = 0; fb[i]; ++i) {
-		// Optimise set to zero loops
+		/* Optimise set to zero loops */
 		if (cmp(fb + i, "[+]")) { fb[i] = 'Z'; fb[i + 1] = fb[i + 2] = ' '; }
 
-		// Optimise move to zero loops
+		/* Optimise move to zero loops */
 		else if (cmp(fb + i, "[>]")) {
 			fb[i] = 'T'; fb[i + 1] = fb[i + 2] = ' '; fc[i] = fc[i + 1];
 		}
 
-		// Optimise backward and forward move to loops
+		/* Optimise backward and forward move to loops */
 		else if (cmp(fb + i, "[+>+>]") && fc[i + 1] == -1 && fc[i + 3] == 1 &&
 				fc[i + 2] == -fc[i + 4]) {
 			fb[i] = 'M';
@@ -108,34 +104,34 @@ int main(int ac, char *av[]) { A0 = av[0];
 		}
 	}
 
-	// Remove resultant spaces of loop optimisations
+	/* Remove resultant spaces of loop optimisations */
 	for (i = 0, j = 0; fb[i]; ++i) if (fb[i] != ' ') {
 		fb[j] = fb[i]; fc[j] = fc[i]; ++j;
 	} fb[j] = 0;
 
 	size_t l = 1024, t = 0, *S = malloc(l * sizeof (*S));
-	if (!S) { error(1, "%s", serr()); }
+	if (!S) { log_fatal(-1, "%s", strerror(errno)); }
 
-	// Find and store bracket pairs
+	/* Find and store bracket pairs */
 	for (size_t i = 0; fb[i]; ++i) switch (fb[i]) {
 	case '[': { S[t++] = i; break; }
 	case ']': { --t; fc[S[t]] = i - S[t]; fc[i] = S[t] - i; break; }
 	}
 
-	if (run(fb, fc)) { error(1, "%s", serr()); }
+	if (run(fb, fc, args.l)) { log_fatal(-1, "%s", strerror(errno)); }
 
 	free(S); free(fb); free(fc); return 0;
 }
 
-/* Check if s2 is fully matched at the start of s1 */
+/* Check if s2 is fully matched at the start of s1. */
 static inline int cmp(const char *s1, const char *s2) {
 	size_t i = 0; for (; s2[i] && s1[i] == s2[i]; ++i) {} return !s2[i];
 }
 
-/* Execute an array of Brainfuck instructions */
-static inline int run(const char *fb, const long *fc) {
-	uint8_t *M = calloc(l, sizeof (*M)), *p = M;
-	if (!M) { error(1, "%s", serr()); }
+/* Execute an array of Brainfuck instructions. */
+static inline int run(const char *fb, const long *fc, u64 len) {
+	uint8_t *M = calloc(len, sizeof (*M)), *p = M;
+	if (!M) { log_fatal(-1, "%s", strerror(errno)); }
 
 	// Execute each optimised Brainfuck instruction
 	for (size_t i = 0; fb[i]; ++i) switch (fb[i]) {
@@ -153,18 +149,17 @@ static inline int run(const char *fb, const long *fc) {
 	free(M); return 0;
 }
 
-/* Print help information */
-static void hlp(void) {
-	puts("OBFI - Brainfuck Interpreter\n");
-	puts("Usage: obfi file\n");
-	puts("Options:");
-	puts("  --help     Display help information");
-	puts("  --version  Display version information");
-}
+static const char *const help =
+	"OBFI - Brainfuck Interpreter\n"
+	"Usage:\n"
+	"  obfi file\n"
+	"Options:\n"
+	"  --help     Display help information\n"
+	"  --version  Display version information\n"
+;
 
-/* Print version information */
-static void ver(void) {
-	puts("OBFI, version " PROJECT_VERSION);
-	puts("Copyright (C) 2020, Jakob Wakeling");
-	puts("MIT Licence (https://opensource.org/licenses/MIT)");
-}
+static const char *const version =
+	"OBFI, version " PROJECT_VERSION "\n"
+	"Copyright (C) 2020, Jakob Wakeling\n"
+	"MIT Licence (https://opensource.org/licenses/MIT)\n"
+;
diff --git a/src/util/error.c b/src/util/error.c
deleted file mode 100644
index 68f230b..0000000
--- a/src/util/error.c
+++ /dev/null
@@ -1,41 +0,0 @@
-// util/error.h, version 1.1.2
-// Error source file from libutil
-// Copyright (C) 2020, Jakob Wakeling
-// MIT Licence
-
-#include "error.h"
-
-#include <errno.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdnoreturn.h>
-#include <string.h>
-
-char *A0 = NULL;
-bool warned = false;
-
-/* Print an error message and exit. */
-noreturn void error(int status, const char *format, ...) {
-	fflush(stdout); if (A0) { fputs(A0, stderr); fputs(": ", stderr); }
-	va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap);
-	fputc('\n', stderr); exit(status);
-}
-
-/* Print a warning message and set the warned flag. */
-void warn(const char *format, ...) {
-	fflush(stdout); if (A0) { fputs(A0, stderr); fputs(": ", stderr); }
-	va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap);
-	fputc('\n', stderr); warned = true; return;
-}
-
-/* Print a warning message but do not set the warned flag. */
-void alert(const char *format, ...) {
-	fflush(stdout); if (A0) { fputs(A0, stderr); fputs(": ", stderr); }
-	va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap);
-	fputc('\n', stderr); return;
-}
-
-/* Shorthand for strerror(errno). DEPRECIATED, use the SERR macro. */
-char *serr(void) { return strerror(errno); }
diff --git a/src/util/error.h b/src/util/error.h
deleted file mode 100644
index 1dbe3a2..0000000
--- a/src/util/error.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// util/error.h, version 1.1.2
-// Error header file from libutil
-// Copyright (C) 2020, Jakob Wakeling
-// MIT Licence
-
-#ifndef UTIL_ERROR_H_38W06M3W
-#define UTIL_ERROR_H_38W06M3W
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stdnoreturn.h>
-#include <string.h>
-
-/* Warn and then return status */
-#define WARN_R(status, format, ...) do { \
-	warn(format, __VA_ARGS__); return status; \
-} while (0)
-
-/* Warn and then reset errno */
-#define WARN_E(format, ...) do { \
-	warn(format, __VA_ARGS__); errno = 0; \
-} while (0)
-
-/* Warn, reset errno, and then return status */
-#define WARN_RE(status, format, ...) do { \
-	warn(format, __VA_ARGS__); errno = 0; return status; \
-} while (0)
-
-/* Alert and then return status */
-#define ALERT_R(status, format, ...) do { \
-	alert(format, __VA_ARGS__); return status; \
-} while (0)
-
-/* Alert and then reset errno */
-#define ALERT_E(format, ...) do { \
-	alert(format, __VA_ARGS__); errno = 0; \
-} while (0)
-
-/* Alert, reset errno, and then return status */
-#define ALERT_RE(status, format, ...) do { \
-	alert(format, __VA_ARGS__); errno = 0; return status; \
-} while (0)
-
-/* Shorthand for strerror(serrno). */
-#define SERR (strerror(errno))
-
-extern char *A0;
-extern bool warned;
-
-/* Print an error message and exit. */
-extern noreturn void error(int status, const char *format, ...);
-/* Print a warning message and set the warned flag. */
-extern void warn(const char *format, ...);
-/* Print a warning message but do not set the warned flag. */
-extern void alert(const char *format, ...);
-
-/* Shorthand for strerror(errno). DEPRECIATED, use the SERR macro. */
-extern char *serr(void);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // UTIL_ERROR_H_38W06M3W
diff --git a/src/util/log.c b/src/util/log.c
new file mode 100644
index 0000000..fbf33d1
--- /dev/null
+++ b/src/util/log.c
@@ -0,0 +1,14 @@
+// Copyright (C) 2020, Jakob Wakeling
+// MIT Licence
+
+#include "log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+[[noreturn]] void log_fatal(int status, const char *restrict format, ...) {
+	fflush(stderr); va_list args; va_start(args, format);
+	vfprintf(stderr, format, args); fputc('\n', stderr);
+	va_end(args); exit(status);
+}
diff --git a/src/util/log.h b/src/util/log.h
new file mode 100644
index 0000000..d9fdf83
--- /dev/null
+++ b/src/util/log.h
@@ -0,0 +1,9 @@
+// Copyright (C) 2020, Jakob Wakeling
+// MIT Licence
+
+#ifndef OBFI_UTIL_LOG_H_YOZR3H3C
+#define OBFI_UTIL_LOG_H_YOZR3H3C
+
+[[noreturn]] extern void log_fatal(int status, const char *restrict format, ...);
+
+#endif // OBFI_UTIL_LOG_H_YOZR3H3C
diff --git a/src/util/optget.c b/src/util/optget.c
index 62b6272..a21e736 100644
--- a/src/util/optget.c
+++ b/src/util/optget.c
@@ -1,9 +1,6 @@
-// util/optget.h, version 1.6.2
-// optget source file from libutil
 // Copyright (C) 2020, Jakob Wakeling
 // MIT Licence
 
-#include "error.h"
 #include "optget.h"
 
 #include <stddef.h>
@@ -78,12 +75,12 @@ nol:	opt->pos = 0;
 	}
 
 	if (optret == '?' && opt->str[0] != ':') {
-		if (opt->opt) { warn("%c: invalid option", opt->opt); }
-		else if (opt->lop) { warn("%s: invalid option", opt->lop); }
+		if (opt->opt) { fprintf(stderr, "%c: invalid option", opt->opt); }
+		else if (opt->lop) { fprintf(stderr, "%s: invalid option", opt->lop); }
 	}
 	if (optret == ':' && opt->str[0] != ':') {
-		if (opt->opt) { warn("%c: option requires argument", opt->opt); }
-		else if (opt->lop) { warn("%s: option requires argument", opt->lop); }
+		if (opt->opt) { fprintf(stderr, "%c: option requires argument", opt->opt); }
+		else if (opt->lop) { fprintf(stderr, "%s: option requires argument", opt->lop); }
 	}
 
 	return optret;
diff --git a/src/util/optget.h b/src/util/optget.h
index 0ee1b84..c1b30a9 100644
--- a/src/util/optget.h
+++ b/src/util/optget.h
@@ -1,14 +1,8 @@
-// util/optget.h, version 1.6.2
-// optget header file from libutil
 // Copyright (C) 2020, Jakob Wakeling
 // MIT Licence
 
-#ifndef UTIL_OPTGET_H_W3LIZK1S
-#define UTIL_OPTGET_H_W3LIZK1S
-
-#ifdef __cplusplus
-extern "C" {
-#endif
+#ifndef OBFI_UTIL_OPTGET_H_EUX4U3SZ
+#define OBFI_UTIL_OPTGET_H_EUX4U3SZ
 
 #define ARG_NUL 0
 #define ARG_REQ 1
@@ -29,8 +23,4 @@ extern const struct opt OPTGET_INIT;
 
 extern int optget(struct opt *opt, char *av[], int flags);
 
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // UTIL_OPTGET_H_W3LIZK1S
+#endif // OBFI_UTIL_OPTGET_H_EUX4U3SZ
diff --git a/src/util/util.h b/src/util/util.h
index 3d85789..d71b87e 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -1,39 +1,29 @@
-// util/util.h, version 1.0.1
-// Utility header file from libutil
-// Copyright (C) 2021, Jakob Wakeling
+// Copyright (C) 2023, Jakob Wakeling
 // MIT Licence
 
-#ifndef UTIL_UTIL_H_KP8NS9DC
-#define UTIL_UTIL_H_KP8NS9DC
+#ifndef OBFI_UTIL_UTIL_H_R14PPGRZ
+#define OBFI_UTIL_UTIL_H_R14PPGRZ
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <assert.h>
 #include <float.h>
-#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-/* Type Definitions */
 typedef uint8_t   u8;
 typedef uint16_t  u16;
 typedef uint32_t  u32;
 typedef uint64_t  u64;
-typedef uintptr_t UINT;
+typedef uintptr_t uptr;
 
 typedef int8_t   s8;
 typedef int16_t  s16;
 typedef int32_t  s32;
 typedef int64_t  s64;
-typedef intptr_t sint;
+typedef intptr_t sptr;
 
 typedef float       f32;
 typedef double      f64;
 typedef long double f128;
 
-/* Type Limits */
 #define U8_MIN   UINT8_MIN
 #define U8_MAX   UINT8_MAX
 #define U16_MIN  UINT16_MIN
@@ -42,8 +32,8 @@ typedef long double f128;
 #define U32_MAX  UINT32_MAX
 #define U64_MIN  UINT64_MIN
 #define U64_MAX  UINT64_MAX
-#define UINT_MIN UINTPTR_MIN
-#define UINT_MAX UINTPTR_MAX
+#define UPTR_MIN UINTPTR_MIN
+#define UPTR_MAX UINTPTR_MAX
 
 #define S8_MIN   INT8_MIN
 #define S8_MAX   INT8_MAX
@@ -53,8 +43,8 @@ typedef long double f128;
 #define S32_MAX  INT32_MAX
 #define S64_MIN  INT64_MIN
 #define S64_MAX  INT64_MAX
-#define SINT_MIN INTPTR_MIN
-#define SINT_MAX INTPTR_MAX
+#define SPTR_MIN INTPTR_MIN
+#define SPTR_MAX INTPTR_MAX
 
 #define F32_MIN  FLT_MIN
 #define F32_MAX  FLT_MAX
@@ -63,11 +53,4 @@ typedef long double f128;
 #define F128_MIN LDBL_MIN
 #define F128_MAX LDBL_MAX
 
-/* Miscellaneous */
-#define BIT(x) (1 << (x))
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // UTIL_UTIL_H_KP8NS9DC
+#endif // OBFI_UTIL_UTIL_H_R14PPGRZ