From 078d024e1ef844b730db7868e70ddd4e4b6fab15 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 14 Jul 2024 16:13:47 +0300 Subject: [PATCH] add compile command --- .gitignore | 2 + README.md | 32 +++++++++- build.zig | 33 ++++++++++ build.zig.zon | 17 +++++ include/bf_compiler.h | 20 ++++++ include/bf_emulator.h | 31 +++++++++ src/bf_compiler.c | 26 ++++++++ src/bf_compiler_tinycc.c | 133 +++++++++++++++++++++++++++++++++++++++ src/bf_emulator.c | 22 +------ src/main.c | 106 ++++++++++++++++++++++--------- 10 files changed, 371 insertions(+), 51 deletions(-) create mode 100644 build.zig.zon create mode 100644 include/bf_compiler.h create mode 100644 include/bf_emulator.h create mode 100644 src/bf_compiler.c create mode 100644 src/bf_compiler_tinycc.c diff --git a/.gitignore b/.gitignore index 4c82b07..897c816 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ zig-cache zig-out +compile_commands.json +.cache diff --git a/README.md b/README.md index 40eebe1..8162eb2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,37 @@ -# Brainfuck interpreter +# Brainfuck interpreter & compiler https://en.wikipedia.org/wiki/Brainfuck ## Building & Usage -``` +To compile CLI +```shell zig build -Doptimize=ReleaseFast -./zig-out/bin/brainfuck ``` +Will run using emulator +```shell +./zig-out/bin/brainfuck run +``` + +Will compile program to executable +```shell +./zig-out/bin/brainfuck compile +``` + +## Developing + +This command will create a `compile_commands.json` file which an IDE can use to more smartly say from where +each file is included. If you don't do this, some defines not be defined and then what you see in the IDE +wont match what the compiler is doing. +``` +zig build cdb +``` + +## Feature wishlist + +* Update TinyCC backend, so host would not need to have it installed. Relies on host having include paths used by TinyCC. +* Add more backends. Maybe LLVM, nasm or create my own +* Perform optimizations? Like removing not needed `[]` blocks which are known never to be ran? Idk, what could be optimized +* Add WASM/Web build + diff --git a/build.zig b/build.zig index 2567c8f..1d1f897 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const zcc = @import("compile_commands"); const Build = std.Build; pub fn build(b: *Build) !void { @@ -11,9 +12,41 @@ pub fn build(b: *Build) !void { .target = target }); exe.addIncludePath(.{ .path = "src" }); + exe.addIncludePath(.{ .path = "include" }); exe.addCSourceFile(.{ .file = b.path("src/main.c") }); + exe.addCSourceFile(.{ .file = b.path("src/bf_compiler.c") }); + exe.addCSourceFile(.{ .file = b.path("src/bf_emulator.c") }); exe.linkLibC(); + if (b.option(bool, "tinycc", "Toggle 'tinycc' compiler backend (Enabled by default)") orelse true) { + if (b.lazyDependency("tinycc", .{})) |tinycc| { + const tinycc_source = tinycc.path("."); + + const configure_cmd = b.addSystemCommand(&.{ "./configure", "--config-musl" }); + _ = configure_cmd.captureStdOut(); + configure_cmd.setCwd(tinycc_source); + + const make_cmd = b.addSystemCommand(&.{ "make", "libtcc.a" }); + _ = make_cmd.captureStdOut(); + make_cmd.setCwd(tinycc_source); + make_cmd.step.dependOn(&configure_cmd.step); + + const install_cmd = b.addSystemCommand(&.{ "make", "install" }); + _ = install_cmd.captureStdOut(); + install_cmd.setEnvironmentVariable("DESTDIR", "build"); + install_cmd.setCwd(tinycc_source); + install_cmd.step.dependOn(&make_cmd.step); + + exe.step.dependOn(&install_cmd.step); + exe.addObjectFile(tinycc.path("libtcc.a")); + exe.addIncludePath(tinycc.path("build/usr/local/include")); + + exe.root_module.addCMacro("BF_BACKEND_TINYCC", ""); + } + } + + zcc.createStep(b, "cdb", .{ .target = exe }); + b.installArtifact(exe); const run_exe = b.addRunArtifact(exe); diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..c88dd13 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = "brainfuck", + .version = "0.1.0", + .minimum_zig_version = "0.12.0", + .dependencies = .{ + .tinycc = .{ + .url = "https://github.com/TinyCC/tinycc/archive/refs/tags/release_0_9_27.tar.gz", + .hash = "122015b387754b02f220e29c9cb163d9c395c5928e6e50efe72df0734debd95c0b22", + .lazy = true + }, + .compile_commands = .{ + .url = "https://github.com/bcrist/zig-compile-commands/archive/a64abd947b58655f118884709d939645871e94fc.tar.gz", + .hash = "1220d3150db10226f4d853eb519468de5c49cb820f08c722608a500b9f7d64e4c780", + }, + }, + .paths = .{ "" } +} diff --git a/include/bf_compiler.h b/include/bf_compiler.h new file mode 100644 index 0000000..adbed7a --- /dev/null +++ b/include/bf_compiler.h @@ -0,0 +1,20 @@ +#ifndef BF_COMPILER_H_ +#define BF_COMPILER_H_ + +#include +#include + +enum bf_compiler_backend { + BF_COMPILER_TINYCC +}; + +struct bf_compiler { + enum bf_compiler_backend backend; + uint32_t data_len; + uint32_t cell_size; +}; + +void bf_compiler_init(struct bf_compiler *compiler, uint32_t cell_size, uint32_t data_len); +int bf_compiler_compile(struct bf_compiler *compiler, const char *output_filename, const char *program, size_t program_len); + +#endif //BF_COMPILER_H_ diff --git a/include/bf_emulator.h b/include/bf_emulator.h new file mode 100644 index 0000000..c4b6f8a --- /dev/null +++ b/include/bf_emulator.h @@ -0,0 +1,31 @@ +#ifndef BF_EMULATOR_H_ +#define BF_EMULATOR_H_ + +#include +#include + +enum bf_step_result { + BF_STEP_OK, + BF_STEP_FINISHED, + BF_STEP_ERROR, +}; + +struct bf_emulator { + uint32_t data_pointer; + + void *data; + uint32_t data_len; + uint32_t cell_size; + + const char *program; + size_t program_len; + size_t program_pointer; +}; + +void bf_emulator_init(struct bf_emulator *emulator, uint32_t cell_size, uint32_t data_len); +void bf_emulator_deinit(struct bf_emulator *emulator); +enum bf_step_result bf_emulator_step(struct bf_emulator *emulator); +enum bf_step_result bf_emulator_continue(struct bf_emulator *emulator); +enum bf_step_result bf_emulator_run(struct bf_emulator *emulator, const char *program, size_t program_len); + +#endif //BF_EMULATOR_H_ diff --git a/src/bf_compiler.c b/src/bf_compiler.c new file mode 100644 index 0000000..c5e8ba0 --- /dev/null +++ b/src/bf_compiler.c @@ -0,0 +1,26 @@ +#include "bf_compiler.h" + +#include +#include + +#ifdef BF_BACKEND_TINYCC +#include "bf_compiler_tinycc.c" +#endif + +void bf_compiler_init(struct bf_compiler *compiler, uint32_t cell_size, uint32_t data_len) +{ + assert(cell_size == 8 || cell_size == 16 || cell_size == 32); + compiler->cell_size = cell_size; + compiler->data_len = data_len; +} + +int bf_compiler_compile(struct bf_compiler *compiler, const char *output_filename, const char *program, size_t program_len) +{ +#ifdef BF_BACKEND_TINYCC + if (compiler->backend == BF_COMPILER_TINYCC) { + return bf_compiler_compile_tinycc(compiler, output_filename, program, program_len); + } +#endif + + return -1; +} diff --git a/src/bf_compiler_tinycc.c b/src/bf_compiler_tinycc.c new file mode 100644 index 0000000..1c2cb0f --- /dev/null +++ b/src/bf_compiler_tinycc.c @@ -0,0 +1,133 @@ +#include "bf_compiler.h" + +#include +#include +#include +#include + +struct str_buffer { + char *data; + size_t len; + size_t capacity; +}; + +static void str_buffer_init(struct str_buffer *buffer, size_t capacity) +{ + buffer->data = malloc(capacity); + assert(buffer->data != NULL); + buffer->len = 0; + buffer->capacity = capacity; +} + +static void str_buffer_deinit(struct str_buffer *buffer) +{ + free(buffer->data); + buffer->data = NULL; + buffer->capacity = 0; + buffer->len = 0; +} + +static void str_buffer_append_with_len(struct str_buffer *buffer, const char *text, size_t text_len) +{ + if (buffer->len + text_len > buffer->capacity) { + while (buffer->len + text_len > buffer->capacity) { + buffer->capacity *= 2; + } + + buffer->data = realloc(buffer->data, buffer->capacity); + assert(buffer->data != NULL); + } + + memcpy(buffer->data + buffer->len, text, text_len); + buffer->len += text_len; +} + +static void str_buffer_append(struct str_buffer *buffer, const char *text) +{ + str_buffer_append_with_len(buffer, text, strlen(text)); +} + +static int bf_compiler_compile_tinycc(struct bf_compiler *compiler, const char *output_filename, const char *program, size_t program_len) +{ + struct str_buffer c_program = { 0 }; + str_buffer_init(&c_program, 128); + + int rc = -1; + TCCState *state = tcc_new(); + if (!state) { + goto err; + } + + const char *cell_type = NULL; + if (compiler->cell_size == 8) { + cell_type = "uint8_t"; + } else if (compiler->cell_size == 16) { + cell_type = "uint16_t"; + } else if (compiler->cell_size == 32) { + cell_type = "uint32_t"; + } else { + return -1; + } + + char line_buffer[256] = { 0 }; + + str_buffer_append(&c_program, "#include \n"); + str_buffer_append(&c_program, "#include \n"); + str_buffer_append(&c_program, "#include \n"); + + snprintf(line_buffer, sizeof(line_buffer), "typedef %s cell_t;\n", cell_type); + str_buffer_append(&c_program, line_buffer); + + str_buffer_append(&c_program, "int main()\n"); + str_buffer_append(&c_program, "{\n"); + + snprintf(line_buffer, sizeof(line_buffer), "size_t data_len = %d;\n", compiler->data_len); + str_buffer_append(&c_program, line_buffer); + + str_buffer_append(&c_program, "cell_t data_pointer = 0;\n"); + str_buffer_append(&c_program, "cell_t *data = calloc(data_len, sizeof(cell_t));\n"); + str_buffer_append(&c_program, "assert(data != NULL);\n"); + + for (int i = 0; i < program_len; i++) { + char inst = program[i]; + + if (inst == '>') { + str_buffer_append(&c_program, "data_pointer++;\n"); + } else if (inst == '<') { + str_buffer_append(&c_program, "data_pointer--;\n"); + } else if (inst == '+') { + str_buffer_append(&c_program, "data[data_pointer]++;\n"); + } else if (inst == '-') { + str_buffer_append(&c_program, "data[data_pointer]--;\n"); + } else if (inst == '.') { + str_buffer_append(&c_program, "putchar(data[data_pointer]); fflush(0);\n"); + } else if (inst == ',') { + str_buffer_append(&c_program, "data[data_pointer] = getchar();\n"); + } else if (inst == '[') { + str_buffer_append(&c_program, "while (data[data_pointer] != 0) {\n"); + } else if (inst == ']') { + str_buffer_append(&c_program, "}\n"); + } + } + + str_buffer_append(&c_program, "free(data);\n"); + str_buffer_append(&c_program, "return 0;\n"); + str_buffer_append(&c_program, "}\n"); + str_buffer_append_with_len(&c_program, "\0", 1); + + tcc_set_output_type(state, TCC_OUTPUT_EXE); + tcc_set_options(state, "-w"); + if (tcc_compile_string(state, c_program.data) == -1) { + goto err; + } + + if (tcc_output_file(state, output_filename)) { + goto err; + } + + rc = 0; +err: + tcc_delete(state); + str_buffer_deinit(&c_program); + return rc; +} diff --git a/src/bf_emulator.c b/src/bf_emulator.c index b15f54f..eb22128 100644 --- a/src/bf_emulator.c +++ b/src/bf_emulator.c @@ -1,28 +1,10 @@ -#include +#include "bf_emulator.h" + #include -#include #include #include #include -enum bf_step_result { - BF_STEP_OK, - BF_STEP_FINISHED, - BF_STEP_ERROR, -}; - -struct bf_emulator { - uint32_t data_pointer; - - void *data; - uint32_t data_len; - uint32_t cell_size; - - const char *program; - size_t program_len; - size_t program_pointer; -}; - void bf_emulator_init(struct bf_emulator *emulator, uint32_t cell_size, uint32_t data_len) { assert(cell_size == 8 || cell_size == 16 || cell_size == 32); diff --git a/src/main.c b/src/main.c index f659eab..ed9de21 100644 --- a/src/main.c +++ b/src/main.c @@ -2,9 +2,11 @@ #include #include #include +#include #include -#include "bf_emulator.c" +#include "bf_emulator.h" +#include "bf_compiler.h" uint64_t get_file_size(const char *filename) { struct stat result; @@ -12,37 +14,49 @@ uint64_t get_file_size(const char *filename) { return result.st_size; } -int main(int argc, const char **argv) { - if (argc < 2) { - printf("Usage: %s \n", argv[0]); - return 1; +char *read_file(const char *filename, size_t *file_len) +{ + char *result = NULL; + + *file_len = get_file_size(filename); + char *contents = calloc(*file_len, sizeof(uint8_t)); + if (contents == NULL) { + return NULL; } - const char *filename = argv[1]; - size_t program_len = get_file_size(filename); - char *program = calloc(program_len, sizeof(uint8_t)); + FILE *f = fopen(filename, "r"); + if (f == NULL) { + printf("ERROR: Failed to open file\n"); + goto err; + } + + uint32_t read_amount = 0; + while (read_amount < *file_len) { + int result = fread(contents + read_amount, 1, *file_len - read_amount, f); + if (result <= 0) { + printf("ERROR: Failed to read everything from file\n"); + goto err; + } + read_amount += result; + } + + fclose(f); + + result = contents; +err: + if (!result) { + free(contents); + } + return result; +} + +int run_command(int argc, const char **argv) { + const char *filename = argv[0]; + + size_t program_len = 0; + char *program = read_file(filename, &program_len); assert(program != NULL); - { // Read program from file - FILE *f = fopen(filename, "r"); - if (f == NULL) { - printf("ERROR: Failed to open file\n"); - return 2; - } - - uint32_t read_amount = 0; - while (read_amount < program_len) { - int result = fread(program + read_amount, 1, program_len - read_amount, f); - if (result <= 0) { - printf("ERROR: Failed to read everything from file\n"); - return 3; - } - read_amount += result; - } - - fclose(f); - } - struct bf_emulator emulator = { 0 }; bf_emulator_init(&emulator, 16, 4096); @@ -55,3 +69,39 @@ int main(int argc, const char **argv) { bf_emulator_deinit(&emulator); return 0; } + +int compile_command(int argc, const char **argv) { + const char *input_filename = argv[0]; + const char *output_filename = argv[1]; + + size_t program_len = 0; + char *program = read_file(input_filename, &program_len); + assert(program != NULL); + + struct bf_compiler compiler = { 0 }; + bf_compiler_init(&compiler, 16, 4096); + + if (bf_compiler_compile(&compiler, output_filename, program, program_len)) { + printf("ERROR: Failed to compile program\n"); + return -1; + } + + return 0; +} + +void show_usage() { + printf("Usage: brainfuck run \n"); + printf(" brainfuck compile \n"); +} + +int main(int argc, const char **argv) { + if (argc >= 3 && !strncmp(argv[1], "run", sizeof("run"))) { + return run_command(argc - 2, argv + 2); + + } else if (argc >= 4 && !strncmp(argv[1], "compile", sizeof("compile"))) { + return compile_command(argc - 2, argv + 2); + } + + show_usage(); + return 1; +}