Skip to content

Instantly share code, notes, and snippets.

@jpeoples
Created November 5, 2017 02:42
Show Gist options
  • Save jpeoples/e71d432d1765c1d67f9ebb91b9b906e2 to your computer and use it in GitHub Desktop.
Save jpeoples/e71d432d1765c1d67f9ebb91b9b906e2 to your computer and use it in GitHub Desktop.
A brainfuck interpreter in C with dynamic data array
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum error_codes {
SUCCESS = 0,
ALLOCATION_ERROR,
INVALID_DATA_POINTER_ERROR,
STREAM_OUT_ERROR,
STREAM_IN_ERROR,
UNMATCHED_OPEN_ERROR,
UNMATCHED_CLOSE_ERROR
};
static char *error_msgs[] = {
"",
"Could not allocate necessary data.",
"Tried to decrement data pointer when data pointer is already zero.",
"Could not write byte to output stream.",
"Could not read byte from input stream.",
"Could not find matching bracket ].",
"Could not find matching bracket [."
};
char *match_pairs(char *start, char *begin, char *end, char is, char needs, int dir)
{
int dist = 1;
while (dist > 0) {
start += dir;
if (*start == is) dist++;
if (*start == needs) dist--;
if (start < begin || start >= end) return NULL;
}
return start;
}
struct block_data
{
size_t block_size;
size_t blocks_allocated;
size_t block_array_cap;
unsigned char **blocks;
};
void create_block_data(struct block_data *blks, size_t block_size)
{
blks->block_size = block_size;
blks->blocks_allocated = 0;
blks->block_array_cap = 0;
blks->blocks = NULL;
}
int block_data_grow_array(struct block_data *blks, size_t min_cap)
{
while (min_cap > blks->block_array_cap) {
size_t new_cap = (size_t)(1.5 * blks->block_array_cap);
new_cap = (new_cap > 0) ? new_cap : 12;
if (!(blks->blocks = realloc(blks->blocks, new_cap * sizeof(char *)))) {
return 0;
}
blks->block_array_cap = new_cap;
}
return 1;
}
int block_data_allocate_blocks(struct block_data *blks, size_t up_to)
{
size_t b = blks->blocks_allocated;
for (; b < up_to; ++b) {
if (!(blks->blocks[b] = malloc(blks->block_size * sizeof(unsigned char)))) {
return 0;
}
memset(blks->blocks[b], 0, blks->block_size * sizeof(unsigned char));
}
blks->blocks_allocated = up_to;
return 1;
}
unsigned char *block_data_get_cell(struct block_data *blks, size_t i)
{
size_t block = i / blks->block_size;
size_t ix = i % blks->block_size;
/* If the block is already allocated, we're done */
if (block < blks->blocks_allocated) {
return &blks->blocks[block][ix];
}
/* Otherwise ensure capacity and allocate the block */
if (!block_data_grow_array(blks, block+1))
return NULL;
if (!block_data_allocate_blocks(blks, block+1))
return NULL;
return &blks->blocks[block][ix];
}
void destroy_block_data(struct block_data *blks)
{
size_t i;
for (i = 0; i < blks->blocks_allocated; ++i) {
free(blks->blocks[i]);
blks->blocks[i] = NULL;
}
free(blks->blocks);
}
int interpret(char *begin, char *end, FILE *in, FILE *out, struct block_data *blocks)
{
int r = 0;
char *ip = begin;
size_t data_ptr_ix = 0;
unsigned char *data_ptr = block_data_get_cell(blocks, data_ptr_ix);
if (! data_ptr) return ALLOCATION_ERROR;
while (ip < end) {
switch (*ip) {
case '>':
data_ptr_ix++;
if (!(data_ptr = block_data_get_cell(blocks, data_ptr_ix)))
return ALLOCATION_ERROR;
break;
case '<':
if (data_ptr_ix == 0) return INVALID_DATA_POINTER_ERROR;
data_ptr_ix--;
data_ptr = block_data_get_cell(blocks, data_ptr_ix);
break;
case '+':
*data_ptr += 1;
break;
case '-':
*data_ptr -= 1;
break;
case '.':
if (fputc(*data_ptr, out) == EOF) return STREAM_OUT_ERROR;
break;
case ',':
if (EOF == (r = fgetc(in))) return STREAM_IN_ERROR;
*data_ptr = r;
break;
case '[':
if (!(*data_ptr || (ip = match_pairs(ip, begin, end, '[', ']', 1))))
return UNMATCHED_OPEN_ERROR;
break;
case ']':
if (!(!(*data_ptr) || (ip = match_pairs(ip, begin, end, ']', '[', -1))))
return UNMATCHED_CLOSE_ERROR;
break;
default: /* All other characters are simply ignored */
break;
}
ip++;
}
return SUCCESS;
}
int read_file(const char *fname, char **begin, char **end)
{
FILE *f = fopen(fname, "rb");
/* Get file size */
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
/* Allocate buffer and read data in from file */
char *data = malloc(sz);
if (!data) return 0;
size_t read = fread(data, 1, sz, f);
if (read != sz) {
fprintf(stderr, "Error reading file %s. Read %zd, requested %d\n", fname,read, sz);
free(data);
return 0;
}
*begin = data;
*end = data + sz;
return 1;
}
int main(int argc, char *argv[])
{
char *begin = NULL;
char *end = NULL;
int from_file = 0;
if (argc == 2) {
/* Either interpret a program passed directly on the command
* line*/
begin = argv[1];
end = begin;
while (*end != 0) end++;
} else if (argc == 3) {
/* Or load the program from a file */
if (strcmp(argv[1], "-f")) {
fprintf(stderr, "Bad cmd arg\n");
return 0;
}
if (!read_file(argv[2], &begin, &end)) {
fprintf(stderr, "Could not read file %s\n", argv[2]);
return 0;
}
from_file = 1;
} else {
fprintf(stderr, "Usage:\n bf prog\n bf -f file\n");
return 0;
}
int err;
char *err_msg;
struct block_data blocks;
create_block_data(&blocks, 30000);
if (err = interpret(begin, end, stdin, stdout, &blocks)) {
fprintf(stderr, "ERROR: %s\n", error_msgs[err]);
}
destroy_block_data(&blocks);
/* If we loaded from file we need to free the instruction buffer */
if (from_file) free(begin);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment