@@ -27,6 +27,187 @@ static void wait_if_paused(Interpreter* interp) {
2727
2828static mtx_t g_tns_lock ;
2929static 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
31212typedef 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
15601761static 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
23512576ExecResult 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