See this post for the previous question. I have made several enhancements to this compiler and language that make it a lot easier to work with, as well as slightly decreasing executable size (if new features are used properly). I've decided to call the new language brainfix, as I don't think anyone has used this name before.
Here's the compiler:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void usage(const char *);
void compile_and_write(int);
void print_header(void);
void print_footer(void);
static FILE *in = NULL;
static FILE *out = NULL;
int main(int argc, char *argv[])
{
int ch;
int opt;
while ((opt = getopt(argc, argv, "i:o:h")) != -1) {
switch (opt) {
case 'i':
in = fopen(optarg, "r");
break;
case 'o':
out = fopen(optarg, "w");
break;
case 'h':
usage(argv[0]);
return EXIT_SUCCESS;
default:
usage(argv[0]);
return EXIT_FAILURE;
}
}
in = in ? in : stdin;
out = out ? out : stdout;
print_header();
while ((ch = fgetc(in)) != EOF) {
compile_and_write(ch);
}
compile_and_write('\0');
print_footer();
fclose(in);
fclose(out);
return EXIT_SUCCESS;
}
void print_header(void)
{
fputs("[bits 64]\n"
"[section .bss]\n"
"mem: resb 32768\n"
"hld: resb 1\n"
"[section .text]\n"
"[global _start]\n"
"putc:\n"
"xor rax, rax\n"
"xor rdi, rdi\n"
"xor rdx, rdx\n"
"inc rax\n"
"inc rdi\n"
"inc rdx\n"
"syscall\n"
"ret\n"
"getc:\n"
"xor rax, rax\n"
"xor rdi, rdi\n"
"xor rdx, rdx\n"
"inc rdx\n"
"syscall\n"
"ret\n"
"exit:\n"
"mov rax, 60\n"
"movzx rdi, byte [rsi]\n"
"syscall\n"
"ret\n"
"_start:\n"
"mov rsi, mem\n", out);
}
void print_footer(void)
{
fputs("mov byte [rsi], 0\n"
"call exit\n", out);
}
void compile_and_write(int ch)
{
static int loop = 0;
static int loops[0x10000] = { 0 };
static size_t lp = 0;
static unsigned cnt = 1;
static int last = ' ';
static unsigned call = 0;
static int comment = 0;
if (ch == '#') {
comment = comment ? 0 : 1;
}
if (comment) {
return;
}
if ((last == '>' && ch == '<') ||
(last == '<' && ch == '>') ||
(last == '+' && ch == '-') ||
(last == '-' && ch == '+') ||
(last == '(' && ch == ')') ||
(last == ')' && ch == '(') ||
(last == '^' && ch == '^')) {
/* If two conflicting directives that together have no
* effect whatsoever appear, do nothing and don't write
* them out. */
goto cleanup_exit;
}
if (ch == last) {
++cnt;
return;
}
switch (last) {
case '+':
if (cnt == 1) {
fputs("inc byte [rsi]\n", out);
} else {
fprintf(out, "add byte [rsi], %u\n", cnt);
}
break;
case '-':
if (cnt == 1) {
fputs("dec byte [rsi]\n", out);
} else {
fprintf(out, "sub byte [rsi], %u\n", cnt);
}
break;
case '>':
if (cnt == 1) {
fputs("inc rsi\n", out);
} else {
fprintf(out, "add rsi, %u\n", cnt);
}
break;
case '<':
if (cnt == 1) {
fputs("dec rsi\n", out);
} else {
fprintf(out, "sub rsi, %u\n", cnt);
}
break;
case '.':
if (cnt < 4) while (cnt--) {
fputs("call putc\n", out);
} else {
fprintf(out, "mov rcx, %u\n", cnt);
fprintf(out, "C%u:\n", call);
fputs("call putc\n", out);
fprintf(out, "loop C%u\n", call++);
}
break;
case ',':
if (cnt < 4) while (cnt--) {
fputs("call getc\n", out);
} else {
fprintf(out, "mov rcx, %u\n", cnt);
fprintf(out, "C%u:\n", call);
fputs("call getc\n", out);
fprintf(out, "loop C%u\n", call++);
}
break;
case '[':
while (cnt--) {
loops[lp] = loop++;
fprintf(out, "L%d:\n", loops[lp]);
fprintf(out, "cmp byte [rsi], 0\n");
fprintf(out, "jz E%d\n", loops[lp]);
++lp;
}
break;
case ']':
while (cnt--) {
--lp;
fprintf(out, "jmp L%d\n", loops[lp]);
fprintf(out, "E%d:\n", loops[lp]);
}
break;
case '$':
fputs("mov al, byte [rsi]\n", out);
fputs("mov byte [hld], al\n", out);
break;
case '!':
fputs("mov al, byte [hld]\n", out);
fputs("mov byte [rsi], al\n", out);
break;
case '^':
if (cnt % 2) {
fputs("mov al, byte [rsi]\n", out);
fputs("xor al, byte [hld]\n", out);
fputs("mov byte [rsi], al\n", out);
}
break;
case '|':
if (cnt % 2) {
fputs("mov al, byte [rsi]\n", out);
fputs("xor al, byte [hld]\n", out);
fputs("mov byte [rsi], al\n", out);
}
break;
case '~':
if (cnt % 2) {
fputs("not byte [rsi]\n", out);
}
break;
case '&':
if (cnt % 2) {
fputs("mov al, byte [rsi]\n", out);
fputs("and al, byte [hld]\n", out);
fputs("mov byte [rsi], al\n", out);
}
break;
case '}':
fprintf(out, "shr byte [rsi], %u\n", cnt);
break;
case '{':
fprintf(out, "shl byte [rsi], %u\n", cnt);
break;
case ')':
fprintf(out, "ror byte [rsi], %u\n", cnt);
break;
case '(':
fprintf(out, "rol byte [rsi], %u\n", cnt);
break;
case '@':
fputs("call exit\n", out);
break;
case '%':
fputs("nop\n", out);
break;
default:
break;
}
cleanup_exit:
last = ch;
cnt = 1;
}
void usage(const char *pname)
{
fprintf(stderr, "usage: %s [-h] -i input -o output\n", pname);
}
Changes from previous post:
- new language features
- change
static
functions to normal ones - removed useless header files
- used ternary operator instead of if statements
Compilation command:
$ clang -O2 -Weverything -Werror -o bfc bfc.c
Testing:
$ ./bfc -i mandelbrot.bf -o mandelbrot.asm
$ nasm -f elf64 mandelbrot.asm
$ ld.gold -s -o mandelbrot mandelbrot.o
$ strip -s mandelbrot
$ ./mandelbrot
... mandelbrot fractal appears here ...
I also made a quick document summarising the changes from vanilla Brainfuck if anyone's interested. If it's off topic or something I'll remove it:
Compiler Directives
===================
Standard Brainf*ck
------------------
+ increment the value of current cell
- decrement the value of current cell
> increment pointer
< decrement pointer
[ jump to ] if cell value is zero, else jump past
] jump to [
. output current cell as ascii
, input to current cell as ascii
Brainfix Extensions
-------------------
$ place value of current cell into hold space (NOT a stack!).
! place value of hold space into current cell.
^ xor hold space with current cell and place result in cell.
| or hold space with current cell and place result in cell.
& and hold space with current cell and place result in cell.
~ not hold space with current cell and place result in cell.
} shift right current cell by 1.
{ shift left current cell by 1.
) rotate right current cell by 1.
( rotate left current cell by 1.
@ exit program with status as value of current cell.
% instruct compiler to emit literal NOP instruction, unoptimised.
Comments are started and ended by # character. Multi line comments are
permitted.
Language Idioms
===============
[-] set current cell to zero.
$^ set current cell to zero in more efficient manner.
[>] find and move to zero cell to the right of ptr
[<] find and move to zero cell to the left of ptr
[.>] print null terminated string
$^> ,[>,] <[<]>
get delimited string until EOF and move to start of string
<!-- language: lang-none -->
just before it in your post. \$\endgroup\$