DEF AREA(S@ AS RECT) = S@.W# * S@.H#
DEF POKEIT(P@ AS PTR TO RECT)
A parameter names its type, exactly as DIM does. A bare `DEF F(S@)` is refused:
@ says "a structure" without saying which, so it does not state a contract the
way S$ does, and accepting it would mean checking fields at the call rather than
at the declaration -- which is the hole naming the type closes. The cost is that
there are no generic functions, and that is a real loss rather than an oversight.
Passing is by value, because a parameter is bound by assignment and assignment
copies; a pointer parameter copies its reference and lets a function change its
caller's record on purpose. Neither is a special rule. What a structure
parameter does need is its storage prepared before the copy, since a structure
variable is a run of slots and there is nothing to copy into until the run
exists.
A DEF parameter list is no longer parsed as an argument list, because a
parameter is a declaration rather than an expression: `S@ AS RECT` stopped that
parser dead with "Unbalanced parenthesis".
akbasic_value_is_truthy() learned that a pointer is true when it points at
something, which had to come with this. Without it there is no way to test for
the end of a list at all -- comparing a pointer to 0 reads a numeric field it
does not carry and answers whatever that field held. A structure is deliberately
given no truth value: it always exists, so the question has no answer worth
guessing at.
And a regression I introduced last commit, plus the older one underneath it.
prev_environment() released a scope but not the variables the scope created, so
a call leaked one slot per parameter and two hundred calls exhausted the
128-slot pool. Giving each DEF call its own scope made that reachable; it was
there for GOSUB all along, measured on a stashed build -- a subroutine with a
local of its own failed after about 128 calls before any of this work. The
release is safe because a scope's table holds only what it created, and it is
the variable slot that comes back rather than its storage, so a pointer into a
record DIMmed in that scope stays sound.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
48 lines
1.4 KiB
QBasic
48 lines
1.4 KiB
QBasic
10 REM A structure parameter must name its type: @ says "a structure" without
|
|
20 REM saying which, so B@ does not state a contract the way B$ does.
|
|
30 TYPE CRATE
|
|
40 W#
|
|
50 H#
|
|
60 END TYPE
|
|
70 DIM A@ AS CRATE
|
|
80 A@.W# = 5
|
|
90 A@.H# = 3
|
|
100 DEF AREA(B@ AS CRATE) = B@.W# * B@.H#
|
|
110 PRINT AREA(A@)
|
|
120 REM Passing is BY VALUE, because a parameter is bound by assignment and
|
|
130 REM assignment copies. A function cannot change its caller's record by
|
|
140 REM accident.
|
|
150 DEF WIDEN(B@ AS CRATE)
|
|
160 B@.W# = 99
|
|
170 RETURN B@.W#
|
|
180 PRINT WIDEN(A@)
|
|
190 PRINT A@.W#
|
|
200 REM To change one on purpose, pass a pointer. Assignment copies a pointer's
|
|
210 REM reference, so the callee is looking at the caller's own record.
|
|
220 DIM Q@ AS PTR TO CRATE
|
|
230 POINT Q@ AT A@
|
|
240 DEF POKEIT(P@ AS PTR TO CRATE)
|
|
250 P@->W# = 42
|
|
260 RETURN P@->W#
|
|
270 PRINT POKEIT(Q@)
|
|
280 PRINT A@.W#
|
|
290 REM And a recursive function can carry a record down with it.
|
|
300 TYPE NODE
|
|
310 COUNT#
|
|
320 TAIL@ AS PTR TO NODE
|
|
330 END TYPE
|
|
340 DIM N1@ AS NODE
|
|
350 DIM N2@ AS NODE
|
|
360 N1@.COUNT# = 10
|
|
370 N2@.COUNT# = 20
|
|
380 POINT N1@.TAIL@ AT N2@
|
|
390 DEF TOTAL(P@ AS PTR TO NODE)
|
|
395 REM A pointer is true when it points at something, which is how a walk knows
|
|
396 REM where the list ends. NOT is the bitwise operator here, so the test is
|
|
397 REM written the positive way round.
|
|
400 IF P@->TAIL@ THEN RETURN P@->COUNT# + TOTAL(P@->TAIL@)
|
|
410 RETURN P@->COUNT#
|
|
420 DIM W@ AS PTR TO NODE
|
|
430 POINT W@ AT N1@
|
|
440 PRINT TOTAL(W@)
|