Commit Graph

2 Commits

Author SHA1 Message Date
221ed7219d Keep BASIC fixtures within the input line limit
Reflow the approved reference cases, shorten local fixture comments, and split the executable docs line so the 80-byte input buffer can read every source line.

Co-authored-by: andrew <andrew@aklabs.net>
2026-08-04 18:50:44 -04:00
c4ac0e3a5f Give each DEF call its own environment, so recursion returns
The function's environment was owned by the funcdef and re-initialised on every
call, which made a function not re-entrant and cost two silent defects:

    DEF DBL(N#) = N# * 2
    PRINT DBL(10) + DBL(1)      was 4, should be 22

The result was a pointer into the funcdef's own environment, so the second call
overwrote the first before the operator saw it -- both operands became the last
call's answer. Two *different* functions in one expression were fine, which is
most of why it was invisible.

    DEF FACT(N#)
    IF N# <= 1 THEN RETURN 1
    RETURN N# * FACT(N# - 1)
    PRINT FACT(5)               never returned

The recursive call re-initialised the environment the outer call was still
using, so the loop waiting for control to come back could not see it. No error,
no bound, no diagnostic -- the one place in this interpreter that looped forever
rather than raising.

A call takes an environment from the pool now, exactly as GOSUB does. The result
is copied into a caller-scope scratch before that environment goes back, because
handing back a pointer into the callee is what made two calls collide and would
now be a pointer into a released slot as well. RETURN parks its result on the
*parent* rather than on the environment it is about to release, so nothing reads
a freed slot to find it.

Recursion depth answers to AKBASIC_MAX_ENVIRONMENTS like every other nesting, so
too deep is "Environment pool exhausted" -- a diagnosis where there was none.

akbasic_FunctionDef.environment goes with it, as dead state.

One thing this exposed but did not cause, measured against a stashed build and
recorded rather than fixed: a statement containing a failed multi-line DEF call
still completes and prints a junk value. It is visible more often now only
because runaway recursion reaches it where it used to hang.
tests/language/functions/recursion.bas deliberately does not pin that answer.

Chapter 16 loses its "walk a list with a loop, not a recursive DEF" caveat and
gains the one that is still true: a function cannot take a structure parameter
yet, so it reaches a record by name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-01 12:29:35 -04:00