1 Commits

Author SHA1 Message Date
01adf80751 Rework the megademo to fit the 80-column source line limit
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m29s
akbasic CI Build / coverage (push) Failing after 3m58s
akbasic CI Build / sanitizers (push) Failing after 4m36s
akbasic CI Build / mutation_test (push) Failing after 3m52s
akbasic CI Build / akgl_build (push) Failing after 7m29s
AKBASIC_MAX_LINE_LENGTH's cut from 256 to 80 left seventeen lines of
examples/megademo unloadable: the sixteen IM$() picture strings (up to
252 characters) and TUNEA/TUNEB's four-bar PLAY strings (174 and 175).
The real ceiling is 78 characters, not 80 -- stdio_readline() refuses a
read that fills the 80-byte buffer without a newline, so content plus
its terminator must fit in 79.

The picture: vaporwave.py's PAYLOAD drops from 240 to 64, so every
emitted IM$(NN) = "..." line fits under the ceiling. chop() no longer
slices blind; it walks the stream a record at a time -- two characters
for a run, three for an R row record -- and never cuts inside one,
because the decoder reads a record's tail with MID on the string it is
walking and a record straddling two IM$ entries decodes as garbage.
The old blind slice at 240 only happened to be safe. verify() now
simulates the CHOPPED strings with the cursor threaded across the
boundaries exactly the way DRAWSTREAM executes them, so a bad cut is
an assertion failure instead of a corrupted screen, and emit_block()
asserts every emitted line fits. The picture is 56 strings where it
was 16; the decoder needed no changes at all, since it already carries
X#/Y# from one IM$ entry to the next.

The music: TUNEA and TUNEB each become four PLAY statements, one bar
apiece. play.c keeps voice, envelope, level and duration state on the
runtime across statements and every PLAY appends to the same queue, so
four bars queue exactly as one long string did. Each bar restates the
V1T3U9S prefix so a bar dropped by QFULL cannot leave the next batch
playing on the drum kit's envelope.

Everything still clears the shrunken pools with room to spare: 1625
source lines of 2048, ~704 array slots of 2048, identifiers within the
24-character symtab key. Verified end to end against this branch's
build: the demo loads, the offscreen host renders every scene, and the
scene-5 still is pixel-identical to vaporwave.py's own preview. The
test suite fails the same seventeen cases with and without this
commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACffnV6F7sxQuG3Y8a1L3s
2026-08-03 22:57:57 -04:00
18 changed files with 41 additions and 42 deletions

View File

@@ -685,9 +685,9 @@ seconds asks for fifty frames a second.
### Why `GOTO` rather than `DO ... LOOP` ### Why `GOTO` rather than `DO ... LOOP`
A `DO ... LOOP` around the frame would read better, and it is not usable here: **a `GOTO` A `DO ... LOOP` around the frame would read better, and it is not usable here: **a `GOTO`
that jumps out of a `FOR` or a `DO` does not release the loop's scope.** There are 12 that jumps out of a `FOR` or a `DO` does not release the loop's scope.** There are 32
scopes, so a game that leaves its main loop once per lost life stops on the scopes, so a game that leaves its main loop once per lost life stops on the
twelfth one: thirty-second one:
```basic ```basic
N# = 0 N# = 0
@@ -700,7 +700,7 @@ PRINT "SURVIVED " + N#
``` ```
```output ```output
? 3 : PARSE ERROR Environment pool exhausted at line 3 (12 in use) ? 3 : PARSE ERROR Environment pool exhausted at line 3 (32 in use)
``` ```

View File

@@ -1423,8 +1423,7 @@ IF STATE# = 2 THEN GOSUB UNSTICK
RETURN RETURN
LABEL PRESSPAUSE LABEL PRESSPAUSE
IF STATE# = 2 THEN STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" IF STATE# = 2 THEN STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" : GOSUB SETBANNER : RETURN
IF STATE# = 6 THEN GOSUB SETBANNER : RETURN
IF STATE# = 6 THEN STATE# = 2 : BAN$ = "" : GOSUB SETBANNER IF STATE# = 6 THEN STATE# = 2 : BAN$ = "" : GOSUB SETBANNER
RETURN RETURN
``` ```

View File

@@ -1,6 +1,6 @@
10 REM An array reference used as a function argument, and as one of several. 10 REM An array reference used as a function argument, and as one of several.
20 REM An identifier's subscript list used to hang off .right, which is also 20 REM An identifier's subscript list used to hang off .right, which is also
30 REM where an argument list chains its arguments; the arity counter walked 30 REM where an argument list chains its arguments -- so the arity counter walked
40 REM straight into the subscripts and refused the call. TODO.md section 4. 40 REM straight into the subscripts and refused the call. TODO.md section 4.
50 DIM C#(4) 50 DIM C#(4)
60 C#(1) = -9 60 C#(1) = -9

View File

@@ -1,6 +1,6 @@
10 REM FILTER has no device capability; akgl_audio_* synthesises and mixes but 10 REM FILTER has no device capability behind it -- akgl_audio_* synthesises and
20 REM has no filter stage, and SDL3 supplies no primitive to build one from. 20 REM mixes but has no filter stage, and SDL3 supplies no primitive to build one
30 REM It is refused rather than silently ignored, so a program that asked 30 REM from. It is refused rather than silently ignored, so a program that asked
40 REM for a low-pass finds out it did not get one. 40 REM for a low-pass finds out it did not get one.
50 PRINT "BEFORE" 50 PRINT "BEFORE"
60 FILTER 1000, 1, 0, 0, 5 60 FILTER 1000, 1, 0, 0, 5

View File

@@ -1,4 +1,4 @@
10 REM The standalone driver lends the script no audio device. ENVELOPE, VOL, 10 REM The standalone driver lends the script no audio device. ENVELOPE, VOL and
20 REM TEMPO only change interpreter state, so they work regardless; SOUND and 20 REM TEMPO only change interpreter state, so they work regardless; SOUND and
30 REM PLAY need the device and must name themselves when there is none. 30 REM PLAY need the device and must name themselves when there is none.
40 ENVELOPE 1, 5, 9, 12, 2 40 ENVELOPE 1, 5, 9, 12, 2

View File

@@ -1,6 +1,6 @@
10 REM A DEF call takes one environment from the pool, exactly as 10 REM A DEF call takes its environment from the pool, one per call, exactly as
20 REM GOSUB does. It used to be owned by the funcdef and reset on every call, 20 REM GOSUB does. It used to be owned by the funcdef and reset on every call,
30 REM which cost two silent defects: calls in one expression shared a slot, 30 REM which cost two silent defects: two calls in one expression shared a slot,
40 REM and recursion never came back at all. 40 REM and recursion never came back at all.
50 DEF FACT(N#) 50 DEF FACT(N#)
60 IF N# <= 1 THEN RETURN 1 60 IF N# <= 1 THEN RETURN 1
@@ -15,9 +15,9 @@
150 DEF DBL(N#) = N# * 2 150 DEF DBL(N#) = N# * 2
160 PRINT DBL(10) + DBL(1) 160 PRINT DBL(10) + DBL(1)
170 PRINT DBL(1) + DBL(10) + DBL(100) 170 PRINT DBL(1) + DBL(10) + DBL(100)
180 REM Depth answers to the environment pool, so runaway recursion reports 180 REM Depth answers to the environment pool now, so runaway recursion reports
190 REM "Environment pool exhausted" rather than hanging. It is not exercised 190 REM "Environment pool exhausted" rather than hanging. It is not exercised here
200 REM here because the failed statement still prints junk afterwards; 200 REM because the statement containing a failed call still prints a junk value
210 REM this is a separate defect in TODO.md, and one this golden file 210 REM afterwards -- a separate, pre-existing defect recorded in TODO.md, and one
220 REM would pin if it went in. tests/user_functions.c asserts 220 REM this golden file would pin if it went in. tests/user_functions.c asserts
230 REM the message instead. 230 REM the message instead.

View File

@@ -1,4 +1,4 @@
10 REM A range check reported through the driver, end to end. The program 10 REM A range check reported through the driver, end to end. The program stops
20 REM at the first error, so this file covers one; the rest of the checks are 20 REM at the first error, so this file covers one; the rest of the checks are
30 REM asserted in tests/graphics_verbs.c against the recording backend. 30 REM asserted in tests/graphics_verbs.c against the recording backend.
40 PRINT "BEFORE" 40 PRINT "BEFORE"

View File

@@ -1,5 +1,5 @@
10 REM The standalone driver lends the script no graphics device. 10 REM The standalone driver lends the script no graphics device.
20 REM COLOR, LOCATE and SCALE touch interpreter state and must still work. 20 REM COLOR, LOCATE and SCALE only touch interpreter state and must still work.
30 COLOR 1, 3 30 COLOR 1, 3
40 LOCATE 40, 50 40 LOCATE 40, 50
50 SCALE 1, 640, 400 50 SCALE 1, 640, 400

View File

@@ -1,5 +1,5 @@
10 REM Group B: housekeeping verbs. None is in the Go reference -- 10 REM Group B: the housekeeping verbs. None of these is in the Go reference --
20 REM they are on its unimplemented list -- so each meaning here is 20 REM they are on its own unimplemented list -- so what each one means here is
30 REM a decision, recorded in src/runtime_housekeeping.c beside the verb. 30 REM a decision, recorded in src/runtime_housekeeping.c beside the verb.
40 A# = 1 : B# = 2 40 A# = 1 : B# = 2
50 PRINT A# : PRINT B# 50 PRINT A# : PRINT B#
@@ -11,7 +11,7 @@
110 P#(2) = 7 : Q#(2) = 9 110 P#(2) = 7 : Q#(2) = 9
120 SWAP P#, Q# 120 SWAP P#, Q#
130 PRINT P#(2) : PRINT Q#(2) 130 PRINT P#(2) : PRINT Q#(2)
140 REM TRON prints each line number inline, as a C128 does. 140 REM TRON prints each line number inline before the line runs, as a C128 does.
150 TRON 150 TRON
160 PRINT "TRACED" 160 PRINT "TRACED"
170 TROFF 170 TROFF

View File

@@ -1,7 +1,7 @@
10 REM A leading zero is padding, not a radix. The reference selected base 8 10 REM A leading zero is padding, not a radix. The reference selects base 8 for
20 REM for lexemes starting with 0; 010 printed 8 and 08 was a parse error -- 20 REM any lexeme starting with 0, so 010 printed 8 and 08 was a parse error --
30 REM TODO.md section 6 item 10 is fixed; Commodore BASIC has no octal. 30 REM TODO.md section 6 item 10, fixed. Commodore BASIC has no octal literals.
40 REM 0x is the one prefix that changes the base, and now reaches the scanner 40 REM 0x is the one prefix that changes the base, and it now reaches the scanner
50 REM whole: that was section 6 item 15. 50 REM whole: that was section 6 item 15.
60 PRINT 010 60 PRINT 010
70 PRINT 08 70 PRINT 08

View File

@@ -1,12 +1,12 @@
10 REM A truth value carries its payload in boolvalue, not floatval. The three 10 REM A truth value carries its payload in boolvalue, not floatval. The three
20 REM numeric operators were `if ( INTEGER ) ... else <treat as float>`, and 20 REM numeric operators were `if ( INTEGER ) ... else <treat as float>`, and
30 REM that else was a catch-all, not a float branch -- so a truth value 30 REM that else was a catch-all rather than a float branch -- so a truth value
40 REM on the LEFT computed 0 - 1 into a field nothing reads, kept its BOOLEAN 40 REM on the LEFT computed 0 - 1 into a field nothing reads, kept its BOOLEAN
50 REM type, and printed `true` instead of -2. Silent, and wrong twice over. 50 REM type, and printed `true` instead of -2. Silent, and wrong twice over.
60 A# = 1 60 A# = 1
70 PRINT (A# == 1) 70 PRINT (A# == 1)
80 REM On the RIGHT it stays legal and stays -1, the same property that 80 REM On the RIGHT it stays legal and stays -1, which is the same property that
90 REM lets AND and OR act as logical operators. Refusing it would have 90 REM lets AND and OR double as logical operators. Refusing it here would have
100 REM broken every condition in the language. 100 REM broken every condition in the language.
110 PRINT 5 - (A# == 1) 110 PRINT 5 - (A# == 1)
120 PRINT 5 * (A# == 1) 120 PRINT 5 * (A# == 1)

View File

@@ -1,11 +1,11 @@
10 REM Statements separated by colons. The COLON token existed from the start; 10 REM Statements separated by colons. The COLON token existed from the start of
20 REM nothing consumed it, so a line could hold only one statement. 20 REM the port and nothing consumed it, so a line could hold only one statement.
30 PRINT "A" : PRINT "B" 30 PRINT "A" : PRINT "B"
40 A# = 1 : B# = 2 : PRINT A# + B# 40 A# = 1 : B# = 2 : PRINT A# + B#
50 REM Empty statements are valid: a trailing separator or runs of them. 50 REM An empty statement is not an error: a trailing separator, or a run of them.
60 PRINT "C" : 60 PRINT "C" :
70 PRINT "D" :: PRINT "E" 70 PRINT "D" :: PRINT "E"
80 REM Everything after THEN belongs to the condition, as in BASIC 7.0, and is 80 REM Everything after THEN belongs to the condition, which is BASIC 7.0 and is
90 REM not something the reference had an opinion about -- it never got here. 90 REM not something the reference had an opinion about -- it never got here.
100 IF 1 == 1 THEN PRINT "TRUE-1" : PRINT "TRUE-2" 100 IF 1 == 1 THEN PRINT "TRUE-1" : PRINT "TRUE-2"
110 IF 1 == 0 THEN PRINT "NEVER-1" : PRINT "NEVER-2" 110 IF 1 == 0 THEN PRINT "NEVER-1" : PRINT "NEVER-2"

View File

@@ -1,5 +1,5 @@
10 REM A field name is checked against the closed set the program declared; 10 REM A field name is checked against a closed set the program declared, which
20 REM this is the one thing whose valid spellings are written down. 20 REM is the one thing in this language whose valid spellings are written down.
30 REM A misspelled *variable* is still silent -- see the last two lines. 30 REM A misspelled *variable* is still silent -- see the last two lines.
40 TYPE RECT 40 TYPE RECT
50 W# 50 W#

View File

@@ -17,7 +17,7 @@
170 RETURN B@.W# 170 RETURN B@.W#
180 PRINT WIDEN(A@) 180 PRINT WIDEN(A@)
190 PRINT A@.W# 190 PRINT A@.W#
200 REM To change one on purpose, pass a pointer. Assignment copies its 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. 210 REM reference, so the callee is looking at the caller's own record.
220 DIM Q@ AS PTR TO CRATE 220 DIM Q@ AS PTR TO CRATE
230 POINT Q@ AT A@ 230 POINT Q@ AT A@
@@ -37,7 +37,7 @@
370 N2@.COUNT# = 20 370 N2@.COUNT# = 20
380 POINT N1@.TAIL@ AT N2@ 380 POINT N1@.TAIL@ AT N2@
390 DEF TOTAL(P@ AS PTR TO NODE) 390 DEF TOTAL(P@ AS PTR TO NODE)
395 REM A pointer is true when it points at something, so a walk knows 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 396 REM where the list ends. NOT is the bitwise operator here, so the test is
397 REM written the positive way round. 397 REM written the positive way round.
400 IF P@->TAIL@ THEN RETURN P@->COUNT# + TOTAL(P@->TAIL@) 400 IF P@->TAIL@ THEN RETURN P@->COUNT# + TOTAL(P@->TAIL@)

View File

@@ -1,4 +1,4 @@
10 REM A TYPE declares its fields; each takes its type from its own suffix, 10 REM A TYPE declares its fields; each takes its own type from its own suffix,
20 REM which is the same rule every other name in this language follows. 20 REM which is the same rule every other name in this language follows.
30 TYPE COORD 30 TYPE COORD
40 X# 40 X#

View File

@@ -1,6 +1,6 @@
10 REM A type name and a field name are bare words, and so is every verb, so 10 REM A type name and a field name are bare words, and so is every verb, so
20 REM they share a namespace whether we like it or not. Both are refused with 20 REM they share a namespace whether we like it or not. Both are refused with
30 REM the scanner applies the same rule to variable names -- and a type 30 REM the same rule the scanner already applies to variable names -- and a type
40 REM name is refused by the prescan, which can say so plainly rather than 40 REM name is refused by the prescan, which can say so plainly rather than
50 REM leaving the parser to report "Expected expression or literal". 50 REM leaving the parser to report "Expected expression or literal".
60 TYPE POINT 60 TYPE POINT

View File

@@ -1,6 +1,6 @@
10 REM This shows the waitingForCommand utility in the BasicEnvironment 10 REM This shows the waitingForCommand utility in the BasicEnvironment
11 REM when we have a nested for loop. The inner loop SHOULD execute, but 11 REM when we have a nested for loop. The inner loop SHOULD execute, but
12 REM the outer loop should NOT execute; neither loop should execute. 12 REM the outer loop should NOT execute. Therefore, neither loop should execute.
20 FOR I# = 1 TO 0 20 FOR I# = 1 TO 0
25 FOR J# = 2 TO 4 25 FOR J# = 2 TO 4
30 PRINT "waitingForCommand FAILS if this is seen" 30 PRINT "waitingForCommand FAILS if this is seen"
@@ -8,4 +8,4 @@
35 NEXT J# 35 NEXT J#
40 NEXT I# 40 NEXT I#
50 PRINT "SUCCESS" 50 PRINT "SUCCESS"
80 QUIT 80 QUIT

View File

@@ -1,6 +1,6 @@
10 PRINT MOD(10, 3) 10 PRINT MOD(10, 3)
20 PRINT MOD(12, 5) 20 PRINT MOD(12, 5)
30 PRINT MOD(4, 2) 30 PRINT MOD(4, 2)
40 REM MOD() ONLY WORKS WITH INTEGERS; FLOATING POINT RESULTS ARE UNRELIABLE 40 REM MOD() ONLY WORKS WITH INTEGERS - RESULTS WITH FLOATING POINT ARE UNRELIABLE
50 REM PRINT MOD(1.2, 0.4) 50 REM PRINT MOD(1.2, 0.4)
60 REM THERE IS NO ERROR THROWN HERE. JUST DONT DO IT. 60 REM THERE IS NO ERROR THROWN HERE. JUST DONT DO IT.