add compile command

This commit is contained in:
Rokas Puzonas 2024-07-14 16:13:47 +03:00
parent ece1e95de7
commit 078d024e1e
10 changed files with 371 additions and 51 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
zig-cache
zig-out
compile_commands.json
.cache

View File

@ -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 <filename>
```
Will run using emulator
```shell
./zig-out/bin/brainfuck run <filename>
```
Will compile program to executable
```shell
./zig-out/bin/brainfuck compile <filename> <output>
```
## 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

View File

@ -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);

17
build.zig.zon Normal file
View File

@ -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 = .{ "" }
}

20
include/bf_compiler.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef BF_COMPILER_H_
#define BF_COMPILER_H_
#include <inttypes.h>
#include <stdlib.h>
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_

31
include/bf_emulator.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef BF_EMULATOR_H_
#define BF_EMULATOR_H_
#include <inttypes.h>
#include <stdlib.h>
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_

26
src/bf_compiler.c Normal file
View File

@ -0,0 +1,26 @@
#include "bf_compiler.h"
#include <stdlib.h>
#include <assert.h>
#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;
}

133
src/bf_compiler_tinycc.c Normal file
View File

@ -0,0 +1,133 @@
#include "bf_compiler.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <libtcc.h>
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 <inttypes.h>\n");
str_buffer_append(&c_program, "#include <stdlib.h>\n");
str_buffer_append(&c_program, "#include <assert.h>\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;
}

View File

@ -1,28 +1,10 @@
#include <inttypes.h>
#include "bf_emulator.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
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);

View File

@ -2,9 +2,11 @@
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "bf_emulator.c"
#include "bf_emulator.h"
#include "bf_compiler.h"
uint64_t get_file_size(const char *filename) {
struct stat result;
@ -12,36 +14,48 @@ 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 <filename>\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));
assert(program != NULL);
{ // Read program from file
FILE *f = fopen(filename, "r");
if (f == NULL) {
printf("ERROR: Failed to open file\n");
return 2;
goto err;
}
uint32_t read_amount = 0;
while (read_amount < program_len) {
int result = fread(program + read_amount, 1, program_len - read_amount, f);
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");
return 3;
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);
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 <filename>\n");
printf(" brainfuck compile <input-file> <output-file>\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;
}