Added backtrace via --trace option and a lot more helpful information for debugging. (0.22.0)
This commit is contained in:
parent
9ac42c6ed1
commit
bd51aea318
8 changed files with 129 additions and 11 deletions
|
|
@ -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
15
examples/fail.fun
Executable 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()
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
23
src/fun.c
23
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 <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);
|
||||
|
|
|
|||
53
src/parser.c
53
src/parser.c
|
|
@ -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;
|
||||
|
|
|
|||
37
src/vm.c
37
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 : "<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"
|
||||
|
|
|
|||
2
src/vm.h
2
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue