Author | Jakob Wakeling <[email protected]> |
Date | 2023-12-28 03:23:49 |
Commit | 78da73c4c5daa6f6b9828bca71816985dc8c7162 |
Parent | 5a79c5977f1a7de29ed9f85fd8e5479c43c7c1d5 |
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