While writing a review of @MotokoKusanagi's Brainfuck interpreter, I decided to write my own implementation to illustrate a few points. In particular, I'd like it to be robust to malformed programs, and reasonably efficient (short of JITting the Brainfuck code).
Please point out any deficiencies you might find in this implementation.
#include <fcntl.h> #include <libgen.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <unistd.h> typedef struct { char *begin, // Pointer to beginning of program *end, // Pointer just beyond the end of the program **jumps; // Pointer to jump table } program; /** * Fills in p.jumps, the jump table. For each '[' or ']' character * in the code, the pointer at the corresponding offset in p.jumps * is set to the matching ']' or '[' (or NULL if it is mismatched). * This function calls itself recursively to handle nesting. */ static char *build_jump_table(program p, char *begin) { char *c = begin; while (c < p.end) { ptrdiff_t i = c - p.begin; switch (*c) { case '[': p.jumps[i] = NULL; // In case no matching ']' is ever found p.jumps[i] = c = build_jump_table(p, c + 1); if (!c) return NULL; // Error: no closing bracket break; case ']': p.jumps[i] = (begin > p.begin) ? begin - 1 : // Normal case NULL; // Error: no opening bracket return c; } c++; } return NULL; } /** * Loads the program from the specified path. On failure, all members of * the returned program will be NULL, and errno is set. */ program load_program(const char *path) { const program FAILURE = { .begin = NULL, .end = NULL, .jumps = NULL }; int fd; struct stat stat_buf; if ( -1 == (fd = open(path, O_RDONLY)) || -1 == fstat(fd, &stat_buf) ) { return FAILURE; } int size = stat_buf.st_size; char *text = malloc(size); if ( (text == NULL) || (size != read(fd, text, size)) ) { free(text); close(fd); return FAILURE; } close(fd); program p = { .begin = text, .end = text + size, .jumps = malloc(size) }; if (p.jumps == NULL) { free(text); return FAILURE; } build_jump_table(p, p.begin); return p; } void free_program(program p) { free(p.jumps); free(p.begin); } int execute_program(program p) { unsigned char mem[30000] = { 0 }; unsigned char *ptr = mem; char *ip = p.begin; while (ip < p.end) { ptrdiff_t i = ip - p.begin; switch (*ip) { case '<': if (--ptr < mem) return i + 1; break; case '>': if (++ptr >= mem + sizeof mem) return i + 1; break; case '-': (*ptr)--; break; case '+': (*ptr)++; break; case '.': putchar(*ptr); fflush(stdout); break; case ',': *ptr = getchar(); break; case '[': if (!*ptr) ip = p.jumps[i]; break; case ']': if ( *ptr) ip = p.jumps[i]; break; } if (!ip++) { return i + 1; } } return 0; } void usage(FILE *f, char *argv[]) { fprintf(f, "Usage: %s PROGRAM.bf\n", basename(argv[0])); } int main(int argc, char *argv[]) { if (argc <= 1) { usage(stderr, argv); return 1; } program prog = load_program(argv[1]); if (!prog.begin) { perror("Error: Could not load program"); return 2; } int error_offset = execute_program(prog); if (error_offset > 0) { fprintf(stderr, "Error at offset %d\n", error_offset); } free_program(prog); }