diff options
| author | jakob <jakob@memeware.net> | 2017-03-15 20:14:00 -0400 |
|---|---|---|
| committer | jakob <jakob@memeware.net> | 2017-03-15 20:14:00 -0400 |
| commit | 4e2b1c27f0dc61d613d3495834e354ccbd5f66b6 (patch) | |
| tree | cafce38cc21befcd9123e7c21acabc04f594416a /skullfuck.c | |
Initial commit.
Diffstat (limited to 'skullfuck.c')
| -rw-r--r-- | skullfuck.c | 270 |
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; +} |