/** * This file is part of the Fun programming language. * https://hanez.org/project/fun/ * * Copyright 2025 Johannes Findeisen * Licensed under the terms of the ISC license. * https://opensource.org/license/isc-license-txt */ /* Bring in split-out built-ins without changing the build system yet */ #include "iter.c" #include "map.c" #include "string.c" #include "vm.h" #include "value.h" #include #include #include #include /* Track the currently running VM to annotate error messages */ static VM *g_active_vm = NULL; /* fprintf wrapper that appends source line info for stderr messages */ static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) { int written = vfprintf(stream, fmt, ap); if (stream == stderr && g_active_vm && g_active_vm->current_line > 0) { /* If original message didn't end with newline, add a space first */ if (written > 0) { /* best-effort: do not attempt to inspect fmt string fully; just append line info */ } fprintf(stream == stderr ? stderr : stream, " (line %d)\n", g_active_vm->current_line); } return written; } static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int r = fun_vm_vfprintf(stream, fmt, ap); va_end(ap); return r; } /* Redirect fprintf within this translation unit so opcode handlers use our wrapper */ #define fprintf fun_vm_fprintf /* 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_FUNCTION: return "function"; case VAL_INT: return "int"; case VAL_NIL: return "nil"; case VAL_STRING: return "string"; 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 < 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 < 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 >= 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 < MAX_FRAME_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 < 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 >= 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 < MAX_FRAME_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 < MAX_FRAME_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; vm->current_line = 1; g_active_vm = vm; /* 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) { /* All opcode handlers as .c includes */ #include "vm/arithmetic/add.c" #include "vm/arithmetic/div.c" #include "vm/arithmetic/mul.c" #include "vm/arithmetic/sub.c" #include "vm/arrays/apop.c" #include "vm/arrays/clear.c" #include "vm/arrays/contains.c" #include "vm/arrays/enumerate.c" #include "vm/arrays/index_get.c" #include "vm/arrays/index_of.c" #include "vm/arrays/index_set.c" #include "vm/arrays/insert.c" #include "vm/arrays/join.c" #include "vm/arrays/make_array.c" #include "vm/arrays/push.c" #include "vm/arrays/remove.c" #include "vm/arrays/set.c" #include "vm/arrays/slice.c" #include "vm/arrays/zip.c" #include "vm/core/call.c" #include "vm/core/dup.c" #include "vm/core/halt.c" #include "vm/core/jump.c" #include "vm/core/jump_if_false.c" #include "vm/core/load_const.c" #include "vm/core/load_global.c" #include "vm/core/load_local.c" #include "vm/core/nop.c" #include "vm/core/pop.c" #include "vm/core/return.c" #include "vm/core/store_global.c" #include "vm/core/store_local.c" #include "vm/core/swap.c" #include "vm/io/read_file.c" #include "vm/io/write_file.c" #include "vm/os/env.c" #include "vm/logic/and.c" #include "vm/logic/eq.c" #include "vm/logic/gt.c" #include "vm/logic/gte.c" #include "vm/logic/lt.c" #include "vm/logic/lte.c" #include "vm/logic/neq.c" #include "vm/logic/not.c" #include "vm/logic/or.c" #include "vm/maps/has_key.c" #include "vm/maps/keys.c" #include "vm/maps/make_map.c" #include "vm/maps/values.c" #include "vm/math/abs.c" #include "vm/math/clamp.c" #include "vm/math/max.c" #include "vm/math/min.c" #include "vm/math/mod.c" #include "vm/math/pow.c" #include "vm/math/random_int.c" #include "vm/math/random_seed.c" #include "vm/strings/find.c" #include "vm/strings/split.c" #include "vm/strings/substr.c" #include "vm/len.c" #include "vm/line.c" #include "vm/print.c" #include "vm/to_number.c" #include "vm/to_string.c" #include "vm/typeof.c" #include "vm/uclamp.c" #include "vm/sclamp.c" 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; } } g_active_vm = NULL; }