summaryrefslogtreecommitdiff
path: root/src/core/interpret.c
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-28 14:15:53 +0200
committerPaul Buetow <paul@buetow.org>2026-02-28 14:15:53 +0200
commit952357132060dd874fc550d35e0e4f8bc61efd87 (patch)
tree2823a61e83dac3ee0d9658767b4f8d9d3c769db2 /src/core/interpret.c
parent40ea1d6c3bc0fa587113225a20a3b52560182761 (diff)
Refactor _control() by extracting per-loop handlers and stack/iter helper
Add _eval_expr_list() to isolate condition evaluation in a temporary stack+iterator, restoring the outer stack before returning. This eliminates the duplicate stack/iter backup boilerplate that existed separately in the while/until and do-while paths. Extract four static handler functions from the 280-line _control() body: _control_if_ifnot() — TT_IF / TT_IFNOT _control_while_until() — TT_WHILE / TT_UNTIL; uses _eval_expr_list _control_loop() — TT_LOOP _control_do() — TT_DO; uses _eval_expr_list for post-condition _control() itself shrinks to a ~30-line dispatcher (ret/break/next inline; loops and conditionals delegated to their handlers). Semantic improvement in while/until: the original code ran the body with a temp stack active and then did a manual stack_merge for CONTROL_RET. Since _eval_expr_list now restores the outer stack before the body runs, interpret_subprocess merges return values into the outer stack directly — the extra stack_merge is no longer needed. Also removes the leftover debug printf("FOO")/exit(0) guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/core/interpret.c')
-rw-r--r--src/core/interpret.c464
1 files changed, 224 insertions, 240 deletions
diff --git a/src/core/interpret.c b/src/core/interpret.c
index f61aa5c..b5ec443 100644
--- a/src/core/interpret.c
+++ b/src/core/interpret.c
@@ -62,6 +62,11 @@ int _block_get(Interpret *p_interpret, List *p_list_block);
int _block_skip(Interpret *p_interpret);
int _compare(Interpret *p_interpret);
int _control(Interpret *p_interpret);
+static Token* _eval_expr_list(Interpret *p_interpret, List *p_list_expr);
+static int _control_if_ifnot(Interpret *p_interpret);
+static int _control_while_until(Interpret *p_interpret);
+static int _control_loop(Interpret *p_interpret);
+static int _control_do(Interpret *p_interpret);
int _expression(Interpret *p_interpret);
int _expression_(Interpret *p_interpret);
int _expression_get(Interpret *p_interpret, List *p_list_block);
@@ -519,282 +524,261 @@ _expression_(Interpret *p_interpret) {
return (_compare(p_interpret));
}
-int
-_control(Interpret *p_interpret) {
- _CHECK TRACK
-
- Token *p_token = p_interpret->p_token;
-
- switch (p_interpret->tt) {
- /* ret; — no return value (stack unchanged after clearing intermediates)
- * ret expr; — single return value pushed to stack
- * ret a, b; — multiple return values; all pushed left-to-right */
- case TT_RET:
- _NEXT /* past 'ret' */
- /* Clear any intermediate values accumulated during the function body
- * so only the explicit return expressions remain on the stack. */
- stack_clear(p_interpret->p_stack);
- while (p_interpret->tt != TT_SEMICOLON
- && p_interpret->tt != TT_NONE) {
- _expression_(p_interpret);
- if (p_interpret->tt == TT_COMMA)
- _NEXT /* past ',' between multiple return values */
- }
- p_interpret->ct = CONTROL_RET;
- return (1);
- /* break; — set the break flag; the statement loop in _program() will
- * stop and the flag propagates up to the enclosing while/until. */
- case TT_BREAK:
- p_interpret->ct = CONTROL_BREAK;
- _NEXT
- return (1);
- /* next; — set the next flag to skip the rest of the loop body and
- * let the loop re-evaluate its condition on the next iteration. */
- case TT_NEXT:
- p_interpret->ct = CONTROL_NEXT;
- _NEXT
- return (1);
- case TT_IF:
- case TT_IFNOT:
- {
- TokenType tt = p_interpret->tt;
- _NEXT
- if (_expression_(p_interpret)) {
- Token *p_token_top = stack_pop(p_interpret->p_stack);
- List *p_list_block = list_new();
- _block_get(p_interpret, p_list_block);
- int ret = 0;
-
- switch (tt) {
- case TT_IF:
- if (convert_to_integer_get(p_token_top)) {
- scope_up(p_interpret->p_scope);
- ret = interpret_subprocess(p_interpret, p_list_block);
- scope_down(p_interpret->p_scope);
- }
- break;
- case TT_IFNOT:
- if (!convert_to_integer_get(p_token_top)) {
- scope_up(p_interpret->p_scope);
- ret = interpret_subprocess(p_interpret, p_list_block);
- scope_down(p_interpret->p_scope);
- }
- break;
- NO_DEFAULT;
- }
-
- list_delete(p_list_block);
- return (1);
+/* ─── Helper: evaluate an expression list with an isolated stack/iter ── */
- } else {
- _INTERPRET_ERROR("Expected expression after control keyword", p_token);
- }
- }
- break;
- case TT_WHILE:
- case TT_UNTIL:
- {
- TokenType tt = p_interpret->tt;
- List *p_list_expr = list_new(), *p_list_block = list_new();
- _Bool b_flag = true;
+/* Evaluates p_list_expr in a temporary stack+iterator context so that
+ * the condition result does not pollute the caller's stack. Returns the
+ * top-of-stack token after evaluation, or NULL if the expression is empty.
+ * The outer stack is fully restored before returning, so interpret_subprocess
+ * calls that follow use the correct (outer) stack directly — no extra
+ * stack_merge is required for CONTROL_RET handling. */
+static Token*
+_eval_expr_list(Interpret *p_interpret, List *p_list_expr) {
+ Stack *p_stack_backup = p_interpret->p_stack;
+ ListIterator *p_iter_backup = p_interpret->p_iter;
- _NEXT
+ p_interpret->p_stack = stack_new();
+ p_interpret->p_iter = listiterator_new(p_list_expr);
+ _next(p_interpret); /* advance to first token in the expression list */
- _expression_get(p_interpret, p_list_expr);
- _block_get(p_interpret, p_list_block);
- Token *p_token_backup = p_interpret->p_token;
+ Token *p_token_top = NULL;
+ if (_expression_(p_interpret))
+ p_token_top = stack_pop(p_interpret->p_stack);
- do {
- Stack *p_stack_backup = p_interpret->p_stack;
- p_interpret->p_stack = stack_new();
+ listiterator_delete(p_interpret->p_iter);
+ p_interpret->p_iter = p_iter_backup;
+ stack_delete(p_interpret->p_stack);
+ p_interpret->p_stack = p_stack_backup;
- ListIterator *p_iter_backup = p_interpret->p_iter;
- p_interpret->p_iter = listiterator_new(p_list_expr);
+ return (p_token_top);
+}
- _NEXT
+/* ─── Per-control-flow handler functions ────────────────────────────── */
- /* Dont use if here, because we want to check the p_itnerpret->ct */
- if (_expression_(p_interpret)) {
- Token *p_token_top = stack_pop(p_interpret->p_stack);
+/* Handle if / ifnot: evaluate the condition, execute the block when the
+ * condition is true (if) or false (ifnot). */
+static int
+_control_if_ifnot(Interpret *p_interpret) {
+ TokenType tt = p_interpret->tt;
+ Token *p_token = p_interpret->p_token;
+ _NEXT
- if (p_token_top == NULL) {
- printf("FOO\n");
- exit(0);
- }
- if (tt == TT_WHILE) {
- if (convert_to_integer_get(p_token_top)) {
- scope_up(p_interpret->p_scope);
- interpret_subprocess(p_interpret, p_list_block);
- scope_down(p_interpret->p_scope);
-
- } else {
- b_flag = false;
- }
+ if (!_expression_(p_interpret))
+ _INTERPRET_ERROR("Expected expression after if/ifnot", p_token);
- } else if (tt == TT_UNTIL) {
- if (!convert_to_integer_get(p_token_top)) {
- scope_up(p_interpret->p_scope);
- interpret_subprocess(p_interpret, p_list_block);
- scope_down(p_interpret->p_scope);
+ Token *p_token_top = stack_pop(p_interpret->p_stack);
+ List *p_list_block = list_new();
+ _block_get(p_interpret, p_list_block);
- } else {
- b_flag = false;
- }
- }
+ _Bool b_run = (tt == TT_IF)
+ ? convert_to_integer_get(p_token_top)
+ : !convert_to_integer_get(p_token_top);
- /* Act on any break/next/ret flag set during loop body execution.
- * break clears the flag and stops iteration; next clears it
- * and lets the loop re-evaluate the condition naturally.
- * ret does not clear the flag so it propagates to the caller;
- * the return value is rescued from cond_stack to p_stack_backup
- * before the cond_stack is deleted below. */
- if (p_interpret->ct == CONTROL_BREAK) {
- p_interpret->ct = CONTROL_NONE;
- b_flag = false;
- } else if (p_interpret->ct == CONTROL_NEXT) {
- p_interpret->ct = CONTROL_NONE;
- /* b_flag stays true; condition re-evaluated next iteration */
- } else if (p_interpret->ct == CONTROL_RET) {
- /* Rescue any return values from cond_stack into the outer
- * stack before cond_stack is destroyed at the end of the
- * do-while iteration. */
- stack_merge(p_stack_backup, p_interpret->p_stack);
- b_flag = false;
- }
+ if (b_run) {
+ scope_up(p_interpret->p_scope);
+ interpret_subprocess(p_interpret, p_list_block);
+ scope_down(p_interpret->p_scope);
+ }
- } else {
- _INTERPRET_ERROR("Expected expression after control keyword",
- p_token);
- }
+ list_delete(p_list_block);
+ return (1);
+}
- listiterator_delete(p_interpret->p_iter);
- p_interpret->p_iter = p_iter_backup;
+/* Handle while / until: evaluate condition before each iteration; run body
+ * while condition is true (while) or false (until).
+ * _eval_expr_list restores the outer stack before the body runs, so
+ * interpret_subprocess places return values directly on the outer stack —
+ * no extra stack_merge is needed for CONTROL_RET. */
+static int
+_control_while_until(Interpret *p_interpret) {
+ TokenType tt = p_interpret->tt;
+ Token *p_token = p_interpret->p_token;
+ List *p_list_expr = list_new();
+ List *p_list_block = list_new();
+ _Bool b_flag = true;
- stack_delete(p_interpret->p_stack);
- p_interpret->p_stack = p_stack_backup;
+ _NEXT
+ _expression_get(p_interpret, p_list_expr);
+ _block_get(p_interpret, p_list_block);
+ Token *p_token_backup = p_interpret->p_token;
- } while (b_flag);
+ do {
+ Token *p_token_top = _eval_expr_list(p_interpret, p_list_expr);
+ if (p_token_top == NULL)
+ _INTERPRET_ERROR("Expected expression after while/until", p_token);
- list_delete(p_list_expr);
- list_delete(p_list_block);
- p_interpret->p_token = p_token_backup;
- p_interpret->tt = token_get_tt(p_token_backup);
+ _Bool b_cond = (tt == TT_WHILE)
+ ? convert_to_integer_get(p_token_top)
+ : !convert_to_integer_get(p_token_top);
- return (1);
- }
- break;
- case TT_LOOP:
- {
- List *p_list_block = list_new();
- _NEXT
- _block_get(p_interpret, p_list_block);
- Token *p_token_backup = p_interpret->p_token;
-
- /* Run forever; break is the only exit, next restarts the body */
- for (;;) {
+ if (b_cond) {
scope_up(p_interpret->p_scope);
interpret_subprocess(p_interpret, p_list_block);
scope_down(p_interpret->p_scope);
+ } else {
+ b_flag = false;
+ }
- if (p_interpret->ct == CONTROL_BREAK) {
- p_interpret->ct = CONTROL_NONE;
- break;
- } else if (p_interpret->ct == CONTROL_NEXT) {
- p_interpret->ct = CONTROL_NONE;
- /* skip the rest of the body; re-run from the top */
- } else if (p_interpret->ct == CONTROL_RET) {
- /* ret inside loop: propagate CONTROL_RET up to the enclosing
- * function without clearing it; return values are on the stack. */
- break;
- }
+ /* Act on break/next/ret from the body. break and ret stop the loop;
+ * next clears the flag and re-evaluates the condition on the next
+ * iteration; ret propagates upward without clearing the flag. */
+ if (p_interpret->ct == CONTROL_BREAK) {
+ p_interpret->ct = CONTROL_NONE;
+ b_flag = false;
+ } else if (p_interpret->ct == CONTROL_NEXT) {
+ p_interpret->ct = CONTROL_NONE;
+ } else if (p_interpret->ct == CONTROL_RET) {
+ b_flag = false;
}
- list_delete(p_list_block);
- p_interpret->p_token = p_token_backup;
- p_interpret->tt = token_get_tt(p_token_backup);
- return (1);
+ } while (b_flag);
+
+ list_delete(p_list_expr);
+ list_delete(p_list_block);
+ p_interpret->p_token = p_token_backup;
+ p_interpret->tt = token_get_tt(p_token_backup);
+ return (1);
+}
+
+/* Handle loop: run body indefinitely; break or ret are the only exits. */
+static int
+_control_loop(Interpret *p_interpret) {
+ List *p_list_block = list_new();
+ _NEXT
+ _block_get(p_interpret, p_list_block);
+ Token *p_token_backup = p_interpret->p_token;
+
+ for (;;) {
+ scope_up(p_interpret->p_scope);
+ interpret_subprocess(p_interpret, p_list_block);
+ scope_down(p_interpret->p_scope);
+
+ if (p_interpret->ct == CONTROL_BREAK) {
+ p_interpret->ct = CONTROL_NONE;
+ break;
+ } else if (p_interpret->ct == CONTROL_NEXT) {
+ p_interpret->ct = CONTROL_NONE;
+ /* next: restart loop body from the top */
+ } else if (p_interpret->ct == CONTROL_RET) {
+ /* Propagate ret upward; return values are already on the stack. */
+ break;
+ }
}
- break;
- case TT_DO:
- {
- /* do { body } while expr; or do { body } until expr;
- * The body always executes at least once; the condition is evaluated
- * at the bottom of each iteration (post-condition loop). */
- List *p_list_block = list_new();
- List *p_list_expr = list_new();
- _NEXT
- _block_get(p_interpret, p_list_block); /* leaves at 'while'/'until' */
+ list_delete(p_list_block);
+ p_interpret->p_token = p_token_backup;
+ p_interpret->tt = token_get_tt(p_token_backup);
+ return (1);
+}
- Token *p_token = p_interpret->p_token;
- TokenType tt = p_interpret->tt;
- if (tt != TT_WHILE && tt != TT_UNTIL)
- _INTERPRET_ERROR(
- "Expected 'while' or 'until' after 'do' block", p_token);
+/* Handle do...while/until: body runs at least once; condition is evaluated
+ * at the bottom of each iteration using _eval_expr_list (isolated stack). */
+static int
+_control_do(Interpret *p_interpret) {
+ List *p_list_block = list_new();
+ List *p_list_expr = list_new();
- _NEXT /* past 'while' or 'until' */
+ _NEXT
+ _block_get(p_interpret, p_list_block); /* leaves cursor at 'while'/'until' */
- /* Collect condition tokens until ';' — mirrors _expression_get but
- * stops at semicolon instead of '{' since there is no block here */
- while (p_interpret->tt != TT_SEMICOLON
- && p_interpret->tt != TT_NONE) {
- list_add_back(p_list_expr, p_interpret->p_token);
- _NEXT
- }
- _NEXT /* past ';' */
- Token *p_token_backup = p_interpret->p_token; /* after ';' */
+ Token *p_token = p_interpret->p_token;
+ TokenType tt = p_interpret->tt;
+ if (tt != TT_WHILE && tt != TT_UNTIL)
+ _INTERPRET_ERROR(
+ "Expected 'while' or 'until' after 'do' block", p_token);
- _Bool b_flag = true;
- do {
- scope_up(p_interpret->p_scope);
- interpret_subprocess(p_interpret, p_list_block);
- scope_down(p_interpret->p_scope);
+ _NEXT /* past 'while' or 'until' */
- /* Handle break/next/ret before re-evaluating the condition.
- * For ret: stop the loop without touching the return value on
- * p_interpret->p_stack; skip condition eval via continue. */
- if (p_interpret->ct == CONTROL_BREAK) {
- p_interpret->ct = CONTROL_NONE;
- b_flag = false;
- continue;
- } else if (p_interpret->ct == CONTROL_NEXT) {
- p_interpret->ct = CONTROL_NONE;
- /* fall through to condition check */
- } else if (p_interpret->ct == CONTROL_RET) {
- /* ret inside do-loop: propagate CONTROL_RET; skip condition
- * re-eval to avoid corrupting the stack with cond results. */
- b_flag = false;
- continue;
- }
+ /* Collect condition tokens up to ';' (no block follows the condition) */
+ while (p_interpret->tt != TT_SEMICOLON && p_interpret->tt != TT_NONE) {
+ list_add_back(p_list_expr, p_interpret->p_token);
+ _NEXT
+ }
+ _NEXT /* past ';' */
+ Token *p_token_backup = p_interpret->p_token;
+
+ _Bool b_flag = true;
+ do {
+ scope_up(p_interpret->p_scope);
+ interpret_subprocess(p_interpret, p_list_block);
+ scope_down(p_interpret->p_scope);
+
+ /* Handle break/next/ret before re-evaluating the condition.
+ * For ret: stop the loop; skip condition eval via continue to avoid
+ * corrupting the stack with extra condition results. */
+ if (p_interpret->ct == CONTROL_BREAK) {
+ p_interpret->ct = CONTROL_NONE;
+ b_flag = false;
+ continue;
+ } else if (p_interpret->ct == CONTROL_NEXT) {
+ p_interpret->ct = CONTROL_NONE;
+ /* fall through to condition re-eval */
+ } else if (p_interpret->ct == CONTROL_RET) {
+ b_flag = false;
+ continue;
+ }
- /* Re-evaluate condition using a temp stack/iterator over
- * p_list_expr, exactly as the while/until loop does */
- Stack *p_stack_backup = p_interpret->p_stack;
- p_interpret->p_stack = stack_new();
- ListIterator *p_iter_backup = p_interpret->p_iter;
- p_interpret->p_iter = listiterator_new(p_list_expr);
- _NEXT
+ Token *p_token_top = _eval_expr_list(p_interpret, p_list_expr);
+ if (p_token_top != NULL) {
+ int i_val = convert_to_integer_get(p_token_top);
+ b_flag = (tt == TT_WHILE) ? (i_val != 0) : (i_val == 0);
+ }
- if (_expression_(p_interpret)) {
- Token *p_token_top = stack_pop(p_interpret->p_stack);
- int i_val = convert_to_integer_get(p_token_top);
- b_flag = (tt == TT_WHILE) ? (i_val != 0) : (i_val == 0);
- }
+ } while (b_flag);
- listiterator_delete(p_interpret->p_iter);
- p_interpret->p_iter = p_iter_backup;
- stack_delete(p_interpret->p_stack);
- p_interpret->p_stack = p_stack_backup;
+ list_delete(p_list_block);
+ list_delete(p_list_expr);
+ p_interpret->p_token = p_token_backup;
+ p_interpret->tt = token_get_tt(p_token_backup);
+ return (1);
+}
- } while (b_flag);
+/* ─── Main control dispatcher ────────────────────────────────────── */
- list_delete(p_list_block);
- list_delete(p_list_expr);
- p_interpret->p_token = p_token_backup;
- p_interpret->tt = token_get_tt(p_token_backup);
+/* Dispatch to the appropriate per-control-flow handler based on the
+ * current token type. Short constructs (ret, break, next) are handled
+ * inline; all loop/conditional forms delegate to their own functions. */
+int
+_control(Interpret *p_interpret) {
+ _CHECK TRACK
+
+ switch (p_interpret->tt) {
+ /* ret; — clear stack, evaluate optional comma-list of return exprs */
+ /* ret expr; — single return value on the stack */
+ /* ret a, b; — multiple return values, left-to-right */
+ case TT_RET:
+ _NEXT /* past 'ret' */
+ /* Clear any intermediate values accumulated during the function body
+ * so only the explicit return expressions remain on the stack. */
+ stack_clear(p_interpret->p_stack);
+ while (p_interpret->tt != TT_SEMICOLON
+ && p_interpret->tt != TT_NONE) {
+ _expression_(p_interpret);
+ if (p_interpret->tt == TT_COMMA)
+ _NEXT /* past ',' between return values */
+ }
+ p_interpret->ct = CONTROL_RET;
return (1);
- }
- break;
+ /* break; — signal the enclosing loop to stop after the current body */
+ case TT_BREAK:
+ p_interpret->ct = CONTROL_BREAK;
+ _NEXT
+ return (1);
+ /* next; — skip the rest of the body and re-evaluate the loop condition */
+ case TT_NEXT:
+ p_interpret->ct = CONTROL_NEXT;
+ _NEXT
+ return (1);
+ case TT_IF:
+ case TT_IFNOT:
+ return (_control_if_ifnot(p_interpret));
+ case TT_WHILE:
+ case TT_UNTIL:
+ return (_control_while_until(p_interpret));
+ case TT_LOOP:
+ return (_control_loop(p_interpret));
+ case TT_DO:
+ return (_control_do(p_interpret));
NO_DEFAULT;
}