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)
|
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 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
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->instr_count = 0;
|
||||||
bc->constants = NULL;
|
bc->constants = NULL;
|
||||||
bc->const_count = 0;
|
bc->const_count = 0;
|
||||||
|
bc->name = NULL;
|
||||||
|
bc->source_file = NULL;
|
||||||
return bc;
|
return bc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +48,8 @@ void bytecode_free(Bytecode *bc) {
|
||||||
}
|
}
|
||||||
free(bc->constants);
|
free(bc->constants);
|
||||||
free(bc->instructions);
|
free(bc->instructions);
|
||||||
|
if (bc->name) free((void*)bc->name);
|
||||||
|
if (bc->source_file) free((void*)bc->source_file);
|
||||||
free(bc);
|
free(bc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,10 @@ typedef struct Bytecode {
|
||||||
|
|
||||||
Value *constants;
|
Value *constants;
|
||||||
int const_count;
|
int const_count;
|
||||||
|
|
||||||
|
/* debug metadata */
|
||||||
|
const char *name; /* function or module name (optional) */
|
||||||
|
const char *source_file; /* originating source filename (optional) */
|
||||||
} Bytecode;
|
} Bytecode;
|
||||||
|
|
||||||
// constructors / manipulation
|
// 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("Fun %s\n", FUN_VERSION);
|
||||||
printf("Usage:\n");
|
printf("Usage:\n");
|
||||||
#ifdef FUN_WITH_REPL
|
#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 --help | -h\n", prog ? prog : "fun");
|
||||||
printf(" %s --version | -V\n", prog ? prog : "fun");
|
printf(" %s --version | -V\n", prog ? prog : "fun");
|
||||||
printf("\n");
|
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");
|
printf("When no script is provided, a REPL starts. Submit an empty line to execute the buffer.\n");
|
||||||
#else
|
#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 --help | -h\n", prog ? prog : "fun");
|
||||||
printf(" %s --version | -V\n", prog ? prog : "fun");
|
printf(" %s --version | -V\n", prog ? prog : "fun");
|
||||||
printf("\n");
|
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");
|
printf("REPL is disabled in this build. Please provide a script file to run.\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -42,8 +44,9 @@ int main(int argc, char **argv) {
|
||||||
VM vm;
|
VM vm;
|
||||||
vm_init(&vm);
|
vm_init(&vm);
|
||||||
|
|
||||||
if (argc > 1) {
|
int argi = 1;
|
||||||
const char *arg = argv[1];
|
for (; argi < argc; ++argi) {
|
||||||
|
const char *arg = argv[argi];
|
||||||
if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
|
if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -52,18 +55,24 @@ int main(int argc, char **argv) {
|
||||||
printf("Fun %s\n", FUN_VERSION);
|
printf("Fun %s\n", FUN_VERSION);
|
||||||
return 0;
|
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
|
#ifndef FUN_WITH_REPL
|
||||||
if (argc <= 1) {
|
if (argi >= argc) {
|
||||||
fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n");
|
fprintf(stderr, "Error: REPL is disabled. Please provide a script to run.\n");
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (argc > 1) {
|
if (argi < argc) {
|
||||||
const char *path = argv[1];
|
const char *path = argv[argi];
|
||||||
Bytecode *bc = parse_file_to_bytecode(path);
|
Bytecode *bc = parse_file_to_bytecode(path);
|
||||||
if (!bc) {
|
if (!bc) {
|
||||||
fprintf(stderr, "Failed to compile script: %s\n", path);
|
fprintf(stderr, "Failed to compile script: %s\n", path);
|
||||||
|
|
|
||||||
53
src/parser.c
53
src/parser.c
|
|
@ -53,6 +53,7 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
/* ---- parser error state ---- */
|
/* ---- parser error state ---- */
|
||||||
|
static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */
|
||||||
static int g_has_error = 0;
|
static int g_has_error = 0;
|
||||||
static size_t g_err_pos = 0;
|
static size_t g_err_pos = 0;
|
||||||
static char g_err_msg[256];
|
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 */
|
/* Build factory function: Name(...) -> instance map with fields and methods */
|
||||||
Bytecode *ctor_bc = bytecode_new();
|
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 */
|
/* local env for the factory to allow temp locals */
|
||||||
LocalEnv ctor_env;
|
LocalEnv ctor_env;
|
||||||
memset(&ctor_env, 0, sizeof(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 */
|
/* Build method function bytecode */
|
||||||
Bytecode *m_bc = bytecode_new();
|
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;
|
LocalEnv m_env;
|
||||||
memset(&m_env, 0, sizeof(m_env));
|
memset(&m_env, 0, sizeof(m_env));
|
||||||
LocalEnv *saved = g_locals;
|
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 */
|
/* compile body into separate Bytecode */
|
||||||
Bytecode *fn_bc = bytecode_new();
|
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 */
|
/* parse body at increased indent if present */
|
||||||
int body_indent = 0;
|
int body_indent = 0;
|
||||||
|
|
@ -3906,7 +3932,22 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
||||||
g_err_line = 0;
|
g_err_line = 0;
|
||||||
g_err_col = 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) {
|
if (g_has_error) {
|
||||||
int line = 1, col = 1;
|
int line = 1, col = 1;
|
||||||
|
|
@ -3984,7 +4025,17 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
||||||
g_err_line = 0;
|
g_err_line = 0;
|
||||||
g_err_col = 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);
|
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) {
|
if (g_has_error) {
|
||||||
int line = 1, col = 1;
|
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",
|
const char *fname = NULL;
|
||||||
g_active_vm->current_line, opname, ip);
|
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;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
@ -183,6 +197,7 @@ void vm_init(VM *vm) {
|
||||||
vm->output_count = 0;
|
vm->output_count = 0;
|
||||||
vm->instr_count = 0;
|
vm->instr_count = 0;
|
||||||
vm->exit_code = 0;
|
vm->exit_code = 0;
|
||||||
|
vm->trace_enabled = 0;
|
||||||
for (int i = 0; i < MAX_GLOBALS; ++i)
|
for (int i = 0; i < MAX_GLOBALS; ++i)
|
||||||
vm->globals[i] = make_nil();
|
vm->globals[i] = make_nil();
|
||||||
}
|
}
|
||||||
|
|
@ -247,6 +262,24 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
Instruction inst = f->fn->instructions[f->ip++];
|
Instruction inst = f->fn->instructions[f->ip++];
|
||||||
vm->instr_count++; /* count each executed instruction */
|
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) {
|
switch (inst.op) {
|
||||||
/* All opcode handlers as .c includes */
|
/* All opcode handlers as .c includes */
|
||||||
#include "vm/arithmetic/add.c"
|
#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 current_line; // last executed source line (debug)
|
||||||
|
|
||||||
int exit_code; // process exit code set by OP_EXIT
|
int exit_code; // process exit code set by OP_EXIT
|
||||||
|
|
||||||
|
int trace_enabled; // when non-zero, print executed ops and stack
|
||||||
} VM;
|
} VM;
|
||||||
|
|
||||||
// initialize VM (zero state)
|
// initialize VM (zero state)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue