0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
|
// Copyright (C) 2020, Jakob Wakeling
// MIT Licence
#include "util/log.h"
#include "util/optget.h"
#include "util/util.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static inline int cmp(const char *s1, const char *s2);
static inline int run(const char *b, const u64 *c, uptr len);
static const char *const help;
static const char *const version;
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, 257 },
{ NULL, 0, 0 }
};
struct { uptr 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 (args.l = 0; *s >= '0' && *s <= '9'; s += 1) { c = *s - '0';
if (args.l > (UPTR_MAX - c) / 10) { break; } args.l = args.l * 10 + c;
}
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) { log_fatal(-1, "missing operand"); }
/* Open and read source file */
FILE *file = fopen(av[opt.ind], "r");
if (!file) { log_fatal(-1, "%s: %s", av[opt.ind], strerror(errno)); }
fseek(file, 0, SEEK_END); uptr fl = ftell(file); rewind(file);
/* Instruction operators */
char *fb = malloc(fl + 1 * sizeof (*fb)); fb[fl] = 0;
if (!fb) { log_fatal(-1, "%s", strerror(errno)); }
/* Instruction operands */
u64 *fc = malloc(fl * sizeof (*fc));
if (!fc) { log_fatal(-1, "%s", strerror(errno)); }
fread(fb, 1, fl, file); fclose(file);
/* Remove comments from instruction buffer */
register char *p = fb, *q = fb; for (; *p; p += 1) {
if (strchr("><+-.,[]", *p)) { *q = *p; q += 1; }
} *q = 0;
register uptr i, j;
for (i = 0; fb[i]; i += 1) {
switch (fb[i]) { /* Generate initial operands */
case '<': case '-': { fc[i] = -1; break; }
default: { fc[i] = 1; break; }
}
switch (fb[i]) { /* Standardise instructions */
case '>': case '<': { fb[i] = '>'; break; }
case '+': case '-': { fb[i] = '+'; break; }
}
}
/* Compress movement and additive instructions */
for (i = 0, j = 0; fb[i]; j += 1) {
fb[j] = fb[i]; fc[j] = fc[i];
if (strchr(">+", fb[i += 1])) for (; fb[j] == fb[i]; fc[j] += fc[i], i += 1);
} fb[j] = 0;
for (i = 0; fb[i]; i += 1) {
/* Optimise set to zero loops */
if (cmp(fb + i, "[+]")) { fb[i] = 'Z'; fb[i + 1] = fb[i + 2] = ' '; }
/* 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 */
else if (cmp(fb + i, "[+>+>]") && fc[i + 1] == -1 && fc[i + 3] == 1 && fc[i + 2] == -fc[i + 4]) {
fb[i] = 'M';
fb[i + 1] = fb[i + 2] = fb[i + 3] = fb[i + 4] = fb[i + 5] = ' ';
fc[i] = fc[i + 2];
}
else if (cmp(fb + i, "[>+>+]") && fc[i + 4] == -1 && fc[i + 2] == 1 && fc[i + 1] == -fc[i + 3]) {
fb[i] = 'M';
fb[i + 1] = fb[i + 2] = fb[i + 3] = fb[i + 4] = fb[i + 5] = ' ';
fc[i] = fc[i + 1];
}
}
/* Remove resultant spaces of loop optimisations */
for (i = 0, j = 0; fb[i]; i += 1) if (fb[i] != ' ') {
fb[j] = fb[i]; fc[j] = fc[i]; j += 1;
} fb[j] = 0;
uptr l = 1024, t = 0, *S = malloc(l * sizeof (*S));
if (!S) { log_fatal(-1, "%s", strerror(errno)); }
/* Find and store bracket pairs */
for (uptr i = 0; fb[i]; i += 1) switch (fb[i]) {
case '[': { S[t] = i; t += 1; break; }
case ']': { --t; fc[S[t]] = i - S[t]; fc[i] = S[t] - i; break; }
}
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. */
static inline int cmp(const char *s1, const char *s2) {
uptr i = 0; for (; s2[i] && s1[i] == s2[i]; i += 1) {} return !s2[i];
}
/* Execute an array of Brainfuck instructions. */
static inline int run(const char *fb, const u64 *fc, uptr len) {
u8 *M = calloc(len, sizeof (*M)), *p = M + 256; /* Allow slight movement into "negative" memory */
if (!M) { log_fatal(-1, "%s", strerror(errno)); }
for (uptr i = 0; fb[i]; i += 1) switch (fb[i]) {
case '>': { p += fc[i]; } break;
case '+': { *p += fc[i]; } break;
case '.': { fputc(*p, stdout); } break;
case ',': { int c = fgetc(stdin); *p = c != EOF ? c : 0; } break;
case '[': { i += !*p ? fc[i] : 0; } break;
case ']': { i += *p ? fc[i] : 0; } break;
case 'Z': { *p = 0; } break;
case 'T': { for (; *p; p += fc[i]); } break;
case 'M': { *(p + fc[i]) += *p; *p = 0; } break;
}
free(M); return 0;
}
static const char *const help =
"OBFI - Brainfuck Interpreter\n"
"Usage:\n"
" obfi file\n"
"Options:\n"
" -l length Set tape length\n"
" --help Display help information\n"
" --version Display version information\n"
;
static const char *const version =
"OBFI, version " VERSION "\n"
"Copyright (C) 2020, Jakob Wakeling\n"
"MIT Licence (https://opensource.org/licenses/MIT)\n"
;
|