diff --git a/compiler/Makefile b/compiler/Makefile new file mode 100644 index 0000000..301bff7 --- /dev/null +++ b/compiler/Makefile @@ -0,0 +1,28 @@ +CC := clang++ +CFLAGS := -Wall +CFLAGS += -std=c++11 +CFLAGS += -O2 +LFLAGS := + +ODIR := .OBJ + +MAIN_SOURCE_DEPS := main.cpp cli.h common.h entire_file.h + +all: binaries + +$(ODIR): + @mkdir $(ODIR) + +binaries: bytecode-cc + +MAIN_OBJECT_DEPS := $(ODIR)/main.o + +bytecode-cc: $(ODIR) $(MAIN_OBJECT_DEPS) + $(CC) $(CFLAGS) $(MAIN_OBJECT_DEPS) -o bytecode-cc $(LFLAGS) + +$(ODIR)/main.o: $(ODIR) $(MAIN_SOURCE_DEPS) + $(CC) -c $(CFLAGS) main.cpp -o $(ODIR)/main.o + +.PHONY: clean +clean: + rm -rf .OBJ bytecode-cc diff --git a/compiler/cli.h b/compiler/cli.h new file mode 100644 index 0000000..5098921 --- /dev/null +++ b/compiler/cli.h @@ -0,0 +1,103 @@ +#ifndef _CLI_H_ +#define _CLI_H_ + +#include +#include +#include +#include +#include +#include +#include + +struct Cli { + std::string input; + std::string output; + + void print_debug() { + printf("Cli {\n"); + printf("\tinput: %s\n", this->input.c_str()); + printf("\toutput: %s\n", this->output.c_str()); + printf("}\n"); + } + + static void help() { + printf("Usage: Cli [OPTIONS]\n" + "\n" + "Options:\n" + " -h, --help\n" + " -i, --input \n" + " -o, --output \n" + ); + exit(0); +} + + static bool is_option(char* arg) { + static const char* valid_options[] = { + "-i", + "--input", + "-o", + "--output", + }; + + for (size_t i = 0; i != 4; ++i) { + if (strcmp(arg, valid_options[i]) == 0) { + return true; + } + } + + return false; + } + + static Cli parse (int argc, char *args[]) { + --argc; + ++args; + + const char* mandatory_field_names[] = { "input", "output", }; + bool mandatory_fields_seen[sizeof(mandatory_field_names)/sizeof(mandatory_field_names[0])] = { false }; + + Cli res = {}; + for (int i = 0; i != argc; ++i, ++args) { + char *arg = args[0]; + if (strcmp("-h", arg) == 0 || strcmp("--help", arg) == 0) { + Cli::help(); + } else if (strcmp(arg, "-i") == 0 || strcmp(arg, "--input") == 0) { + ++args; + ++i; + if (i == argc) { + printf("Expected value for option '%s' but no value was provided", arg); + exit(1); + } + std::string arg_res = args[0]; + res.input = arg_res; + mandatory_fields_seen[0] = true; + } else if (strcmp(arg, "-o") == 0 || strcmp(arg, "--output") == 0) { + ++args; + ++i; + if (i == argc) { + printf("Expected value for option '%s' but no value was provided", arg); + exit(1); + } + std::string arg_res = args[0]; + res.output = arg_res; + mandatory_fields_seen[1] = true; + } else { + printf("Unknown option '%s'\n", arg); + exit(1); + } + } + + bool not_seen_any = false; + for (size_t i = 0; i != sizeof(mandatory_field_names)/sizeof(mandatory_field_names[0]); ++i) { + if (!mandatory_fields_seen[i]) { + printf("--%s was required but it was not provided\n", mandatory_field_names[i]); + not_seen_any = true; + } + } + if (not_seen_any) { + exit(1); + } + return res; + } +}; + +#endif // _CLI_H_ diff --git a/compiler/cli.in b/compiler/cli.in new file mode 100644 index 0000000..f2cee1f --- /dev/null +++ b/compiler/cli.in @@ -0,0 +1,7 @@ +#[main] +struct Cli { + #[short, long] + input: string, + #[short, long] + output: string, +} diff --git a/compiler/common.h b/compiler/common.h new file mode 100644 index 0000000..ac9cdcd --- /dev/null +++ b/compiler/common.h @@ -0,0 +1,14 @@ +// +// Created by George Liontos on 20/4/24. +// + +#ifndef COMPILER_COMMON_H +#define COMPILER_COMMON_H + +#include + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +#define MALLOC(size, type) ((type*) malloc((size) * sizeof(type))) + +#endif //COMPILER_COMMON_H diff --git a/compiler/entire_file.h b/compiler/entire_file.h new file mode 100644 index 0000000..1acee81 --- /dev/null +++ b/compiler/entire_file.h @@ -0,0 +1,59 @@ +// +// Created by George Liontos on 20/4/24. +// + +#ifndef CPP_ENTIRE_FILE_H +#define CPP_ENTIRE_FILE_H + +#include +#include +#include "common.h" + +struct EntireFile { + char* contents; + size_t num_bytes; +}; + +EntireFile read_entire_file_into_memory(const char* path) { + EntireFile res = {0}; + FILE* f = fopen(path, "rb"); + size_t num_bytes_read = 0U; + + if (f == nullptr) { + fprintf(stderr, "Could not open '%s' for reading\n", path); + return res; + } + + if (fseek(f, 0, SEEK_END) != 0) { + goto err; + } + + res.num_bytes = ftell(f); + if (res.num_bytes == -1L) { + goto err; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + goto err; + } + + res.contents = MALLOC(res.num_bytes + 1, char); + num_bytes_read = fread(res.contents, sizeof(char), res.num_bytes, f); + if (num_bytes_read != res.num_bytes) { + goto err; + } + + fclose(f); + return res; + + err: + fclose(f); + if (res.contents) { + free(res.contents); + } + res.contents = nullptr; + res.num_bytes = 0; + return res; +} + +#endif //CPP_ENTIRE_FILE_H diff --git a/compiler/example b/compiler/example new file mode 100644 index 0000000..cea20c5 --- /dev/null +++ b/compiler/example @@ -0,0 +1,3 @@ +CONST 1 +CONST 7 +ADD diff --git a/compiler/example.out b/compiler/example.out new file mode 100644 index 0000000..ec36cf1 Binary files /dev/null and b/compiler/example.out differ diff --git a/compiler/main.cpp b/compiler/main.cpp new file mode 100644 index 0000000..0e9ae73 --- /dev/null +++ b/compiler/main.cpp @@ -0,0 +1,274 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "cli.h" +#include "common.h" +#include "entire_file.h" + +#include +#include +#include +#include +#include + +#define BYTECODE_ITEM(name, has_operand) {#name, Bytecode::name, has_operand} +#define BYTECODE_ITEM_NO_OPERAND(name) BYTECODE_ITEM(name, false) +#define BYTECODE_ITEM_WITH_OPERAND(name) BYTECODE_ITEM(name, true) + +enum class Bytecode: int32_t { + NOP, + DUMP, + TRACE, + PRINT, + HALT, + FATAL, + + // Stack opcodes + CONST, + POP, + + // Math opcodes (binary) + ADD, + SUB, + MUL, + DIV, + MOD, + + // Math opcodes (unary) + ABS, + NEG, + + // Comparison + EQ, + NEQ, + GT, + LT, + GTE, + LTE, + + // Branching opcodes + JMP, + JMPI, + RJMP, + RJMPI, + JZ, + JNZ, + + // Globals + GSTORE, + GLOAD, + + // Procedures/locals + CALL, + RET, + LOAD, + STORE +}; + +struct BytecodeItem { + const char* name; + Bytecode value; + bool accepts_value; +}; + +static const BytecodeItem bytecode_items[] = { + BYTECODE_ITEM_NO_OPERAND(NOP), + BYTECODE_ITEM_NO_OPERAND(DUMP), + BYTECODE_ITEM_NO_OPERAND(TRACE), + BYTECODE_ITEM_NO_OPERAND(HALT), + BYTECODE_ITEM_NO_OPERAND(FATAL), + BYTECODE_ITEM_WITH_OPERAND(CONST), + BYTECODE_ITEM_NO_OPERAND(POP), + BYTECODE_ITEM_NO_OPERAND(ADD), + BYTECODE_ITEM_NO_OPERAND(SUB), + BYTECODE_ITEM_NO_OPERAND(MUL), + BYTECODE_ITEM_NO_OPERAND(DIV), + BYTECODE_ITEM_NO_OPERAND(MOD), + BYTECODE_ITEM_NO_OPERAND(ABS), + BYTECODE_ITEM_NO_OPERAND(NEG), + BYTECODE_ITEM_NO_OPERAND(EQ), + BYTECODE_ITEM_NO_OPERAND(NEQ), + BYTECODE_ITEM_NO_OPERAND(GT), + BYTECODE_ITEM_NO_OPERAND(LT), + BYTECODE_ITEM_NO_OPERAND(GTE), + BYTECODE_ITEM_NO_OPERAND(LTE), + BYTECODE_ITEM_WITH_OPERAND(JMP), + BYTECODE_ITEM_NO_OPERAND(JMPI), + BYTECODE_ITEM_WITH_OPERAND(RJMP), + BYTECODE_ITEM_NO_OPERAND(RJMPI), + BYTECODE_ITEM_WITH_OPERAND(JZ), + BYTECODE_ITEM_WITH_OPERAND(JNZ), + BYTECODE_ITEM_WITH_OPERAND(GSTORE), + BYTECODE_ITEM_WITH_OPERAND(GLOAD), + BYTECODE_ITEM_WITH_OPERAND(CALL), + BYTECODE_ITEM_NO_OPERAND(RET), + BYTECODE_ITEM_WITH_OPERAND(LOAD), + BYTECODE_ITEM_WITH_OPERAND(STORE), +}; + +struct BytecodeBuffer { + Bytecode* buf = nullptr; + size_t len = 0U; + size_t capacity = 0U; +}; + +static void BytecodeBuffer_init(BytecodeBuffer* buffer, size_t capacity) { + buffer->buf = MALLOC(capacity, Bytecode); + buffer->len = 0U; + buffer->capacity = capacity; +} + +static void BytecodeBuffer_push(BytecodeBuffer* buffer, Bytecode code) { + if (buffer->len == buffer->capacity) { + Bytecode* new_buf = MALLOC(buffer->capacity * 2, Bytecode); + memcpy(new_buf, buffer->buf, buffer->capacity * sizeof(Bytecode)); + free(buffer->buf); + buffer->buf = new_buf; + buffer->capacity *= 2; + } + + buffer->buf[buffer->len++] = code; +} + +struct Lexer { + char* stream; + size_t len; + size_t cursor = 0; + size_t line_no = 1; +}; + +static void Lexer_skip_whitespace(Lexer* lexer) { + while (lexer->cursor != lexer->len && isspace(lexer->stream[lexer->cursor])) { + ++lexer->cursor; + if (lexer->stream[0] == '\n') { + ++lexer->line_no; + } + } +} + +struct Token { + char* token; + size_t token_len; +}; + +static Token Lexer_next_token(Lexer* lexer) { + if (lexer->cursor == lexer->len) { + return (Token) {0}; + } + + Lexer_skip_whitespace(lexer); + char* token_start = &lexer->stream[lexer->cursor]; + size_t cursor_start = lexer->cursor; + while (lexer->cursor != lexer->len && !isspace(lexer->stream[lexer->cursor])) { + ++lexer->cursor; + } + + Token res = {0}; + res.token = token_start; + res.token_len = lexer->cursor - cursor_start; + return res; +} + +static bool parse_int(Token token, int32_t* res) { + assert(token.token); + int32_t result = 0; + size_t i = 0U; + + int32_t sign = 1; + if (token.token[0] == '-') { + sign = -1; + ++i; + } + + for (; i != token.token_len; ++i) { + if (isdigit(token.token[i])) { + result = result * 10 + (token.token[i] - '0'); + } else { + return false; + } + } + + result *= sign; + *res = result; + + return true; +} + +static bool compile_input(const char* path, BytecodeBuffer* out_buffer) { + EntireFile file = read_entire_file_into_memory(path); + if (!file.contents) { + return false; + } + + Lexer lexer = {}; + lexer.stream = file.contents; + lexer.len = file.num_bytes; + + for (;;) { + Token next = Lexer_next_token(&lexer); + if (!next.token) { + break; + } + + ssize_t bytecode_item_index = -1; + for (size_t i = 0U; i != ARRAY_SIZE(bytecode_items); ++i) { + BytecodeItem bytecode_item = bytecode_items[i]; + if (strncmp(next.token, bytecode_item.name, next.token_len) == 0) { + bytecode_item_index = i; + break; + } + } + + if (bytecode_item_index == -1) { + fprintf(stderr, "Unknown token '%.*s' at line %zu\n", next.token_len, next.token, lexer.line_no); + return false; + } + + BytecodeItem bytecode_item = bytecode_items[bytecode_item_index]; + BytecodeBuffer_push(out_buffer, bytecode_item.value); + if (bytecode_item.accepts_value) { + next = Lexer_next_token(&lexer); + if (!next.token) { + fprintf(stderr, "Expected operand after %s at line %zu\n", bytecode_item.name, lexer.line_no); + return false; + } + + int32_t operand; + if (!parse_int(next, &operand)) { + fprintf(stderr, "Could not parse number %.*s at line %zu\n", next.token_len, next.token, lexer.line_no); + return false; + } + + BytecodeBuffer_push(out_buffer, (Bytecode) operand); + } + } + + return true; +} + +int main(int argc, char* args[]) { + Cli cli = Cli::parse(argc, args); + + BytecodeBuffer buffer = {}; + BytecodeBuffer_init(&buffer, 256); + + if (!compile_input(cli.input.c_str(), &buffer)) { + fprintf(stderr, "Could not compile input file '%s'\n", cli.input.c_str()); + return EXIT_FAILURE; + } + + FILE* output = fopen(cli.output.c_str(), "wb"); + if (!output) { + fprintf(stderr, "Could not open %s for writing\n", cli.output.c_str()); + return EXIT_FAILURE; + } + + if (fwrite(buffer.buf, sizeof(Bytecode), buffer.len - 1, output) != buffer.len - 1) { + fprintf(stderr, "Could not write bytecode to file\n"); + return EXIT_FAILURE; + } + + fclose(output); + return EXIT_SUCCESS; +} diff --git a/cpp/Makefile b/cpp/Makefile new file mode 100644 index 0000000..bab0de9 --- /dev/null +++ b/cpp/Makefile @@ -0,0 +1,92 @@ +CC := clang++ +CFLAGS := -Wall +CFLAGS += -std=c++11 +CFLAGS += -O2 +LFLAGS := + +ODIR := .OBJ + +TESTS_JUMP_TESTS_SOURCE_DEPS := tests/jump_tests.cpp tests/unit_test.h common.h virtual_machine.h virtual_machine.cpp format_buffer.h format_buffer.cpp +TESTS_GLOBAL_TESTS_SOURCE_DEPS := tests/global_tests.cpp tests/unit_test.h common.h virtual_machine.h virtual_machine.cpp format_buffer.h format_buffer.cpp +TESTS_CALL_TESTS_SOURCE_DEPS := tests/call_tests.cpp tests/unit_test.h common.h virtual_machine.h virtual_machine.cpp format_buffer.h format_buffer.cpp +MAIN_SOURCE_DEPS := main.cpp cli.h virtual_machine.h virtual_machine.cpp common.h format_buffer.h format_buffer.cpp entire_file.h +TESTS_STACK_TESTS_SOURCE_DEPS := tests/stack_tests.cpp tests/unit_test.h common.h virtual_machine.h virtual_machine.cpp format_buffer.h format_buffer.cpp +TESTS_MATH_TESTS_SOURCE_DEPS := tests/math_tests.cpp tests/unit_test.h common.h virtual_machine.h virtual_machine.cpp format_buffer.h format_buffer.cpp +FORMAT_BUFFER_SOURCE_DEPS := format_buffer.cpp common.h format_buffer.h +TESTS_COMPARISON_TESTS_SOURCE_DEPS := tests/comparison_tests.cpp tests/unit_test.h common.h virtual_machine.h virtual_machine.cpp format_buffer.h format_buffer.cpp +VIRTUAL_MACHINE_SOURCE_DEPS := virtual_machine.cpp common.h virtual_machine.h format_buffer.h format_buffer.cpp + +all: binaries + +$(ODIR): + @mkdir $(ODIR) + +binaries: vm + +MAIN_OBJECT_DEPS := $(ODIR)/main.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +vm: $(ODIR) $(MAIN_OBJECT_DEPS) + $(CC) $(CFLAGS) $(MAIN_OBJECT_DEPS) -o vm $(LFLAGS) + +tests: tests_math_tests tests_stack_tests tests_call_tests tests_comparison_tests tests_jump_tests tests_global_tests + +TESTS_MATH_TESTS_OBJECT_DEPS := $(ODIR)/tests_math_tests.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +tests_math_tests: $(ODIR) $(TESTS_MATH_TESTS_OBJECT_DEPS) + $(CC) $(CFLAGS) $(TESTS_MATH_TESTS_OBJECT_DEPS) -o tests/math_tests + +TESTS_STACK_TESTS_OBJECT_DEPS := $(ODIR)/tests_stack_tests.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +tests_stack_tests: $(ODIR) $(TESTS_STACK_TESTS_OBJECT_DEPS) + $(CC) $(CFLAGS) $(TESTS_STACK_TESTS_OBJECT_DEPS) -o tests/stack_tests + +TESTS_CALL_TESTS_OBJECT_DEPS := $(ODIR)/tests_call_tests.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +tests_call_tests: $(ODIR) $(TESTS_CALL_TESTS_OBJECT_DEPS) + $(CC) $(CFLAGS) $(TESTS_CALL_TESTS_OBJECT_DEPS) -o tests/call_tests + +TESTS_COMPARISON_TESTS_OBJECT_DEPS := $(ODIR)/tests_comparison_tests.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +tests_comparison_tests: $(ODIR) $(TESTS_COMPARISON_TESTS_OBJECT_DEPS) + $(CC) $(CFLAGS) $(TESTS_COMPARISON_TESTS_OBJECT_DEPS) -o tests/comparison_tests + +TESTS_JUMP_TESTS_OBJECT_DEPS := $(ODIR)/tests_jump_tests.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +tests_jump_tests: $(ODIR) $(TESTS_JUMP_TESTS_OBJECT_DEPS) + $(CC) $(CFLAGS) $(TESTS_JUMP_TESTS_OBJECT_DEPS) -o tests/jump_tests + +TESTS_GLOBAL_TESTS_OBJECT_DEPS := $(ODIR)/tests_global_tests.o $(ODIR)/virtual_machine.o $(ODIR)/format_buffer.o + +tests_global_tests: $(ODIR) $(TESTS_GLOBAL_TESTS_OBJECT_DEPS) + $(CC) $(CFLAGS) $(TESTS_GLOBAL_TESTS_OBJECT_DEPS) -o tests/global_tests + +$(ODIR)/tests_jump_tests.o: $(ODIR) $(TESTS_JUMP_TESTS_SOURCE_DEPS) + $(CC) -c $(CFLAGS) tests/jump_tests.cpp -o $(ODIR)/tests_jump_tests.o + +$(ODIR)/tests_global_tests.o: $(ODIR) $(TESTS_GLOBAL_TESTS_SOURCE_DEPS) + $(CC) -c $(CFLAGS) tests/global_tests.cpp -o $(ODIR)/tests_global_tests.o + +$(ODIR)/tests_call_tests.o: $(ODIR) $(TESTS_CALL_TESTS_SOURCE_DEPS) + $(CC) -c $(CFLAGS) tests/call_tests.cpp -o $(ODIR)/tests_call_tests.o + +$(ODIR)/main.o: $(ODIR) $(MAIN_SOURCE_DEPS) + $(CC) -c $(CFLAGS) main.cpp -o $(ODIR)/main.o + +$(ODIR)/tests_stack_tests.o: $(ODIR) $(TESTS_STACK_TESTS_SOURCE_DEPS) + $(CC) -c $(CFLAGS) tests/stack_tests.cpp -o $(ODIR)/tests_stack_tests.o + +$(ODIR)/tests_math_tests.o: $(ODIR) $(TESTS_MATH_TESTS_SOURCE_DEPS) + $(CC) -c $(CFLAGS) tests/math_tests.cpp -o $(ODIR)/tests_math_tests.o + +$(ODIR)/format_buffer.o: $(ODIR) $(FORMAT_BUFFER_SOURCE_DEPS) + $(CC) -c $(CFLAGS) format_buffer.cpp -o $(ODIR)/format_buffer.o + +$(ODIR)/tests_comparison_tests.o: $(ODIR) $(TESTS_COMPARISON_TESTS_SOURCE_DEPS) + $(CC) -c $(CFLAGS) tests/comparison_tests.cpp -o $(ODIR)/tests_comparison_tests.o + +$(ODIR)/virtual_machine.o: $(ODIR) $(VIRTUAL_MACHINE_SOURCE_DEPS) + $(CC) -c $(CFLAGS) virtual_machine.cpp -o $(ODIR)/virtual_machine.o + +.PHONY: clean +clean: + rm -rf .OBJ vm tests/math_tests tests/stack_tests tests/call_tests tests/comparison_tests tests/jump_tests tests/global_tests diff --git a/cpp/cli.h b/cpp/cli.h new file mode 100644 index 0000000..7a55aa3 --- /dev/null +++ b/cpp/cli.h @@ -0,0 +1,96 @@ +#ifndef _CLI_H_ +#define _CLI_H_ + +#include +#include +#include +#include +#include +#include +#include + +struct Cli { + std::string input; + bool trace; + + void print_debug() { + printf("Cli {\n"); + printf("\tinput: %s\n", this->input.c_str()); + printf("\ttrace: %s\n", this->trace ? "true" : "false"); + printf("}\n"); + } + + static void help() { + printf("Usage: Cli [OPTIONS]\n" + "\n" + "Options:\n" + " -h, --help\n" + " -i, --input \n" + " -t, --trace\n" + ); + exit(0); +} + + static bool is_option(char* arg) { + static const char* valid_options[] = { + "-i", + "--input", + "-t", + "--trace", + }; + + for (size_t i = 0; i != 4; ++i) { + if (strcmp(arg, valid_options[i]) == 0) { + return true; + } + } + + return false; + } + + static Cli parse (int argc, char *args[]) { + --argc; + ++args; + + const char* mandatory_field_names[] = { "input"}; + bool mandatory_fields_seen[sizeof(mandatory_field_names)/sizeof(mandatory_field_names[0])] = { false }; + + Cli res = {}; + for (int i = 0; i != argc; ++i, ++args) { + char *arg = args[0]; + if (strcmp("-h", arg) == 0 || strcmp("--help", arg) == 0) { + Cli::help(); + } else if (strcmp(arg, "-i") == 0 || strcmp(arg, "--input") == 0) { + ++args; + ++i; + if (i == argc) { + printf("Expected value for option '%s' but no value was provided", arg); + exit(1); + } + std::string arg_res = args[0]; + res.input = arg_res; + mandatory_fields_seen[0] = true; + } else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--trace") == 0) { + bool arg_res = true; + res.trace = arg_res; + } else { + printf("Unknown option '%s'\n", arg); + exit(1); + } + } + + bool not_seen_any = false; + for (size_t i = 0; i != sizeof(mandatory_field_names)/sizeof(mandatory_field_names[0]); ++i) { + if (!mandatory_fields_seen[i]) { + printf("--%s was required but it was not provided\n", mandatory_field_names[i]); + not_seen_any = true; + } + } + if (not_seen_any) { + exit(1); + } + return res; + } +}; + +#endif // _CLI_H_ diff --git a/cpp/cli.in b/cpp/cli.in new file mode 100644 index 0000000..f4fdd0d --- /dev/null +++ b/cpp/cli.in @@ -0,0 +1,7 @@ +#[main] +struct Cli { + #[short, long] + input: string, + #[short, long] + trace: bool, +} diff --git a/cpp/common.h b/cpp/common.h new file mode 100644 index 0000000..308302f --- /dev/null +++ b/cpp/common.h @@ -0,0 +1,34 @@ +// +// Created by George Liontos on 19/4/24. +// + +#ifndef CPP_COMMON_H +#define CPP_COMMON_H + +#include +#include + +#define internal static + +#define MALLOC(size, type) ((type*) malloc((size) * sizeof(type))) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) + +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ABS(x) ((x) > 0 ? (x) : -(x)) + +inline int number_of_digits(int n) { + if (n == 0) { + return 1; + } + + int count = 0; + while (n != 0) { + n /= 10; + ++count; + } + + return count; +} + +#endif //CPP_COMMON_H diff --git a/cpp/entire_file.h b/cpp/entire_file.h new file mode 100644 index 0000000..1acee81 --- /dev/null +++ b/cpp/entire_file.h @@ -0,0 +1,59 @@ +// +// Created by George Liontos on 20/4/24. +// + +#ifndef CPP_ENTIRE_FILE_H +#define CPP_ENTIRE_FILE_H + +#include +#include +#include "common.h" + +struct EntireFile { + char* contents; + size_t num_bytes; +}; + +EntireFile read_entire_file_into_memory(const char* path) { + EntireFile res = {0}; + FILE* f = fopen(path, "rb"); + size_t num_bytes_read = 0U; + + if (f == nullptr) { + fprintf(stderr, "Could not open '%s' for reading\n", path); + return res; + } + + if (fseek(f, 0, SEEK_END) != 0) { + goto err; + } + + res.num_bytes = ftell(f); + if (res.num_bytes == -1L) { + goto err; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + goto err; + } + + res.contents = MALLOC(res.num_bytes + 1, char); + num_bytes_read = fread(res.contents, sizeof(char), res.num_bytes, f); + if (num_bytes_read != res.num_bytes) { + goto err; + } + + fclose(f); + return res; + + err: + fclose(f); + if (res.contents) { + free(res.contents); + } + res.contents = nullptr; + res.num_bytes = 0; + return res; +} + +#endif //CPP_ENTIRE_FILE_H diff --git a/cpp/format_buffer.cpp b/cpp/format_buffer.cpp new file mode 100644 index 0000000..5f7fc28 --- /dev/null +++ b/cpp/format_buffer.cpp @@ -0,0 +1,88 @@ +// +// Created by George Liontos on 19/4/24. +// + +#include "common.h" +#include "format_buffer.h" + +#include +#include +#include + +void FormatBuffer_init(FormatBuffer* buf, size_t capacity) { + buf->buf = MALLOC(capacity, char); + buf->len = 0; + buf->capacity = capacity; +} + +void FormatBuffer_free(FormatBuffer* buf) { + if (buf->buf) { + free(buf->buf); + } +} + +internal inline size_t FormatBuffer_available_bytes(FormatBuffer* fmt_buf) { + return fmt_buf->capacity - fmt_buf->len; +} + +internal void FormatBuffer_grow_if_necessary(FormatBuffer* fmt_buf, size_t required_len) { + if (FormatBuffer_available_bytes(fmt_buf) < required_len + 1) { + char* new_buf = MALLOC(fmt_buf->capacity + required_len + 1, char); + memcpy(new_buf, fmt_buf->buf, fmt_buf->len); + free(fmt_buf->buf); + fmt_buf->buf = new_buf; + fmt_buf->capacity += required_len + 1; + } +} + +void FormatBuffer_push_str(FormatBuffer* fmt_buf, const char* str) { + size_t str_size = strlen(str); + FormatBuffer_grow_if_necessary(fmt_buf, str_size); + + int bytes_written = snprintf(fmt_buf->buf + fmt_buf->len, FormatBuffer_available_bytes(fmt_buf), "%s", str); + + assert(bytes_written != -1); + assert(bytes_written == str_size); + + fmt_buf->buf[fmt_buf->len + bytes_written] = 0; + fmt_buf->len += bytes_written; +} + +void FormatBuffer_push_number(FormatBuffer* fmt_buf, int32_t n) { + int num_digits = number_of_digits(n); + FormatBuffer_grow_if_necessary(fmt_buf, num_digits); + + int bytes_written = snprintf(fmt_buf->buf + fmt_buf->len, FormatBuffer_available_bytes(fmt_buf), "%d", n); + + assert(bytes_written != -1); + assert(bytes_written == num_digits); + fmt_buf->buf[fmt_buf->len + bytes_written] = 0; + fmt_buf->len += bytes_written; +} + +void FormatBuffer_clear(FormatBuffer* fmt_buf) { + fmt_buf->len = 0; +} + + +void format_number_array(int32_t* arr, size_t arr_size, FormatBuffer* fmt_buf) { + if (arr_size == 0) { + FormatBuffer_push_str(fmt_buf, "[]"); + return; + } + + for (size_t i = 0U; i != arr_size; ++i) { + FormatBuffer_push_number(fmt_buf, arr[i]); + + if (i != arr_size - 1) { + FormatBuffer_push_str(fmt_buf, ", "); + } + } +} + +FormatBuffer allocate_and_format_number_array(int32_t* arr, size_t arr_size) { + FormatBuffer res = {0}; + FormatBuffer_init(&res, MAX(arr_size, 3)); + format_number_array(arr, arr_size, &res); + return res; +} diff --git a/cpp/format_buffer.h b/cpp/format_buffer.h new file mode 100644 index 0000000..95aa184 --- /dev/null +++ b/cpp/format_buffer.h @@ -0,0 +1,23 @@ +// +// Created by George Liontos on 19/4/24. +// + +#ifndef CPP_FORMAT_BUFFER_H +#define CPP_FORMAT_BUFFER_H + +struct FormatBuffer { + char* buf; + size_t len; + size_t capacity; +}; + +void FormatBuffer_init(FormatBuffer* buf, size_t capacity); +void FormatBuffer_free(FormatBuffer* buf); +void FormatBuffer_push_str(FormatBuffer* buf, const char* str); +void FormatBuffer_push_number(FormatBuffer* buf, int n); +void FormatBuffer_clear(FormatBuffer* buf); + +void format_number_array(int* arr, size_t arr_size, FormatBuffer* fmt_buf); +FormatBuffer allocate_and_format_number_array(int* arr, size_t arr_size); + +#endif //CPP_FORMAT_BUFFER_H diff --git a/cpp/main.cpp b/cpp/main.cpp new file mode 100644 index 0000000..75a224c --- /dev/null +++ b/cpp/main.cpp @@ -0,0 +1,40 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "cli.h" +#include "virtual_machine.h" +#include "common.h" +#include "entire_file.h" + + +internal bool read_bytecode(const char* path, Bytecode** bytecode_out, size_t* num_codes) { + EntireFile file = read_entire_file_into_memory(path); + if (!file.contents) { + return false; + } + + *bytecode_out = (Bytecode*) file.contents; + *num_codes = file.num_bytes / sizeof(Bytecode); + return true; +} + +int main(int argc, char* args[]) { + Cli cli = Cli::parse(argc, args); + + Bytecode* codes = nullptr; + size_t num_codes = 0; + if (!read_bytecode(cli.input.c_str(), &codes, &num_codes)) { + fprintf(stderr, "Failed to read bytecode from file '%s'\n", cli.input.c_str()); + return EXIT_FAILURE; + } + + VirtualMachine vm = {}; + vm.trace = cli.trace; + + vm_execute(&vm, codes, num_codes); + + free(codes); + + return EXIT_SUCCESS; +} diff --git a/cpp/tests/call_tests.cpp b/cpp/tests/call_tests.cpp new file mode 100644 index 0000000..7600908 --- /dev/null +++ b/cpp/tests/call_tests.cpp @@ -0,0 +1,73 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "unit_test.h" +#include "../virtual_machine.h" + +UNIT_TEST(simple_call) { + VirtualMachine vm = {}; + Bytecode codes[] = { + // 0: entrypoint + /* 0*/ Bytecode::TRACE, + /* 1*/ Bytecode::CALL, (Bytecode) 5, + /* 3*/ Bytecode::JMP, (Bytecode) 10, + // 5: a simple print(12) function + /* 5*/ Bytecode::CONST, (Bytecode) 12, + /* 6*/ Bytecode::PRINT, + /* 7*/ Bytecode::RET, + // 8: end + /* 8*/ Bytecode::NOP, + /* 9*/ Bytecode::NOP, + /*10*/ Bytecode::NOP + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(countdown_function) { + VirtualMachine vm = {}; + Bytecode codes[] = { + // 0: entrypoint + /* 0*/ Bytecode::TRACE, + /* 1*/ Bytecode::JMP, (Bytecode) 25, + // function countdown(count) + // expects count on top of stack; no return value + /* 3*/ Bytecode::STORE, (Bytecode) 0, // move count into locals[0] + /* 5*/ Bytecode::LOAD, (Bytecode) 0, // locals[0] + /* 7*/ Bytecode::PRINT, // print + /* 8*/ Bytecode::LOAD, (Bytecode) 0, // locals[0] + /*10*/ Bytecode::CONST, (Bytecode) 0, // 0 + /*12*/ Bytecode::EQ, // locals[0] == 0 ? + /*13*/ Bytecode::JNZ, (Bytecode) 24, // jump to return + /*15*/ Bytecode::LOAD, (Bytecode) 0, // locals[0] = locals[0] - 1 + /*17*/ Bytecode::CONST, (Bytecode) 1, + /*19*/ Bytecode::SUB, + /*20*/ Bytecode::STORE, (Bytecode) 0, + /*22*/ Bytecode::JMP, (Bytecode) 5, // jump to top of loop + /*24*/ Bytecode::RET, + // end + /*25*/ Bytecode::CONST, (Bytecode) 5, + /*27*/ Bytecode::CALL, (Bytecode) 3, // CALL line 3 + /*29*/ Bytecode::NOP + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + TEST_PASSED(); +} + +TestFn tests[] = { + test_simple_call, + test_countdown_function, +}; + +int main() { + run_tests(tests, ARRAY_SIZE(tests)); + return 0; +} diff --git a/cpp/tests/comparison_tests.cpp b/cpp/tests/comparison_tests.cpp new file mode 100644 index 0000000..0be0bd5 --- /dev/null +++ b/cpp/tests/comparison_tests.cpp @@ -0,0 +1,116 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "unit_test.h" +#include "../virtual_machine.h" + +UNIT_TEST(eq) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::CONST, (Bytecode) 5, + Bytecode::EQ + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 1); + + TEST_PASSED(); +} + +UNIT_TEST(neq) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::CONST, (Bytecode) 5, + Bytecode::NEQ + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 0); + + TEST_PASSED(); +} + +UNIT_TEST(gt) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 2, + Bytecode::CONST, (Bytecode) 5, + Bytecode::GT + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 0); + + TEST_PASSED(); +} + +UNIT_TEST(lt) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 2, + Bytecode::CONST, (Bytecode) 5, + Bytecode::LT + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 1); + + TEST_PASSED(); +} + +UNIT_TEST(gte) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::CONST, (Bytecode) 5, + Bytecode::GTE + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 1); + + TEST_PASSED(); +} + +UNIT_TEST(lte) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::CONST, (Bytecode) 5, + Bytecode::LTE + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 1); + + TEST_PASSED(); +} + +TestFn tests[] = { + test_eq, + test_neq, + test_gt, + test_lt, + test_gte, + test_lte +}; + +int main() { + run_tests(tests, ARRAY_SIZE(tests)); + return 0; +} diff --git a/cpp/tests/global_tests.cpp b/cpp/tests/global_tests.cpp new file mode 100644 index 0000000..70c2d58 --- /dev/null +++ b/cpp/tests/global_tests.cpp @@ -0,0 +1,72 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "unit_test.h" +#include "../virtual_machine.h" + +UNIT_TEST(store) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 42, + Bytecode::GSTORE, (Bytecode) 0 + }; + + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + ASSERT(vm.globals[0] == 42); + + TEST_PASSED(); +} + +UNIT_TEST(load) { + VirtualMachine vm = {}; + vm.globals[0] = 42; + Bytecode codes[] = { + Bytecode::GLOAD, (Bytecode) 0, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.globals[0] == 42); + ASSERT(vm.stack[0] == 42); + + TEST_PASSED(); +} + +UNIT_TEST(load_and_store) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 42, + Bytecode::GSTORE, (Bytecode) 0, + Bytecode::CONST, (Bytecode) 3, + Bytecode::GSTORE, (Bytecode) 1, + Bytecode::GLOAD, (Bytecode) 0, + Bytecode::GLOAD, (Bytecode) 1, + Bytecode::ADD, + Bytecode::GSTORE, (Bytecode) 2 + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + ASSERT(vm.globals[0] == 42); + ASSERT(vm.globals[1] == 3); + ASSERT(vm.globals[2] == 45); + + TEST_PASSED(); +} + +TestFn tests[] = { + test_store, + test_load, + test_load_and_store, +}; + +int main() { + run_tests(tests, ARRAY_SIZE(tests)); + return 0; +} diff --git a/cpp/tests/jump_tests.cpp b/cpp/tests/jump_tests.cpp new file mode 100644 index 0000000..7c1f8d4 --- /dev/null +++ b/cpp/tests/jump_tests.cpp @@ -0,0 +1,142 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "unit_test.h" +#include "../virtual_machine.h" + +UNIT_TEST(jmp) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::JMP, (Bytecode) 3, + Bytecode::FATAL, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(rjmp) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::RJMP, (Bytecode) 6, + Bytecode::CONST, (Bytecode) 12, + Bytecode::FATAL, + Bytecode::FATAL, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(jmpi) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 7, + Bytecode::JMPI, + Bytecode::CONST, (Bytecode) 12, + Bytecode::CONST, (Bytecode) 27, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(rjmpi) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::RJMPI, + Bytecode::CONST, (Bytecode) 12, + Bytecode::FATAL, + Bytecode::FATAL, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(jnz) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::JNZ, (Bytecode) 6, + Bytecode::FATAL, + Bytecode::FATAL, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(jz) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 0, + Bytecode::JZ, (Bytecode) 6, + Bytecode::FATAL, + Bytecode::FATAL, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(many_jumps) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::TRACE, + Bytecode::JMP, (Bytecode) 4, + Bytecode::FATAL, + Bytecode::JMP, (Bytecode) 7, + Bytecode::FATAL, + Bytecode::JMP, (Bytecode) 10, + Bytecode::FATAL, + Bytecode::NOP, + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +TestFn tests[] = { + test_jmp, + test_jmpi, + test_rjmp, + test_rjmpi, + test_jnz, + test_jz, + test_many_jumps, +}; + +int main() { + run_tests(tests, ARRAY_SIZE(tests)); + return 0; +} diff --git a/cpp/tests/math_tests.cpp b/cpp/tests/math_tests.cpp new file mode 100644 index 0000000..e0dea1d --- /dev/null +++ b/cpp/tests/math_tests.cpp @@ -0,0 +1,138 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "unit_test.h" +#include "../virtual_machine.h" + +UNIT_TEST(add) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 5, + Bytecode::CONST, (Bytecode) 5, + Bytecode::ADD + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +UNIT_TEST(sub) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 15, + Bytecode::CONST, (Bytecode) 5, + Bytecode::SUB + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +UNIT_TEST(mul) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 2, + Bytecode::CONST, (Bytecode) 5, + Bytecode::MUL + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +UNIT_TEST(div) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 50, + Bytecode::CONST, (Bytecode) 5, + Bytecode::DIV + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +UNIT_TEST(mod) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 30, + Bytecode::CONST, (Bytecode) 20, + Bytecode::MOD + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +UNIT_TEST(abs) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) -10, + Bytecode::ABS + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +UNIT_TEST(neg) { + VirtualMachine vm = {}; + Bytecode codes[] = { + Bytecode::CONST, (Bytecode) 10, + Bytecode::NEG + }; + + vm_execute(&vm, codes, ARRAY_SIZE(codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == -10); + + Bytecode neg = Bytecode::NEG; + vm_execute(&vm, &neg, 1); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 10); + + TEST_PASSED(); +} + +TestFn tests[] = { + test_add, + test_sub, + test_mul, + test_div, + test_mod, + test_abs, + test_neg, +}; + +int main() { + run_tests(tests, ARRAY_SIZE(tests)); + return 0; +} + diff --git a/cpp/tests/stack_tests.cpp b/cpp/tests/stack_tests.cpp new file mode 100644 index 0000000..9da932c --- /dev/null +++ b/cpp/tests/stack_tests.cpp @@ -0,0 +1,67 @@ +// +// Created by George Liontos on 20/4/24. +// + +#include "unit_test.h" +#include "../virtual_machine.h" + +UNIT_TEST(pop) { + VirtualMachine vm = {}; + Bytecode push_codes[] = { + Bytecode::CONST, (Bytecode) 27 + }; + + vm_execute(&vm, push_codes, ARRAY_SIZE(push_codes)); + + ASSERT(vm.stack_pointer == 0); + ASSERT(vm.stack[0] == 27); + + Bytecode pop = Bytecode::POP; + vm_execute(&vm, &pop, 1); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + +UNIT_TEST(const) { + VirtualMachine vm = {}; + Bytecode push_codes[] = { + Bytecode::CONST, (Bytecode) 27, + Bytecode::CONST, (Bytecode) 34 + }; + + vm_execute(&vm, push_codes, ARRAY_SIZE(push_codes)); + + ASSERT(vm.stack_pointer == 1); + ASSERT(vm.stack[0] == 27); + ASSERT(vm.stack[1] == 34); + + TEST_PASSED(); +} + +UNIT_TEST(const_pop) { + VirtualMachine vm = {}; + Bytecode push_codes[] = { + Bytecode::CONST, (Bytecode) 27, + Bytecode::POP, + }; + + vm_execute(&vm, push_codes, ARRAY_SIZE(push_codes)); + + ASSERT(vm.stack_pointer == -1); + + TEST_PASSED(); +} + + +TestFn tests[] = { + test_pop, + test_const, + test_const_pop, +}; + +int main() { + run_tests(tests, ARRAY_SIZE(tests)); + return 0; +} diff --git a/cpp/tests/unit_test.h b/cpp/tests/unit_test.h new file mode 100644 index 0000000..86b4fe3 --- /dev/null +++ b/cpp/tests/unit_test.h @@ -0,0 +1,87 @@ +// +// Created by George Liontos on 20/4/24. +// + +#ifndef CPP_UNIT_TEST_H +#define CPP_UNIT_TEST_H + +#ifndef UNIT_TEST_H +#define UNIT_TEST_H + +#include +#include +#include "../common.h" + +#define XCONCAT(lhs, rhs) lhs##rhs +#define CONCAT(lhs, rhs) XCONCAT(lhs, rhs) + +#define XSTRINGIFY(s) #s +#define STRINGIFY(s) XSTRINGIFY(s) + +/* FOREGROUND */ +#define RST "\x1B[0m" +#define KBOLD "\x1B[1m" +#define KUNDL "\x1B[4m" +#define KRED "\x1B[31m" +#define KGRN "\x1B[32m" +#define KYEL "\x1B[33m" +#define KBLU "\x1B[34m" +#define KMAG "\x1B[35m" +#define KCYN "\x1B[36m" +#define KWHT "\x1B[37m" + +#define FRED(x) KRED x RST +#define FGRN(x) KGRN x RST +#define FYEL(x) KYEL x RST +#define FBLU(x) KBLU x RST +#define FMAG(x) KMAG x RST +#define FCYN(x) KCYN x RST +#define FWHT(x) KWHT x RST + +#define BOLD(x) KBOLD x RST +#define UNDL(x) KUNDL x RST + +#ifdef _WIN32 +#define NULL_DEV "NUL:" +#else +#define NULL_DEV "/dev/null" +#endif + +#define REPORT_ASSERTION_FAILED(cond) \ + do { \ + fprintf( \ + stderr, \ + BOLD(FRED("[TEST] `%s` failed")) "\nAssertion failed in file `%s`, " \ + "line `%d`. Condition: `%s`\n", \ + __func__, __FILE__, __LINE__, STRINGIFY(cond)); \ + } while (0) + +#define TEST_PASSED() \ + do { \ + fprintf(stderr, BOLD(FGRN("[TEST] `%s` passed successfully")) "\n", \ + __func__); \ + return; \ + } while (0) + +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + REPORT_ASSERTION_FAILED(cond); \ + return; \ + } \ + } while (0) + +#define UNIT_TEST(name) void test_##name() + +using TestFn = void (*)(); + +inline void run_tests(TestFn tests[], size_t num_tests) { + freopen(NULL_DEV, "w", stdout); + for (size_t i = 0U; i != num_tests; ++i) { + tests[i](); + } +} + +#endif + +#endif //CPP_UNIT_TEST_H diff --git a/cpp/virtual_machine.cpp b/cpp/virtual_machine.cpp new file mode 100644 index 0000000..27ffc33 --- /dev/null +++ b/cpp/virtual_machine.cpp @@ -0,0 +1,339 @@ +// +// Created by George Liontos on 19/4/24. +// + +#include "common.h" +#include "virtual_machine.h" +#include "format_buffer.h" + +#include +#include +#include +#include + +#define VM_BIN_OP(op) \ +do { \ + int rhs = vm_pop(vm); \ + int lhs = vm_pop(vm); \ + vm_push(vm, lhs op rhs); \ +} while (0) + +internal void format_vm_stack(VirtualMachine* vm, FormatBuffer* fmt_buf) { + format_number_array(vm->stack, vm->stack_pointer + 1, fmt_buf); +} + +internal void format_vm_globals(VirtualMachine* vm, FormatBuffer* fmt_buf) { + format_number_array(vm->globals, ARRAY_SIZE(vm->globals), fmt_buf); +} + +void vm_dump(VirtualMachine* vm) { + FormatBuffer fmt_buf = {0}; + FormatBuffer_init(&fmt_buf, MAX(vm->stack_pointer + 1, 8)); + + printf("SimpleVM - DUMP\n"); + printf("===============\n"); + printf("IP: %d / Trace: %s\n", vm->instruction_pointer, vm->trace ? "True" : "False"); + + format_vm_stack(vm, &fmt_buf); + printf("Working Stack (SP %d): %s\n", vm->stack_pointer, fmt_buf.buf); + FormatBuffer_clear(&fmt_buf); + + format_vm_globals(vm, &fmt_buf); + printf("Globals: %s\n", fmt_buf.buf); + FormatBuffer_clear(&fmt_buf); + + printf("Call stack (FP: %d)\n", vm->frame_pointer); + CallFrames* call_frames = &vm->call_frames; + for (ssize_t i = call_frames->len - 1; i > -1; --i) { + CallFrame* frame = &call_frames->frames[i]; + printf(" Call Frame %zd\n", i); + printf(" +- Return address: %d\n", frame->return_address); + format_number_array(frame->locals, ARRAY_SIZE(frame->locals), &fmt_buf); + printf(" +- Locals: %s\n", fmt_buf.buf); + FormatBuffer_clear(&fmt_buf); + printf(" +------------------------------------"); + } + + FormatBuffer_free(&fmt_buf); +} + +__attribute__((format(printf, 2, 3))) internal inline void vm_trace(VirtualMachine* vm, const char* fmt, ...) { + if (vm->trace) { + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + printf("\n"); + va_end(args); + } +} + +internal int32_t vm_pop(VirtualMachine* vm) { + int res = vm->stack[vm->stack_pointer--]; + + if (vm->trace) { + FormatBuffer fmt_buf = allocate_and_format_number_array(vm->stack, vm->stack_pointer + 1); + vm_trace(vm, "Pop --> Stack(SP: %d): %s", vm->stack_pointer, fmt_buf.buf); + FormatBuffer_free(&fmt_buf); + } + + return res; +} + +internal void vm_push(VirtualMachine* vm, int operand) { + vm->stack[++vm->stack_pointer] = operand; + + if (vm->trace) { + FormatBuffer fmt_buf = allocate_and_format_number_array(vm->stack, vm->stack_pointer + 1); + vm_trace(vm, "Push: %d --> Stack(SP: %d): %s", operand, vm->stack_pointer, fmt_buf.buf); + FormatBuffer_free(&fmt_buf); + } +} + +internal void vm_push_call_frame(VirtualMachine* vm, CallFrame* call_frame) { + CallFrames* call_frames = &vm->call_frames; + + if (call_frames->len == call_frames->capacity) { + size_t new_capacity = MAX(call_frames->capacity, 8); + CallFrame* new_frames = MALLOC(new_capacity, CallFrame); + + if (call_frames->frames) { + memcpy(new_frames, call_frames->frames, call_frames->len * sizeof(CallFrame)); + free(call_frames->frames); + } + + call_frames->frames = new_frames; + call_frames->capacity = new_capacity; + } + + call_frames->frames[call_frames->len] = *call_frame; + ++call_frames->len; +} + +internal void advance_vm_ip(VirtualMachine* vm, Bytecode code) { + switch (code) { + case Bytecode::JMP: + case Bytecode::JMPI: + case Bytecode::RJMP: + case Bytecode::RJMPI: + case Bytecode::JZ: + case Bytecode::JNZ: + case Bytecode::CALL: + case Bytecode::RET: + break; + case Bytecode::NOP: + case Bytecode::DUMP: + case Bytecode::TRACE: + case Bytecode::PRINT: + case Bytecode::FATAL: + case Bytecode::POP: + case Bytecode::ADD: + case Bytecode::SUB: + case Bytecode::MUL: + case Bytecode::DIV: + case Bytecode::MOD: + case Bytecode::ABS: + case Bytecode::NEG: + case Bytecode::EQ: + case Bytecode::NEQ: + case Bytecode::GT: + case Bytecode::LT: + case Bytecode::GTE: + case Bytecode::LTE: + ++vm->instruction_pointer; + break; + case Bytecode::CONST: + case Bytecode::GSTORE: + case Bytecode::GLOAD: + case Bytecode::LOAD: + case Bytecode::STORE: + vm->instruction_pointer += 2; + break; + default: + assert(false); + } +} + +void vm_execute(VirtualMachine* vm, Bytecode* codes, size_t num_codes) { + for (vm->instruction_pointer = 0; vm->instruction_pointer != num_codes; ) { + Bytecode code = codes[vm->instruction_pointer]; + switch (code) { + case Bytecode::NOP: + vm_trace(vm, "NOP"); + break; + case Bytecode::DUMP: + vm_trace(vm, "DUMP"); + vm_dump(vm); + break; + case Bytecode::TRACE: + vm->trace = !vm->trace; + vm_trace(vm, vm->trace ? "TRACE enabled" : "TRACE disabled"); + break; + case Bytecode::PRINT: + { + vm_trace(vm, "PRINT"); + int32_t value = vm_pop(vm); + printf("%d\n", value); + } break; + case Bytecode::HALT: + vm_trace(vm, "HALT"); + return; + case Bytecode::FATAL: + abort(); + case Bytecode::CONST: + { + int32_t operand = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "CONST %d", operand); + vm_push(vm, operand); + } break; + case Bytecode::POP: + vm_trace(vm, "POP"); + vm_pop(vm); + break; + case Bytecode::ADD: + vm_trace(vm, "ADD"); + VM_BIN_OP(+); + break; + case Bytecode::SUB: + vm_trace(vm, "SUB"); + VM_BIN_OP(-); + break; + case Bytecode::MUL: + vm_trace(vm, "MUL"); + VM_BIN_OP(*); + break; + case Bytecode::DIV: + vm_trace(vm, "DIV"); + VM_BIN_OP(/); + break; + case Bytecode::MOD: + vm_trace(vm, "MOD"); + VM_BIN_OP(%); + break; + case Bytecode::ABS: + { + vm_trace(vm, "ABS"); + int32_t value = vm_pop(vm); + vm_push(vm, ABS(value)); + } break; + case Bytecode::NEG: + { + vm_trace(vm, "NEG"); + int32_t value = vm_pop(vm); + vm_push(vm, -value); + } break; + case Bytecode::EQ: + vm_trace(vm, "EQ"); + VM_BIN_OP(==); + break; + case Bytecode::NEQ: + vm_trace(vm, "NEQ"); + VM_BIN_OP(!=); + break; + case Bytecode::GT: + vm_trace(vm, "GT"); + VM_BIN_OP(>); + break; + case Bytecode::LT: + vm_trace(vm, "LT"); + VM_BIN_OP(<); + break; + case Bytecode::GTE: + vm_trace(vm,"GTE"); + VM_BIN_OP(>=); + break; + case Bytecode::LTE: + vm_trace(vm, "LTE"); + VM_BIN_OP(<=); + break; + case Bytecode::JMP: + { + int32_t operand = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "JMP %d", operand); + vm->instruction_pointer = operand; + } break; + case Bytecode::RJMP: + { + int32_t operand = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "RJMP %d", operand); + vm->instruction_pointer += operand; + } break; + case Bytecode::JMPI: + { + int32_t location = vm_pop(vm); + vm_trace(vm, "JMPI %d", location); + vm->instruction_pointer = location; + } break; + case Bytecode::RJMPI: + { + int32_t location = vm_pop(vm); + vm_trace(vm, "RJMPI %d", location); + vm->instruction_pointer += location; + } break; + case Bytecode::JZ: + { + int32_t operand = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "JZ %d", operand); + if (vm_pop(vm) == 0) { + vm->instruction_pointer = operand; + } else { + vm->instruction_pointer += 2; + } + } break; + case Bytecode::JNZ: + { + int32_t operand = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "JNZ %d", operand); + if (vm_pop(vm) != 0) { + vm->instruction_pointer = operand; + } else { + vm->instruction_pointer += 2; + } + } break; + case Bytecode::GSTORE: + { + int32_t index = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "GSTORE %d", index); + vm->globals[index] = vm_pop(vm); + } break; + case Bytecode::GLOAD: + { + int32_t index = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "GLOAD %d", index); + vm_push(vm, vm->globals[index]); + } break; + case Bytecode::CALL: + { + int32_t operand = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "CALL %d", operand); + + CallFrame new_frame = {}; + new_frame.return_address = vm->instruction_pointer + 2; + vm_push_call_frame(vm, &new_frame); + ++vm->frame_pointer; + + vm->instruction_pointer = operand; + } break; + case Bytecode::RET: + vm_trace(vm, "RET"); + vm->instruction_pointer = vm->call_frames.frames[vm->frame_pointer].return_address; + --vm->call_frames.len; + --vm->frame_pointer; + break; + case Bytecode::STORE: + { + int32_t index = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "STORE %d", index); + vm->call_frames.frames[vm->frame_pointer].locals[index] = vm_pop(vm); + } break; + case Bytecode::LOAD: + { + int32_t index = (int32_t) codes[vm->instruction_pointer + 1]; + vm_trace(vm, "LOAD %d", index); + int32_t local_value = vm->call_frames.frames[vm->frame_pointer].locals[index]; + vm_push(vm, local_value); + } break; + } + + advance_vm_ip(vm, code); + } +} diff --git a/cpp/virtual_machine.h b/cpp/virtual_machine.h new file mode 100644 index 0000000..b9971b0 --- /dev/null +++ b/cpp/virtual_machine.h @@ -0,0 +1,90 @@ +// +// Created by George Liontos on 19/4/24. +// + +#ifndef CPP_VIRTUAL_MACHINE_H +#define CPP_VIRTUAL_MACHINE_H + +#include +#include +#include + +#define VM_STACK_SIZE 200 +#define VM_GLOBALS_SIZE 32 +#define VM_LOCALS_SIZE 32 + +enum class Bytecode: int32_t { + NOP, + DUMP, + TRACE, + PRINT, + HALT, + FATAL, + + // Stack opcodes + CONST, + POP, + + // Math opcodes (binary) + ADD, + SUB, + MUL, + DIV, + MOD, + + // Math opcodes (unary) + ABS, + NEG, + + // Comparison + EQ, + NEQ, + GT, + LT, + GTE, + LTE, + + // Branching opcodes + JMP, + JMPI, + RJMP, + RJMPI, + JZ, + JNZ, + + // Globals + GSTORE, + GLOAD, + + // Procedures/locals + CALL, + RET, + LOAD, + STORE +}; + +struct CallFrame { + int32_t return_address = -1; + int32_t locals[VM_LOCALS_SIZE] = {0}; +}; + +struct CallFrames { + CallFrame* frames = nullptr; + ssize_t len = 0U; + size_t capacity = 0U; +}; + +struct VirtualMachine { + int32_t instruction_pointer = -1; + int32_t stack_pointer = -1; + int32_t frame_pointer = -1; + CallFrames call_frames = {}; + int32_t stack[VM_STACK_SIZE] = {0}; + int32_t globals[VM_GLOBALS_SIZE] = {0}; + bool trace = false; +}; + +void vm_dump(VirtualMachine* vm); +void vm_execute(VirtualMachine* vm, Bytecode* codes, size_t num_codes); + +#endif //CPP_VIRTUAL_MACHINE_H