summaryrefslogtreecommitdiff
path: root/skullfuck.c
diff options
context:
space:
mode:
authorjakob <jakob@memeware.net>2017-03-15 20:14:00 -0400
committerjakob <jakob@memeware.net>2017-03-15 20:14:00 -0400
commit4e2b1c27f0dc61d613d3495834e354ccbd5f66b6 (patch)
treecafce38cc21befcd9123e7c21acabc04f594416a /skullfuck.c
Initial commit.
Diffstat (limited to 'skullfuck.c')
-rw-r--r--skullfuck.c270
1 files changed, 270 insertions, 0 deletions
diff --git a/skullfuck.c b/skullfuck.c
new file mode 100644
index 0000000..e98ea56
--- /dev/null
+++ b/skullfuck.c
@@ -0,0 +1,270 @@
+/* Skullfuck -- A dead simple, non-optimizing Brainfuck compiler.
+
+ Copyright (C) 2017 Jakob Kreuze, All Rights Reserved.
+
+ Skullfuck is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at
+ your option) any later version.
+
+ Skullfuck is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Skullfuck. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <getopt.h>
+
+
+/* Constants specific to the target architecture. */
+#ifndef MAX_CELLS
+#define MAX_CELLS 30000
+#endif
+
+#ifndef MAX_LOOP_DEPTH
+#define MAX_LOOP_DEPTH 64
+#endif
+
+
+/* Constants for general usage. */
+#define EXIT_SUCCESS 0
+#define EXIT_FAILURE 1
+
+#define VERSION "0.1.0"
+
+
+/* Boilerplate assembly to initialize a memory region on the .bss
+ segment, and load registers with some constant values. */
+#define PRELUDE "\t.section .bss\n" \
+ "\t.comm mem, %d\n" \
+ "\t.section .text\n" \
+ "\t.globl _start\n" \
+ "_start:\n" \
+ "\tmovq $mem, %%rsi\n" \
+ "\tmovq $0x01, %%rdx\n"
+
+
+
+/* Structure for storing runtime parameters. */
+struct params {
+ bool compile_only;
+ char *in, *out, *as;
+};
+
+
+/* A stack data structure for storing loop depth. */
+struct loop_stack {
+ int _mem[MAX_LOOP_DEPTH];
+ int _stack_index;
+};
+
+/* Pushes `n` onto the stack specified by `s`. */
+static void push_loop_index(struct loop_stack *s, int n) {
+ s->_mem[s->_stack_index] = n;
+ s->_stack_index++;
+}
+
+/* Pops the top of `s` off and returns it. */
+static int pop_loop_index(struct loop_stack *s) {
+ s->_stack_index--;
+ return s->_mem[s->_stack_index];
+}
+
+
+/* Converts the brainfuck instructions read from `in` to assembly
+ instructions and writes them to `out`. `in` is expected to be
+ null-terminated. */
+static void write_instructions(FILE *out, char *in) {
+ int next_loop = 0, cur_loop = 0;
+ struct loop_stack *loops = calloc(sizeof(struct loop_stack), 1);
+
+ for (int i = 0; in[i] != '\0'; i++) {
+ switch (in[i]) {
+ case '>':
+ fputs("\tincq %rsi\n", out);
+ break;
+ case '<':
+ fputs("\tdecq %rsi\n", out);
+ break;
+ case '+':
+ fputs("\tincb (%rsi)\n", out);
+ break;
+ case '-':
+ fputs("\tdecb (%rsi)\n", out);
+ break;
+ case '.':
+ fputs("\tmovq $0x01, %rax\n", out);
+ fputs("\tmovq $0x01, %rdi\n", out);
+ fputs("\tsyscall\n", out);
+ break;
+ case ',':
+ fputs("\tmovq $0x00, %rax\n", out);
+ fputs("\tmovq $0x00, %rdi\n", out);
+ fputs("\tsyscall\n", out);
+ break;
+ case '[':
+ cur_loop = next_loop;
+ push_loop_index(loops, next_loop++);
+ fprintf(out, "S%d:\n", cur_loop);
+ fputs("\tmovb (%rsi), %cl\n", out);
+ fputs("\tcmpb $0x00, %cl\n", out);
+ fprintf(out, "\tje E%d\n", cur_loop);
+ break;
+ case ']':
+ cur_loop = pop_loop_index(loops);
+ fprintf(out, "E%d:\n", cur_loop);
+ fputs("\tmovb (%rsi), %cl\n", out);
+ fputs("\tcmpb $0x00, %cl\n", out);
+ fprintf(out, "\tjne S%d\n", cur_loop);
+ break;
+ }
+ }
+ fputs("\tmovq $0x3c, %rax\n", out);
+ fputs("\tmovq $0x00, %rdi\n", out);
+ fputs("\tsyscall\n", out);
+
+ free(loops);
+}
+
+
+/* Prints error information and terminates with minimal cleanup. */
+static void panic(char *msg) {
+ perror(msg);
+ fprintf(stderr, "Fatal error encountered. Terminating!\n");
+ exit(EXIT_FAILURE);
+}
+
+
+/* Attempts to create a new file at `path`, and writes a generic prelude
+ to initialize memory sections. If `path` is NULL, a temporary file
+ will be created. A FILE pointer will be returned, or NULL if the file
+ could not be created. */
+static FILE *out_init(char *path) {
+ FILE *fp = path == NULL ? tmpfile() : fopen(path, "w+");
+ if (fp == NULL) return fp;
+ fprintf(fp, PRELUDE, MAX_CELLS);
+ return fp;
+}
+
+
+/* Returns a buffer containing the contents of `path`. */
+static char *read_infile(char *path) {
+ FILE *fp = fopen(path, "r");
+ if (fp == NULL) panic(path);
+ fseek(fp, 0, SEEK_END);
+ size_t len = ftell(fp);
+ char *in = malloc(len);
+ fseek(fp, 0, SEEK_SET);
+ fread(in, len, 1, fp);
+ fclose(fp);
+ return in;
+}
+
+
+/* Assembles and links the compiled source code into a binary. */
+static void create_binary(struct params p) {
+ pid_t pid;
+ char *const as_argv[] = {"as", "-o", "/tmp/skullfuck_tmp.o", p.as, NULL};
+ char *const ld_argv[] = {"ld", "-o", p.out, "/tmp/skullfuck_tmp.o", NULL};
+
+ if ((pid = fork()) == -1) {
+ panic("fork");
+ } else if (pid == 0) {
+ execv("/bin/as", as_argv);
+ } else {
+ wait(NULL);
+ if ((pid = fork()) == -1) {
+ panic("fork");
+ } else if (pid == 0) {
+ execv("/bin/ld", ld_argv);
+ }
+ }
+}
+
+
+/* Parses command-line arguments into a params structure. */
+static struct params parse_args(int argc, char **argv) {
+ struct params p = {0};
+
+ if (argc < 2) {
+ fprintf(stderr, "USAGE: %s [options] (infile)\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ int cur = 0, opt_index = 0, count = 0;
+ static struct option long_opts[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'v'},
+ {NULL, 0, NULL, 0}
+ };
+
+ do {
+ count++;
+ cur = getopt_long(argc, argv, "hvSo:", long_opts, &opt_index);
+ switch (cur) {
+ case 'h':
+ printf("A dead simple, non-optimizing Brainfuck compiler.\n\n"
+ "\t-h, --help\tDisplay this help page and exit.\n"
+ "\t-v, --version\tDisplay versioning information and "
+ "exit\n\n"
+ "\t-S\t\tStop after the stage of compilation proper; "
+ "do not assemble.\n"
+ "\t-o file\t\tPlace the output in file.\n");
+ exit(EXIT_SUCCESS);
+ case 'v':
+ printf("Skullfuck compiler version %s\nProgrammed by Jakob "
+ "<http://jakob.space>\n", VERSION);
+ exit(EXIT_SUCCESS);
+ case 'S':
+ p.compile_only = true;
+ break;
+ case 'o':
+ count++;
+ p.out = optarg;
+ break;
+ }
+ } while (cur >= 0);
+
+ if (count >= argc) {
+ fprintf(stderr, "Fatal error: No input files.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ p.in = argv[count];
+
+ p.as = "/tmp/skullfuck_tmp.s";
+ if (p.compile_only) {
+ p.out = p.out == NULL ? "./out.s" : p.out;
+ p.as = p.out;
+ } else if (p.out == NULL) {
+ p.out = "./a.out";
+ }
+
+ return p;
+}
+
+
+int main(int argc, char **argv) {
+ struct params p = parse_args(argc, argv);
+ FILE *fp = out_init(p.as);
+
+ if (fp == NULL) panic(p.as);
+ char *in = read_infile(p.in);
+ write_instructions(fp, in);
+ fclose(fp);
+
+ if (!p.compile_only)
+ create_binary(p);
+
+ free(in);
+ return EXIT_SUCCESS;
+}