2026-07-31 21:50:50 -04:00
|
|
|
# 13. Differences from BASIC 7.0
|
|
|
|
|
|
|
|
|
|
If you already write Commodore BASIC, this is the chapter to read first. Everything
|
|
|
|
|
here is deliberate, and everything is recorded in the repository's `TODO.md` with the
|
|
|
|
|
reasoning — this is the short version.
|
|
|
|
|
|
|
|
|
|
## The language
|
|
|
|
|
|
|
|
|
|
### Variables carry a type suffix, and the suffixes differ
|
|
|
|
|
|
|
|
|
|
| | Integer | Float | String |
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
| C128 | `A%` | `A` | `A$` |
|
|
|
|
|
| akbasic | `A#` | `A%` | `A$` |
|
|
|
|
|
|
|
|
|
|
There is **no such thing as an unsuffixed variable**. A bare name is a label.
|
|
|
|
|
|
|
|
|
|
### `=` works in a condition, and `==` works everywhere
|
|
|
|
|
|
|
|
|
|
`IF A = 5 THEN` does what you expect. `==` also means equality and is what the older
|
|
|
|
|
programs in this repository use. Outside a condition `=` is assignment, as always.
|
|
|
|
|
|
|
|
|
|
### `AND`, `OR` and `NOT` in conditions
|
|
|
|
|
|
|
|
|
|
These work, and a condition is a whole expression rather than a single comparison, so
|
|
|
|
|
`IF A = 1 AND B = 2 THEN` parses. Truth is nonzero, so `IF A THEN` works too.
|
|
|
|
|
|
|
|
|
|
### `MID` and `INSTR` count from zero
|
|
|
|
|
|
|
|
|
|
A C128 counts from one. A failed `INSTR` gives -1 rather than 0.
|
|
|
|
|
|
|
|
|
|
### `THEN` needs a verb
|
|
|
|
|
|
|
|
|
|
`IF X THEN 100` is not a jump. Write `IF X THEN GOTO 100`.
|
|
|
|
|
|
|
|
|
|
### Strings are 255 characters and cannot contain a quote
|
|
|
|
|
|
|
|
|
|
There is no escape character.
|
|
|
|
|
|
|
|
|
|
### Numbers
|
|
|
|
|
|
|
|
|
|
Integers are 64-bit and floats are IEEE doubles, so `PRINT 1.5` gives `1.500000`. A
|
|
|
|
|
leading zero is not octal; `0x` is hexadecimal.
|
|
|
|
|
|
|
|
|
|
## Block structure
|
|
|
|
|
|
|
|
|
|
**A whole loop on one line does not loop.**
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic
|
2026-07-31 21:50:50 -04:00
|
|
|
10 FOR I# = 1 TO 3 : PRINT I# : NEXT I#
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
20 PRINT "DONE"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
DONE
|
2026-07-31 21:50:50 -04:00
|
|
|
```
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
The loop prints nothing. Block skipping walks source *lines*, so a `NEXT` on the same line as
|
2026-07-31 21:50:50 -04:00
|
|
|
its `FOR` is never reached. The same applies to `DO`/`LOOP`. Write loops across lines.
|
|
|
|
|
|
|
|
|
|
## Two known defects in `FOR`
|
|
|
|
|
|
|
|
|
|
Both are recorded, both have tests asserting the correct behaviour, and both are
|
|
|
|
|
waiting on a decision rather than on work:
|
|
|
|
|
|
|
|
|
|
- **A step that overshoots runs the body one extra time.** `FOR I = 1 TO 9 STEP 3` runs
|
|
|
|
|
with 1, 4, 7 *and 10*.
|
|
|
|
|
- **`FOR I = 1 TO 1` does not run its body at all**, where every other BASIC runs it
|
|
|
|
|
once.
|
|
|
|
|
|
|
|
|
|
The two errors cancel out for a step of 1, which is why they went unnoticed. Fixing
|
|
|
|
|
them would change the output of a checked-in acceptance file, which is not something
|
|
|
|
|
this project does silently.
|
|
|
|
|
|
|
|
|
|
**A loop counter does not survive its loop.** It lives in the loop's own scope, so
|
|
|
|
|
reading it afterwards gives zero.
|
|
|
|
|
|
|
|
|
|
## Direct mode
|
|
|
|
|
|
|
|
|
|
A statement typed with no line number runs immediately, as it should. This was not
|
|
|
|
|
true until recently — the interpreter used to file everything but a handful of verbs as
|
|
|
|
|
program text.
|
|
|
|
|
|
|
|
|
|
## Errors
|
|
|
|
|
|
|
|
|
|
`ER` and `EL` are **`ER#` and `EL#`**, ordinary global variables. `ER#` holds this
|
|
|
|
|
interpreter's error code, which bears no relation to a Commodore error number. Print
|
|
|
|
|
`ERR(ER#)` for the text.
|
|
|
|
|
|
|
|
|
|
## Graphics
|
|
|
|
|
|
|
|
|
|
- **Coordinates are always 320 by 200**, whatever the window size.
|
|
|
|
|
- **`SSHAPE` puts a handle in the string, not the pixels.** You can pass it to `GSHAPE`
|
|
|
|
|
and `SPRSAV`; you cannot save it or take its `LEN`.
|
|
|
|
|
- **`WIDTH` is emulated** by drawing parallel passes.
|
|
|
|
|
- **Drawing does not persist across frames.** The verbs draw straight to the renderer,
|
|
|
|
|
so anything drawn is overwritten on the next frame unless the program redraws it.
|
|
|
|
|
This is a defect, not a design.
|
|
|
|
|
- **`CHAR` ignores its colour argument** and needs a text device with a cursor.
|
|
|
|
|
|
|
|
|
|
## Sound
|
|
|
|
|
|
|
|
|
|
- **`PLAY` and `SOUND` do not block.** The statement after them runs immediately.
|
|
|
|
|
- **`FILTER` is refused.** There is no filter stage to configure.
|
|
|
|
|
- **`PLAY`'s `M` is accepted and does nothing.**
|
|
|
|
|
- **`TEMPO`'s calibration is a choice**, not a transcription.
|
|
|
|
|
|
|
|
|
|
## Sprites
|
|
|
|
|
|
|
|
|
|
- **Coordinates are the 320 by 200 drawing space**, not the VIC-II's raster space.
|
|
|
|
|
- **`SPRSAV` takes an integer array**, not a string, for the data form — a string here
|
|
|
|
|
cannot hold a zero byte. It also takes an **image file path**, which a C128 cannot.
|
|
|
|
|
- **A sprite loaded from a file keeps the image's own size**, not 24 by 21.
|
|
|
|
|
- **`MOVSPR`'s speed unit is a choice.** The manual does not say what a unit is worth.
|
|
|
|
|
- **Collision is by bounding box**, not by pixel, and only type 1 exists.
|
|
|
|
|
- **Priority and multicolour are recorded but not drawn.**
|
|
|
|
|
- **`SPRDEF` is out of scope.**
|
|
|
|
|
|
|
|
|
|
## Files
|
|
|
|
|
|
|
|
|
|
- **`PRINT #` and `INPUT #` need a space before the `#`.** `PRINT#1` scans as a
|
|
|
|
|
variable name.
|
|
|
|
|
- **`RECORD` counts lines**, not fixed-length records.
|
|
|
|
|
- **`BLOAD` requires a length.**
|
|
|
|
|
- **`HEADER`, `COLLECT`, `BACKUP` and `BOOT` are refused.** They operate on a physical
|
|
|
|
|
disk.
|
|
|
|
|
- **`DIRECTORY` is refused** pending a wrapper in the standard library.
|
|
|
|
|
|
|
|
|
|
## Machine
|
|
|
|
|
|
|
|
|
|
- **`SYS` is refused.** There is no 6502 and no ROM.
|
|
|
|
|
- **`FETCH` and `STASH` are the same byte copy.** There is no expansion RAM to tell
|
|
|
|
|
them apart.
|
|
|
|
|
- **`POKE`, `PEEK` and `POINTER` use real process addresses.** A wrong one is a
|
|
|
|
|
segmentation fault, not an error message.
|
|
|
|
|
- **`BANK`, `FAST` and `MONITOR` do not exist.**
|
|
|
|
|
|
|
|
|
|
## Formatting
|
|
|
|
|
|
|
|
|
|
- **`PRINT USING` renders one field per statement.** `PRINT USING "### ###"; A, B` is
|
|
|
|
|
not supported.
|
|
|
|
|
- **Exponential fields (`^^^^`) are not implemented.**
|
|
|
|
|
|
|
|
|
|
## Console
|
|
|
|
|
|
|
|
|
|
- **`SLEEP` and `WAIT` hold the program without blocking the host.** `SLEEP` with no
|
|
|
|
|
host clock does nothing at all rather than waiting forever.
|
|
|
|
|
- **`TI` and `TI$` are `TI#` and `TI$`**, refreshed once per step.
|
|
|
|
|
- **`WAIT` polls ordinary process memory.** Nothing changes it but the host.
|
|
|
|
|
- **`KEY` stores macros and nothing expands them.**
|
|
|
|
|
|
|
|
|
|
## Limits
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|---|---|
|
|
|
|
|
| Source lines | 9999 |
|
|
|
|
|
| Line length | 255 |
|
|
|
|
|
| String length | 255 |
|
|
|
|
|
| Variables | 128 |
|
|
|
|
|
| Array elements | 1024 per array, 4096 in total |
|
|
|
|
|
| Scopes | 32 |
|
|
|
|
|
| Labels | 64 |
|
|
|
|
|
| `DATA` items | 512 |
|
|
|
|
|
| File channels | 10 |
|
|
|
|
|
| Operations per line | roughly 16 |
|
|
|
|
|
|
|
|
|
|
Every one is a fixed pool. Nothing in the interpreter calls `malloc`, which is what
|
|
|
|
|
makes it safe to embed in a game that cannot afford a surprise allocation.
|