aboutsummaryrefslogtreecommitdiff
path: root/bfc.c
blob: 8a65cd84ba2878b939fbf554a57ef601e02718c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#define _XOPEN_SOURCE 500
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#ifdef __linux__
#define NASM_FORMAT "elf64"
#endif
#ifdef __APPLE__
#define NASM_FORMAT "macho64"
#endif
#ifndef NASM_FORMAT
#error "architecture not supported"
#endif

#define LABEL_COUNT_STACK_SIZE 4096
static size_t label_stack[LABEL_COUNT_STACK_SIZE + 1] = {0};
static size_t label_stack_frame = 0;

static bool   compile_to_exec = true;
static char  *output_filename = NULL;
static size_t buffer_len = 256;
static size_t buffer_elem_bytes = 1;

static char *elem_bytes_asm_str = "byte";
static char *elem_bytes_asm_str_res = "resb";

#define TMP_FILEPATH_TEMPLATE "/tmp/bfc_asm_XXXXXX"
#define TMP_OBJ_FILEPATH_TEMPLATE (TMP_FILEPATH_TEMPLATE ".o")
static char tmp_filepath[] = TMP_FILEPATH_TEMPLATE;
static char tmp_obj_filepath[] = TMP_OBJ_FILEPATH_TEMPLATE;

static void
die(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    fputs("bfc: ", stderr);
    vfprintf(stderr, fmt, ap);
    fputc('\n', stderr);
    va_end(ap);
    exit(EXIT_FAILURE);
}

static size_t
xatoul(char *s)
{
    if (!isdigit(*s))
        die("'%s' is not a valid number", s);
    char  *end;
    size_t num = strtoul(s, &end, 10);
    if (*end != '\0')
        die("'%s' is not a valid number", s);
    return num;
}

static void
run_subprocess(char *name, char *const argv[])
{
    pid_t pid = fork();
    if (pid == -1)
        die("%s", strerror(errno));
    if (pid == 0)
    {
        execvp(name, argv);
        die("%s", strerror(errno));
    }
    waitpid(pid, &pid, 0);
    int status = WEXITSTATUS(pid);
    if (status != 0)
        die("%s process failed with %d\n", name, status);
}

static void
remove_tmp_file(void)
{
    if (strcmp(tmp_filepath, TMP_FILEPATH_TEMPLATE) != 0)
        remove(tmp_filepath);
    if (strcmp(tmp_obj_filepath, TMP_OBJ_FILEPATH_TEMPLATE) != 0)
        remove(tmp_obj_filepath);
}

int
main(int argc, char *argv[])
{
    int option;

    while ((option = getopt(argc, argv, "So:b:e:h")) != -1)
    {
        switch (option)
        {
        case 'S':
            compile_to_exec = false;
            break;
        case 'o':
            output_filename = optarg;
            break;
        case 'b':
            buffer_len = xatoul(optarg);
            break;
        case 'e':
            buffer_elem_bytes = xatoul(optarg);
            switch (buffer_elem_bytes)
            {
            case 1:
                elem_bytes_asm_str = "byte";
                elem_bytes_asm_str_res = "resb";
                break;
            case 2:
                elem_bytes_asm_str = "word";
                elem_bytes_asm_str_res = "resw";
                break;
            case 4:
                elem_bytes_asm_str = "dword";
                elem_bytes_asm_str_res = "resd";
                break;
            case 8:
                elem_bytes_asm_str = "qword";
                elem_bytes_asm_str_res = "resq";
                break;
            default:
                die("%lu: is not a valid number of bytes for buffer element", buffer_elem_bytes);
            }
            break;
        case 'h':
            puts("Usage: bfc [-h] [-S] [-o file] [-b buffer_len] [-e "
                 "buffer_elem_bytes] [INPUT_FILE]");
            puts("");
            puts("INPUT_FILE  Brainfuck source (read stdin if not present)");
            puts("");
            puts("-h          Print this message");
            puts("-S          Output assembly instead of compiled executable");
            puts("-o          Output filename ('-' for stdout)");
            puts("-b          Length of the buffer available the program");
            puts("-e          Number of bytes in each element of the program's "
                 "buffer");
            puts("            (can only be: 1, 2, 4 or 8)");
            exit(EXIT_SUCCESS);
        }
    }
    if (optind > argc + 1)
        die("Unexpected argument: %s\n", argv[optind + 1]);

    if (output_filename == NULL)
        output_filename = compile_to_exec ? "a.out" : "a.asm";

    atexit(remove_tmp_file);

    FILE *input_file = NULL;
    if (optind == argc)
        input_file = stdin;
    else
    {
        input_file = fopen(argv[optind], "r");
        if (input_file == NULL)
            die("%s: %s\n", strerror(errno), argv[optind]);
    }

    FILE *output_file = NULL;
    if (strcmp(output_filename, "-") == 0)
        output_file = stdout;
    else
    {
        if (compile_to_exec)
            output_file = fdopen(mkstemp(tmp_filepath), "w");
        else
            output_file = fopen(output_filename, "w");
        if (output_file == NULL)
            die("%s: %s\n", strerror(errno), argv[optind]);
    }

    fprintf(output_file,
            "global _start\n\n"
            "section .bss\n"
            "\tbuffer: %s %zu\n\n"
            "section .text\n"
            "_start:\n"
            "\tmov rbx, buffer\n",
            elem_bytes_asm_str_res,
            buffer_len);
    size_t label_frame_id;

    char c;
    while ((c = fgetc(input_file)) != EOF)
    {
        switch (c)
        {
        case '>':
            fprintf(output_file, "    add rbx, %zu                ; >\n", buffer_elem_bytes);
            break;
        case '<':
            fprintf(output_file, "    sub rbx, %zu                ; <\n", buffer_elem_bytes);
            break;
        case '+':
            fprintf(output_file, "    inc %s [rbx]           ; +\n", elem_bytes_asm_str);
            break;
        case '-':
            fprintf(output_file, "    dec %s [rbx]           ; -\n", elem_bytes_asm_str);
            break;
        case '.':
            fprintf(output_file, "    mov rdi, 1               ; .\n");
            fprintf(output_file, "    mov rsi, rbx             ; .\n");
            fprintf(output_file, "    mov rdx, 1               ; .\n");
            fprintf(output_file, "    mov rax, 1               ; .\n");
            fprintf(output_file, "    syscall                  ; .\n");
            break;
        case ',':
            fprintf(output_file, "    mov rdi, 0               ; ,\n");
            fprintf(output_file, "    mov rsi, rbx             ; ,\n");
            fprintf(output_file, "    mov rdx, 1               ; ,\n");
            fprintf(output_file, "    mov rax, 0               ; ,\n");
            fprintf(output_file, "    syscall                  ; ,\n");
            break;
        case '[':
            label_frame_id = label_stack[label_stack_frame];
            fprintf(output_file,
                    "label_open_%03zu_%03zu:          ; [\n",
                    label_stack_frame,
                    label_frame_id);
            fprintf(output_file, "    cmp %s [rbx], 0        ; [\n", elem_bytes_asm_str);
            fprintf(output_file,
                    "    je  label_close_%03zu_%03zu  ; [\n",
                    label_stack_frame,
                    label_frame_id);
            label_stack_frame++;
            break;
        case ']':
            label_stack_frame--;
            label_frame_id = label_stack[label_stack_frame];
            fprintf(output_file,
                    "label_close_%03zu_%03zu:         ; ]\n",
                    label_stack_frame,
                    label_frame_id);
            fprintf(output_file, "    cmp %s [rbx], 0        ; ]\n", elem_bytes_asm_str);
            fprintf(output_file,
                    "    jne label_open_%03zu_%03zu   ; ]\n",
                    label_stack_frame,
                    label_frame_id);
            label_stack[label_stack_frame]++;
            break;
        case ';':
            while (c != EOF && c != '\n')
                c = fgetc(input_file);
            break;
        default:
            break;
        }
    }
    if (input_file != stdin)
        fclose(input_file);
    fprintf(output_file, "\n");
    fprintf(output_file, "    mov rdi, 0   ; exit\n");
    fprintf(output_file, "    mov rax, 60  ; exit\n");
    fprintf(output_file, "    syscall      ; exit\n");
    if (output_file != stdout)
        fclose(output_file);

    if (compile_to_exec)
    {
        strcpy(tmp_obj_filepath, tmp_filepath);
        strcat(tmp_obj_filepath, ".o");
        char *const nasm_argv[] = {
            "nasm", "-f", NASM_FORMAT, "-o", tmp_obj_filepath, tmp_filepath, NULL};
        run_subprocess("nasm", nasm_argv);
        char *const ld_argv[] = {"ld", "-o", output_filename, tmp_obj_filepath, NULL};
        run_subprocess("ld", ld_argv);
    }

    return 0;
}