Skip to content

Commit 50860ec

Browse files
gh-26: Add tracebacks.
1 parent a227cd4 commit 50860ec

File tree

8 files changed

+349
-18
lines changed

8 files changed

+349
-18
lines changed

src/ast.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
#include <string.h>
44
#include <stdio.h>
55

6+
#ifdef _MSC_VER
7+
#define strdup _strdup
8+
#endif
9+
610
static void* ast_alloc(size_t size) {
711
void* ptr = malloc(size);
812
if (!ptr) {
@@ -494,7 +498,8 @@ void free_stmt(Stmt* stmt) {
494498
default:
495499
break;
496500
}
497-
free(stmt);
501+
if (stmt->src_text) free(stmt->src_text);
502+
free(stmt);
498503
}
499504

500505
Expr* expr_ptr(char* name, int line, int column) {
@@ -504,4 +509,14 @@ Expr* expr_ptr(char* name, int line, int column) {
504509
expr->column = column;
505510
expr->as.ptr_name = name;
506511
return expr;
512+
}
513+
514+
void stmt_set_src(Stmt* stmt, const char* src) {
515+
if (!stmt) return;
516+
if (stmt->src_text) free(stmt->src_text);
517+
if (!src) {
518+
stmt->src_text = NULL;
519+
return;
520+
}
521+
stmt->src_text = strdup(src);
507522
}

src/ast.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ struct Stmt {
115115
StmtType type;
116116
int line;
117117
int column;
118+
char* src_text;
118119
union {
119120
StmtList block;
120121
struct { Expr* expr; } expr_stmt;
@@ -179,6 +180,9 @@ Stmt* stmt_gotopoint(Expr* target, int line, int column);
179180
void stmt_list_add(StmtList* list, Stmt* stmt);
180181
void param_list_add(ParamList* list, Param param);
181182

183+
// Attach original source text (single line) to a statement node.
184+
void stmt_set_src(Stmt* stmt, const char* src);
185+
182186
void free_expr(Expr* expr);
183187
void free_stmt(Stmt* stmt);
184188

src/interpreter.c

Lines changed: 246 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,187 @@ static void wait_if_paused(Interpreter* interp) {
2727

2828
static mtx_t g_tns_lock;
2929
static mtx_t g_parfor_merge_lock;
30+
31+
static const char* stmt_type_name(StmtType type) {
32+
switch (type) {
33+
case STMT_BLOCK: return "BLOCK";
34+
case STMT_ASYNC: return "ASYNC";
35+
case STMT_EXPR: return "EXPR";
36+
case STMT_ASSIGN: return "ASSIGN";
37+
case STMT_DECL: return "DECL";
38+
case STMT_IF: return "IF";
39+
case STMT_WHILE: return "WHILE";
40+
case STMT_FOR: return "FOR";
41+
case STMT_PARFOR: return "PARFOR";
42+
case STMT_FUNC: return "FUNC";
43+
case STMT_RETURN: return "RETURN";
44+
case STMT_BREAK: return "BREAK";
45+
case STMT_CONTINUE: return "CONTINUE";
46+
case STMT_THR: return "THR";
47+
case STMT_POP: return "POP";
48+
case STMT_TRY: return "TRY";
49+
case STMT_GOTO: return "GOTO";
50+
case STMT_GOTOPOINT: return "GOTOPOINT";
51+
default: return "UNKNOWN";
52+
}
53+
}
54+
55+
static int trace_stack_grow(Interpreter* interp) {
56+
if (!interp) return -1;
57+
if (interp->trace_stack_count + 1 <= interp->trace_stack_capacity) return 0;
58+
size_t new_cap = interp->trace_stack_capacity == 0 ? 8 : interp->trace_stack_capacity * 2;
59+
TraceFrame* grown = realloc(interp->trace_stack, new_cap * sizeof(TraceFrame));
60+
if (!grown) return -1;
61+
interp->trace_stack = grown;
62+
interp->trace_stack_capacity = new_cap;
63+
return 0;
64+
}
65+
66+
static int trace_push_frame(Interpreter* interp, const char* name, Env* env, int call_line, int call_col, int has_call_location) {
67+
if (!interp) return -1;
68+
if (trace_stack_grow(interp) != 0) return -1;
69+
TraceFrame* frame = &interp->trace_stack[interp->trace_stack_count];
70+
memset(frame, 0, sizeof(*frame));
71+
frame->name = strdup(name ? name : "<frame>");
72+
if (!frame->name) return -1;
73+
interp->trace_stack_count++;
74+
frame->env = env;
75+
frame->call_line = call_line;
76+
frame->call_col = call_col;
77+
frame->has_call_location = has_call_location;
78+
frame->last_step_index = -1;
79+
return 0;
80+
}
81+
82+
static void trace_pop_frame(Interpreter* interp) {
83+
if (!interp || interp->trace_stack_count == 0) return;
84+
TraceFrame* frame = &interp->trace_stack[interp->trace_stack_count - 1];
85+
free(frame->name);
86+
frame->name = NULL;
87+
interp->trace_stack_count--;
88+
}
89+
90+
static void trace_log_step(Interpreter* interp, Stmt* stmt, Env* env) {
91+
if (!interp || !stmt) return;
92+
if (interp->trace_stack_count == 0) {
93+
if (trace_push_frame(interp, "<top-level>", env ? env : interp->global_env, 0, 0, 0) != 0) return;
94+
}
95+
96+
TraceFrame* frame = &interp->trace_stack[interp->trace_stack_count - 1];
97+
frame->env = env;
98+
frame->last_step_index = interp->trace_next_step_index;
99+
snprintf(frame->state_id, sizeof(frame->state_id), "s_%06d", interp->trace_next_step_index);
100+
frame->has_state_entry = 1;
101+
frame->last_line = stmt->line;
102+
frame->last_col = stmt->column;
103+
if (stmt && stmt->src_text && stmt->src_text[0]) {
104+
// store a short snippet of the original source line
105+
snprintf(frame->last_statement, sizeof(frame->last_statement), "%s", stmt->src_text);
106+
} else {
107+
snprintf(frame->last_statement, sizeof(frame->last_statement), "%s", stmt_type_name(stmt->type));
108+
}
109+
110+
snprintf(interp->trace_last_state_id, sizeof(interp->trace_last_state_id), "%s", frame->state_id);
111+
snprintf(interp->trace_last_rule, sizeof(interp->trace_last_rule), "%s", stmt_type_name(stmt->type));
112+
interp->trace_next_step_index++;
113+
}
114+
115+
static void trace_append(char** dst, size_t* len, size_t* cap, const char* text) {
116+
if (!dst || !len || !cap || !text) return;
117+
size_t add = strlen(text);
118+
size_t need = *len + add + 1;
119+
if (need > *cap) {
120+
size_t new_cap = *cap == 0 ? 256 : *cap;
121+
while (new_cap < need) new_cap *= 2;
122+
char* grown = realloc(*dst, new_cap);
123+
if (!grown) return;
124+
*dst = grown;
125+
*cap = new_cap;
126+
}
127+
memcpy(*dst + *len, text, add);
128+
*len += add;
129+
(*dst)[*len] = '\0';
130+
}
131+
132+
static char* trace_env_snapshot(Env* env) {
133+
if (!env || env->count == 0) return strdup("");
134+
char* out = NULL;
135+
size_t len = 0, cap = 0;
136+
size_t shown = 0;
137+
for (size_t i = 0; i < env->count; i++) {
138+
EnvEntry* e = &env->entries[i];
139+
if (!e->name || !e->initialized) continue;
140+
if (shown >= 8) {
141+
trace_append(&out, &len, &cap, ", ...");
142+
break;
143+
}
144+
if (shown > 0) trace_append(&out, &len, &cap, ", ");
145+
trace_append(&out, &len, &cap, e->name);
146+
trace_append(&out, &len, &cap, "=");
147+
trace_append(&out, &len, &cap, value_type_name(e->value));
148+
shown++;
149+
}
150+
if (!out) return strdup("");
151+
return out;
152+
}
153+
154+
char* interpreter_format_traceback(Interpreter* interp, const char* error_msg, int line, int col) {
155+
(void)col;
156+
if (!interp) return strdup(error_msg ? error_msg : "Runtime error");
157+
158+
char* out = NULL;
159+
size_t len = 0, cap = 0;
160+
trace_append(&out, &len, &cap, "Traceback (most recent call last):\n");
161+
162+
const char* file = (interp->source_path && interp->source_path[0] != '\0') ? interp->source_path : "<unknown>";
163+
for (size_t i = 0; i < interp->trace_stack_count; i++) {
164+
TraceFrame* frame = &interp->trace_stack[i];
165+
int frame_line = 0;
166+
if (frame->has_state_entry) frame_line = frame->last_line;
167+
else if (frame->has_call_location) frame_line = frame->call_line;
168+
else frame_line = line;
169+
170+
char row[512];
171+
snprintf(row, sizeof(row), " File \"%s\", line %d, in %s\n", file, frame_line > 0 ? frame_line : 0, frame->name ? frame->name : "<frame>");
172+
trace_append(&out, &len, &cap, row);
173+
174+
if (frame->has_state_entry && frame->last_statement[0] != '\0') {
175+
snprintf(row, sizeof(row), " %s\n", frame->last_statement);
176+
trace_append(&out, &len, &cap, row);
177+
snprintf(row, sizeof(row), " State log index: %d State id: %s\n", frame->last_step_index, frame->state_id);
178+
trace_append(&out, &len, &cap, row);
179+
if (interp->verbose) {
180+
char* snap = trace_env_snapshot(frame->env);
181+
if (snap && snap[0] != '\0') {
182+
trace_append(&out, &len, &cap, " Env snapshot: ");
183+
trace_append(&out, &len, &cap, snap);
184+
trace_append(&out, &len, &cap, "\n");
185+
}
186+
if (snap) free(snap);
187+
}
188+
}
189+
}
190+
191+
char tail[512];
192+
snprintf(tail, sizeof(tail), "RuntimeError: %s (rewrite: %s)",
193+
error_msg ? error_msg : "runtime error",
194+
interp->trace_last_rule[0] ? interp->trace_last_rule : "runtime");
195+
trace_append(&out, &len, &cap, tail);
196+
197+
if (!out) return strdup(error_msg ? error_msg : "Runtime error");
198+
return out;
199+
}
200+
201+
void interpreter_reset_traceback(Interpreter* interp, Env* top_env) {
202+
if (!interp) return;
203+
while (interp->trace_stack_count > 0) trace_pop_frame(interp);
204+
interp->trace_next_step_index = 0;
205+
snprintf(interp->trace_last_state_id, sizeof(interp->trace_last_state_id), "seed");
206+
interp->trace_last_rule[0] = '\0';
207+
if (top_env) {
208+
(void)trace_push_frame(interp, "<top-level>", top_env, 0, 0, 0);
209+
}
210+
}
30211
// Thread worker for THR blocks
31212
typedef struct {
32213
Interpreter* interp;
@@ -898,6 +1079,19 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
8981079
if (kw_used) free(kw_used);
8991080

9001081
// Execute function body
1082+
if (trace_push_frame(interp,
1083+
user_func->name ? user_func->name : "<lambda>",
1084+
call_env,
1085+
expr->line,
1086+
expr->column,
1087+
1) != 0) {
1088+
env_free(call_env);
1089+
interp->error = strdup("Out of memory");
1090+
interp->error_line = expr->line;
1091+
interp->error_col = expr->column;
1092+
return value_null();
1093+
}
1094+
9011095
LabelMap local_labels = {0};
9021096
ExecResult res = exec_stmt(interp, user_func->body, call_env, &local_labels);
9031097

@@ -930,14 +1124,21 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
9301124
value_free(res.value);
9311125
return value_null();
9321126
}
1127+
trace_pop_frame(interp);
9331128
return res.value;
9341129
}
9351130

9361131
// No explicit return - return default value
9371132
switch (user_func->return_type) {
938-
case TYPE_INT: return value_int(0);
939-
case TYPE_FLT: return value_flt(0.0);
940-
case TYPE_STR: return value_str("");
1133+
case TYPE_INT:
1134+
trace_pop_frame(interp);
1135+
return value_int(0);
1136+
case TYPE_FLT:
1137+
trace_pop_frame(interp);
1138+
return value_flt(0.0);
1139+
case TYPE_STR:
1140+
trace_pop_frame(interp);
1141+
return value_str("");
9411142
case TYPE_TNS:
9421143
interp->error = strdup("TNS-returning function must return a value");
9431144
interp->error_line = expr->line;
@@ -1559,6 +1760,7 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
15591760

15601761
static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap* labels) {
15611762
if (!stmt) return make_ok(value_null());
1763+
trace_log_step(interp, stmt, env);
15621764

15631765
switch (stmt->type) {
15641766
case STMT_BLOCK:
@@ -2289,7 +2491,7 @@ static ExecResult exec_stmt_list(Interpreter* interp, StmtList* list, Env* env,
22892491

22902492
// ============ Main entry point ============
22912493

2292-
void interpreter_init(Interpreter* interp, const char* source_path) {
2494+
void interpreter_init(Interpreter* interp, const char* source_path, bool verbose) {
22932495
if (!interp) return;
22942496
memset(interp, 0, sizeof(*interp));
22952497
interp->global_env = env_create(NULL);
@@ -2298,6 +2500,19 @@ void interpreter_init(Interpreter* interp, const char* source_path) {
22982500
interp->in_try_block = false;
22992501
interp->modules = NULL;
23002502
interp->current_thr = NULL;
2503+
interp->verbose = verbose ? 1 : 0;
2504+
interp->source_path = source_path ? strdup(source_path) : NULL;
2505+
interp->trace_stack = NULL;
2506+
interp->trace_stack_count = 0;
2507+
interp->trace_stack_capacity = 0;
2508+
interp->trace_next_step_index = 0;
2509+
snprintf(interp->trace_last_state_id, sizeof(interp->trace_last_state_id), "seed");
2510+
interp->trace_last_rule[0] = '\0';
2511+
2512+
if (trace_push_frame(interp, "<top-level>", interp->global_env, 0, 0, 0) != 0) {
2513+
fprintf(stderr, "Out of memory\n");
2514+
exit(1);
2515+
}
23012516

23022517
builtins_init();
23032518
mtx_init(&g_tns_lock, 0);
@@ -2343,17 +2558,33 @@ void interpreter_destroy(Interpreter* interp) {
23432558
interp->error = NULL;
23442559
}
23452560

2561+
if (interp->source_path) {
2562+
free(interp->source_path);
2563+
interp->source_path = NULL;
2564+
}
2565+
2566+
while (interp->trace_stack_count > 0) trace_pop_frame(interp);
2567+
free(interp->trace_stack);
2568+
interp->trace_stack = NULL;
2569+
interp->trace_stack_capacity = 0;
2570+
23462571
ns_buffer_shutdown();
23472572
mtx_destroy(&g_tns_lock);
23482573
mtx_destroy(&g_parfor_merge_lock);
23492574
}
23502575

23512576
ExecResult exec_program(Stmt* program, const char* source_path) {
23522577
Interpreter interp;
2353-
interpreter_init(&interp, source_path);
2578+
interpreter_init(&interp, source_path, false);
23542579

23552580
LabelMap labels = {0};
23562581
ExecResult res = exec_stmt_list(&interp, &program->as.block, interp.global_env, &labels);
2582+
2583+
if (res.status == EXEC_ERROR) {
2584+
char* tb = interpreter_format_traceback(&interp, res.error, res.error_line, res.error_column);
2585+
free(res.error);
2586+
res.error = tb;
2587+
}
23572588

23582589
// Clean up
23592590
for (size_t i = 0; i < labels.count; i++) value_free(labels.items[i].key);
@@ -2420,9 +2651,19 @@ ExecResult exec_program_in_env(Interpreter* interp, Stmt* program, Env* env) {
24202651
return r;
24212652
}
24222653

2654+
if (interp->trace_stack_count == 0) {
2655+
(void)trace_push_frame(interp, "<top-level>", env, 0, 0, 0);
2656+
}
2657+
24232658
LabelMap labels = {0};
24242659
ExecResult res = exec_stmt_list(interp, &program->as.block, env, &labels);
24252660

2661+
if (res.status == EXEC_ERROR) {
2662+
char* tb = interpreter_format_traceback(interp, res.error, res.error_line, res.error_column);
2663+
free(res.error);
2664+
res.error = tb;
2665+
}
2666+
24262667
for (size_t i = 0; i < labels.count; i++) value_free(labels.items[i].key);
24272668
free(labels.items);
24282669

0 commit comments

Comments
 (0)