Skip to content

Commit 78a9a7f

Browse files
gh-75: Make FOR counter loop-local.
1 parent 5192c72 commit 78a9a7f

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

src/interpreter.c

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static void wait_if_paused(Interpreter* interp) {
2727

2828
static mtx_t g_tns_lock;
2929
static mtx_t g_parfor_merge_lock;
30+
static int g_for_temp_id = 0;
3031

3132
static const char* stmt_type_name(StmtType type) {
3233
switch (type) {
@@ -2796,29 +2797,114 @@ static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap*
27962797
int64_t limit = target.as.i;
27972798
value_free(target);
27982799

2800+
/*
2801+
* Create a loop-local binding for the counter so it does not
2802+
* persist after the loop finishes. We implement this by
2803+
* creating a unique temporary local variable and aliasing the
2804+
* user-visible counter name to that temporary binding for the
2805+
* duration of the loop. After the loop we remove the alias and
2806+
* restore any previous local binding if necessary.
2807+
*/
2808+
char temp_name[64];
2809+
int my_temp_id = ++g_for_temp_id;
2810+
snprintf(temp_name, sizeof(temp_name), "__for_cnt_%d_%d_%d", my_temp_id, stmt->line, stmt->column);
2811+
2812+
/* Save any previous value (in local or parent) so we can restore local bindings */
2813+
Value prev_val = value_null();
2814+
DeclType prev_type = TYPE_UNKNOWN;
2815+
bool prev_initialized = false;
2816+
env_get(env, stmt->as.for_stmt.counter, &prev_val, &prev_type, &prev_initialized);
2817+
2818+
/* Detect whether a local binding already exists (trial define) */
2819+
bool local_existed = true;
2820+
if (env_define(env, stmt->as.for_stmt.counter, TYPE_INT)) {
2821+
/* no local existed; clean up the probe */
2822+
env_delete(env, stmt->as.for_stmt.counter);
2823+
local_existed = false;
2824+
}
2825+
2826+
/* Create the temporary target binding */
2827+
if (!env_define(env, temp_name, TYPE_INT)) {
2828+
int retries = 0;
2829+
while (retries < 1000 && !env_define(env, temp_name, TYPE_INT)) {
2830+
my_temp_id = ++g_for_temp_id;
2831+
snprintf(temp_name, sizeof(temp_name), "__for_cnt_%d_%d_%d", my_temp_id, stmt->line, stmt->column);
2832+
retries++;
2833+
}
2834+
if (retries >= 1000) {
2835+
value_free(prev_val);
2836+
interp->loop_depth--;
2837+
return make_error("Internal error setting up FOR counter", stmt->line, stmt->column);
2838+
}
2839+
}
2840+
2841+
/* Create a local alias `counter -> temp_name` (creates local entry if missing)
2842+
* This will shadow any parent binding for the duration of the loop.
2843+
*/
2844+
if (!env_set_alias(env, stmt->as.for_stmt.counter, temp_name, TYPE_INT, true)) {
2845+
/* cleanup temp binding */
2846+
env_delete(env, temp_name);
2847+
value_free(prev_val);
2848+
interp->loop_depth--;
2849+
return make_error("Cannot create loop-local counter", stmt->line, stmt->column);
2850+
}
2851+
27992852
for (int64_t idx = 1; idx <= limit; idx++) {
28002853
if (++iteration_count > max_iterations) {
2854+
/* cleanup alias/temp and restore previous local if needed */
2855+
env_delete(env, stmt->as.for_stmt.counter);
2856+
env_delete(env, temp_name);
2857+
if (local_existed) {
2858+
env_define(env, stmt->as.for_stmt.counter, prev_type);
2859+
if (prev_initialized) env_assign(env, stmt->as.for_stmt.counter, prev_val, prev_type, false);
2860+
}
2861+
value_free(prev_val);
28012862
interp->loop_depth--;
28022863
return make_error("Infinite loop detected", stmt->line, stmt->column);
28032864
}
28042865

2805-
// Bind or assign the loop counter in the current environment
2866+
/* Assign to the aliased counter (writes to temp_name) */
28062867
if (!env_assign(env, stmt->as.for_stmt.counter, value_int(idx), TYPE_INT, true)) {
28072868
char buf[256];
28082869
snprintf(buf, sizeof(buf), "Cannot assign to frozen identifier '%s'", stmt->as.for_stmt.counter);
2870+
/* cleanup alias and temp binding before returning */
2871+
env_delete(env, stmt->as.for_stmt.counter);
2872+
env_delete(env, temp_name);
2873+
if (local_existed) {
2874+
env_define(env, stmt->as.for_stmt.counter, prev_type);
2875+
if (prev_initialized) env_assign(env, stmt->as.for_stmt.counter, prev_val, prev_type, false);
2876+
}
2877+
value_free(prev_val);
2878+
interp->loop_depth--;
28092879
return make_error(buf, stmt->line, stmt->column);
28102880
}
28112881

28122882
ExecResult res = exec_stmt(interp, stmt->as.for_stmt.body, env, labels);
28132883

28142884
if (res.status == EXEC_ERROR || res.status == EXEC_RETURN || res.status == EXEC_GOTO) {
2885+
/* cleanup before propagating */
2886+
env_delete(env, stmt->as.for_stmt.counter);
2887+
env_delete(env, temp_name);
2888+
if (local_existed) {
2889+
env_define(env, stmt->as.for_stmt.counter, prev_type);
2890+
if (prev_initialized) env_assign(env, stmt->as.for_stmt.counter, prev_val, prev_type, false);
2891+
}
2892+
value_free(prev_val);
28152893
interp->loop_depth--;
28162894
return res;
28172895
}
28182896

28192897
if (res.status == EXEC_BREAK) {
28202898
if (res.break_count > 1) {
28212899
res.break_count--;
2900+
/* cleanup before returning */
2901+
env_delete(env, stmt->as.for_stmt.counter);
2902+
env_delete(env, temp_name);
2903+
if (local_existed) {
2904+
env_define(env, stmt->as.for_stmt.counter, prev_type);
2905+
if (prev_initialized) env_assign(env, stmt->as.for_stmt.counter, prev_val, prev_type, false);
2906+
}
2907+
value_free(prev_val);
28222908
interp->loop_depth--;
28232909
return res;
28242910
}
@@ -2828,6 +2914,15 @@ static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap*
28282914
// EXEC_CONTINUE is treated as a normal completion (loop continues)
28292915
}
28302916

2917+
/* Normal loop completion: remove alias and temp binding, restore local if needed */
2918+
env_delete(env, stmt->as.for_stmt.counter);
2919+
env_delete(env, temp_name);
2920+
if (local_existed) {
2921+
env_define(env, stmt->as.for_stmt.counter, prev_type);
2922+
if (prev_initialized) env_assign(env, stmt->as.for_stmt.counter, prev_val, prev_type, false);
2923+
}
2924+
value_free(prev_val);
2925+
28312926
interp->loop_depth--;
28322927
return make_ok(value_null());
28332928
}

tests/test2.pre

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ DEL(pause_thr)
140140
DEL(waited_p)
141141
PRINT("PAUSE/RESUME/PAUSED: PASS\n")
142142

143+
! --- FOR counter scope tests (regression gh-75) ---
144+
FOR(i, 0b1) { }
145+
ASSERT(EQ(EXIST(i), 0d0))
146+
147+
INT: j = 0b101
148+
FOR(j, 0b10) { }
149+
ASSERT(EQ(j, 0b101))
150+
DEL(j)
151+
143152
! Regression test for GH-66: ASYNC argument execution must be deferred
144153
! until STOP/PAUSE call evaluation completes.
145154
PRINT("Testing deferred ASYNC in STOP/PAUSE (regression GH-66)...")

0 commit comments

Comments
 (0)