#ifndef BDL_VM_H #define BDL_VM_H #include "types.h" #include "errors.h" #include "chunk.h" #include "ops.h" #include "debug.h" #define VM_STACK_CAP 1024 typedef struct VM { // Program code. Chunk *chunk; // Program counter. u8 *pc; // Stack. Object *stack; } VM; void vm_init(VM *vm); void vm_free(VM *vm); void vm_reset(VM *vm); void vm_interpret(VM *vm, Chunk *chunk); void vm_init(VM *vm) { *vm = (VM){0}; array_init(vm->stack, VM_STACK_CAP); } void vm_free(VM *vm) { array_free(vm->stack); } void vm_reset(VM *vm) { vm_free(vm); vm_init(vm); } // Helper macros for a more clear VM switch. #define FIXNUM_BINARY_OP(OP) \ do { \ Object a = array_pop(vm->stack); \ Object b = array_pop(vm->stack); \ if (!IS_FIXNUM(a) || !IS_FIXNUM(b)) { \ error_push((Error){ \ .type = ERR_TYPE_RUNTIME, \ .value = ERR_WRONG_ARG_TYPE, \ .line = vm->chunk->lines[vm->pc - vm->chunk->code - 1].line, \ .col = vm->chunk->lines[vm->pc - vm->chunk->code - 1].col, \ }); \ return; \ } \ ssize_t x = AS_FIXNUM(a); \ ssize_t y = AS_FIXNUM(b); \ array_push(vm->stack, FIXNUM_VAL(y OP x)); \ } while (false) #define FIXNUM_CMP_OP(OP) \ do { \ Object a = array_pop(vm->stack); \ Object b = array_pop(vm->stack); \ if (!IS_FIXNUM(a) || !IS_FIXNUM(b)) { \ error_push((Error){ \ .type = ERR_TYPE_RUNTIME, \ .value = ERR_WRONG_ARG_TYPE, \ .line = vm->chunk->lines[vm->pc - vm->chunk->code - 1].line, \ .col = vm->chunk->lines[vm->pc - vm->chunk->code - 1].col, \ }); \ return; \ } \ ssize_t x = AS_FIXNUM(a); \ ssize_t y = AS_FIXNUM(b); \ Object result = y OP x ? TRUE_VAL : FALSE_VAL; \ array_push(vm->stack, result); \ } while (false) #define LOGIC_OP(OP) \ do { \ Object a = array_pop(vm->stack); \ Object b = array_pop(vm->stack); \ bool x = IS_TRUE(a); \ bool y = IS_TRUE(b); \ Object result = y OP x ? TRUE_VAL : FALSE_VAL; \ array_push(vm->stack, result); \ } while (false) void vm_interpret(VM *vm, Chunk *chunk) { vm->chunk = chunk; vm->pc = vm->chunk->code; if (vm->chunk->code == NULL || array_size(vm->chunk->code) == 0) { error_push((Error){ .type = ERR_TYPE_RUNTIME, .value = ERR_EMPTY_CHUNK, }); return; } u8 *last = vm->chunk->code + array_size(vm->chunk->code); while (vm->pc < last) { #ifdef DEBUG_TRACE_EXECUTION printf("stack: [ "); for (size_t i = 0; i < array_size(vm->stack); i++) { display(vm->stack[i]); if (i < array_size(vm->stack) - 1) { printf(" | "); } } printf(" ]\nop: "); disassemble_instruction(vm->chunk, (vm->pc - vm->chunk->code)); #endif u8 instruction = *vm->pc++; switch (instruction) { case OP_CONSTANT: { u8 constant = *vm->pc++; Object obj = vm->chunk->constants[constant]; array_push(vm->stack, obj); } break; case OP_SUM: { FIXNUM_BINARY_OP(+); } break; case OP_SUB: { FIXNUM_BINARY_OP(-); } break; case OP_MUL: { FIXNUM_BINARY_OP(*); } break; case OP_DIV: { FIXNUM_BINARY_OP(/); } break; case OP_MOD: { FIXNUM_BINARY_OP(%); } break; case OP_NOT: { Object prev = array_pop(vm->stack); Object new = IS_TRUE(prev) ? FALSE_VAL : TRUE_VAL; array_push(vm->stack, new); } break; case OP_AND: { LOGIC_OP(&&); } break; case OP_OR: { LOGIC_OP(||); } break; case OP_EQUAL: { FIXNUM_CMP_OP(==); } break; case OP_LESS: { FIXNUM_CMP_OP(<); } break; case OP_GREATER: { FIXNUM_CMP_OP(>); } break; case OP_LESS_EQUAL: { FIXNUM_CMP_OP(<=); } break; case OP_GREATER_EQUAL: { FIXNUM_CMP_OP(>=); } break; case OP_RETURN: { display(array_pop(vm->stack)); printf("\n"); return; } break; default: { error_push((Error){ .type = ERR_TYPE_RUNTIME, .value = ERR_NOT_IMPLEMENTED, .line = vm->chunk->lines[vm->pc - vm->chunk->code - 1].line, .col = vm->chunk->lines[vm->pc - vm->chunk->code - 1].col, }); return; } break; } } error_push((Error){ .type = ERR_TYPE_RUNTIME, .value = ERR_PC_OOB, .line = vm->chunk->lines[0].line, .col = vm->chunk->lines[0].col, }); } #undef FIXNUM_BINARY_OP #undef FIXNUM_CMP_OP #undef LOGIC_OP #endif // BDL_VM_H