diff --git a/CMakeLists.txt b/CMakeLists.txt index 0013498..1086f30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,8 @@ add_executable(fzc src/compiler/utils/fzl_const_pool.c src/compiler/main.c) -# VM -# add_executable(fzvm -# src/vm/main.c) +# Virtual machine +add_executable(fz + src/vm/fzl_vm.c + src/vm/main.c) diff --git a/include/vm/fzl_vm.h b/include/vm/fzl_vm.h new file mode 100644 index 0000000..811c3ea --- /dev/null +++ b/include/vm/fzl_vm.h @@ -0,0 +1,38 @@ +#ifndef FZL_VM_H +#define FZL_VM_H + +#include // NOTE: move outside compiler scope to common +#include +#include + +// VM descriptor +typedef struct +{ + fzl_const_t *consts; + size_t consts_count; + + uint64_t *ops; + size_t ops_count; + + size_t ip; + + fzl_const_t stack[256]; + size_t stack_top; + + fzl_const_t arg_stack[256]; + size_t arg_top; +} fzl_vm_t; + +// Allocate FZL VM +fzl_vm_t *fzl_mkvm(const char *filepath); + +// Load and run code +int fzl_vm_run(fzl_vm_t *vm); + +// Get last VM error message +char *fzl_vm_geterror(); + +// Free VM +void fzl_freevm(fzl_vm_t *vm); + +#endif // FZL_VM_H diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c new file mode 100644 index 0000000..defbf98 --- /dev/null +++ b/src/vm/fzl_vm.c @@ -0,0 +1,183 @@ +#include "common/fzl_builtins.h" +#include "common/fzl_opcodes.h" +#include "compiler/utils/fzl_const_pool.h" +#include +#include +#include +#include +#include + +char last_err[256]; + +static char *expect_hdr = "FZL\0\1"; + +fzl_vm_t *fzl_mkvm(const char *filepath) +{ + fzl_vm_t *vm = malloc(sizeof(fzl_vm_t)); + if (!vm) + { + snprintf(last_err, sizeof(last_err), "failed to allocate vm"); + return NULL; + } + + vm->consts = NULL; + vm->consts_count = 0; + vm->ops = NULL; + vm->ops_count = 0; + vm->ip = 0; + memset(vm->stack, 0, sizeof(vm->stack)); + vm->stack_top = 0; + memset(vm->arg_stack, 0, sizeof(vm->arg_stack)); + vm->arg_top = 0; + + FILE *fd = fopen(filepath, "rb"); + if (!fd) + { + snprintf(last_err, sizeof(last_err), "failed to open file '%s'", filepath); + return NULL; + } + + char hdr[5]; + fread(hdr, sizeof(char), 5, fd); + if (memcmp(hdr, expect_hdr, 5) != 0) + { + snprintf(last_err, sizeof(last_err), "magic check error"); + return NULL; + } + + size_t consts_count, ops_count; + fread(&consts_count, sizeof(size_t), 1, fd); + fread(&ops_count, sizeof(size_t), 1, fd); + + if (ops_count < 1) + { + snprintf(last_err, sizeof(last_err), "there is nothing to do (0 ops)"); + return NULL; + } + + if (consts_count > 0) + { + vm->consts_count = consts_count; + vm->consts = malloc(consts_count * sizeof(fzl_const_t)); + + for (size_t i = 0; i < consts_count; i++) + { + fzl_consttype_t type; + fread(&type, sizeof(fzl_consttype_t), 1, fd); + + if (type == FZL_CONSTTYPE_STRING) + { + size_t length; + char *chars; + + fread(&length, sizeof(size_t), 1, fd); + + chars = malloc(length * sizeof(char) + 1); + fread(chars, sizeof(char), length, fd); + chars[length] = 0; + + vm->consts[i] = (fzl_const_t){ + .type = type, .as.string.length = length, .as.string.chars = chars}; + } + else + { + snprintf(last_err, sizeof(last_err), "unknown const type at index %ld", + i); + return NULL; + } + } + } + + vm->ops_count = ops_count; + vm->ops = malloc(ops_count * sizeof(uint64_t)); + for (size_t i = 0; i < ops_count; i++) + { + uint64_t op; + fread(&op, sizeof(uint64_t), 1, fd); + vm->ops[i] = op; + } + + return vm; +} + +int fzl_vm_run(fzl_vm_t *vm) +{ + if (!vm) + { + snprintf(last_err, sizeof(last_err), "vm is null"); + return -1; + } + + vm->ip = 0; + while (vm->ip < vm->ops_count) + { + uint64_t opcode = vm->ops[vm->ip]; + + switch (opcode) + { + case FZL_OPCODE_CALL_BUILTIN: + uint64_t fn_id = vm->ops[++vm->ip]; + if (fn_id == FZL_BUILTIN_PRINTLINE) + { + fzl_const_t arg = vm->arg_stack[--vm->arg_top]; + printf("%s\n", arg.as.string.chars); + } + else + { + snprintf(last_err, sizeof(last_err), + "runtime error - undefined builtin %lu", fn_id); + return -1; + } + + break; + + case FZL_OPCODE_LOADCONST: + uint64_t cn = vm->ops[++vm->ip]; + if (vm->consts_count <= cn) + { + snprintf(last_err, sizeof(last_err), "runtime error - unknown const"); + return -1; + } + fzl_const_t c = vm->consts[cn]; + vm->stack[vm->stack_top++] = c; + break; + + case FZL_OPCODE_PUSHARG: + if (vm->stack_top < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot push argument: stack is empty"); + return -1; + } + vm->arg_stack[vm->arg_top++] = vm->stack[--vm->stack_top]; + break; + + default: + snprintf(last_err, sizeof(last_err), "runtime error - unknown opcode %lu", + opcode); + return -1; + } + + vm->ip++; + } + + return 0; +} + +char *fzl_vm_geterror() { return last_err; } + +void fzl_freevm(fzl_vm_t *vm) +{ + if (!vm) + return; + + for (size_t i = 0; i < vm->consts_count; i++) + { + if (vm->consts[i].type == FZL_CONSTTYPE_STRING) + free((void *)vm->consts[i].as.string.chars); + } + + free(vm->consts); + free(vm->ops); + free(vm); +} diff --git a/src/vm/main.c b/src/vm/main.c new file mode 100644 index 0000000..d1e09c0 --- /dev/null +++ b/src/vm/main.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include + +typedef struct +{ + char *filepath; + bool help; +} run_args_t; + +void print_hint() +{ + printf("Usage: fz [flags] \n"); + printf("Flags:\n"); + printf("\tno flags available yet :(\n"); +} + +// Parse passed arguments and build args setup structure +run_args_t *parse_args(int argc, char **argv) +{ + if (argc < 2) + { + print_hint(); + return NULL; + } + + run_args_t *args = malloc(sizeof(run_args_t)); + args->filepath = NULL; + args->help = false; + + for (size_t i = 0; i < argc; i++) + { + if (argv[i][0] != '-') + continue; + + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + args->help = true; + return args; + } + else + { + printf("Error: unknown flag '%s'.\n", argv[i]); + printf("Use 'fz --help' to read the usage\n"); + return NULL; + } + } + + if (argv[argc - 1][0] == '-') + { + printf("Error: last argument MUST be a file path\n"); + printf("Use 'fz --help' for help\n"); + return NULL; + } + + args->filepath = argv[argc - 1]; + + return args; +} + +int main(int argc, char **argv) +{ + run_args_t *args = parse_args(argc, argv); + if (!args) + { + return -1; + } + + fzl_vm_t *vm = fzl_mkvm(args->filepath); + if (!vm) + { + printf("Error: %s\n", fzl_vm_geterror()); + return -1; + } + + int result = fzl_vm_run(vm); + if (result != 0) + { + printf("Error: %s\n", fzl_vm_geterror()); + return -1; + } + + free(args); + fzl_freevm(vm); + + return 0; +}