Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 699ac9ab93 |
@@ -17,7 +17,7 @@ jobs:
|
|||||||
# Not recursive, deliberately. The top-level build needs
|
# Not recursive, deliberately. The top-level build needs
|
||||||
# deps/libakerror and deps/libakstdlib, via add_subdirectory, and
|
# deps/libakerror and deps/libakstdlib, via add_subdirectory, and
|
||||||
# nothing else: the golden corpus and the Commodore font now live in
|
# nothing else: the golden corpus and the Commodore font now live in
|
||||||
# this repository (tests/language/ and assets/fonts/), so
|
# this repository (tests/reference/ and assets/fonts/), so
|
||||||
# deps/basicinterpret is no longer a build dependency at all.
|
# deps/basicinterpret is no longer a build dependency at all.
|
||||||
# It does *not* need deps/libakgl, which is guarded behind
|
# It does *not* need deps/libakgl, which is guarded behind
|
||||||
# AKBASIC_WITH_AKGL and defaults OFF -- and recursing into it would
|
# AKBASIC_WITH_AKGL and defaults OFF -- and recursing into it would
|
||||||
@@ -62,9 +62,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake -S . -B build
|
cmake -S . -B build
|
||||||
cmake --build build --parallel 2
|
cmake --build build --parallel 2
|
||||||
# The suite is 112 cases: 65 language files with sibling expectations,
|
# The suite is 78 cases: 41 golden files byte-compared against the Go
|
||||||
# 43 unit tests, 3 embedding examples, and docs_examples.
|
# reference's own corpus (checked in at tests/reference/, see its README),
|
||||||
# Some unit tests assert the *correct* contract for known defects (TODO.md
|
# 9 local golden cases for verbs the reference never implemented, 25 unit
|
||||||
|
# tests, 2 embedding examples, and 1 known-failing test that asserts the
|
||||||
|
# *correct* contract for defects carried over from the reference (TODO.md
|
||||||
# section 6). A green run therefore does not mean defect-free -- see
|
# section 6). A green run therefore does not mean defect-free -- see
|
||||||
# AKBASIC_KNOWN_FAILING_TESTS.
|
# AKBASIC_KNOWN_FAILING_TESTS.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -57,9 +57,9 @@ repeating where you will see them:
|
|||||||
`include/akgl/SDL_GameControllerDB.h`. Change the template or the generator script.
|
`include/akgl/SDL_GameControllerDB.h`. Change the template or the generator script.
|
||||||
- **Do not reformat code you are not otherwise changing.** Several files mix tabs and spaces
|
- **Do not reformat code you are not otherwise changing.** Several files mix tabs and spaces
|
||||||
and there is no repo-wide formatter; style conversions get their own commit.
|
and there is no repo-wide formatter; style conversions get their own commit.
|
||||||
- **Keep `tests/language/` editable.** Its `.bas` programs and sibling `.txt` expectations
|
- **Do not edit `tests/reference/`.** Those expectations came from the Go implementation and
|
||||||
are changed together when behavior changes. Record deliberate language decisions in
|
are never edited to suit this interpreter. A deliberate divergence goes in
|
||||||
`TODO.md` or `docs/13-differences.md`.
|
`tests/reference/README.md`'s divergence table and `docs/13-differences.md`.
|
||||||
- **Open an issue for outstanding work; do not add it to `TODO.md`.**
|
- **Open an issue for outstanding work; do not add it to `TODO.md`.**
|
||||||
<https://source.starfort.tech/andrew/akbasic/issues>, or `tea issues create --repo
|
<https://source.starfort.tech/andrew/akbasic/issues>, or `tea issues create --repo
|
||||||
andrew/akbasic`. Name the file and line, the functional consequence, and what closing it would
|
andrew/akbasic`. Name the file and line, the functional consequence, and what closing it would
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ if(AKBASIC_WITH_AKGL)
|
|||||||
# against.
|
# against.
|
||||||
#
|
#
|
||||||
# **A byte comparison of a rendered PNG is a deliberate bet**, the same bet
|
# **A byte comparison of a rendered PNG is a deliberate bet**, the same bet
|
||||||
# the language corpus makes about golden output: that the dummy video
|
# tests/reference/ already makes about golden output: that the dummy video
|
||||||
# driver and the software renderer are reproducible. They are, run to run and
|
# driver and the software renderer are reproducible. They are, run to run and
|
||||||
# build to build. What is untested is an SDL upgrade that shifts one pixel of
|
# build to build. What is untested is an SDL upgrade that shifts one pixel of
|
||||||
# a diagonal, and the answer to that is to regenerate the figures in the same
|
# a diagonal, and the answer to that is to regenerate the figures in the same
|
||||||
@@ -531,20 +531,63 @@ if(AKBASIC_WILL_FAIL_TESTS OR AKBASIC_KNOWN_FAILING_TESTS)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The editable language corpus. One CTest case per .bas so a failure names the
|
# The reference's own corpus, byte-compared against the sibling .txt. One CTest
|
||||||
# file. Each program is paired with a sibling .txt expectation; see
|
# case per .bas so a failure names the file.
|
||||||
# tests/language/README.md for the editing rule.
|
|
||||||
#
|
#
|
||||||
# The corpus includes programs carried over from the deprecated Go implementation
|
# It used to be driven in place out of deps/basicinterpret, on the reasoning that
|
||||||
# and cases written for this interpreter. Their provenance is useful when
|
# copying a submodule's corpus guarantees drift. That reasoning was sound and it
|
||||||
# investigating a regression, but it does not make any case immutable.
|
# has been overruled deliberately: the Go dependency is being deprecated, and a
|
||||||
|
# build that cannot run its acceptance suite without cloning the implementation
|
||||||
|
# it replaced is not finished. The copy is byte-identical to
|
||||||
|
# basicinterpreter@d76162c and tests/reference/README.md records
|
||||||
|
# where it came from and what the drift now costs.
|
||||||
|
file(GLOB_RECURSE AKBASIC_GOLDEN_CASES
|
||||||
|
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/tests/reference"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/reference/*.bas"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach(_case IN LISTS AKBASIC_GOLDEN_CASES)
|
||||||
|
string(REGEX REPLACE "^tests/" "" _name "${_case}")
|
||||||
|
string(REGEX REPLACE "\\.bas$" "" _name "${_name}")
|
||||||
|
string(REPLACE "/" "_" _name "${_name}")
|
||||||
|
_add_test(
|
||||||
|
NAME golden_${_name}
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DBASIC=$<TARGET_FILE:basic>
|
||||||
|
-DCASE=${CMAKE_CURRENT_SOURCE_DIR}/tests/reference/${_case}
|
||||||
|
-P ${CMAKE_CURRENT_SOURCE_DIR}/tests/golden.cmake
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(AKBASIC_GOLDEN_CASES)
|
||||||
|
set(AKBASIC_GOLDEN_NAMES)
|
||||||
|
foreach(_case IN LISTS AKBASIC_GOLDEN_CASES)
|
||||||
|
string(REGEX REPLACE "^tests/" "" _name "${_case}")
|
||||||
|
string(REGEX REPLACE "\\.bas$" "" _name "${_name}")
|
||||||
|
string(REPLACE "/" "_" _name "${_name}")
|
||||||
|
list(APPEND AKBASIC_GOLDEN_NAMES golden_${_name})
|
||||||
|
endforeach()
|
||||||
|
_set_tests_properties(${AKBASIC_GOLDEN_NAMES} PROPERTIES TIMEOUT 30)
|
||||||
|
# An AKGL build of `basic` opens a window, and forty-one of them is not what
|
||||||
|
# anybody running the suite wanted. The dummy driver produces the same stdout,
|
||||||
|
# which is the only thing a golden case compares.
|
||||||
|
if(AKBASIC_WITH_AKGL)
|
||||||
|
_set_tests_properties(${AKBASIC_GOLDEN_NAMES} PROPERTIES
|
||||||
|
ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_AUDIODRIVER=dummy;SDL_RENDER_DRIVER=software")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# The local golden corpus, for verbs the reference never implemented.
|
||||||
#
|
#
|
||||||
# Programs carried over from the deprecated Go implementation and cases written
|
# Still separate now that the reference's corpus lives in this repository too,
|
||||||
# for this interpreter use the same editable `.bas`/`.txt` contract. Registered
|
# and the reason changed rather than went away: tests/reference/ is a *record* of
|
||||||
# under local_ so every failure identifies the program that produced it.
|
# what the Go implementation did and nothing in it should ever be edited to suit
|
||||||
|
# this one, while tests/language/ is ours to change. Registered under local_ so a
|
||||||
|
# failure says at a glance which of the two it came from -- and so a diff that
|
||||||
|
# touches tests/reference/ stands out as the thing it is.
|
||||||
#
|
#
|
||||||
# Note what this can and cannot cover. The graphics and sound verbs draw and play
|
# Note what this can and cannot cover. The graphics and sound verbs draw and play
|
||||||
# rather than print, so what a golden file sees of them is their refusals and
|
# rather than print, so what a golden file sees of them is their *refusals* and
|
||||||
# whatever a program can PRINT about the state they changed. The behaviour that
|
# whatever a program can PRINT about the state they changed. The behaviour that
|
||||||
# reaches a device is asserted against tests/mockdevice.h instead.
|
# reaches a device is asserted against tests/mockdevice.h instead.
|
||||||
file(GLOB_RECURSE AKBASIC_LOCAL_CASES
|
file(GLOB_RECURSE AKBASIC_LOCAL_CASES
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ source stays readable as documentation of what the original did. Neither is bind
|
|||||||
|
|
||||||
**It is not a build or test dependency.** Both configurations have been configured, built and
|
**It is not a build or test dependency.** Both configurations have been configured, built and
|
||||||
run from scratch with it moved out of the tree. Its acceptance corpus is checked in at
|
run from scratch with it moved out of the tree. Its acceptance corpus is checked in at
|
||||||
`tests/language/` and its Commodore font at `assets/fonts/`.
|
`tests/reference/` and its Commodore font at `assets/fonts/`.
|
||||||
|
|
||||||
```sh norun
|
```sh norun
|
||||||
cd deps/basicinterpret
|
cd deps/basicinterpret
|
||||||
@@ -351,10 +351,15 @@ name. That is not cosmetic: `add_executable` creates a dependency's targets even
|
|||||||
|
|
||||||
### The golden corpora
|
### The golden corpora
|
||||||
|
|
||||||
`tests/language/` is the editable language corpus. It includes cases carried over from the
|
`tests/reference/` is the Go implementation's own acceptance suite, byte-compared.
|
||||||
deprecated Go implementation as well as cases written for this interpreter. Every `.bas` file
|
**Nothing in it is ever edited to suit this interpreter.** If a case fails, either this
|
||||||
has a sibling `.txt` expectation, and a new language feature needs that pair as well as unit
|
interpreter is wrong or the divergence is deliberate — and a deliberate one goes in
|
||||||
tests. Change both deliberately in the same commit; provenance does not make a case immutable.
|
`tests/reference/README.md`'s divergence table and `docs/13-differences.md`, not into the
|
||||||
|
expectation file. `tests/reference/README.md`
|
||||||
|
says the same thing at more length.
|
||||||
|
|
||||||
|
`tests/language/` is ours and may be changed freely. A new language feature needs a
|
||||||
|
`.bas`/`.txt` pair there as well as unit tests.
|
||||||
|
|
||||||
### Mutation-check a fix before you believe it
|
### Mutation-check a fix before you believe it
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ implementation that started from the Java Lox instructions in
|
|||||||
[craftinginterpreters.com](https://craftinginterpreters.com) and then struck off on its own. That
|
[craftinginterpreters.com](https://craftinginterpreters.com) and then struck off on its own. That
|
||||||
project is deprecated. It is vendored here as the behavioural spec to read when a question about
|
project is deprecated. It is vendored here as the behavioural spec to read when a question about
|
||||||
semantics comes up, and its acceptance corpus is checked in at
|
semantics comes up, and its acceptance corpus is checked in at
|
||||||
[`tests/language/`](tests/language/README.md) and runs on every build — so nothing about
|
[`tests/reference/`](tests/reference/README.md) and runs on every build — so nothing about
|
||||||
building or testing this project needs it.
|
building or testing this project needs it.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
@@ -24,7 +24,7 @@ ctest --test-dir build --output-on-failure
|
|||||||
|
|
||||||
```sh norun
|
```sh norun
|
||||||
./build/basic # the REPL
|
./build/basic # the REPL
|
||||||
./build/basic tests/language/functions.bas # run a program
|
./build/basic tests/reference/language/functions.bas # run a program
|
||||||
```
|
```
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
@@ -129,7 +129,7 @@ version are catalogued in [`TODO.md`](TODO.md) and summarised for a BASIC progra
|
|||||||
| [`docs/`](docs/README.md) | The guide: eighteen chapters, the language then each hardware area then a reference section for every verb and function, [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture, [Chapter 15](docs/15-error-codes.md) listing every error code, and [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) building a whole game twice |
|
| [`docs/`](docs/README.md) | The guide: eighteen chapters, the language then each hardware area then a reference section for every verb and function, [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture, [Chapter 15](docs/15-error-codes.md) listing every error code, and [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) building a whole game twice |
|
||||||
| [`MAINTENANCE.md`](MAINTENANCE.md) | For contributors and maintainers: the documentation-example harness, the three test lists, mutation testing, error-code allocation, style |
|
| [`MAINTENANCE.md`](MAINTENANCE.md) | For contributors and maintainers: the documentation-example harness, the three test lists, mutation testing, error-code allocation, style |
|
||||||
| [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence |
|
| [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence |
|
||||||
| [`tests/language/README.md`](tests/language/README.md) | The editable language corpus and the rule for changing it |
|
| [`tests/reference/README.md`](tests/reference/README.md) | Where the golden corpus came from, and the rule for changing it |
|
||||||
|
|
||||||
API documentation builds with `doxygen Doxyfile`, into `build/docs/html`.
|
API documentation builds with `doxygen Doxyfile`, into `build/docs/html`.
|
||||||
|
|
||||||
|
|||||||
37
TODO.md
37
TODO.md
@@ -38,7 +38,7 @@ What it changes:
|
|||||||
- **§1.8's message-text contract is now a convention.** Improving a message is allowed; it costs
|
- **§1.8's message-text contract is now a convention.** Improving a message is allowed; it costs
|
||||||
a golden file, which is a cost rather than a veto.
|
a golden file, which is a cost rather than a veto.
|
||||||
- **§5's bar drops** from "defensible against the golden suite" to defensible on its own merits.
|
- **§5's bar drops** from "defensible against the golden suite" to defensible on its own merits.
|
||||||
- **`tests/language/` is the editable language corpus rather than a protected specification.** Diverging from
|
- **`tests/reference/` becomes a regression suite rather than a specification.** Diverging from
|
||||||
it is allowed and must be deliberate and recorded — see its README.
|
it is allowed and must be deliberate and recorded — see its README.
|
||||||
|
|
||||||
What it does **not** change:
|
What it does **not** change:
|
||||||
@@ -306,13 +306,13 @@ to `Println`, which adds another.
|
|||||||
**This used to be a hard contract and is now a default.** The Go implementation is deprecated
|
**This used to be a hard contract and is now a default.** The Go implementation is deprecated
|
||||||
and will not be updated, so the two projects are no longer required to match — see §0.1. What
|
and will not be updated, so the two projects are no longer required to match — see §0.1. What
|
||||||
survives is the practical half: these strings and this newline behaviour are what every
|
survives is the practical half: these strings and this newline behaviour are what every
|
||||||
expectation in `tests/language/` was written against, so changing one means changing the paired
|
expectation in `tests/reference/` was written against, so changing one means changing golden
|
||||||
files, and that is worth doing on purpose rather than by accident. A message that reads
|
files, and that is worth doing on purpose rather than by accident. A message that reads
|
||||||
awkwardly *may* now be improved; do it deliberately, move the expectations in the same commit,
|
awkwardly *may* now be improved; do it deliberately, move the expectations in the same commit,
|
||||||
and add a line to §5.
|
and add a line to §5.
|
||||||
|
|
||||||
Numeric formatting still matches the reference: integers via `%" PRId64 "`, floats via `%f`
|
Numeric formatting still matches the reference: integers via `%" PRId64 "`, floats via `%f`
|
||||||
(Go's `%f` and C's `%f` both give six decimals — `tests/language/arithmetic/float.txt`
|
(Go's `%f` and C's `%f` both give six decimals — `tests/reference/language/arithmetic/float.txt`
|
||||||
confirms). No reason to change it, which is different from not being allowed to.
|
confirms). No reason to change it, which is different from not being allowed to.
|
||||||
|
|
||||||
### 1.9 Which `libakstdlib` calls are cleared for use — **the bans are lifted**
|
### 1.9 Which `libakstdlib` calls are cleared for use — **the bans are lifted**
|
||||||
@@ -375,7 +375,7 @@ Phases 0 through 6 of the original plan are done. The interpreter builds clean u
|
|||||||
|
|
||||||
It *did* reproduce the reference byte for byte, and that claim is retired rather than broken:
|
It *did* reproduce the reference byte for byte, and that claim is retired rather than broken:
|
||||||
§0.1 released it, and one case has since diverged deliberately (§6 item 16, listed in
|
§0.1 released it, and one case has since diverged deliberately (§6 item 16, listed in
|
||||||
`tests/language/README.md`). Everything else still matches, which is worth knowing but is no
|
`tests/reference/README.md`). Everything else still matches, which is worth knowing but is no
|
||||||
longer a gate.
|
longer a gate.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -408,16 +408,17 @@ and the only path that existed — `AKBASIC_MODE_RUNSTREAM` reading through the
|
|||||||
is the code the README quotes, built by every build and registered as a CTest case so a
|
is the code the README quotes, built by every build and registered as a CTest case so a
|
||||||
signature change breaks the build rather than rotting the document.
|
signature change breaks the build rather than rotting the document.
|
||||||
|
|
||||||
**The acceptance suite is the editable language corpus, checked in at `tests/language/`.** All
|
**The acceptance suite is the reference's own corpus, checked in at `tests/reference/`.** All
|
||||||
65 `.bas` files are registered as individual CTest cases and compared against their `.txt`
|
41 `.bas` files are registered as individual CTest cases and byte-compared against their `.txt`
|
||||||
— including the trailing double newline on an error line (§1.8).
|
— including the trailing double newline on an error line (§1.8).
|
||||||
|
|
||||||
It was driven *in place* out of `deps/basicinterpret` until 2026-07-31, on the reasoning that
|
It was driven *in place* out of `deps/basicinterpret` until 2026-07-31, on the reasoning that
|
||||||
copying a submodule's corpus guarantees drift. That reasoning was sound and was overruled
|
copying a submodule's corpus guarantees drift. That reasoning was sound and was overruled
|
||||||
deliberately: the Go dependency is being deprecated, and a build that cannot run its own
|
deliberately: the Go dependency is being deprecated, and a build that cannot run its own
|
||||||
acceptance suite without cloning the implementation it replaced is not finished. The copy is
|
acceptance suite without cloning the implementation it replaced is not finished. The copy is
|
||||||
originally byte-identical to `basicinterpreter@d76162c`, and `tests/language/README.md` records the
|
byte-identical to `basicinterpreter@d76162c`, and `tests/reference/README.md` records the
|
||||||
provenance and the rule that programs and expectations are edited together deliberately.
|
provenance, the cost of the drift nobody is watching for now, and the rule that those
|
||||||
|
expectations are never edited to suit this interpreter.
|
||||||
|
|
||||||
**Nothing in the build or the suite needs `deps/basicinterpret` any more**, and that is checked
|
**Nothing in the build or the suite needs `deps/basicinterpret` any more**, and that is checked
|
||||||
rather than assumed — both configurations were configured, built and run from scratch with the
|
rather than assumed — both configurations were configured, built and run from scratch with the
|
||||||
@@ -1025,7 +1026,7 @@ deviations from the reference's *program*: `main.go` and the SDL half of
|
|||||||
|
|
||||||
**What it costs a program:** a listing that used `INPUT$`, `LEN#` or `GOTO%` as a variable
|
**What it costs a program:** a listing that used `INPUT$`, `LEN#` or `GOTO%` as a variable
|
||||||
stops parsing, and the fix is to rename the variable. One case in the reference's own corpus
|
stops parsing, and the fix is to rename the variable. One case in the reference's own corpus
|
||||||
did exactly that; see `tests/language/examples/strreverse.bas`.
|
did exactly that; see `tests/reference/README.md`.
|
||||||
|
|
||||||
### Deviations in statement separation
|
### Deviations in statement separation
|
||||||
|
|
||||||
@@ -1390,10 +1391,10 @@ deviations from the reference's *program*: `main.go` and the SDL half of
|
|||||||
**This one moved a golden file.** A line with no number used to be filed under the loader's
|
**This one moved a golden file.** A line with no number used to be filed under the loader's
|
||||||
cursor unchanged — that is, on top of the line before it — so two unnumbered lines in a row
|
cursor unchanged — that is, on top of the line before it — so two unnumbered lines in a row
|
||||||
silently lost the first, and a *blank* line erased whatever preceded it. The reference does
|
silently lost the first, and a *blank* line erased whatever preceded it. The reference does
|
||||||
the same, and `tests/language/arithmetic/integer.bas` is the proof: four `PRINT`
|
the same, and `tests/reference/language/arithmetic/integer.bas` is the proof: four `PRINT`
|
||||||
statements, an expectation with three values, and a trailing blank line that erased
|
statements, an expectation with three values, and a trailing blank line that erased
|
||||||
`40 PRINT 4 - 2` before the program ran. The expectation is now `4 4 2 2` and
|
`40 PRINT 4 - 2` before the program ran. The expectation is now `4 4 2 2` and
|
||||||
`tests/language/README.md` records it.
|
`tests/reference/README.md` records it.
|
||||||
|
|
||||||
In its place: `akbasic_runtime_file_line()` (`src/runtime.c`) is the one implementation of
|
In its place: `akbasic_runtime_file_line()` (`src/runtime.c`) is the one implementation of
|
||||||
the rule, shared by `akbasic_runtime_load()`, RUNSTREAM and `DLOAD`. A numbered line is
|
the rule, shared by `akbasic_runtime_load()`, RUNSTREAM and `DLOAD`. A numbered line is
|
||||||
@@ -1585,9 +1586,9 @@ be reproduced before it can be fixed.
|
|||||||
|
|
||||||
**It cost one golden case, exactly as predicted, and the cost turned out to be nothing.**
|
**It cost one golden case, exactly as predicted, and the cost turned out to be nothing.**
|
||||||
The reference's `examples/strreverse.bas` names a variable `INPUT$`; the variable is renamed
|
The reference's `examples/strreverse.bas` names a variable `INPUT$`; the variable is renamed
|
||||||
to `SOURCE$` in `tests/language/examples/strreverse.bas`, the expectation is byte-for-byte unchanged — the program
|
to `SOURCE$` in `tests/reference/`, the expectation is byte-for-byte unchanged — the program
|
||||||
still prints `REVERSED: OLLEH` — and the case keeps every bit of its coverage. Recorded in
|
still prints `REVERSED: OLLEH` — and the case keeps every bit of its coverage. Recorded in
|
||||||
`tests/language/README.md` records the corpus editing rule.
|
`tests/reference/README.md`'s divergence table, which this is the first entry in.
|
||||||
|
|
||||||
This item sat parked because the corpus was the acceptance contract and lived in a submodule
|
This item sat parked because the corpus was the acceptance contract and lived in a submodule
|
||||||
this repository could not edit. Both premises are gone: the corpus is checked in, and
|
this repository could not edit. Both premises are gone: the corpus is checked in, and
|
||||||
@@ -2231,10 +2232,10 @@ update --init --recursive` gets them.
|
|||||||
|
|
||||||
## 8. Status
|
## 8. Status
|
||||||
|
|
||||||
**The port is done.** The C interpreter passes the language corpus and passes clean
|
**The port is done.** The C interpreter passes the Go reference's entire corpus and passes clean
|
||||||
under ASan and UBSan. It reproduced that corpus byte for byte until §0.1 retired the
|
under ASan and UBSan. It reproduced that corpus byte for byte until §0.1 retired the
|
||||||
requirement; two cases have diverged on purpose since, and
|
requirement; two cases have diverged on purpose since, and
|
||||||
`tests/language/README.md` lists them.
|
`tests/reference/README.md` lists them.
|
||||||
|
|
||||||
| Gate | Result |
|
| Gate | Result |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -2242,7 +2243,7 @@ requirement; two cases have diverged on purpose since, and
|
|||||||
| `ctest` with `-DAKBASIC_WITH_AKGL=ON` | 112/112 headless, with `akgl_typing` skipping itself. The same set minus the four `no_device` cases the SDL driver contradicts, plus `akgl_backends`, `akgl_frontend`, `docs_screenshots` and `akgl_typing` — the last of which is the skip, and the `akgl_build` CI job is where it skips |
|
| `ctest` with `-DAKBASIC_WITH_AKGL=ON` | 112/112 headless, with `akgl_typing` skipping itself. The same set minus the four `no_device` cases the SDL driver contradicts, plus `akgl_backends`, `akgl_frontend`, `docs_screenshots` and `akgl_typing` — the last of which is the skip, and the `akgl_build` CI job is where it skips |
|
||||||
| `docs_examples` | Every fenced block in `README.md`, `MAINTENANCE.md` and `docs/` executed and byte-compared: 71 programs, 9 transcripts, 79 output comparisons, 4 C snippets, 2 excerpts, 2 shell blocks and 19 figures in the default build. The C-snippet count reads 0 when the harness is run by hand without `--cflags-file`; CTest passes it. `MAINTENANCE.md` documents the fence-tag convention |
|
| `docs_examples` | Every fenced block in `README.md`, `MAINTENANCE.md` and `docs/` executed and byte-compared: 71 programs, 9 transcripts, 79 output comparisons, 4 C snippets, 2 excerpts, 2 shell blocks and 19 figures in the default build. The C-snippet count reads 0 when the harness is run by hand without `--cflags-file`; CTest passes it. `MAINTENANCE.md` documents the fence-tag convention |
|
||||||
| `docs_screenshots` | 19/19 figures re-rendered and byte-identical to the checked-in PNGs. AKGL build only — rendering a picture needs the SDL half |
|
| `docs_screenshots` | 19/19 figures re-rendered and byte-identical to the checked-in PNGs. AKGL build only — rendering a picture needs the SDL half |
|
||||||
| Language corpus | 65/65 paired expectations — **and 65/65 again through the SDL binary**, which is most of what proves the frontend changes no output |
|
| Golden corpus | 41/41 byte-exact from `tests/reference/` — **and 41/41 again through the SDL binary**, which is most of what proves the frontend changes no output |
|
||||||
| ASan + UBSan | 112/112 |
|
| ASan + UBSan | 112/112 |
|
||||||
| Line coverage | 94.1% (7227/7681) — above the 90% gate |
|
| Line coverage | 94.1% (7227/7681) — above the 90% gate |
|
||||||
| Function coverage | 97.9% (474/484) |
|
| Function coverage | 97.9% (474/484) |
|
||||||
@@ -2552,7 +2553,7 @@ What remains, in priority order:
|
|||||||
this whole arrangement exists against.
|
this whole arrangement exists against.
|
||||||
|
|
||||||
The byte comparison is a deliberate bet that the dummy driver and the software renderer
|
The byte comparison is a deliberate bet that the dummy driver and the software renderer
|
||||||
are reproducible, which is the same bet `tests/language/` makes about golden
|
are reproducible, which is the same bet `tests/reference/` already makes about golden
|
||||||
output. Verified run-to-run and build-to-build here; what is untested is an SDL upgrade
|
output. Verified run-to-run and build-to-build here; what is untested is an SDL upgrade
|
||||||
that moves one pixel of a diagonal. **If that happens, regenerate the figures in the same
|
that moves one pixel of a diagonal. **If that happens, regenerate the figures in the same
|
||||||
commit as the bump** — do not weaken the test to a size check, which would pass for every
|
commit as the bump** — do not weaken the test to a size check, which would pass for every
|
||||||
@@ -2635,7 +2636,7 @@ reduced against `build/basic`, the stdio build, unless it says otherwise.
|
|||||||
|
|
||||||
**It is narrower than it looks, and the golden corpus is why.** The first attempt
|
**It is narrower than it looks, and the golden corpus is why.** The first attempt
|
||||||
released the scope on *any* skip, which broke
|
released the scope on *any* skip, which broke
|
||||||
`tests/language/flowcontrol/nestedforloopwaitingforcommand.bas`: a zero-
|
`tests/reference/language/flowcontrol/nestedforloopwaitingforcommand.bas`: a zero-
|
||||||
iteration `FOR` skips its body by the same mechanism, and there the orphan is
|
iteration `FOR` skips its body by the same mechanism, and there the orphan is
|
||||||
load-bearing -- it is what absorbs the inner `NEXT` so the outer one still finds its
|
load-bearing -- it is what absorbs the inner `NEXT` so the outer one still finds its
|
||||||
`FOR`. Releasing it turns that case into "NEXT outside the context of FOR". So the
|
`FOR`. Releasing it turns that case into "NEXT outside the context of FOR". So the
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ so a call with the wrong number is a syntax error rather than a surprise.
|
|||||||
| Function | Args | Form | What it gives |
|
| Function | Args | Form | What it gives |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `ABS` | 1 | `ABS(n)` | The absolute value of an integer or float. |
|
| `ABS` | 1 | `ABS(n)` | The absolute value of an integer or float. |
|
||||||
|
| `ASC` | 1 | `ASC(A$)` | The Unicode code point of a string's first character. |
|
||||||
| `ATN` | 1 | `ATN(n)` | Arctangent, in radians. |
|
| `ATN` | 1 | `ATN(n)` | Arctangent, in radians. |
|
||||||
| `BUMP` | 1 | `BUMP(1)` | Which sprites have collided, as a bitmask. **Reading clears it.** |
|
| `BUMP` | 1 | `BUMP(1)` | Which sprites have collided, as a bitmask. **Reading clears it.** |
|
||||||
| `CHR` | 1 | `CHR(n)` | The character for a Unicode code point, as a string. |
|
| `CHR` | 1 | `CHR(n)` | The character for a Unicode code point, as a string. |
|
||||||
@@ -29,6 +30,7 @@ so a call with the wrong number is a syntax error rather than a surprise.
|
|||||||
| `RGR` | 1 | `RGR(f)` | The `GRAPHIC` mode (0), the drawing surface's width (1) or height (2) in pixels, or a character cell's width (3) or height (4). |
|
| `RGR` | 1 | `RGR(f)` | The `GRAPHIC` mode (0), the drawing surface's width (1) or height (2) in pixels, or a character cell's width (3) or height (4). |
|
||||||
| `RIGHT` | 2 | `RIGHT(A$, n)` | The rightmost `n` characters. Clamped. |
|
| `RIGHT` | 2 | `RIGHT(A$, n)` | The rightmost `n` characters. Clamped. |
|
||||||
| `RMENU` | 2 | `RMENU(n, f)` | A menu's state: field 0 the highlighted entry, field 1 whether it has been confirmed. **Reading field 1 clears it.** |
|
| `RMENU` | 2 | `RMENU(n, f)` | A menu's state: field 0 the highlighted entry, field 1 whether it has been confirmed. **Reading field 1 clears it.** |
|
||||||
|
| `RND` | 1 | `RND(n)` | A random integer from 0 up to but not including `n`. |
|
||||||
| `RWINDOW` | 1 | `RWINDOW(f)` | The current text window's rows (0) or columns (1). Field 2 is a C128 screen mode and is refused. |
|
| `RWINDOW` | 1 | `RWINDOW(f)` | The current text window's rows (0) or columns (1). Field 2 is a C128 screen mode and is refused. |
|
||||||
| `RSPCOLOR` | 1 | `RSPCOLOR(n)` | One of `SPRCOLOR`'s two shared registers, 1 or 2. |
|
| `RSPCOLOR` | 1 | `RSPCOLOR(n)` | One of `SPRCOLOR`'s two shared registers, 1 or 2. |
|
||||||
| `RSPHIT` | 2 | `RSPHIT(n, f)` | One of `SPRHIT`'s settings for sprite `n`, in `SPRHIT`'s own argument order: 0 the kind, 1 to 4 the two corners. |
|
| `RSPHIT` | 2 | `RSPHIT(n, f)` | One of `SPRHIT`'s settings for sprite `n`, in `SPRHIT`'s own argument order: 0 the kind, 1 to 4 the two corners. |
|
||||||
|
|||||||
@@ -743,8 +743,8 @@ way to see that something pushed a scope and never popped it.
|
|||||||
|
|
||||||
```sh norun
|
```sh norun
|
||||||
ctest --test-dir build --output-on-failure -R for_next # one unit test
|
ctest --test-dir build --output-on-failure -R for_next # one unit test
|
||||||
ctest --test-dir build --output-on-failure -R local_ # the language corpus
|
ctest --test-dir build --output-on-failure -R golden_ # the reference corpus
|
||||||
./build/basic tests/language/functions.bas | diff - tests/language/functions.txt
|
./build/basic tests/reference/language/functions.bas | diff - tests/reference/language/functions.txt
|
||||||
./tests/docs_examples.sh --root . --basic ./build/basic \
|
./tests/docs_examples.sh --root . --basic ./build/basic \
|
||||||
--cflags-file build/docs_cflags.txt docs/14-architecture.md
|
--cflags-file build/docs_cflags.txt docs/14-architecture.md
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ nothing would say which type it is.
|
|||||||
```
|
```
|
||||||
|
|
||||||
```output
|
```output
|
||||||
? 10 : PARSE ERROR TYPE POINT: POINT is a reserved word and cannot name a type
|
? 40 : PARSE ERROR TYPE POINT: POINT is a reserved word and cannot name a type
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1005,20 +1005,48 @@ IF NUDGE# = 1 THEN GOSUB UNSTICK
|
|||||||
LABEL UNSTICK
|
LABEL UNSTICK
|
||||||
NUDGE# = 0
|
NUDGE# = 0
|
||||||
STALL# = 0
|
STALL# = 0
|
||||||
RMAX# = 4
|
BVX# = (RND(4) * 3) - 6
|
||||||
GOSUB RANDOM
|
|
||||||
BVX# = (RND# * 3) - 6
|
|
||||||
IF BVX# = 0 THEN BVX# = 3
|
IF BVX# = 0 THEN BVX# = 3
|
||||||
RETURN
|
RETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
### You have to write your own random numbers
|
### Random numbers are built in
|
||||||
|
|
||||||
**There is no `RND` in this dialect**, and no `INT`, `SQR`, `ASC` or `TIMER` either. A
|
There is no `INT`, `SQR` or `TIMER` in this dialect, but
|
||||||
linear congruential generator is nine tokens and does the job. Put the number of possible
|
`RND(n)` returns an integer from zero through `n - 1`. It seeds itself
|
||||||
answers in `RMAX#` and read the result from `RND#`:
|
from the host clock the first time it is called, so a program only needs the bound:
|
||||||
|
|
||||||
```basic
|
```basic
|
||||||
|
I# = 0
|
||||||
|
FOR I# = 1 TO 5
|
||||||
|
PRINT "ROLL " + (RND(6) + 1)
|
||||||
|
NEXT I#
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `RND` for the serve, too, so the ball does not always leave in the same direction:
|
||||||
|
|
||||||
|
```basic norun
|
||||||
|
LABEL SERVE
|
||||||
|
PX# = (SCW# - PW#) / 2
|
||||||
|
HELD# = 1
|
||||||
|
BX# = PX# + ((PW# / 2) - 4)
|
||||||
|
BY# = PY# - 10
|
||||||
|
BVX# = BSPD#
|
||||||
|
IF RND(2) = 0 THEN BVX# = 0 - BSPD#
|
||||||
|
BVY# = 0 - BSPD#
|
||||||
|
PDEC# = 0
|
||||||
|
GOSUB SHOWSPR
|
||||||
|
RETURN
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Historical aside: the LCG this chapter used to teach</summary>
|
||||||
|
|
||||||
|
Before `RND` existed, this nine-token linear congruential generator was copied into
|
||||||
|
every program. It remains a useful from-scratch PRNG example:
|
||||||
|
|
||||||
|
```basic norun
|
||||||
SEED# = 12345
|
SEED# = 12345
|
||||||
RMAX# = 6
|
RMAX# = 6
|
||||||
RND# = 0
|
RND# = 0
|
||||||
@@ -1035,43 +1063,11 @@ RND# = MOD((SEED# / 65536), RMAX#)
|
|||||||
RETURN
|
RETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
```output
|
The multiplication stays inside a 64-bit integer for any seed below 2147483648. The
|
||||||
ROLL 1
|
answer is taken from the middle bits because the low bits of a power-of-two modulus
|
||||||
ROLL 5
|
barely change from one call to the next. This used to be required; it is now built in.
|
||||||
ROLL 2
|
|
||||||
ROLL 1
|
|
||||||
ROLL 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The multiplication stays inside a 64-bit integer for any seed below 2147483648, which is
|
</details>
|
||||||
why the modulus is that number. The answer is taken from the middle bits — `SEED# / 65536`
|
|
||||||
— because the low bits of a power-of-two modulus barely change from one call to the next.
|
|
||||||
Integer division truncating for free is the `INT` you do not have.
|
|
||||||
|
|
||||||
Seed it from the clock at startup. `TI#` is the host's uptime in sixtieths of a second,
|
|
||||||
which is different every time the game is run:
|
|
||||||
|
|
||||||
```basic norun
|
|
||||||
SEED# = TI#
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `RANDOM` for the serve, too, so the ball does not always leave in the same direction:
|
|
||||||
|
|
||||||
```basic norun
|
|
||||||
LABEL SERVE
|
|
||||||
PX# = (SCW# - PW#) / 2
|
|
||||||
HELD# = 1
|
|
||||||
BX# = PX# + ((PW# / 2) - 4)
|
|
||||||
BY# = PY# - 10
|
|
||||||
RMAX# = 2
|
|
||||||
GOSUB RANDOM
|
|
||||||
BVX# = BSPD#
|
|
||||||
IF RND# = 0 THEN BVX# = 0 - BSPD#
|
|
||||||
BVY# = 0 - BSPD#
|
|
||||||
PDEC# = 0
|
|
||||||
GOSUB SHOWSPR
|
|
||||||
RETURN
|
|
||||||
```
|
|
||||||
|
|
||||||
`HELD#` is the flag Step 6's loop tests: while it is 1 the ball sits on the paddle, and
|
`HELD#` is the flag Step 6's loop tests: while it is 1 the ball sits on the paddle, and
|
||||||
`HOLDBAL` keeps it there:
|
`HOLDBAL` keeps it there:
|
||||||
@@ -1422,9 +1418,7 @@ PX# = PX# + D#
|
|||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
LABEL DEMOAIM
|
LABEL DEMOAIM
|
||||||
RMAX# = 81
|
DOFF# = RND(81) - 40
|
||||||
GOSUB RANDOM
|
|
||||||
DOFF# = RND# - 40
|
|
||||||
RETURN
|
RETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1501,7 +1495,7 @@ This is the shape of the whole file:
|
|||||||
LABEL SETUP the geometry from Step 2
|
LABEL SETUP the geometry from Step 2
|
||||||
the declaration block from Step 3
|
the declaration block from Step 3
|
||||||
the brick faces from Step 5
|
the brick faces from Step 5
|
||||||
SEED# = TI#
|
RND(n) seeds itself from the host clock
|
||||||
the ceiling from Step 9
|
the ceiling from Step 9
|
||||||
GOSUB MKSPR Step 4
|
GOSUB MKSPR Step 4
|
||||||
GOSUB SNDPROBE Step 14
|
GOSUB SNDPROBE Step 14
|
||||||
@@ -1576,10 +1570,7 @@ BB# = 0
|
|||||||
RX# = 0
|
RX# = 0
|
||||||
N# = 0
|
N# = 0
|
||||||
MROW# = 0
|
MROW# = 0
|
||||||
RMAX# = 2
|
|
||||||
RND# = 0
|
|
||||||
SND# = 0
|
SND# = 0
|
||||||
SEED# = 0
|
|
||||||
P$ = ""
|
P$ = ""
|
||||||
H$ = ""
|
H$ = ""
|
||||||
S$ = ""
|
S$ = ""
|
||||||
|
|||||||
@@ -248,6 +248,11 @@ typedef struct akbasic_Runtime
|
|||||||
*/
|
*/
|
||||||
int64_t timems;
|
int64_t timems;
|
||||||
|
|
||||||
|
/* RND's lazy seed state. The flag distinguishes an unseeded run from a
|
||||||
|
* legitimate LCG state of zero. */
|
||||||
|
int64_t rndseed;
|
||||||
|
bool rndseeded;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set by a branch that has decided the remaining statements on its line
|
* Set by a branch that has decided the remaining statements on its line
|
||||||
* belong to the arm it did not take, and cleared at the top of every line.
|
* belong to the arm it did not take, and cleared at the top of every line.
|
||||||
|
|||||||
@@ -282,8 +282,9 @@ def copy_tree(src, dst):
|
|||||||
# and a coverage tree drags along every .gcno/.gcda as well.
|
# and a coverage tree drags along every .gcno/.gcda as well.
|
||||||
#
|
#
|
||||||
# deps/ is copied wholesale and has to be: the build pulls libakerror and
|
# deps/ is copied wholesale and has to be: the build pulls libakerror and
|
||||||
# libakstdlib in with add_subdirectory. The language corpus is the other
|
# libakstdlib in with add_subdirectory. The golden corpus used to be the
|
||||||
# reason it comes along with the rest of the tree.
|
# other reason -- it was driven in place out of deps/basicinterpret -- and
|
||||||
|
# now lives in tests/reference/, which comes along with the rest of the tree.
|
||||||
# Only vendored Windows DLLs are dead weight, hence "*.dll".
|
# Only vendored Windows DLLs are dead weight, hence "*.dll".
|
||||||
#
|
#
|
||||||
# **PNGs are ours or theirs, and the difference matters.** This used to drop
|
# **PNGs are ours or theirs, and the difference matters.** This used to drop
|
||||||
|
|||||||
@@ -160,21 +160,14 @@ akerr_ErrorContext *akbasic_data_scan(akbasic_Runtime *obj)
|
|||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
int64_t i = 0;
|
int64_t i = 0;
|
||||||
int64_t entry = 0;
|
|
||||||
|
|
||||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in data_scan");
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in data_scan");
|
||||||
FAIL_ZERO_RETURN(errctx, (obj->environment != NULL), AKERR_NULLPOINTER,
|
|
||||||
"Runtime has no environment; call akbasic_runtime_init() first");
|
|
||||||
entry = obj->environment->lineno;
|
|
||||||
PASS(errctx, akbasic_data_state_init(&obj->data_state));
|
PASS(errctx, akbasic_data_state_init(&obj->data_state));
|
||||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||||
if ( obj->source[i].code[0] != '\0' ) {
|
if ( obj->source[i].code[0] != '\0' ) {
|
||||||
/* Keep BASIC's error prefix on the source line being prescanned. */
|
|
||||||
obj->environment->lineno = i;
|
|
||||||
PASS(errctx, scan_line(&obj->data_state, obj->source[i].code, i));
|
PASS(errctx, scan_line(&obj->data_state, obj->source[i].code, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj->environment->lineno = entry;
|
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -893,7 +893,7 @@ akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTL
|
|||||||
*
|
*
|
||||||
* **Only a block skip**, which is what the `BEND` test is for. A
|
* **Only a block skip**, which is what the `BEND` test is for. A
|
||||||
* zero-iteration `FOR` skips its body the same way, and there the
|
* zero-iteration `FOR` skips its body the same way, and there the
|
||||||
* orphan is load-bearing: `tests/language/.../nestedforloopwaiting
|
* orphan is load-bearing: `tests/reference/.../nestedforloopwaiting
|
||||||
* forcommand.bas` nests a loop inside one that runs zero times, and
|
* forcommand.bas` nests a loop inside one that runs zero times, and
|
||||||
* the inner scope is what absorbs the inner `NEXT` so the outer `NEXT`
|
* the inner scope is what absorbs the inner `NEXT` so the outer `NEXT`
|
||||||
* still finds its `FOR`. Popping it there turns that case into "NEXT
|
* still finds its `FOR`. Popping it there turns that case into "NEXT
|
||||||
@@ -1262,7 +1262,7 @@ akerr_ErrorContext *akbasic_runtime_process_line_runstream(akbasic_Runtime *obj)
|
|||||||
* This mode used to file it like any other, under the cursor -- which for a
|
* This mode used to file it like any other, under the cursor -- which for a
|
||||||
* blank line is the number of the line *before* it. A file ending in a blank
|
* blank line is the number of the line *before* it. A file ending in a blank
|
||||||
* line therefore had its last line erased before it ever ran, silently. The
|
* line therefore had its last line erased before it ever ran, silently. The
|
||||||
* reference did the same, and `tests/language/arithmetic/integer.bas`
|
* reference did the same, and `tests/reference/language/arithmetic/integer.bas`
|
||||||
* has an expectation with three values for four PRINT statements to prove it.
|
* has an expectation with three values for four PRINT statements to prove it.
|
||||||
*/
|
*/
|
||||||
if ( buffer[0] == '\0' ) {
|
if ( buffer[0] == '\0' ) {
|
||||||
@@ -1576,22 +1576,17 @@ akerr_ErrorContext *akbasic_runtime_scan_labels(akbasic_Runtime *obj)
|
|||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
akbasic_Environment *root = NULL;
|
akbasic_Environment *root = NULL;
|
||||||
int64_t i = 0;
|
int64_t i = 0;
|
||||||
int64_t entry = 0;
|
|
||||||
|
|
||||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in scan_labels");
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in scan_labels");
|
||||||
FAIL_ZERO_RETURN(errctx, (obj->environment != NULL), AKERR_NULLPOINTER,
|
FAIL_ZERO_RETURN(errctx, (obj->environment != NULL), AKERR_NULLPOINTER,
|
||||||
"Runtime has no environment; call akbasic_runtime_init() first");
|
"Runtime has no environment; call akbasic_runtime_init() first");
|
||||||
for ( root = obj->environment; root->parent != NULL; root = root->parent ) {
|
for ( root = obj->environment; root->parent != NULL; root = root->parent ) {
|
||||||
}
|
}
|
||||||
entry = obj->environment->lineno;
|
|
||||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||||
if ( obj->source[i].code[0] != '\0' ) {
|
if ( obj->source[i].code[0] != '\0' ) {
|
||||||
/* Keep BASIC's error prefix on the source line being prescanned. */
|
|
||||||
obj->environment->lineno = i;
|
|
||||||
PASS(errctx, scan_line_labels(root, obj->source[i].code, i));
|
PASS(errctx, scan_line_labels(root, obj->source[i].code, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj->environment->lineno = entry;
|
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,69 @@ akerr_ErrorContext *akbasic_fn_chr(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
akerr_ErrorContext *akbasic_fn_asc(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
akbasic_Value *arg = NULL;
|
||||||
|
akbasic_Value *out = NULL;
|
||||||
|
const unsigned char *text = NULL;
|
||||||
|
int64_t codepoint = 0;
|
||||||
|
|
||||||
|
(void)lval; (void)rval;
|
||||||
|
PASS(errctx, first_arg(obj, expr, "ASC", NULL, &arg, &out));
|
||||||
|
FAIL_NONZERO_RETURN(errctx, (arg->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||||||
|
"ASC expected a string");
|
||||||
|
FAIL_ZERO_RETURN(errctx, (arg->stringval[0] != '\0'), AKBASIC_ERR_BOUNDS,
|
||||||
|
"ASC expected a non-empty string");
|
||||||
|
|
||||||
|
/* Decode the first UTF-8 code point, the inverse of CHR's encoder. */
|
||||||
|
text = (const unsigned char *)arg->stringval;
|
||||||
|
if ( text[0] < 0x80 ) {
|
||||||
|
codepoint = text[0];
|
||||||
|
} else if ( (text[0] & 0xE0) == 0xC0 ) {
|
||||||
|
codepoint = ((int64_t)(text[0] & 0x1F) << 6) |
|
||||||
|
(text[1] & 0x3F);
|
||||||
|
} else if ( (text[0] & 0xF0) == 0xE0 ) {
|
||||||
|
codepoint = ((int64_t)(text[0] & 0x0F) << 12) |
|
||||||
|
((int64_t)(text[1] & 0x3F) << 6) |
|
||||||
|
(text[2] & 0x3F);
|
||||||
|
} else {
|
||||||
|
codepoint = ((int64_t)(text[0] & 0x07) << 18) |
|
||||||
|
((int64_t)(text[1] & 0x3F) << 12) |
|
||||||
|
((int64_t)(text[2] & 0x3F) << 6) |
|
||||||
|
(text[3] & 0x3F);
|
||||||
|
}
|
||||||
|
out->valuetype = AKBASIC_TYPE_INTEGER;
|
||||||
|
out->intval = codepoint;
|
||||||
|
*dest = out;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
akerr_ErrorContext *akbasic_fn_rnd(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
akbasic_Value *arg = NULL;
|
||||||
|
akbasic_Value *out = NULL;
|
||||||
|
const int64_t modulus = 2147483648;
|
||||||
|
|
||||||
|
(void)lval; (void)rval;
|
||||||
|
PASS(errctx, first_arg(obj, expr, "RND", NULL, &arg, &out));
|
||||||
|
FAIL_NONZERO_RETURN(errctx, (arg->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
||||||
|
"RND expected an integer");
|
||||||
|
FAIL_ZERO_RETURN(errctx, (arg->intval > 0), AKBASIC_ERR_VALUE,
|
||||||
|
"RND count %" PRId64 " must be positive", arg->intval);
|
||||||
|
|
||||||
|
if ( !obj->rndseeded ) {
|
||||||
|
obj->rndseed = obj->timems % modulus;
|
||||||
|
obj->rndseeded = true;
|
||||||
|
}
|
||||||
|
obj->rndseed = (obj->rndseed * 1103515245 + 12345) % modulus;
|
||||||
|
out->valuetype = AKBASIC_TYPE_INTEGER;
|
||||||
|
out->intval = (obj->rndseed / 65536) % arg->intval;
|
||||||
|
*dest = out;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akbasic_fn_hex(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
akerr_ErrorContext *akbasic_fn_hex(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
|
|||||||
@@ -260,13 +260,11 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
|||||||
size_t namelen = 0;
|
size_t namelen = 0;
|
||||||
bool matched = false;
|
bool matched = false;
|
||||||
const akbasic_Verb *verb = NULL;
|
const akbasic_Verb *verb = NULL;
|
||||||
int64_t entry = obj->environment->lineno;
|
|
||||||
|
|
||||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||||
if ( obj->source[i].code[0] == '\0' ) {
|
if ( obj->source[i].code[0] == '\0' ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
obj->environment->lineno = i;
|
|
||||||
cursor = next_word(skip_lineno(obj->source[i].code), word, sizeof(word));
|
cursor = next_word(skip_lineno(obj->source[i].code), word, sizeof(word));
|
||||||
|
|
||||||
PASS(errctx, word_is(word, "END", &matched));
|
PASS(errctx, word_is(word, "END", &matched));
|
||||||
@@ -329,7 +327,6 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
|||||||
|
|
||||||
FAIL_NONZERO_RETURN(errctx, (open >= 0), AKBASIC_ERR_SYNTAX,
|
FAIL_NONZERO_RETURN(errctx, (open >= 0), AKBASIC_ERR_SYNTAX,
|
||||||
"TYPE %s is never closed with END TYPE", table->types[open >= 0 ? open : 0].name);
|
"TYPE %s is never closed with END TYPE", table->types[open >= 0 ? open : 0].name);
|
||||||
obj->environment->lineno = entry;
|
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,7 +440,7 @@ static akerr_ErrorContext *parse_field(akbasic_StructTypeTable *table, akbasic_S
|
|||||||
* each other by value, which has no finite size. That is the diagnosis rather
|
* each other by value, which has no finite size. That is the diagnosis rather
|
||||||
* than a stack overflow later.
|
* than a stack overflow later.
|
||||||
*/
|
*/
|
||||||
static akerr_ErrorContext *resolve_sizes(akbasic_Runtime *runtime, akbasic_StructTypeTable *table)
|
static akerr_ErrorContext *resolve_sizes(akbasic_StructTypeTable *table)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
bool progress = true;
|
bool progress = true;
|
||||||
@@ -480,7 +477,6 @@ static akerr_ErrorContext *resolve_sizes(akbasic_Runtime *runtime, akbasic_Struc
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ( i = 0; i < table->count; i++ ) {
|
for ( i = 0; i < table->count; i++ ) {
|
||||||
runtime->environment->lineno = table->types[i].firstline;
|
|
||||||
FAIL_NONZERO_RETURN(errctx, (table->types[i].slotcount < 0), AKBASIC_ERR_VALUE,
|
FAIL_NONZERO_RETURN(errctx, (table->types[i].slotcount < 0), AKBASIC_ERR_VALUE,
|
||||||
"TYPE %s contains itself by value, so it has no size. "
|
"TYPE %s contains itself by value, so it has no size. "
|
||||||
"A type may only refer to itself through PTR TO",
|
"A type may only refer to itself through PTR TO",
|
||||||
@@ -497,13 +493,9 @@ akerr_ErrorContext *akbasic_structtype_scan(akbasic_Runtime *obj)
|
|||||||
akbasic_StructTypeTable *table = NULL;
|
akbasic_StructTypeTable *table = NULL;
|
||||||
int64_t i = 0;
|
int64_t i = 0;
|
||||||
int t = 0;
|
int t = 0;
|
||||||
int64_t entry = 0;
|
|
||||||
|
|
||||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in structtype scan");
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in structtype scan");
|
||||||
FAIL_ZERO_RETURN(errctx, (obj->environment != NULL), AKERR_NULLPOINTER,
|
|
||||||
"Runtime has no environment; call akbasic_runtime_init() first");
|
|
||||||
table = &obj->structtypes;
|
table = &obj->structtypes;
|
||||||
entry = obj->environment->lineno;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Drop what the *script* declared and keep what the *host* registered.
|
* Drop what the *script* declared and keep what the *host* registered.
|
||||||
@@ -533,12 +525,10 @@ akerr_ErrorContext *akbasic_structtype_scan(akbasic_Runtime *obj)
|
|||||||
if ( obj->source[i].code[0] == '\0' ) {
|
if ( obj->source[i].code[0] == '\0' ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
obj->environment->lineno = i;
|
|
||||||
PASS(errctx, parse_field(table, type, obj->source[i].code, i));
|
PASS(errctx, parse_field(table, type, obj->source[i].code, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PASS(errctx, resolve_sizes(obj, table));
|
PASS(errctx, resolve_sizes(table));
|
||||||
obj->environment->lineno = entry;
|
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ static const akbasic_Verb VERBS[] = {
|
|||||||
{ "ABS", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_abs },
|
{ "ABS", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_abs },
|
||||||
{ "AND", AKBASIC_TOK_AND, -1, NULL, NULL },
|
{ "AND", AKBASIC_TOK_AND, -1, NULL, NULL },
|
||||||
{ "APPEND", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_append },
|
{ "APPEND", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_append },
|
||||||
|
{ "ASC", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_asc },
|
||||||
{ "ATN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_atn },
|
{ "ATN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_atn },
|
||||||
{ "AUTO", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_auto },
|
{ "AUTO", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_auto },
|
||||||
{ "BACKUP", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_backup },
|
{ "BACKUP", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_backup },
|
||||||
@@ -146,6 +147,7 @@ static const akbasic_Verb VERBS[] = {
|
|||||||
{ "RGR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rgr },
|
{ "RGR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rgr },
|
||||||
{ "RIGHT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_right },
|
{ "RIGHT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_right },
|
||||||
{ "RMENU", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rmenu },
|
{ "RMENU", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rmenu },
|
||||||
|
{ "RND", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rnd },
|
||||||
{ "RSPCOLOR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rspcolor },
|
{ "RSPCOLOR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rspcolor },
|
||||||
{ "RSPHIT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rsphit },
|
{ "RSPHIT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rsphit },
|
||||||
{ "RSPPOS", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rsppos },
|
{ "RSPPOS", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rsppos },
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_cmd_stop(struct akbasic_Runtime *obj,
|
|||||||
|
|
||||||
/* Function handlers -- src/runtime_functions.c */
|
/* Function handlers -- src/runtime_functions.c */
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_abs(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_abs(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_asc(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_atn(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_atn(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_chr(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_chr(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_cos(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_cos(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
@@ -154,6 +155,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_peek(struct akbasic_Runtime *obj,
|
|||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_pointer(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_pointer(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_pointervar(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_pointervar(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_rad(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_rad(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_rnd(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_right(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_right(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_sgn(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_sgn(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_shl(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_fn_shl(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ static void test_counter_survives_the_loop(void)
|
|||||||
* `FOR I = 1 TO 1` executes the body one time on every BASIC there has ever
|
* `FOR I = 1 TO 1` executes the body one time on every BASIC there has ever
|
||||||
* been. Here the entry test treats "the counter has reached the limit" as
|
* been. Here the entry test treats "the counter has reached the limit" as
|
||||||
* "do not enter", so the body is skipped entirely -- and
|
* "do not enter", so the body is skipped entirely -- and
|
||||||
* tests/language/flowcontrol/forloopwaitingforcommand.bas pins that,
|
* tests/reference/language/flowcontrol/forloopwaitingforcommand.bas pins that,
|
||||||
* which is why this cannot simply be corrected.
|
* which is why this cannot simply be corrected.
|
||||||
*/
|
*/
|
||||||
static void test_single_iteration_loop(void)
|
static void test_single_iteration_loop(void)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
* below.
|
* below.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "harness.h"
|
#include "harness.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -343,28 +341,6 @@ static akerr_ErrorContext AKERR_NOIGNORE *test_prescan_boundaries(void)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief A full label table reports the line whose label could not be filed. */
|
|
||||||
static void test_label_prescan_error_line(void)
|
|
||||||
{
|
|
||||||
char source[4096] = "";
|
|
||||||
size_t used = 0;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for ( i = 1; i <= AKBASIC_MAX_LABELS + 1; i++ ) {
|
|
||||||
used += (size_t)snprintf(source + used, sizeof(source) - used,
|
|
||||||
"%d LABEL L%d\n", i, i);
|
|
||||||
}
|
|
||||||
(void)snprintf(source + used, sizeof(source) - used, "100 PRINT 1\n");
|
|
||||||
|
|
||||||
TEST_REQUIRE_OK(harness_start(NULL));
|
|
||||||
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, source));
|
|
||||||
TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
|
||||||
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "? 65 : PARSE ERROR") != NULL,
|
|
||||||
"a full label table should report its source line, got \"%s\"",
|
|
||||||
HARNESS_OUTPUT);
|
|
||||||
harness_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -379,7 +355,6 @@ int main(void)
|
|||||||
CATCH(errctx, test_undefined_label_is_reported());
|
CATCH(errctx, test_undefined_label_is_reported());
|
||||||
CATCH(errctx, test_arm_refusals());
|
CATCH(errctx, test_arm_refusals());
|
||||||
CATCH(errctx, test_prescan_boundaries());
|
CATCH(errctx, test_prescan_boundaries());
|
||||||
test_label_prescan_error_line();
|
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
} PROCESS(errctx) {
|
} PROCESS(errctx) {
|
||||||
} HANDLE_DEFAULT(errctx) {
|
} HANDLE_DEFAULT(errctx) {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# The language test corpus
|
|
||||||
|
|
||||||
The `.bas` files in this directory and their sibling `.txt` files are the editable language
|
|
||||||
corpus. CMake registers every program as an individual `local_*` CTest case and compares its
|
|
||||||
output with the sibling expectation.
|
|
||||||
|
|
||||||
The corpus includes cases carried over from the deprecated Go implementation as well as cases
|
|
||||||
written for this interpreter. Their provenance is useful when investigating a regression, but it
|
|
||||||
does not make any case immutable or turn the old implementation into a specification.
|
|
||||||
|
|
||||||
**Change a program and its expectation deliberately and in the same commit.** If the changed
|
|
||||||
output represents an intentional language decision, record the reason in `TODO.md` or the
|
|
||||||
relevant documentation. If it is an accidental change, fix the interpreter instead.
|
|
||||||
|
|
||||||
The `examples/` subdirectory contains complete BASIC programs used as worked examples. It is
|
|
||||||
part of the same corpus and follows the same `.bas`/`.txt` pairing rule.
|
|
||||||
3
tests/language/functions/asc.bas
Normal file
3
tests/language/functions/asc.bas
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
10 PRINT "97 : " + ASC("a")
|
||||||
|
20 PRINT "65 : " + ASC("A")
|
||||||
|
30 PRINT "64 : " + ASC("@")
|
||||||
3
tests/language/functions/asc.txt
Normal file
3
tests/language/functions/asc.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
97 : 97
|
||||||
|
65 : 65
|
||||||
|
64 : 64
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
? 60 : PARSE ERROR TYPE POINT: POINT is a reserved word and cannot name a type
|
? 90 : PARSE ERROR TYPE POINT: POINT is a reserved word and cannot name a type
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
* TODO.md section 6.
|
* TODO.md section 6.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <akbasic/error.h>
|
#include <akbasic/error.h>
|
||||||
@@ -175,34 +174,6 @@ static void test_colon_ends_data(void)
|
|||||||
harness_stop();
|
harness_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief DATA overflow reports the line where the item limit was crossed. */
|
|
||||||
static void test_data_prescan_error_line(void)
|
|
||||||
{
|
|
||||||
char source[4096] = "";
|
|
||||||
size_t used = 0;
|
|
||||||
int line = 0;
|
|
||||||
int item = 0;
|
|
||||||
|
|
||||||
for ( line = 1; line <= 34; line++ ) {
|
|
||||||
used += (size_t)snprintf(source + used, sizeof(source) - used, "%d DATA ", line);
|
|
||||||
for ( item = 0; item < 15; item++ ) {
|
|
||||||
used += (size_t)snprintf(source + used, sizeof(source) - used,
|
|
||||||
"%s1", (item == 0 ? "" : ","));
|
|
||||||
}
|
|
||||||
used += (size_t)snprintf(source + used, sizeof(source) - used, "\n");
|
|
||||||
}
|
|
||||||
used += (size_t)snprintf(source + used, sizeof(source) - used, "100 DATA 1,1\n");
|
|
||||||
(void)snprintf(source + used, sizeof(source) - used, "101 DATA 1\n200 PRINT 1\n");
|
|
||||||
|
|
||||||
TEST_REQUIRE_OK(harness_start(NULL));
|
|
||||||
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, source));
|
|
||||||
TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
|
||||||
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "? 101 : PARSE ERROR") != NULL,
|
|
||||||
"DATA overflow should report its source line, got \"%s\"",
|
|
||||||
HARNESS_OUTPUT);
|
|
||||||
harness_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @brief A float item fills a float variable with its fractional part intact. */
|
/** @brief A float item fills a float variable with its fractional part intact. */
|
||||||
static void test_float_items(void)
|
static void test_float_items(void)
|
||||||
{
|
{
|
||||||
@@ -256,7 +227,6 @@ int main(void)
|
|||||||
test_type_mismatch();
|
test_type_mismatch();
|
||||||
test_quoted_items();
|
test_quoted_items();
|
||||||
test_colon_ends_data();
|
test_colon_ends_data();
|
||||||
test_data_prescan_error_line();
|
|
||||||
test_float_items();
|
test_float_items();
|
||||||
test_negative_items();
|
test_negative_items();
|
||||||
return akbasic_test_failures;
|
return akbasic_test_failures;
|
||||||
|
|||||||
63
tests/reference/README.md
Normal file
63
tests/reference/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# The reference interpreter's own test corpus
|
||||||
|
|
||||||
|
These 41 `.bas` files and their 41 `.txt` expectations are **not this project's tests.** They
|
||||||
|
are a byte-for-byte copy of the acceptance corpus belonging to the Go implementation this
|
||||||
|
interpreter was ported from:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Source | `https://source.starfort.tech/andrew/basicinterpreter`, `tests/` |
|
||||||
|
| Copied at | commit `d76162cb37eeaccddae2cf6cab7af7bc8175d6d2` ("Update README.md") |
|
||||||
|
| Copied on | 2026-07-31 |
|
||||||
|
| Verified | every file `cmp`-identical to the submodule at the time of the copy |
|
||||||
|
|
||||||
|
They are registered as the `golden_*` CTest cases and run against `basic` on every build, in
|
||||||
|
both the default and the `-DAKBASIC_WITH_AKGL=ON` configurations.
|
||||||
|
|
||||||
|
## Why they are a copy, when they deliberately were not
|
||||||
|
|
||||||
|
Until 2026-07-31 these were driven *in place* out of `deps/basicinterpret`, and the reasoning
|
||||||
|
was written down in `CMakeLists.txt`: the corpus is a submodule, and copying a submodule's
|
||||||
|
corpus guarantees drift.
|
||||||
|
|
||||||
|
That reasoning was sound and it stopped applying. The Go project is deprecated and will not be
|
||||||
|
updated, so there is nothing left to drift *from* — and a build that cannot run its own
|
||||||
|
acceptance suite without cloning the implementation it replaced is not finished.
|
||||||
|
|
||||||
|
## What these are for now
|
||||||
|
|
||||||
|
**A regression suite, not a specification.** That distinction is the whole of how to treat
|
||||||
|
them, and it changed on 2026-07-31 (`TODO.md` §0.1): matching the Go implementation is no
|
||||||
|
longer a goal, so a difference between it and this interpreter is not automatically a bug in
|
||||||
|
this one.
|
||||||
|
|
||||||
|
What they are still good for is unchanged and is worth a lot: forty-one real BASIC programs
|
||||||
|
with known-good output, covering arithmetic, arrays, flow control, every builtin function,
|
||||||
|
`READ`/`DATA`, labels and three worked examples. That catches regressions whatever its
|
||||||
|
provenance.
|
||||||
|
|
||||||
|
## The rule for editing these
|
||||||
|
|
||||||
|
**Diverge deliberately, never by accident.**
|
||||||
|
|
||||||
|
A failure here means this interpreter's behaviour changed. Nine times in ten that is a bug you
|
||||||
|
just introduced and the answer is to fix the code. The tenth time it is an improvement on the
|
||||||
|
reference, and then the answer is:
|
||||||
|
|
||||||
|
1. Change the expectation in the same commit as the code.
|
||||||
|
2. Say why in the commit message.
|
||||||
|
3. Add a line to `TODO.md` §5, which is the list of deliberate deviations.
|
||||||
|
|
||||||
|
What is not acceptable is editing a `.txt` to make a red suite green without deciding which of
|
||||||
|
the two you are doing.
|
||||||
|
|
||||||
|
New cases for this project's own behaviour go in `tests/language/`, registered under `local_*`.
|
||||||
|
The prefix says which corpus a failure came from, and a diff touching `tests/reference/` stands
|
||||||
|
out as the thing it is.
|
||||||
|
|
||||||
|
## Divergences so far
|
||||||
|
|
||||||
|
| Case | Change | Why |
|
||||||
|
|---|---|---|
|
||||||
|
| `language/arithmetic/integer.txt` | Expectation grew from `4 4 2` to `4 4 2 2` | The program has **four** `PRINT` statements and the expectation had three values. The file ends in a blank line, and both interpreters filed a blank line under the loader's cursor — which for a line with no number of its own is the number of the line before it. So `40 PRINT 4 - 2` was erased before the program ran. The reference lost it the same way, which is why the expectation was written short. Fixed by skipping blank lines in RUNSTREAM, as `akbasic_runtime_load()` and `DLOAD` already did. `TODO.md` §5. |
|
||||||
|
| `examples/strreverse.bas` | Variable `INPUT$` renamed to `SOURCE$` | `INPUT$` is a reserved word with a type suffix, which a real C128 refuses and which this interpreter now refuses too (`TODO.md` §6 item 16). The reference accepted it only because its own reserved-word check never fired. The expectation is byte-for-byte unchanged — the program still prints `REVERSED: OLLEH` — so the case keeps all of its value. |
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user