add compile command
This commit is contained in:
parent
ece1e95de7
commit
078d024e1e
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
zig-cache
|
||||
zig-out
|
||||
compile_commands.json
|
||||
.cache
|
||||
|
32
README.md
32
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 <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
|
||||
|
||||
|
33
build.zig
33
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);
|
||||
|
17
build.zig.zon
Normal file
17
build.zig.zon
Normal 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
20
include/bf_compiler.h
Normal 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
31
include/bf_emulator.h
Normal 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
26
src/bf_compiler.c
Normal 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
133
src/bf_compiler_tinycc.c
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
106
src/main.c
106
src/main.c
@ -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,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 <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));
|
||||
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 <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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user