Skip to content

Instantly share code, notes, and snippets.

@luavixen
Last active April 18, 2021 19:21
Show Gist options
  • Save luavixen/3a586c1add36c312795da0516dbecef6 to your computer and use it in GitHub Desktop.
Save luavixen/3a586c1add36c312795da0516dbecef6 to your computer and use it in GitHub Desktop.
Speedy Brainfuck interpreter with a convienent REPL.
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <setjmp.h>
#include <errno.h>
#include <unistd.h>
#include <readline/readline.h>
typedef uint8_t byte;
static inline void *allocate(void *ptr, size_t size) {
ptr = realloc(ptr, size);
if (!ptr) {
fputs("Out of memory!", stderr);
exit(EXIT_FAILURE);
}
return ptr;
}
typedef enum error_code {
ERR_OK,
ERR_OVERFLOW,
ERR_UNDERFLOW,
ERR_CLOSE_EXPECTED,
ERR_CLOSE_UNEXPECTED
} error_code;
static const char *const error_messages[] = {
/* ERR_OK */ "OK",
/* ERR_OVERFLOW */ "Tried to go past end of tape",
/* ERR_UNDERFLOW */ "Tried to go past start of tape",
/* ERR_CLOSE_EXPECTED */ "Expected a closing bracket ']' to close loop",
/* ERR_CLOSE_UNEXPECTED */ "Unexpected closing bracket ']'"
};
static inline const char *error_message(error_code code) {
return error_messages[code];
}
static jmp_buf error_jump;
static inline void error(error_code code) {
longjmp(error_jump, code);
}
static inline void ensure(int condition, error_code code) {
#ifndef UNSAFE
if (!condition) error(code);
#endif
}
#define TAPE_SIZE 3000
static byte tape[TAPE_SIZE] = {0};
static byte *head = tape;
const char *step(const char *scan) {
const char *scan_start = scan;
static void *dispatch_table[256] = {
[0 ... 255] = &&op_ignore,
[0] = &&op_return,
['>'] = &&op_next,
['<'] = &&op_prev,
['+'] = &&op_inc,
['-'] = &&op_dec,
['.'] = &&op_output,
[','] = &&op_input,
['['] = &&op_loop,
[']'] = &&op_end,
['@'] = &&op_debug,
['^'] = &&op_reset
};
#define NEXT goto *dispatch_table[(byte)(*(scan++))]
op_ignore:
NEXT;
op_next:
ensure(head < tape + sizeof(tape), ERR_OVERFLOW);
head++;
NEXT;
op_prev:
ensure(head > tape, ERR_UNDERFLOW);
head--;
NEXT;
op_inc:
*head += 1;
NEXT;
op_dec:
*head -= 1;
NEXT;
op_output:
putchar(*head);
NEXT;
op_input:
*head = getchar();
NEXT;
op_loop:
if (*head) {
scan = step(scan);
ensure(scan != NULL, ERR_CLOSE_EXPECTED);
} else {
for (size_t level = 1; level; ) {
char character = *scan++;
ensure(character != 0, ERR_CLOSE_EXPECTED);
switch (character) {
case '[':
level++;
break;
case ']':
level--;
break;
}
}
}
NEXT;
op_end:
if (*head) {
scan = scan_start;
} else {
return scan;
}
NEXT;
op_debug:
printf(
"Cell #%u\n %02hhX %02hhX %02hhX %02hhX %02hhX\n ^^\n",
(unsigned int)(head - tape + 1),
(byte)(head - 2 < tape ? 0xFF : head[-2]),
(byte)(head - 1 < tape ? 0xFF : head[-1]),
(byte)(head[0]),
(byte)(head + 1 > (tape + sizeof(tape)) ? 0xFF : head[1]),
(byte)(head + 2 > (tape + sizeof(tape)) ? 0xFF : head[2])
);
NEXT;
op_reset:
memset(tape, 0, TAPE_SIZE);
head = tape;
NEXT;
op_return:
return NULL;
}
static error_code interpret(const char *text) {
static byte tape_copy[TAPE_SIZE];
byte *head_copy = head;
memcpy(tape_copy, tape, TAPE_SIZE);
error_code code = setjmp(error_jump);
if (code) {
head = head_copy;
memcpy(tape, tape_copy, TAPE_SIZE);
return code;
}
const char *scan = step(text);
ensure(scan == NULL, ERR_CLOSE_UNEXPECTED);
return ERR_OK;
}
typedef struct dstring {
size_t length, capacity;
char *body;
} dstring;
static void dstring_init(dstring *this, size_t capacity) {
if (capacity < 8) capacity = 8;
this->length = 0;
this->capacity = capacity;
this->body = allocate(NULL, capacity);
this->body[0] = 0;
}
static void dstring_free(dstring *this) {
free(this->body);
this->length = 0;
this->capacity = 0;
this->body = NULL;
}
static void dstring_clear(dstring *this) {
this->length = 0;
this->body[0] = 0;
}
static void dstring_resize(dstring *this, size_t reqcap) {
size_t curcap = this->capacity;
if (curcap < reqcap) {
curcap *= 2;
if (curcap < reqcap) curcap = reqcap;
this->body = allocate(this->body, this->capacity = curcap);
}
}
static void dstring_append_char(dstring *this, char value) {
dstring_resize(this, this->length + 1);
this->body[this->length] = value;
this->body[this->length += 1] = 0;
}
static void dstring_append_bytes(dstring *this, const byte *buffer, size_t buffer_length) {
dstring_resize(this, this->length + buffer_length);
memcpy(this->body + this->length, buffer, buffer_length);
this->body[this->length += buffer_length] = 0;
}
static void dstring_append_string(dstring *this, const char *string) {
dstring_append_bytes(this, (const byte *)string, strlen(string));
}
static void dstring_readline(dstring *this, const char *prompt) {
char *line = readline(prompt);
dstring_append_string(this, line);
dstring_append_char(this, '\n');
free(line);
}
static void dstring_readfile(dstring *this, FILE *file) {
#define BUFFER_SIZE 65536
byte buffer[BUFFER_SIZE];
while (1) {
int count = fread(buffer, 1, BUFFER_SIZE, file);
if (ferror(file)) {
fprintf(stderr, "file read failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
dstring_append_bytes(this, buffer, count);
if (feof(file)) break;
}
}
static int executefile(FILE *file) {
dstring text;
dstring_init(&text, 65536);
dstring_readfile(&text, file);
fclose(file);
error_code code = interpret(text.body);
if (code) {
fprintf(
stderr,
"%s error: %s\n",
code == ERR_CLOSE_EXPECTED || code == ERR_CLOSE_UNEXPECTED
? "syntax"
: "runtime",
error_message(code)
);
}
dstring_free(&text);
return code ? code + 10 : code;
}
static int interactive(void) {
error_code code;
dstring text;
dstring_init(&text, 64);
while (1) {
dstring_clear(&text);
dstring_readline(&text, "\xCE\xBB ");
if (strcmp(text.body, "exit\n") == 0) break;
while (code = interpret(text.body)) {
if (code == ERR_CLOSE_EXPECTED) {
dstring_readline(&text, "\xE2\x80\xA6 ");
} else {
fprintf(stderr, "\xE2\x81\x87 %s\n", error_message(code));
break;
}
}
}
dstring_free(&text);
return EXIT_SUCCESS;
}
int main(int argc, char **argv) {
if (argc > 1) {
FILE *file = fopen(argv[1], "r");
if (!file) {
fprintf(stderr, "file open failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
return executefile(file);
}
if (isatty(STDIN_FILENO)) {
return interactive();
}
return executefile(stdin);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment