1
0
Fork 0
forked from fun/fun

Added backtrace via --trace option and a lot more helpful information for debugging. (0.22.0)

This commit is contained in:
Johannes Findeisen 2025-10-04 17:03:41 +02:00
commit bd51aea318
8 changed files with 129 additions and 11 deletions

View file

@ -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)

15
examples/fail.fun Executable file
View file

@ -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 <you@hanez.org>
* 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()

View file

@ -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);
}

View file

@ -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

View file

@ -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 <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("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);

View file

@ -53,6 +53,7 @@
#include <stdarg.h>
/* ---- 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("<input>");
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 : "<input>");
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 <input> 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("<input>");
if (bc->name) free((void*)bc->name);
bc->name = strdup("<input>");
}
g_current_source_path = prev_src;
if (g_has_error) {
int line = 1, col = 1;

View file

@ -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 : "<unknown>",
g_active_vm->current_line,
fname ? fname : "<entry>",
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 : "<entry>";
const char *sfile = f->fn && f->fn->source_file ? f->fn->source_file : "<unknown>";
/* 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("<oom>");
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"

View file

@ -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)