#define _GNU_SOURCE #include "vm.h" #include "value.h" #include #include #include /* Bring in split-out built-ins without changing the build system yet */ #include "builtins_math.c" #include "builtins_io.c" #include "map.c" #include "builtins_string.c" #include "builtins_iter.c" /* Opcode case include index (vm_case_*.inc): - Core/stack/frame: vm_case_nop.inc, vm_case_halt.inc, vm_case_load_const.inc, vm_case_load_local.inc, vm_case_store_local.inc, vm_case_load_global.inc, vm_case_store_global.inc, vm_case_pop.inc, vm_case_dup.inc, vm_case_swap.inc, vm_case_call.inc, vm_case_return.inc, vm_case_print.inc, vm_case_jump.inc, vm_case_jump_if_false.inc - Arithmetic and logic: vm_case_add.inc, vm_case_sub.inc, vm_case_mul.inc, vm_case_div.inc, vm_case_mod.inc, vm_case_lt.inc, vm_case_lte.inc, vm_case_gt.inc, vm_case_gte.inc, vm_case_eq.inc, vm_case_neq.inc, vm_case_and.inc, vm_case_or.inc, vm_case_not.inc - Conversions: vm_case_to_number.inc, vm_case_to_string.inc - Arrays and slices: vm_case_make_array.inc, vm_case_len.inc, vm_case_index_get.inc, vm_case_index_set.inc, vm_case_arr_push.inc, vm_case_arr_pop.inc, vm_case_arr_set.inc, vm_case_arr_insert.inc, vm_case_arr_remove.inc, vm_case_slice.inc - Strings and iteration helpers: vm_case_split.inc, vm_case_join.inc, vm_case_substr.inc, vm_case_find.inc, vm_case_enumerate.inc, vm_case_zip.inc - Maps and I/O: vm_case_make_map.inc, vm_case_keys.inc, vm_case_values.inc, vm_case_has_key.inc, vm_case_read_file.inc, vm_case_write_file.inc - Math utils / RNG: vm_case_min.inc, vm_case_max.inc, vm_case_clamp.inc, vm_case_abs.inc, vm_case_pow.inc, vm_case_random_seed.inc, vm_case_random_int.inc Dev tips: - When adding a new opcode: 1) Define OP_ in bytecode.h and opcode_names[] in vm.h. 2) Implement its VM handler in src/vm_case_.inc. 3) Include it in the switch below. 4) Run scripts/check_op_includes.py to verify coverage. - You can run scripts/run_examples.sh to sanity-check examples quickly. */ static const char* value_type_name(ValueType t) { switch (t) { case VAL_INT: return "int"; case VAL_STRING: return "string"; case VAL_FUNCTION: return "function"; case VAL_NIL: return "nil"; default: return "unknown"; } } void vm_clear_output(VM *vm) { for (int i = 0; i < vm->output_count; ++i) { free_value(vm->output[i]); } vm->output_count = 0; } void vm_free(VM *vm) { // currently nothing persistent allocated inside VM itself } /* forward declaration for helper used in vm_reset */ static void vm_pop_frame(VM *vm); void vm_reset(VM *vm) { // Pop all frames (free locals) while (vm->fp >= 0) { vm_pop_frame(vm); } // Clear stack vm->sp = -1; // Free globals for (int i = 0; i < VM_MAX_GLOBALS; ++i) { free_value(vm->globals[i]); vm->globals[i] = make_nil(); } // Clear output buffer vm_clear_output(vm); } void vm_dump_globals(VM *vm) { printf("=== globals ===\n"); for (int i = 0; i < VM_MAX_GLOBALS; ++i) { if (vm->globals[i].type != VAL_NIL) { printf("[%d] ", i); print_value(&vm->globals[i]); printf("\n"); } } printf("===============\n"); } static void push_value(VM *vm, Value v) { if (vm->sp >= VM_STACK_SIZE - 1) { fprintf(stderr, "Runtime error: stack overflow\n"); exit(1); } vm->stack[++vm->sp] = v; /* take ownership of v */ } static Value pop_value(VM *vm) { if (vm->sp < 0) { fprintf(stderr, "Runtime error: stack underflow\n"); exit(1); } return vm->stack[vm->sp--]; /* caller owns returned Value */ } static void frame_init(Frame *f) { f->fn = NULL; f->ip = 0; for (int i = 0; i < FRAME_MAX_LOCALS; ++i) f->locals[i] = make_nil(); } void vm_init(VM *vm) { vm->sp = -1; vm->fp = -1; vm->output_count = 0; vm->instr_count = 0; for (int i = 0; i < VM_MAX_GLOBALS; ++i) vm->globals[i] = make_nil(); } /* push a new frame, transferring ownership of args[] into frame->locals[0..argc-1] */ static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) { if (vm->fp >= VM_MAX_FRAMES - 1) { fprintf(stderr, "Runtime error: too many frames\n"); exit(1); } Frame *f = &vm->frames[++vm->fp]; frame_init(f); f->fn = fn; f->ip = 0; /* move args into locals 0..argc-1 */ for (int i = 0; i < argc && i < FRAME_MAX_LOCALS; ++i) { f->locals[i] = args[i]; /* transfer ownership */ } } /* pop current frame and free its locals */ static void vm_pop_frame(VM *vm) { if (vm->fp < 0) { fprintf(stderr, "Runtime error: pop frame with empty frame stack\n"); exit(1); } Frame *f = &vm->frames[vm->fp]; for (int i = 0; i < FRAME_MAX_LOCALS; ++i) { free_value(f->locals[i]); f->locals[i] = make_nil(); } vm->fp--; } void vm_print_output(VM *vm) { for (int i = 0; i < vm->output_count; ++i) { print_value(&vm->output[i]); printf("\n"); } } void vm_run(VM *vm, Bytecode *entry) { /* reset instruction count for this run */ vm->instr_count = 0; /* start with entry frame (no args) */ vm_push_frame(vm, entry, 0, NULL); while (vm->fp >= 0) { Frame *f = &vm->frames[vm->fp]; if (f->ip < 0 || f->ip >= f->fn->instr_count) { /* no more instructions in this frame -> implicit return nil */ Value nilv = make_nil(); vm_pop_frame(vm); push_value(vm, nilv); continue; } Instruction inst = f->fn->instructions[f->ip++]; vm->instr_count++; /* count each executed instruction */ switch (inst.op) { #include "vm_case_nop.inc" #include "vm_case_load_const.inc" #include "vm_case_load_local.inc" #include "vm_case_store_local.inc" #include "vm_case_add.inc" #include "vm_case_sub.inc" #include "vm_case_mul.inc" #include "vm_case_div.inc" #include "vm_case_lt.inc" #include "vm_case_lte.inc" #include "vm_case_jump.inc" #include "vm_case_gt.inc" #include "vm_case_gte.inc" #include "vm_case_eq.inc" #include "vm_case_neq.inc" #include "vm_case_jump_if_false.inc" #include "vm_case_call.inc" #include "vm_case_return.inc" #include "vm_case_print.inc" #include "vm_case_halt.inc" #include "vm_case_pop.inc" #include "vm_case_dup.inc" #include "vm_case_swap.inc" #include "vm_case_make_array.inc" #include "vm_case_index_get.inc" #include "vm_case_index_set.inc" #include "vm_case_len.inc" #include "vm_case_arr_push.inc" #include "vm_case_arr_pop.inc" #include "vm_case_arr_set.inc" #include "vm_case_arr_insert.inc" #include "vm_case_arr_remove.inc" #include "vm_case_slice.inc" #include "vm_case_to_number.inc" #include "vm_case_to_string.inc" #include "vm_case_split.inc" #include "vm_case_join.inc" #include "vm_case_substr.inc" #include "vm_case_find.inc" #include "vm_case_contains.inc" #include "vm_case_index_of.inc" #include "vm_case_clear.inc" #include "vm_case_enumerate.inc" #include "vm_case_zip.inc" #include "vm_case_min.inc" #include "vm_case_max.inc" #include "vm_case_clamp.inc" #include "vm_case_abs.inc" #include "vm_case_pow.inc" #include "vm_case_random_seed.inc" #include "vm_case_random_int.inc" #include "vm_case_make_map.inc" #include "vm_case_keys.inc" #include "vm_case_values.inc" #include "vm_case_has_key.inc" #include "vm_case_read_file.inc" #include "vm_case_write_file.inc" #include "vm_case_load_global.inc" #include "vm_case_store_global.inc" #include "vm_case_mod.inc" #include "vm_case_and.inc" #include "vm_case_or.inc" #include "vm_case_not.inc" default: if (!opcode_is_valid(inst.op)) { fprintf(stderr, "Runtime error: unknown opcode %d (%s) at instruction %d\n", inst.op, (inst.op >= 0 && inst.op < sizeof(opcode_names)/sizeof(opcode_names[0])) ? opcode_names[inst.op] : "???", f->ip - 1); exit(1); } break; } } }