From bd51aea3188a9948719953be2d0f7eb5e404c120 Mon Sep 17 00:00:00 2001 From: hanez Date: Sat, 4 Oct 2025 17:03:41 +0200 Subject: [PATCH] Added backtrace via --trace option and a lot more helpful information for debugging. (0.22.0) --- CMakeLists.txt | 2 +- examples/fail.fun | 15 ++++++++++++++ src/bytecode.c | 4 ++++ src/bytecode.h | 4 ++++ src/fun.c | 23 +++++++++++++------- src/parser.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++- src/vm.c | 37 +++++++++++++++++++++++++++++++-- src/vm.h | 2 ++ 8 files changed, 129 insertions(+), 11 deletions(-) create mode 100755 examples/fail.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e1f033..b80abb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.21.4 LANGUAGES C) +project(fun VERSION 0.22.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/fail.fun b/examples/fail.fun new file mode 100755 index 0000000..ad9f64a --- /dev/null +++ b/examples/fail.fun @@ -0,0 +1,15 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-04 + */ + +// intentionally wrong: call an integer as a function to trigger a runtime error +1() diff --git a/src/bytecode.c b/src/bytecode.c index 6fa5091..678f2ef 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -17,6 +17,8 @@ Bytecode *bytecode_new(void) { bc->instr_count = 0; bc->constants = NULL; bc->const_count = 0; + bc->name = NULL; + bc->source_file = NULL; return bc; } @@ -46,6 +48,8 @@ void bytecode_free(Bytecode *bc) { } free(bc->constants); free(bc->instructions); + if (bc->name) free((void*)bc->name); + if (bc->source_file) free((void*)bc->source_file); free(bc); } diff --git a/src/bytecode.h b/src/bytecode.h index c40f07b..4a5c29e 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -174,6 +174,10 @@ typedef struct Bytecode { Value *constants; int const_count; + + /* debug metadata */ + const char *name; /* function or module name (optional) */ + const char *source_file; /* originating source filename (optional) */ } Bytecode; // constructors / manipulation diff --git a/src/fun.c b/src/fun.c index 7d3d2cd..2773992 100644 --- a/src/fun.c +++ b/src/fun.c @@ -24,16 +24,18 @@ static void print_usage(const char *prog) { printf("Fun %s\n", FUN_VERSION); printf("Usage:\n"); #ifdef FUN_WITH_REPL - printf(" %s [script.fun]\n", prog ? prog : "fun"); + printf(" %s [--trace|-t] [script.fun]\n", prog ? prog : "fun"); printf(" %s --help | -h\n", prog ? prog : "fun"); printf(" %s --version | -V\n", prog ? prog : "fun"); printf("\n"); + printf("Options:\n --trace, -t Print executed ops and stack tops during run\n\n"); printf("When no script is provided, a REPL starts. Submit an empty line to execute the buffer.\n"); #else - printf(" %s \n", prog ? prog : "fun"); + printf(" %s [--trace|-t] \n", prog ? prog : "fun"); printf(" %s --help | -h\n", prog ? prog : "fun"); printf(" %s --version | -V\n", prog ? prog : "fun"); printf("\n"); + printf("Options:\n --trace, -t Print executed ops and stack tops during run\n\n"); printf("REPL is disabled in this build. Please provide a script file to run.\n"); #endif } @@ -42,8 +44,9 @@ int main(int argc, char **argv) { VM vm; vm_init(&vm); - if (argc > 1) { - const char *arg = argv[1]; + int argi = 1; + for (; argi < argc; ++argi) { + const char *arg = argv[argi]; if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { print_usage(argv[0]); return 0; @@ -52,18 +55,24 @@ int main(int argc, char **argv) { printf("Fun %s\n", FUN_VERSION); return 0; } + if (strcmp(arg, "--trace") == 0 || strcmp(arg, "-t") == 0) { + vm.trace_enabled = 1; + continue; + } + /* first non-option assumed to be script path */ + break; } #ifndef FUN_WITH_REPL - if (argc <= 1) { + if (argi >= argc) { fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n"); print_usage(argv[0]); return 2; } #endif - if (argc > 1) { - const char *path = argv[1]; + if (argi < argc) { + const char *path = argv[argi]; Bytecode *bc = parse_file_to_bytecode(path); if (!bc) { fprintf(stderr, "Failed to compile script: %s\n", path); diff --git a/src/parser.c b/src/parser.c index 86efa72..d4ff507 100644 --- a/src/parser.c +++ b/src/parser.c @@ -53,6 +53,7 @@ #include /* ---- parser error state ---- */ +static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */ static int g_has_error = 0; static size_t g_err_pos = 0; static char g_err_msg[256]; @@ -2792,6 +2793,13 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* Build factory function: Name(...) -> instance map with fields and methods */ Bytecode *ctor_bc = bytecode_new(); + /* set debug metadata for class factory */ + if (ctor_bc) { + if (ctor_bc->name) free((void*)ctor_bc->name); + ctor_bc->name = strdup(cname); + if (ctor_bc->source_file) free((void*)ctor_bc->source_file); + if (g_current_source_path) ctor_bc->source_file = strdup(g_current_source_path); + } /* local env for the factory to allow temp locals */ LocalEnv ctor_env; memset(&ctor_env, 0, sizeof(ctor_env)); @@ -3019,6 +3027,18 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* Build method function bytecode */ Bytecode *m_bc = bytecode_new(); + if (m_bc) { + if (m_bc->name) free((void*)m_bc->name); + /* method qualified name: Class.method */ + size_t qlen = strlen(cname) + 1 + strlen(mname) + 1; + char *q = (char*)malloc(qlen); + if (q) { + snprintf(q, qlen, "%s.%s", cname, mname); + m_bc->name = q; + } + if (m_bc->source_file) free((void*)m_bc->source_file); + if (g_current_source_path) m_bc->source_file = strdup(g_current_source_path); + } LocalEnv m_env; memset(&m_env, 0, sizeof(m_env)); LocalEnv *saved = g_locals; @@ -3267,6 +3287,12 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* compile body into separate Bytecode */ Bytecode *fn_bc = bytecode_new(); + if (fn_bc) { + if (fn_bc->name) free((void*)fn_bc->name); + fn_bc->name = strdup(fname); + if (fn_bc->source_file) free((void*)fn_bc->source_file); + if (g_current_source_path) fn_bc->source_file = strdup(g_current_source_path); + } /* parse body at increased indent if present */ int body_indent = 0; @@ -3906,7 +3932,22 @@ Bytecode *parse_file_to_bytecode(const char *path) { g_err_line = 0; g_err_col = 0; - Bytecode *bc = compile_minimal(compile_src, compile_len); + /* Set current source path for nested bytecodes to inherit */ + const char *prev_source = g_current_source_path; + g_current_source_path = path; + Bytecode *bc = compile_minimal(compile_src, compile_len); + /* assign debug metadata to module bytecode */ + if (bc) { + if (bc->source_file) free((void*)bc->source_file); + bc->source_file = path ? strdup(path) : strdup(""); + if (bc->name) free((void*)bc->name); + /* derive name from basename of path */ + const char *bn = path ? strrchr(path, '/') : NULL; + const char *base = bn ? bn + 1 : (path ? path : ""); + bc->name = strdup(base); + } + /* restore previous */ + g_current_source_path = prev_source; if (g_has_error) { int line = 1, col = 1; @@ -3984,7 +4025,17 @@ Bytecode *parse_string_to_bytecode(const char *source) { g_err_line = 0; g_err_col = 0; + /* Set current source path to for nested bytecodes */ + const char *prev_src = g_current_source_path; + g_current_source_path = NULL; Bytecode *bc = compile_minimal(compile_src, len); + if (bc) { + if (bc->source_file) free((void*)bc->source_file); + bc->source_file = strdup(""); + if (bc->name) free((void*)bc->name); + bc->name = strdup(""); + } + g_current_source_path = prev_src; if (g_has_error) { int line = 1, col = 1; diff --git a/src/vm.c b/src/vm.c index a074024..a9790cb 100644 --- a/src/vm.c +++ b/src/vm.c @@ -46,8 +46,22 @@ static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) { } } } - fprintf(stream == stderr ? stderr : stream, " (line %d, op %s @ip %d)\n", - g_active_vm->current_line, opname, ip); + const char *fname = NULL; + const char *sfile = NULL; + if (g_active_vm->fp >= 0) { + Frame *f = &g_active_vm->frames[g_active_vm->fp]; + if (f->fn) { + fname = f->fn->name; + sfile = f->fn->source_file; + } + } + fprintf(stream == stderr ? stderr : stream, + " (at %s:%d in %s, op %s @ip %d)\n", + sfile ? sfile : "", + g_active_vm->current_line, + fname ? fname : "", + opname, + ip); } return written; } @@ -183,6 +197,7 @@ void vm_init(VM *vm) { vm->output_count = 0; vm->instr_count = 0; vm->exit_code = 0; + vm->trace_enabled = 0; for (int i = 0; i < MAX_GLOBALS; ++i) vm->globals[i] = make_nil(); } @@ -247,6 +262,24 @@ void vm_run(VM *vm, Bytecode *entry) { Instruction inst = f->fn->instructions[f->ip++]; vm->instr_count++; /* count each executed instruction */ + if (vm->trace_enabled) { + const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0]))) + ? opcode_names[inst.op] : "???"; + const char *fname = f->fn && f->fn->name ? f->fn->name : ""; + const char *sfile = f->fn && f->fn->source_file ? f->fn->source_file : ""; + /* Dump up to top 4 stack values */ + int count = vm->sp + 1; + int start = count - 4; if (start < 0) start = 0; + fprintf(stdout, "TRACE %s:%d %s ip=%d %-14s %d | stack[%d]=[", sfile, vm->current_line, fname, f->ip - 1, opname, inst.operand, count); + for (int i = start; i < count; ++i) { + char *sv = value_to_string_alloc(&vm->stack[i]); + if (!sv) sv = strdup(""); + fprintf(stdout, "%s%s", sv, (i == count - 1 ? "" : ", ")); + free(sv); + } + fprintf(stdout, "]\n"); + } + switch (inst.op) { /* All opcode handlers as .c includes */ #include "vm/arithmetic/add.c" diff --git a/src/vm.h b/src/vm.h index f406521..f2b60de 100644 --- a/src/vm.h +++ b/src/vm.h @@ -66,6 +66,8 @@ typedef struct { int current_line; // last executed source line (debug) int exit_code; // process exit code set by OP_EXIT + + int trace_enabled; // when non-zero, print executed ops and stack } VM; // initialize VM (zero state)