Last active
April 18, 2021 19:21
-
-
Save luavixen/3a586c1add36c312795da0516dbecef6 to your computer and use it in GitHub Desktop.
Speedy Brainfuck interpreter with a convienent REPL.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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