Compare commits
4 Commits
36
...
deps-updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
13f1df03ef
|
|||
|
a15e172c2a
|
|||
|
ae2c702c2b
|
|||
|
5c58f4c16d
|
@@ -51,12 +51,14 @@ option(AKBASIC_SANITIZE "Build with ASan + UBSan" OFF
|
|||||||
# through: the dependencies set target and directory properties that their own
|
# through: the dependencies set target and directory properties that their own
|
||||||
# builds depend on.
|
# builds depend on.
|
||||||
#
|
#
|
||||||
# libakerror additionally namespaces its `mutation` target when embedded but not
|
# All three dependencies now namespace both their `coverage` and their
|
||||||
# its `coverage` target (deps/libakerror/CMakeLists.txt:194 vs :172), so a
|
# `mutation` targets when embedded, so there is no custom-target collision left
|
||||||
# coverage build collides on the `coverage` target and fails to configure at all.
|
# to work around. libakerror was the last holdout -- it namespaced `mutation`
|
||||||
# Rename the dependency's on the way past. Remove this once libakerror applies
|
# but not `coverage`, and a coverage build collided on the bare name and failed
|
||||||
# the same CMAKE_SOURCE_DIR test to `coverage` that it already applies to
|
# to configure at all. 2.0.2 applies the same CMAKE_SOURCE_DIR test to both
|
||||||
# `mutation` -- filed as libakerror issue #15.
|
# (deps/libakerror/CMakeLists.txt:429-434), closing libakerror issue #15, and
|
||||||
|
# the add_custom_target() shadow that renamed it on the way past is gone with
|
||||||
|
# this comment.
|
||||||
#
|
#
|
||||||
# **Only one project in a tree may shadow add_test(), and this is that project.**
|
# **Only one project in a tree may shadow add_test(), and this is that project.**
|
||||||
# CMake exposes an overridden command as `_name` and chains exactly one level: a
|
# CMake exposes an overridden command as `_name` and chains exactly one level: a
|
||||||
@@ -86,14 +88,6 @@ function(set_property _scope)
|
|||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(add_custom_target _name)
|
|
||||||
if(AKBASIC_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage")
|
|
||||||
_add_custom_target(akerror_coverage ${ARGN})
|
|
||||||
else()
|
|
||||||
_add_custom_target(${ARGV})
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL)
|
add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL)
|
||||||
add_subdirectory(deps/libakstdlib EXCLUDE_FROM_ALL)
|
add_subdirectory(deps/libakstdlib EXCLUDE_FROM_ALL)
|
||||||
if(AKBASIC_WITH_AKGL)
|
if(AKBASIC_WITH_AKGL)
|
||||||
|
|||||||
117
MAINTENANCE.md
117
MAINTENANCE.md
@@ -68,12 +68,19 @@ the `akgl_*` or `aksl_*` entry point should look like, and what tests would cove
|
|||||||
decision, which is true of *changing* it and not of *reporting* it. Follow the prose-paragraph style of the entries already
|
decision, which is true of *changing* it and not of *reporting* it. Follow the prose-paragraph style of the entries already
|
||||||
there. Growing the dependency to serve the interpreter is a wanted outcome, not a detour.
|
there. Growing the dependency to serve the interpreter is a wanted outcome, not a detour.
|
||||||
|
|
||||||
It works. Four gaps were filed this way — text measurement, immediate-mode drawing, audio,
|
It works. **Five** gaps were filed this way and all five landed upstream: text measurement,
|
||||||
and a non-blocking keystroke read — and all four landed upstream as `akgl_text_measure`, the
|
immediate-mode drawing, audio and a non-blocking keystroke read became `akgl_text_measure`,
|
||||||
`akgl_draw_*` family, `akgl_audio_*` and `akgl_controller_poll_key`. `FILTER` is the one verb
|
the `akgl_draw_*` family, `akgl_audio_*` and `akgl_controller_poll_key`; and the
|
||||||
still blocked on a gap, and `DIRECTORY` is refused pending an `opendir`/`readdir` wrapper in
|
directory-reading wrapper `DIRECTORY` was waiting on became `aksl_opendir`, `aksl_readdir`,
|
||||||
`libakstdlib`. Both refuse at execution and say so, rather than being silently ignored: a
|
`aksl_closedir` and `aksl_rewinddir` — `libakstdlib` issue #10, in the revision this tree
|
||||||
program that asks for a low-pass filter and gets an unfiltered square wave has been lied to.
|
pins.
|
||||||
|
|
||||||
|
That leaves the two refusals in different positions, and the difference is worth keeping
|
||||||
|
straight. **`FILTER` is the one verb still blocked on a gap** — there is no filter stage in
|
||||||
|
`akgl_audio_*` to configure. **`DIRECTORY` is no longer blocked on anything**; it is simply
|
||||||
|
unwritten, and its refusal says so rather than naming a wrapper that now exists. Both refuse
|
||||||
|
at execution and say so, rather than being silently ignored: a program that asks for a
|
||||||
|
low-pass filter and gets an unfiltered square wave has been lied to.
|
||||||
|
|
||||||
### The Go reference
|
### The Go reference
|
||||||
|
|
||||||
@@ -156,10 +163,10 @@ guards every dependency with `if(NOT TARGET ...)`, so a top-level build must def
|
|||||||
`akerror::akerror` and `akstdlib::akstdlib` from `deps/libakerror` and `deps/libakstdlib`
|
`akerror::akerror` and `akstdlib::akstdlib` from `deps/libakerror` and `deps/libakstdlib`
|
||||||
**before** `add_subdirectory(deps/libakgl)`, or the targets are declared twice.
|
**before** `add_subdirectory(deps/libakgl)`, or the targets are declared twice.
|
||||||
|
|
||||||
That order is load-bearing for a second reason: `deps/libakerror` is at **2.0.1**, whose 2.0.0
|
That order is load-bearing for a second reason: `deps/libakerror` is at **2.0.2**, whose 2.0.0
|
||||||
was a source and ABI break carrying an soname (`libakerror.so.2`). `libakstdlib` and `libakgl`
|
was a source and ABI break carrying an soname (`libakerror.so.2`). `libakstdlib` and `libakgl`
|
||||||
must be compiled against that header, not a 1.x one, and an installed `libakerror.so.1` must
|
must be compiled against that header, not a 1.x one, and an installed `libakerror.so.1` must
|
||||||
not be picked up. The break is quiet if you get it wrong: `__akerr_last_ignored` became
|
not be picked up. The break is quiet if you get it wrong: the context behind `IGNORE` became
|
||||||
thread-local and `akerr_next_error()` now returns a context that already holds a reference, so
|
thread-local and `akerr_next_error()` now returns a context that already holds a reference, so
|
||||||
a mixed build leaks pool slots or frees one twice rather than failing to link.
|
a mixed build leaks pool slots or frees one twice rather than failing to link.
|
||||||
|
|
||||||
@@ -167,10 +174,47 @@ a mixed build leaks pool slots or frees one twice rather than failing to link.
|
|||||||
|
|
||||||
| Submodule | Version | soname | ABI rule | Version API |
|
| Submodule | Version | soname | ABI rule | Version API |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `deps/libakerror` | 2.0.1 | `libakerror.so.2` | major only | **none** — no version macro; `include/akbasic/error.h` feature-tests `AKERR_THREAD_SAFE` and `AKERR_EXIT_STATUS_UNREPRESENTABLE` instead |
|
| `deps/libakerror` | 2.0.2 | `libakerror.so.2` | major only | **none** — no version macro; `include/akbasic/error.h` feature-tests `AKERR_THREAD_SAFE` and `AKERR_EXIT_STATUS_UNREPRESENTABLE` instead |
|
||||||
| `deps/libakstdlib` | 0.2.0 | `libakstdlib.so.0.2` | **`MAJOR.MINOR` while major is 0** | `AKSL_VERSION_*`, `aksl_version()`, `AKSL_VERSION_CHECK()` |
|
| `deps/libakstdlib` | 0.2.0 | `libakstdlib.so.0.2` | **`MAJOR.MINOR` while major is 0** | `AKSL_VERSION_*`, `aksl_version()`, `AKSL_VERSION_CHECK()` |
|
||||||
| `deps/libakgl` | 0.9.0 | `libakgl.so.0.9` | **`MAJOR.MINOR` while major is 0** | `AKGL_VERSION*`, `akgl_version()`, `AKGL_VERSION_AT_LEAST()` |
|
| `deps/libakgl` | 0.9.0 | `libakgl.so.0.9` | **`MAJOR.MINOR` while major is 0** | `AKGL_VERSION*`, `akgl_version()`, `AKGL_VERSION_AT_LEAST()` |
|
||||||
|
|
||||||
|
**The `libakerror` row is what `project(akerror VERSION ...)` declares, and it disagrees with
|
||||||
|
that library's own release notes**, which carry a "Release 2.0.3" section describing CI-only
|
||||||
|
changes. The version the build stamps into the soname and into
|
||||||
|
`akerrorConfigVersion.cmake` is 2.0.2; nothing consumers can observe says 2.0.3. Read the
|
||||||
|
table as the ABI and the notes as the changelog, and do not "correct" this row to 2.0.3
|
||||||
|
without the upstream `project()` bump to go with it. Reported as `libakerror` issue #38.
|
||||||
|
|
||||||
|
#### What 2.0.2's `IGNORE` change costs, measured
|
||||||
|
|
||||||
|
`IGNORE` now takes a *copy* of the context so the pool slot can be released, which fixes a
|
||||||
|
real leak. The copy lives in the public header as a file-scope `static`:
|
||||||
|
|
||||||
|
```c norun
|
||||||
|
static AKERR_THREAD_LOCAL akerr_ErrorContext akerr_last_ignored;
|
||||||
|
```
|
||||||
|
|
||||||
|
`static` means one per translation unit, and `AKERR_THREAD_LOCAL` means each one is
|
||||||
|
thread-local. `sizeof(akerr_ErrorContext)` is **37,296 bytes**, so:
|
||||||
|
|
||||||
|
| Build | TLS segment of `build/basic` |
|
||||||
|
|---|---|
|
||||||
|
| libakerror 2.0.1 | none at all |
|
||||||
|
| libakerror 2.0.2 | 1,417,248 bytes — **1.35 MiB**, per thread |
|
||||||
|
|
||||||
|
38 copies × 37,296 accounts for the segment exactly; `readelf -sW build/basic` shows all 38.
|
||||||
|
It scales with translation-unit count, not with anything about the program, and every thread
|
||||||
|
a host game spawns pays it again — which is worth knowing in a tree that spent a release
|
||||||
|
taking `akbasic_Runtime` from 10.75 MiB to 2.40 MiB.
|
||||||
|
|
||||||
|
It also emits **84 `-Wunused-variable` warnings**, one per TU that includes `akerror.h`
|
||||||
|
without using `IGNORE`. Nothing here builds with `-Werror`, so it is noise rather than a
|
||||||
|
failure — but a consumer that does cannot include the header.
|
||||||
|
|
||||||
|
**Both are `libakerror` issue #37 and neither is worked around here**, per the rule above: a
|
||||||
|
one-line `extern` in the header and a definition in `src/error.c` fixes it upstream, and
|
||||||
|
patching our copy would fork the submodule. Delete this subsection when it lands.
|
||||||
|
|
||||||
For both 0.x libraries the soname carries `MAJOR.MINOR` deliberately: 0.1 and 0.2 are
|
For both 0.x libraries the soname carries `MAJOR.MINOR` deliberately: 0.1 and 0.2 are
|
||||||
*different* ABIs, and both become major-only at 1.0. Do not read `0.1 → 0.2` as a compatible
|
*different* ABIs, and both become major-only at 1.0. Do not read `0.1 → 0.2` as a compatible
|
||||||
bump — both libraries have actually made that jump, so anything built against the 0.1 headers
|
bump — both libraries have actually made that jump, so anything built against the 0.1 headers
|
||||||
@@ -196,16 +240,32 @@ precedes the build tree on the include path and a stray copy there would shadow
|
|||||||
one and pin every consumer. It publishes `AKGL_VERSION_AT_LEAST(major, minor, patch)` — the
|
one and pin every consumer. It publishes `AKGL_VERSION_AT_LEAST(major, minor, patch)` — the
|
||||||
compile-time test `libakstdlib` could not write against `libakerror` — and `akgl_version()`.
|
compile-time test `libakstdlib` could not write against `libakerror` — and `akgl_version()`.
|
||||||
|
|
||||||
**Version-pinning in `find_package` is asymmetric, and that is deliberate.**
|
**Version-pinning in `find_package` used to be asymmetric. It no longer is.**
|
||||||
`find_package(akstdlib 0.1)` and `find_package(akgl 0.1)` both work; each ships a
|
`find_package(akstdlib 0.1)` and `find_package(akgl 0.1)` both work; each ships a
|
||||||
`ConfigVersion.cmake` at `SameMinorVersion`, mirroring its soname. `find_package(akerror 1.0)`
|
`ConfigVersion.cmake` at `SameMinorVersion`, mirroring its soname. `libakerror` shipped
|
||||||
**fails against a correct install**, because `libakerror` ships `akerrorConfig.cmake` and
|
`akerrorConfig.cmake` and `akerrorTargets.cmake` but no `akerrorConfigVersion.cmake`, so any
|
||||||
`akerrorTargets.cmake` but no `akerrorConfigVersion.cmake`. Ask for `akerror` unversioned. Its
|
versioned request failed against a correct install and the advice here was to ask for
|
||||||
floor is enforced instead by an `#error` feature-testing `AKERR_FIRST_CONSUMER_STATUS`, which
|
`akerror` unversioned. That was `libakerror` issue #16 — closed — and `libakstdlib` issue #5,
|
||||||
`akstdlib.h`, `akgl/error.h` and our own `include/akbasic/error.h` all carry — include any of
|
which tracks the same fix from the other side and is still open only because nobody has shut
|
||||||
them and you inherit the guard. The missing version file is filed in
|
it. It has landed: `libakerror` now writes `akerrorConfigVersion.cmake` at
|
||||||
`libakstdlib` issue #5 and `libakerror` issue #16; when it lands, add the `1.0` floor to the `find_dependency`
|
**`SameMajorVersion`**, matching the soname's major-only rule, rather than the
|
||||||
calls.
|
`SameMinorVersion` the other two use to match theirs.
|
||||||
|
|
||||||
|
Two things follow, and the second is the one that bites:
|
||||||
|
|
||||||
|
- **A versioned request now works** — but the floor to ask for is **`2.0`**, not the `1.0`
|
||||||
|
this file used to say. `find_package(akerror 1.0)` fails *harder* than before: it is a
|
||||||
|
request for major 1 against a major-2 install, which `SameMajorVersion` correctly rejects.
|
||||||
|
- **There is nothing in this repository to change.** `akbasic` reaches all three dependencies
|
||||||
|
by `add_subdirectory`, not `find_package`, and ships no CMake package config of its own —
|
||||||
|
so it has no `find_dependency` calls to add a floor to. The instruction that used to live
|
||||||
|
here was written for a consumer this project never became. It matters to anyone *installing*
|
||||||
|
these libraries and linking `akbasic` against the installed copies, which is why it is
|
||||||
|
recorded rather than deleted.
|
||||||
|
|
||||||
|
The compile-time floor is unchanged and still the real guard: an `#error` feature-testing
|
||||||
|
`AKERR_FIRST_CONSUMER_STATUS`, which `akstdlib.h`, `akgl/error.h` and our own
|
||||||
|
`include/akbasic/error.h` all carry — include any of them and you inherit it.
|
||||||
|
|
||||||
### Embedding all three dependencies collides four ways
|
### Embedding all three dependencies collides four ways
|
||||||
|
|
||||||
@@ -250,14 +310,19 @@ CTest names. `libakstdlib` still uses bare `test_<name>` targets. **Name every t
|
|||||||
this repo `akbasic_test_<name>`** — it costs nothing and it is the collision that actually
|
this repo `akbasic_test_<name>`** — it costs nothing and it is the collision that actually
|
||||||
stopped a build.
|
stopped a build.
|
||||||
|
|
||||||
**3. Duplicate custom targets.** `libakerror` namespaces its `mutation` target when embedded
|
**3. Duplicate custom targets — fixed upstream, and the workaround is gone.** `libakerror`
|
||||||
but **not** its `coverage` target, so any coverage-enabled top-level build fails with *"another
|
used to namespace its `mutation` target when embedded but **not** its `coverage` target, so
|
||||||
target with the same name already exists"*. We shadow `add_custom_target` and rename that one
|
any coverage-enabled top-level build failed with *"another target with the same name already
|
||||||
to `akerror_coverage` on the way past. `libakstdlib` (both targets) and `libakgl` (its
|
exists"*. This project shadowed `add_custom_target` and renamed that one to
|
||||||
`mutation` target) namespace themselves correctly. **The real fix is upstream in
|
`akerror_coverage` on the way past, and recorded the real fix as `libakerror` issue #15: the
|
||||||
`libakerror`** — the same `CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR` test it already
|
same `CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR` test it already applied to
|
||||||
applies to `mutation` — and it is filed as `libakerror` issue #15. Delete the
|
`mutation`.
|
||||||
workaround when it lands.
|
|
||||||
|
That landed in 2.0.2. `libakerror` now picks `akerror_coverage` itself when embedded, so the
|
||||||
|
shadow was dead code that could only ever fire on a name the dependency had stopped using —
|
||||||
|
and it is deleted. All three dependencies namespace both targets correctly now, so **there is
|
||||||
|
no custom-target collision left**; only collisions 1, 2 and 4 below are live. The heading says
|
||||||
|
four because four is what a reader coming from the issue tracker will be looking for.
|
||||||
|
|
||||||
**4. Stale build trees poison the coverage report.** See below; it is the reason for
|
**4. Stale build trees poison the coverage report.** See below; it is the reason for
|
||||||
`cmake -S . -B build`.
|
`cmake -S . -B build`.
|
||||||
|
|||||||
@@ -138,12 +138,13 @@ API documentation builds with `doxygen Doxyfile`, into `build/docs/html`.
|
|||||||
Everything is a submodule; `git submodule update --init --recursive` gets all of it. There is
|
Everything is a submodule; `git submodule update --init --recursive` gets all of it. There is
|
||||||
nothing to install first.
|
nothing to install first.
|
||||||
|
|
||||||
* [libakerror](https://source.starfort.tech/andrew/libakerror) 2.0.1 — TRY/CATCH-style error
|
* [libakerror](https://source.starfort.tech/andrew/libakerror) 2.0.2 — TRY/CATCH-style error
|
||||||
contexts. Every function that can fail returns one. 2.0.0 made it thread safe and broke the
|
contexts. Every function that can fail returns one. 2.0.0 made it thread safe and broke the
|
||||||
ABI; anything built against a 1.x header must be rebuilt rather than relinked.
|
ABI; anything built against a 1.x header must be rebuilt rather than relinked.
|
||||||
* [libakstdlib](https://source.starfort.tech/andrew/libakstdlib) 0.2.0 — libc wrappers that
|
* [libakstdlib](https://source.starfort.tech/andrew/libakstdlib) 0.2.0 — libc wrappers that
|
||||||
report through `libakerror`. String-to-number conversion goes straight to it, which is why
|
report through `libakerror`. String-to-number conversion goes straight to it, which is why
|
||||||
`VAL("garbage")` is an error rather than a silent `0`.
|
`VAL("garbage")` is an error rather than a silent `0`. Its public header now also pulls in
|
||||||
|
`<dirent.h>` and `<sys/stat.h>` for the directory and file-metadata wrappers.
|
||||||
* [libakgl](https://source.starfort.tech/andrew/libakgl) 0.9.0 — **optional**, only for
|
* [libakgl](https://source.starfort.tech/andrew/libakgl) 0.9.0 — **optional**, only for
|
||||||
`-DAKBASIC_WITH_AKGL=ON`. Pulls in SDL3. Its soname carries `MAJOR.MINOR` while the major is 0,
|
`-DAKBASIC_WITH_AKGL=ON`. Pulls in SDL3. Its soname carries `MAJOR.MINOR` while the major is 0,
|
||||||
so rebuild rather than relink.
|
so rebuild rather than relink.
|
||||||
|
|||||||
6
TODO.md
6
TODO.md
@@ -2339,9 +2339,9 @@ Dependency baseline:
|
|||||||
|
|
||||||
| Submodule | Version | Notes |
|
| Submodule | Version | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `deps/libakerror` | 2.0.1 | Private ownership-enforced status registry. akbasic reserves 512–767 in `akbasic_error_register()`. Does not namespace its `coverage` target when embedded — worked around in our `CMakeLists.txt`. **2.0.0 is thread safe and an ABI break** (`libakerror.so.2`): `__akerr_last_ignored` is thread-local and `akerr_next_error()` returns an owned reference, neither of which fails to link when mismatched. **2.0.1 fixes an exit status that mattered more to this band than to any other** — see below. |
|
| `deps/libakerror` | 2.0.2 | Private ownership-enforced status registry. akbasic reserves 512–767 in `akbasic_error_register()`. **2.0.0 is thread safe and an ABI break** (`libakerror.so.2`): the `IGNORE` context is thread-local and `akerr_next_error()` returns an owned reference, neither of which fails to link when mismatched. **2.0.1 fixes an exit status that mattered more to this band than to any other** — see below. **2.0.2** namespaces its embedded `coverage` target (issue #15 — our `add_custom_target` shadow is deleted), installs `akerrorConfigVersion.cmake` at `SameMajorVersion` (issue #16), fixes the `AKERR_USE_STDLIB=OFF` build, retires `PATH_MAX` for `AKERR_MAX_ERROR_FNAME_LENGTH`, and turns the `IGNORE` slot into a released copy — `__akerr_last_ignored` was an `extern` pointer and is now a per-TU `static akerr_last_ignored`. **That last one costs us 1.35 MiB of thread-local storage**: 38 translation units x 37,296 bytes, measured as the whole TLS segment of `build/basic`, where 2.0.1 produced no TLS segment at all. Filed upstream as libakerror issue #37, along with the 84 `-Wunused-variable` warnings it emits. Its `UPGRADING.md` describes a 2.0.3 that `project()` never stamped — libakerror issue #38, which is why the version above reads 2.0.2. |
|
||||||
| `deps/libakstdlib` | 0.2.0 | soname `libakstdlib.so.0.2`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. This release fixed all six confirmed defects the port was working around — see §1.9, where the bans are now lifted. |
|
| `deps/libakstdlib` | 0.2.0 | soname `libakstdlib.so.0.2`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. This release fixed all six confirmed defects the port was working around — see §1.9, where the bans are now lifted. Since then, and with no version bump: **directory wrappers landed** (`aksl_opendir`/`readdir`/`closedir`/`rewinddir`, issue #10) — the gap `DIRECTORY` was refused for, so that refusal now says only that the verb is unwritten; file-metadata wrappers landed; and `aksl_snprintf` keeps its `int *count` but now reports the *required* length on truncation rather than 0. Only read on success here, so nothing moved. |
|
||||||
| `deps/libakgl` | 0.7.0 | soname `libakgl.so.0.7`. Owns status codes 256–260. Linked and tested under `-DAKBASIC_WITH_AKGL=ON`, which still defaults OFF so the core library and its whole suite build on a machine with no SDL. **0.3.0 closed every API gap this port had filed** — see §7 — so the four workarounds §3 used to list are gone. 0.4.0 was a leak-and-overread release that changed no public struct. **0.5.0 is the first that broke our source as well as our ABI**: it namespaced every exported symbol, so `akgl_render_bind2d` is `akgl_render_2d_bind`, `akgl_sprite_sheet_coords_for_frame` is `akgl_spritesheet_coords_for_frame`, the `renderer`/`camera`/`window` globals carry the prefix, and `_akgl_renderer`/`_akgl_camera` are `akgl_default_renderer`/`akgl_default_camera`. `include/akbasic/akgl.h` asserts the floor. **0.6.0 and 0.7.0 broke nothing here** — 0.6.0 is three arcade-physics fixes and a `physics.max_timestep` property this port does not use, and 0.7.0 reports failures `libakstdlib`'s wrappers were already catching and takes `libakerror` 2.0.1. The floor moved anyway, because deciding for ourselves which of libakgl's minor releases were really compatible is the judgement the soname exists to take away. |
|
| `deps/libakgl` | 0.9.0 | soname `libakgl.so.0.9`. Owns status codes 256–262. Linked and tested under `-DAKBASIC_WITH_AKGL=ON`, which still defaults OFF so the core library and its whole suite build on a machine with no SDL. **0.3.0 closed every API gap this port had filed** — see §7 — so the four workarounds §3 used to list are gone. 0.4.0 was a leak-and-overread release that changed no public struct. **0.5.0 is the first that broke our source as well as our ABI**: it namespaced every exported symbol, so `akgl_render_bind2d` is `akgl_render_2d_bind`, `akgl_sprite_sheet_coords_for_frame` is `akgl_spritesheet_coords_for_frame`, the `renderer`/`camera`/`window` globals carry the prefix, and `_akgl_renderer`/`_akgl_camera` are `akgl_default_renderer`/`akgl_default_camera`. `include/akbasic/akgl.h` asserts the floor. **0.6.0 and 0.7.0 broke nothing here** — 0.6.0 is three arcade-physics fixes and a `physics.max_timestep` property this port does not use, and 0.7.0 reports failures `libakstdlib`'s wrappers were already catching and takes `libakerror` 2.0.1. **0.8.0 and 0.9.0 broke nothing here either** — 0.8.0 is the collision subsystem (`AKGL_ERR_COLLISION`, code 261, and the vendored `libccd` and `tg` submodules that come with it), and 0.9.0 is the `akgl_ui` subsystem (`AKGL_ERR_UI`, code 262, and vendored `clay`), which is what `src/ui_akgl.c` draws through. Both widen libakgl's reserved band from five codes to seven; the range map in `MAINTENANCE.md` carries it. The floor moved anyway, because deciding for ourselves which of libakgl's minor releases were really compatible is the judgement the soname exists to take away. **Note `deps/libakgl/deps/` pins its own `libakerror` and `libakstdlib` older than ours**; the top-level build declares both targets first and libakgl's `if(NOT TARGET ...)` guards mean its copies are never configured, so what libakgl actually compiles against is what this tree pins. |
|
||||||
|
|
||||||
**An unhandled error in this band used to exit zero, and 512 is the worst possible base for
|
**An unhandled error in this band used to exit zero, and 512 is the worst possible base for
|
||||||
that.** `libakerror`'s default unhandled-error handler ended in `exit(errctx->status)`, and a
|
that.** `libakerror`'s default unhandled-error handler ended in `exit(errctx->status)`, and a
|
||||||
|
|||||||
2
deps/libakerror
vendored
2
deps/libakerror
vendored
Submodule deps/libakerror updated: 5eaa956f50...ee38eed31c
2
deps/libakgl
vendored
2
deps/libakgl
vendored
Submodule deps/libakgl updated: bbb7b8f494...77091ed177
2
deps/libakstdlib
vendored
2
deps/libakstdlib
vendored
Submodule deps/libakstdlib updated: 2b79aca103...821200e618
@@ -126,7 +126,7 @@ exists, and reading into one you never sized writes over something else.
|
|||||||
| `COLLECT` | validates a disk's block allocation map. There is no map |
|
| `COLLECT` | validates a disk's block allocation map. There is no map |
|
||||||
| `BACKUP` | duplicates one disk onto another. There are no disks |
|
| `BACKUP` | duplicates one disk onto another. There are no disks |
|
||||||
| `BOOT` | loads and runs a boot sector. There is no boot sector |
|
| `BOOT` | loads and runs a boot sector. There is no boot sector |
|
||||||
| `DIRECTORY` / `CATALOG` | needs a directory-reading wrapper the standard library does not have yet. Filed upstream |
|
| `DIRECTORY` / `CATALOG` | not written yet. It was blocked on a directory-reading wrapper in the standard library; that landed, so only the verb is outstanding |
|
||||||
|
|
||||||
`DCLEAR` is the exception among the drive verbs: resetting a drive also closes its
|
`DCLEAR` is the exception among the drive verbs: resetting a drive also closes its
|
||||||
channels, and closing the channels is real, so that is what it does.
|
channels, and closing the channels is real, so that is what it does.
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ for the reasoning in each case.
|
|||||||
| `DIALOG` | `DIALOG ["text"]` | Show a text panel across the bottom of the screen. No argument takes it down. See Chapter 19. |
|
| `DIALOG` | `DIALOG ["text"]` | Show a text panel across the bottom of the screen. No argument takes it down. See Chapter 19. |
|
||||||
| `DIM` | `DIM A#(n [,...])` | Make an array. Subscripts start at zero; `n` is the count. |
|
| `DIM` | `DIM A#(n [,...])` | Make an array. Subscripts start at zero; `n` is the count. |
|
||||||
| `DIM` … `AS` | `DIM S@ AS T`, `DIM P@ AS PTR TO T` | Make a structure, or a strict pointer to one. See Chapter 16. |
|
| `DIM` … `AS` | `DIM S@ AS T`, `DIM P@ AS PTR TO T` | Make a structure, or a strict pointer to one. See Chapter 16. |
|
||||||
| `DIRECTORY` | `DIRECTORY` | **Refused.** Needs a directory-reading wrapper that does not exist yet. |
|
| `DIRECTORY` | `DIRECTORY` | **Refused.** Not written yet; the standard-library wrapper it waited on has landed. |
|
||||||
| `DLOAD` | `DLOAD "name"` | Load a program from a file. |
|
| `DLOAD` | `DLOAD "name"` | Load a program from a file. |
|
||||||
| `DO` | `DO [WHILE c | UNTIL c]` | Start a loop. The condition may be here, on the `LOOP`, or neither. |
|
| `DO` | `DO [WHILE c | UNTIL c]` | Start a loop. The condition may be here, on the `LOOP`, or neither. |
|
||||||
| `DOPEN` | `DOPEN n, "name" [,W]` | Open a file on channel `n`. `W` opens it for writing. |
|
| `DOPEN` | `DOPEN n, "name" [,W]` | Open a file on channel `n`. `W` opens it for writing. |
|
||||||
|
|||||||
@@ -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. |
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ interpreter's error code, which bears no relation to a Commodore error number. P
|
|||||||
- **`BLOAD` requires a length.**
|
- **`BLOAD` requires a length.**
|
||||||
- **`HEADER`, `COLLECT`, `BACKUP` and `BOOT` are refused.** They operate on a physical
|
- **`HEADER`, `COLLECT`, `BACKUP` and `BOOT` are refused.** They operate on a physical
|
||||||
disk.
|
disk.
|
||||||
- **`DIRECTORY` is refused** pending a wrapper in the standard library.
|
- **`DIRECTORY` is refused** because it is not written yet. The standard-library
|
||||||
|
wrapper it was waiting on has landed, so the remaining work is the verb.
|
||||||
|
|
||||||
## Machine
|
## Machine
|
||||||
|
|
||||||
|
|||||||
@@ -1005,57 +1005,26 @@ 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
|
||||||
SEED# = 12345
|
|
||||||
RMAX# = 6
|
|
||||||
RND# = 0
|
|
||||||
I# = 0
|
I# = 0
|
||||||
FOR I# = 1 TO 5
|
FOR I# = 1 TO 5
|
||||||
GOSUB RANDOM
|
PRINT "ROLL " + (RND(6) + 1)
|
||||||
PRINT "ROLL " + (RND# + 1)
|
|
||||||
NEXT I#
|
NEXT I#
|
||||||
END
|
END
|
||||||
|
|
||||||
LABEL RANDOM
|
|
||||||
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
|
||||||
RND# = MOD((SEED# / 65536), RMAX#)
|
|
||||||
RETURN
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```output
|
Use `RND` for the serve, too, so the ball does not always leave in the same direction:
|
||||||
ROLL 1
|
|
||||||
ROLL 5
|
|
||||||
ROLL 2
|
|
||||||
ROLL 1
|
|
||||||
ROLL 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The multiplication stays inside a 64-bit integer for any seed below 2147483648, which is
|
|
||||||
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
|
```basic norun
|
||||||
LABEL SERVE
|
LABEL SERVE
|
||||||
@@ -1063,16 +1032,43 @@ PX# = (SCW# - PW#) / 2
|
|||||||
HELD# = 1
|
HELD# = 1
|
||||||
BX# = PX# + ((PW# / 2) - 4)
|
BX# = PX# + ((PW# / 2) - 4)
|
||||||
BY# = PY# - 10
|
BY# = PY# - 10
|
||||||
RMAX# = 2
|
|
||||||
GOSUB RANDOM
|
|
||||||
BVX# = BSPD#
|
BVX# = BSPD#
|
||||||
IF RND# = 0 THEN BVX# = 0 - BSPD#
|
IF RND(2) = 0 THEN BVX# = 0 - BSPD#
|
||||||
BVY# = 0 - BSPD#
|
BVY# = 0 - BSPD#
|
||||||
PDEC# = 0
|
PDEC# = 0
|
||||||
GOSUB SHOWSPR
|
GOSUB SHOWSPR
|
||||||
RETURN
|
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
|
||||||
|
RMAX# = 6
|
||||||
|
ROLL# = 0
|
||||||
|
I# = 0
|
||||||
|
FOR I# = 1 TO 5
|
||||||
|
GOSUB RANDOM
|
||||||
|
PRINT "ROLL " + (ROLL# + 1)
|
||||||
|
NEXT I#
|
||||||
|
END
|
||||||
|
|
||||||
|
LABEL RANDOM
|
||||||
|
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
||||||
|
ROLL# = MOD((SEED# / 65536), RMAX#)
|
||||||
|
RETURN
|
||||||
|
```
|
||||||
|
|
||||||
|
The multiplication stays inside a 64-bit integer for any seed below 2147483648. The
|
||||||
|
answer is taken from the middle bits because the low bits of a power-of-two modulus
|
||||||
|
barely change from one call to the next. This used to be required; it is now built in.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
`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$ = ""
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ whole development loop; the engine never rebuilds.
|
|||||||
- **[Step 2](#step-2-bind-the-engines-own-actor)** — bind the engine's own
|
- **[Step 2](#step-2-bind-the-engines-own-actor)** — bind the engine's own
|
||||||
actor as the second type, which is the point of the whole exercise
|
actor as the second type, which is the point of the whole exercise
|
||||||
- **[Step 3](#step-3-share-the-frame-and-the-dice)** — share the frame state,
|
- **[Step 3](#step-3-share-the-frame-and-the-dice)** — share the frame state,
|
||||||
and give the script randomness it cannot make itself
|
and hand the script dice the engine controls
|
||||||
- **[Step 4](#step-4-why-bindings-and-not-arguments)** — see why the structures
|
- **[Step 4](#step-4-why-bindings-and-not-arguments)** — see why the structures
|
||||||
are bindings rather than function arguments
|
are bindings rather than function arguments
|
||||||
- **[Step 5](#step-5-the-shape-of-the-script)** — learn the three language
|
- **[Step 5](#step-5-the-shape-of-the-script)** — learn the three language
|
||||||
@@ -75,7 +75,7 @@ typedef struct galaga_Enemy
|
|||||||
float t; /* parametric clock for the current maneuver */
|
float t; /* parametric clock for the current maneuver */
|
||||||
int32_t hp;
|
int32_t hp;
|
||||||
int32_t fire; /* outbox: script sets 1, engine consumes */
|
int32_t fire; /* outbox: script sets 1, engine consumes */
|
||||||
float rnd; /* inbox: engine writes fresh 0..1 each call */
|
float rnd; /* inbox: fresh 0..1 each call; ROLL% in BASIC */
|
||||||
} galaga_Enemy;
|
} galaga_Enemy;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ static const akbasic_HostField ENEMY_FIELDS[] = {
|
|||||||
AKBASIC_HOST_FIELD( galaga_Enemy, t, "T%", AKBASIC_HOSTFIELD_FLOAT ),
|
AKBASIC_HOST_FIELD( galaga_Enemy, t, "T%", AKBASIC_HOSTFIELD_FLOAT ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Enemy, hp, "HP#", AKBASIC_HOSTFIELD_INT32 ),
|
AKBASIC_HOST_FIELD( galaga_Enemy, hp, "HP#", AKBASIC_HOSTFIELD_INT32 ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Enemy, fire, "FIRE#", AKBASIC_HOSTFIELD_INT32 ),
|
AKBASIC_HOST_FIELD( galaga_Enemy, fire, "FIRE#", AKBASIC_HOSTFIELD_INT32 ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Enemy, rnd, "RND%", AKBASIC_HOSTFIELD_FLOAT )
|
AKBASIC_HOST_FIELD( galaga_Enemy, rnd, "ROLL%", AKBASIC_HOSTFIELD_FLOAT )
|
||||||
};
|
};
|
||||||
static const akbasic_HostType ENEMY_TYPE = {
|
static const akbasic_HostType ENEMY_TYPE = {
|
||||||
"ENEMY", sizeof(galaga_Enemy), ENEMY_FIELDS, 8
|
"ENEMY", sizeof(galaga_Enemy), ENEMY_FIELDS, 8
|
||||||
@@ -194,7 +194,7 @@ typedef struct galaga_Shared
|
|||||||
float playerx; /* the player actor's position, this frame */
|
float playerx; /* the player actor's position, this frame */
|
||||||
float playery;
|
float playery;
|
||||||
int32_t wave;
|
int32_t wave;
|
||||||
float rnd; /* fresh 0..1 each frame; the issue #16 route */
|
float rnd; /* fresh 0..1 each frame; ROLL% to the script */
|
||||||
} galaga_Shared;
|
} galaga_Shared;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -203,13 +203,24 @@ the engine refreshes it at the top of every frame. The boss reads
|
|||||||
`GAME@.PLAYERX%` to lead its dive; the fire decision reads it to know whether
|
`GAME@.PLAYERX%` to lead its dive; the fire decision reads it to know whether
|
||||||
anything is worth shooting at.
|
anything is worth shooting at.
|
||||||
|
|
||||||
The `rnd` fields — one here per frame, one on each enemy per call — exist
|
The `rnd` fields — one here per frame, one on each enemy per call — carry the
|
||||||
because the engine's PRNG is the script's **only** source of randomness: write
|
engine's PRNG into the script: write `SELF@.ROLL% < DT% * 1.5` and an enemy's
|
||||||
`SELF@.RND% < DT% * 1.5` and an enemy's trigger finger is a dice roll. There
|
trigger finger is a dice roll.
|
||||||
is no `RND` verb in this dialect; issue #16 tracks adding one, and Chapter
|
|
||||||
17's breakout hand-rolls a linear congruential generator in BASIC as the other
|
The dialect does now have a native `RND` function — issue #16 closed, and
|
||||||
route. Here the engine fills the field, which also keeps a headless run the
|
[Chapter 12](12-function-reference.md) documents it — so this is no longer the
|
||||||
same game on every machine — the PRNG is the example's own, not libc's.
|
*only* route; Chapter 17's breakout hand-rolls a linear congruential generator
|
||||||
|
in BASIC as a third. The engine keeps filling the field here on purpose,
|
||||||
|
because it buys something `RND` cannot: the numbers come from the example's own
|
||||||
|
PRNG rather than libc's, so a headless run is the same game on every machine,
|
||||||
|
which is what makes `example_galaga` a test and not just a demo.
|
||||||
|
|
||||||
|
**The BASIC name is `ROLL%`, not `RND%`.** A host field is a bare word and
|
||||||
|
shares a namespace with every verb and function, so once `RND` became a
|
||||||
|
function name a field could no longer be called that — the scanner refuses it
|
||||||
|
with *"Reserved word in variable name"*. The C member stays `rnd`; only the
|
||||||
|
name the script sees had to move. [Chapter 16](16-structures.md) has the same
|
||||||
|
rule for `TYPE` declarations.
|
||||||
|
|
||||||
## Step 4: Why bindings, and not arguments
|
## Step 4: Why bindings, and not arguments
|
||||||
|
|
||||||
@@ -351,7 +362,7 @@ DEF DECIDEFIRE(DT%)
|
|||||||
DX% = GAME@.PLAYERX% - ACTOR@.X%
|
DX% = GAME@.PLAYERX% - ACTOR@.X%
|
||||||
IF ABS(DX%) > 140 THEN RETURN 0
|
IF ABS(DX%) > 140 THEN RETURN 0
|
||||||
IF ACTOR@.Y% > GAME@.PLAYERY% THEN RETURN 0
|
IF ACTOR@.Y% > GAME@.PLAYERY% THEN RETURN 0
|
||||||
IF SELF@.RND% < DT% * 1.5 THEN SELF@.FIRE# = 1
|
IF SELF@.ROLL% < DT% * 1.5 THEN SELF@.FIRE# = 1
|
||||||
RETURN 0
|
RETURN 0
|
||||||
END
|
END
|
||||||
```
|
```
|
||||||
@@ -386,7 +397,7 @@ DEF UPDATEBEE(DT%)
|
|||||||
IF (S# AND 2) > 0 THEN BEGIN
|
IF (S# AND 2) > 0 THEN BEGIN
|
||||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.7) * 16
|
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.7) * 16
|
||||||
ACTOR@.Y% = SELF@.HOMEY%
|
ACTOR@.Y% = SELF@.HOMEY%
|
||||||
IF SELF@.RND% < DT% * 0.04 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
IF SELF@.ROLL% < DT% * 0.04 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||||
BEND
|
BEND
|
||||||
IF (S# AND 4) > 0 THEN BEGIN
|
IF (S# AND 4) > 0 THEN BEGIN
|
||||||
R# = DIVESTEP(DT%, 130, 0.2)
|
R# = DIVESTEP(DT%, 130, 0.2)
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ RX# = 0
|
|||||||
N# = 0
|
N# = 0
|
||||||
MROW# = 0
|
MROW# = 0
|
||||||
RMAX# = 2
|
RMAX# = 2
|
||||||
RND# = 0
|
ROLL# = 0
|
||||||
SND# = 0
|
SND# = 0
|
||||||
P$ = ""
|
P$ = ""
|
||||||
H$ = ""
|
H$ = ""
|
||||||
@@ -172,7 +172,8 @@ BSG$(4) = "[--]"
|
|||||||
BSG$(5) = "[--]"
|
BSG$(5) = "[--]"
|
||||||
|
|
||||||
REM --- the seed -------------------------------------------------------
|
REM --- the seed -------------------------------------------------------
|
||||||
REM There is no RND in this dialect. TI# is jiffies off the host's clock
|
REM RND exists now, but this program keeps its own LCG so a headless run
|
||||||
|
REM is the same game every time. TI# is jiffies off the host's clock
|
||||||
REM and is host uptime rather than zero-based, which makes it a fine seed.
|
REM and is host uptime rather than zero-based, which makes it a fine seed.
|
||||||
SEED# = TI#
|
SEED# = TI#
|
||||||
|
|
||||||
@@ -389,17 +390,18 @@ BY# = PY# - 10
|
|||||||
RMAX# = 2
|
RMAX# = 2
|
||||||
GOSUB RANDOM
|
GOSUB RANDOM
|
||||||
BVX# = BSPD#
|
BVX# = BSPD#
|
||||||
IF RND# = 0 THEN BVX# = 0 - BSPD#
|
IF ROLL# = 0 THEN BVX# = 0 - BSPD#
|
||||||
BVY# = 0 - BSPD#
|
BVY# = 0 - BSPD#
|
||||||
PDEC# = 0
|
PDEC# = 0
|
||||||
GOSUB SHOWSPR
|
GOSUB SHOWSPR
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
REM A linear congruential generator, because this dialect has no RND.
|
REM A linear congruential generator. RND(n) would do this in one token
|
||||||
|
REM now; the LCG stays because its sequence is reproducible.
|
||||||
REM The multiply stays inside int64 for any seed under 2^31.
|
REM The multiply stays inside int64 for any seed under 2^31.
|
||||||
LABEL RANDOM
|
LABEL RANDOM
|
||||||
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
||||||
RND# = MOD((SEED# / 65536), RMAX#)
|
ROLL# = MOD((SEED# / 65536), RMAX#)
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
REM ####################################################################
|
REM ####################################################################
|
||||||
@@ -632,7 +634,7 @@ NUDGE# = 0
|
|||||||
STALL# = 0
|
STALL# = 0
|
||||||
RMAX# = 4
|
RMAX# = 4
|
||||||
GOSUB RANDOM
|
GOSUB RANDOM
|
||||||
BVX# = (RND# * 3) - 6
|
BVX# = (ROLL# * 3) - 6
|
||||||
IF BVX# = 0 THEN BVX# = 3
|
IF BVX# = 0 THEN BVX# = 3
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
@@ -641,7 +643,7 @@ REM like something with a hand on the paddle rather than a mirror.
|
|||||||
LABEL DEMOAIM
|
LABEL DEMOAIM
|
||||||
RMAX# = 81
|
RMAX# = 81
|
||||||
GOSUB RANDOM
|
GOSUB RANDOM
|
||||||
DOFF# = RND# - 40
|
DOFF# = ROLL# - 40
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
REM ####################################################################
|
REM ####################################################################
|
||||||
|
|||||||
@@ -241,7 +241,8 @@ SLX# = 0
|
|||||||
SLY# = 0
|
SLY# = 0
|
||||||
SLI# = 0
|
SLI# = 0
|
||||||
REM The two eraser stamps. Declared here for exactly the same reason -- built
|
REM The two eraser stamps. Declared here for exactly the same reason -- built
|
||||||
REM inside DRAWPROTOS and left undeclared, they were SHAPE:6 and SHAPE:7 inside
|
REM inside DRAWPROTOS and left undeclared, they were SHAPE:6 and SHAPE:7
|
||||||
|
REM inside
|
||||||
REM it and empty everywhere else.
|
REM it and empty everywhere else.
|
||||||
BL$ = ""
|
BL$ = ""
|
||||||
HBL$ = ""
|
HBL$ = ""
|
||||||
@@ -254,10 +255,13 @@ REM cursor through every DATA item in the program in the order they appear
|
|||||||
REM in the file, so whichever loader runs first gets the DATA that is
|
REM in the file, so whichever loader runs first gets the DATA that is
|
||||||
REM written first. The tables are written first.
|
REM written first. The tables are written first.
|
||||||
REM The text layer repaints every row it owns, opaque, so it has to be moved
|
REM The text layer repaints every row it owns, opaque, so it has to be moved
|
||||||
REM out of the way before anything drawn can be seen. Two rows at the bottom is
|
REM out of the way before anything drawn can be seen. Two rows at the bottom
|
||||||
|
REM is
|
||||||
REM enough for the final score, and hands the other thirty-five to the drawing
|
REM enough for the final score, and hands the other thirty-five to the drawing
|
||||||
REM verbs. Everything this game draws then simply stays there -- a drawing goes
|
REM verbs. Everything this game draws then simply stays there -- a drawing
|
||||||
REM into a layer the frame composites, so nothing here is captured into a sprite
|
REM goes
|
||||||
|
REM into a layer the frame composites, so nothing here is captured into a
|
||||||
|
REM sprite
|
||||||
REM and nothing is redrawn every frame.
|
REM and nothing is redrawn every frame.
|
||||||
WINDOW 0, 35, 49, 36
|
WINDOW 0, 35, 49, 36
|
||||||
|
|
||||||
@@ -293,7 +297,8 @@ ENVELOPE 0, 0, 6, 0, 4
|
|||||||
TEMPO 12
|
TEMPO 12
|
||||||
|
|
||||||
COLLISION 2, BRICKHIT
|
COLLISION 2, BRICKHIT
|
||||||
REM The stamps, once. They used to be rebuilt whenever the SSHAPE pool ran dry,
|
REM The stamps, once. They used to be rebuilt whenever the SSHAPE pool ran
|
||||||
|
REM dry,
|
||||||
REM because every frame's capture spent another slot; nothing captures now, so
|
REM because every frame's capture spent another slot; nothing captures now, so
|
||||||
REM eight slots are spent here and never again.
|
REM eight slots are spent here and never again.
|
||||||
GOSUB DRAWPROTOS
|
GOSUB DRAWPROTOS
|
||||||
@@ -374,7 +379,8 @@ COLOR 1, 1
|
|||||||
FOR K# = 0 TO 15
|
FOR K# = 0 TO 15
|
||||||
DRAW 1, 0, 130 + K# TO 67, 130 + K#
|
DRAW 1, 0, 130 + K# TO 67, 130 + K#
|
||||||
NEXT K#
|
NEXT K#
|
||||||
REM And an eighth the width of the HUD strip, for the same reason: the strip is
|
REM And an eighth the width of the HUD strip, for the same reason: the strip
|
||||||
|
REM is
|
||||||
REM rewritten whenever a number in it changes, and the old digits have to go
|
REM rewritten whenever a number in it changes, and the old digits have to go
|
||||||
REM somewhere first.
|
REM somewhere first.
|
||||||
FOR K# = 0 TO 59
|
FOR K# = 0 TO 59
|
||||||
@@ -393,7 +399,8 @@ DPLAY# = 1
|
|||||||
DHUD# = 1
|
DHUD# = 1
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
REM Take one brick off the screen: stamp the blank over it. Called when a brick
|
REM Take one brick off the screen: stamp the blank over it. Called when a
|
||||||
|
REM brick
|
||||||
REM breaks, so the field is never redrawn as a whole during play -- which is
|
REM breaks, so the field is never redrawn as a whole during play -- which is
|
||||||
REM what lets the whole live-list machinery go.
|
REM what lets the whole live-list machinery go.
|
||||||
LABEL ERASEBRICK
|
LABEL ERASEBRICK
|
||||||
@@ -444,7 +451,8 @@ WIDTH 1
|
|||||||
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
|
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
|
||||||
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
|
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
|
||||||
REM The old strip goes first. Nothing here clears the screen -- a drawing
|
REM The old strip goes first. Nothing here clears the screen -- a drawing
|
||||||
REM stays, which is the whole point -- so the digits that were there have to be
|
REM stays, which is the whole point -- so the digits that were there have to
|
||||||
|
REM be
|
||||||
REM stamped over before the new ones are drawn.
|
REM stamped over before the new ones are drawn.
|
||||||
Z$ = HBL$
|
Z$ = HBL$
|
||||||
GSHAPE Z$, 0, 0
|
GSHAPE Z$, 0, 0
|
||||||
@@ -495,7 +503,10 @@ IF SNDON# = 0 THEN VOL 0
|
|||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
LABEL PRESSPAUSE
|
LABEL PRESSPAUSE
|
||||||
IF STATE# = 2 THEN STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" : GOSUB SETBANNER : RETURN
|
IF STATE# <> 2 THEN GOTO PRESSPAUSE2
|
||||||
|
STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" : GOSUB SETBANNER
|
||||||
|
RETURN
|
||||||
|
LABEL PRESSPAUSE2
|
||||||
IF STATE# = 6 THEN STATE# = 2 : BAN$ = "" : GOSUB SETBANNER
|
IF STATE# = 6 THEN STATE# = 2 : BAN$ = "" : GOSUB SETBANNER
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,15 @@ static char *ENEMY_CHARACTER[GALAGA_ENEMY_KINDS] = {
|
|||||||
/* --------------------------------------------------------------- random --- */
|
/* --------------------------------------------------------------- random --- */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* No RND verb exists (issue #16), so the engine is the script's only source
|
* The engine is this script's source of randomness: it refreshes GAME@.ROLL%
|
||||||
* of randomness: it refreshes GAME@.RND% each frame and SELF@.RND% each call
|
* each frame and SELF@.ROLL% each call from this PRNG. A hand-rolled LCG
|
||||||
* from this PRNG. A hand-rolled LCG rather than rand() so a headless run is
|
* rather than rand() so a headless run is the same game on every libc, which
|
||||||
* the same game on every libc.
|
* is what lets interop_test.c assert exact counts. The dialect gained a native
|
||||||
|
* RND (issue #16) after this example was written; the field stays because RND
|
||||||
|
* would reintroduce exactly the per-machine variation this avoids.
|
||||||
|
*
|
||||||
|
* The BASIC-visible name is ROLL%, not RND%: host field names share a
|
||||||
|
* namespace with verbs and functions, so RND stopped being available as one.
|
||||||
*/
|
*/
|
||||||
static uint32_t PRNG_STATE = 0x12345678u;
|
static uint32_t PRNG_STATE = 0x12345678u;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ DEF DECIDEFIRE(DT%)
|
|||||||
DX% = GAME@.PLAYERX% - ACTOR@.X%
|
DX% = GAME@.PLAYERX% - ACTOR@.X%
|
||||||
IF ABS(DX%) > 140 THEN RETURN 0
|
IF ABS(DX%) > 140 THEN RETURN 0
|
||||||
IF ACTOR@.Y% > GAME@.PLAYERY% THEN RETURN 0
|
IF ACTOR@.Y% > GAME@.PLAYERY% THEN RETURN 0
|
||||||
IF SELF@.RND% < DT% * 1.5 THEN SELF@.FIRE# = 1
|
IF SELF@.ROLL% < DT% * 1.5 THEN SELF@.FIRE# = 1
|
||||||
RETURN 0
|
RETURN 0
|
||||||
|
|
||||||
REM Bee: enter, breathe in formation, occasionally dive nearly straight.
|
REM Bee: enter, breathe in formation, occasionally dive nearly straight.
|
||||||
@@ -65,7 +65,7 @@ DEF UPDATEBEE(DT%)
|
|||||||
IF (S# AND 2) > 0 THEN BEGIN
|
IF (S# AND 2) > 0 THEN BEGIN
|
||||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.7) * 16
|
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.7) * 16
|
||||||
ACTOR@.Y% = SELF@.HOMEY%
|
ACTOR@.Y% = SELF@.HOMEY%
|
||||||
IF SELF@.RND% < DT% * 0.04 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
IF SELF@.ROLL% < DT% * 0.04 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||||
BEND
|
BEND
|
||||||
IF (S# AND 4) > 0 THEN BEGIN
|
IF (S# AND 4) > 0 THEN BEGIN
|
||||||
R# = DIVESTEP(DT%, 130, 0.2)
|
R# = DIVESTEP(DT%, 130, 0.2)
|
||||||
@@ -85,7 +85,7 @@ DEF UPDATEBFLY(DT%)
|
|||||||
IF (S# AND 2) > 0 THEN BEGIN
|
IF (S# AND 2) > 0 THEN BEGIN
|
||||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 2.1) * 24
|
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 2.1) * 24
|
||||||
ACTOR@.Y% = SELF@.HOMEY%
|
ACTOR@.Y% = SELF@.HOMEY%
|
||||||
IF SELF@.RND% < DT% * 0.05 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
IF SELF@.ROLL% < DT% * 0.05 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||||
BEND
|
BEND
|
||||||
IF (S# AND 4) > 0 THEN BEGIN
|
IF (S# AND 4) > 0 THEN BEGIN
|
||||||
R# = DIVESTEP(DT%, 260, 0.1)
|
R# = DIVESTEP(DT%, 260, 0.1)
|
||||||
@@ -108,7 +108,7 @@ DEF UPDATEBOSS(DT%)
|
|||||||
IF (S# AND 2) > 0 THEN BEGIN
|
IF (S# AND 2) > 0 THEN BEGIN
|
||||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.1) * 10
|
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.1) * 10
|
||||||
ACTOR@.Y% = SELF@.HOMEY%
|
ACTOR@.Y% = SELF@.HOMEY%
|
||||||
IF SELF@.RND% < DT% * 0.03 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
IF SELF@.ROLL% < DT% * 0.03 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||||
BEND
|
BEND
|
||||||
IF (S# AND 4) > 0 THEN BEGIN
|
IF (S# AND 4) > 0 THEN BEGIN
|
||||||
R# = DIVESTEP(DT%, 60, 0.9)
|
R# = DIVESTEP(DT%, 60, 0.9)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ typedef struct galaga_Enemy
|
|||||||
float t; /* parametric clock for the current maneuver */
|
float t; /* parametric clock for the current maneuver */
|
||||||
int32_t hp;
|
int32_t hp;
|
||||||
int32_t fire; /* outbox: script sets 1, engine consumes */
|
int32_t fire; /* outbox: script sets 1, engine consumes */
|
||||||
float rnd; /* inbox: engine writes fresh 0..1 each call */
|
float rnd; /* inbox: fresh 0..1 each call; ROLL% in BASIC */
|
||||||
} galaga_Enemy;
|
} galaga_Enemy;
|
||||||
|
|
||||||
/** @brief Frame state every enemy may read. Bound once as GAME@. */
|
/** @brief Frame state every enemy may read. Bound once as GAME@. */
|
||||||
@@ -86,7 +86,7 @@ typedef struct galaga_Shared
|
|||||||
float playerx; /* the player actor's position, this frame */
|
float playerx; /* the player actor's position, this frame */
|
||||||
float playery;
|
float playery;
|
||||||
int32_t wave;
|
int32_t wave;
|
||||||
float rnd; /* fresh 0..1 each frame; the issue #16 route */
|
float rnd; /* fresh 0..1 each frame; ROLL% to the script */
|
||||||
} galaga_Shared;
|
} galaga_Shared;
|
||||||
|
|
||||||
/* --------------------------------------------------------------- screens --- */
|
/* --------------------------------------------------------------- screens --- */
|
||||||
|
|||||||
@@ -491,7 +491,8 @@ static akerr_ErrorContext *frame(bool *running)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The shared frame state, refreshed before any enemy thinks. The engine
|
/* The shared frame state, refreshed before any enemy thinks. The engine
|
||||||
* filling GAME@.RND% is the issue #16 route: no RND verb exists. */
|
* fills GAME@.ROLL% from its own PRNG rather than letting the script call
|
||||||
|
* the native RND, so a headless run is the same game on every machine. */
|
||||||
galaga_shared.playerx = galaga_game.player->x + 50.0f;
|
galaga_shared.playerx = galaga_game.player->x + 50.0f;
|
||||||
galaga_shared.playery = galaga_game.player->y;
|
galaga_shared.playery = galaga_game.player->y;
|
||||||
galaga_shared.rnd = galaga_random();
|
galaga_shared.rnd = galaga_random();
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ static const akbasic_HostField ENEMY_FIELDS[] = {
|
|||||||
AKBASIC_HOST_FIELD( galaga_Enemy, t, "T%", AKBASIC_HOSTFIELD_FLOAT ),
|
AKBASIC_HOST_FIELD( galaga_Enemy, t, "T%", AKBASIC_HOSTFIELD_FLOAT ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Enemy, hp, "HP#", AKBASIC_HOSTFIELD_INT32 ),
|
AKBASIC_HOST_FIELD( galaga_Enemy, hp, "HP#", AKBASIC_HOSTFIELD_INT32 ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Enemy, fire, "FIRE#", AKBASIC_HOSTFIELD_INT32 ),
|
AKBASIC_HOST_FIELD( galaga_Enemy, fire, "FIRE#", AKBASIC_HOSTFIELD_INT32 ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Enemy, rnd, "RND%", AKBASIC_HOSTFIELD_FLOAT )
|
AKBASIC_HOST_FIELD( galaga_Enemy, rnd, "ROLL%", AKBASIC_HOSTFIELD_FLOAT )
|
||||||
};
|
};
|
||||||
static const akbasic_HostType ENEMY_TYPE = {
|
static const akbasic_HostType ENEMY_TYPE = {
|
||||||
"ENEMY", sizeof(galaga_Enemy), ENEMY_FIELDS, 8
|
"ENEMY", sizeof(galaga_Enemy), ENEMY_FIELDS, 8
|
||||||
@@ -87,7 +87,7 @@ static const akbasic_HostField GAME_FIELDS[] = {
|
|||||||
AKBASIC_HOST_FIELD( galaga_Shared, playerx, "PLAYERX%", AKBASIC_HOSTFIELD_FLOAT ),
|
AKBASIC_HOST_FIELD( galaga_Shared, playerx, "PLAYERX%", AKBASIC_HOSTFIELD_FLOAT ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Shared, playery, "PLAYERY%", AKBASIC_HOSTFIELD_FLOAT ),
|
AKBASIC_HOST_FIELD( galaga_Shared, playery, "PLAYERY%", AKBASIC_HOSTFIELD_FLOAT ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Shared, wave, "WAVE#", AKBASIC_HOSTFIELD_INT32 ),
|
AKBASIC_HOST_FIELD( galaga_Shared, wave, "WAVE#", AKBASIC_HOSTFIELD_INT32 ),
|
||||||
AKBASIC_HOST_FIELD( galaga_Shared, rnd, "RND%", AKBASIC_HOSTFIELD_FLOAT )
|
AKBASIC_HOST_FIELD( galaga_Shared, rnd, "ROLL%", AKBASIC_HOSTFIELD_FLOAT )
|
||||||
};
|
};
|
||||||
static const akbasic_HostType GAME_TYPE = {
|
static const akbasic_HostType GAME_TYPE = {
|
||||||
"GAME", sizeof(galaga_Shared), GAME_FIELDS, 4
|
"GAME", sizeof(galaga_Shared), GAME_FIELDS, 4
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ REM A demoscene production for the akbasic interpreter, written as if
|
|||||||
REM it were 1985 and this dialect were the machine under the tree.
|
REM it were 1985 and this dialect were the machine under the tree.
|
||||||
REM It leans on every corner of the interpreter on purpose:
|
REM It leans on every corner of the interpreter on purpose:
|
||||||
REM
|
REM
|
||||||
REM - there is no RND, so it carries chapter 17's LCG and seeds it
|
REM - it carries chapter 17's LCG rather than the native RND, and seeds it
|
||||||
REM from the jiffy clock
|
REM from the jiffy clock
|
||||||
REM - the palette cannot be rewritten, so every "colour cycle" is an
|
REM - the palette cannot be rewritten, so every "colour cycle" is an
|
||||||
REM honest redraw of the same strokes in the next colour
|
REM honest redraw of the same strokes in the next colour
|
||||||
@@ -81,7 +81,7 @@ REM machine and a predeclared scratch variable occupies one forever,
|
|||||||
REM where a scoped one gives its slot back. The first cut of this
|
REM where a scoped one gives its slot back. The first cut of this
|
||||||
REM program predeclared everything and ran the pool dry.
|
REM program predeclared everything and ran the pool dry.
|
||||||
RMAX# = 0
|
RMAX# = 0
|
||||||
RND# = 0
|
ROLL# = 0
|
||||||
TX% = 0
|
TX% = 0
|
||||||
TB# = 0
|
TB# = 0
|
||||||
|
|
||||||
@@ -816,11 +816,12 @@ REM =====================================================================
|
|||||||
REM Subroutines.
|
REM Subroutines.
|
||||||
REM =====================================================================
|
REM =====================================================================
|
||||||
|
|
||||||
REM Chapter 17's generator, verbatim: there is no RND in this dialect.
|
REM Chapter 17's historical generator, verbatim. RND(n) is built in now;
|
||||||
REM Answers 0 to RMAX#-1 in RND#, from the middle bits of the seed.
|
REM this stays so the demo runs identically on every machine.
|
||||||
|
REM Answers 0 to RMAX#-1 in ROLL#, from the middle bits of the seed.
|
||||||
LABEL RANDOM
|
LABEL RANDOM
|
||||||
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
||||||
RND# = MOD((SEED# / 65536), RMAX#)
|
ROLL# = MOD((SEED# / 65536), RMAX#)
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
REM NS# stars in four brightnesses. Two LCG pulls a star, and the
|
REM NS# stars in four brightnesses. Two LCG pulls a star, and the
|
||||||
@@ -830,10 +831,10 @@ SI# = 0
|
|||||||
DO WHILE SI# < NS#
|
DO WHILE SI# < NS#
|
||||||
RMAX# = W#
|
RMAX# = W#
|
||||||
GOSUB RANDOM
|
GOSUB RANDOM
|
||||||
X# = RND#
|
X# = ROLL#
|
||||||
RMAX# = H#
|
RMAX# = H#
|
||||||
GOSUB RANDOM
|
GOSUB RANDOM
|
||||||
Y# = RND#
|
Y# = ROLL#
|
||||||
B# = MOD(SEED#, 4)
|
B# = MOD(SEED#, 4)
|
||||||
COLOR 1, STC#(B#)
|
COLOR 1, STC#(B#)
|
||||||
DRAW 1, X#, Y#
|
DRAW 1, X#, Y#
|
||||||
@@ -1291,7 +1292,7 @@ LABEL REHOME
|
|||||||
MOVSPR L2#, 0.5 * W#, 0.5 * H#
|
MOVSPR L2#, 0.5 * W#, 0.5 * H#
|
||||||
RMAX# = 360
|
RMAX# = 360
|
||||||
GOSUB RANDOM
|
GOSUB RANDOM
|
||||||
SBG#(L2#) = RND#
|
SBG#(L2#) = ROLL#
|
||||||
MOVSPR L2#, SBG#(L2#) # 5
|
MOVSPR L2#, SBG#(L2#) # 5
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,12 @@
|
|||||||
* libakerror 2.0.0 is the floor, raised from 1.0.0 because 2.0.0 is an ABI break
|
* libakerror 2.0.0 is the floor, raised from 1.0.0 because 2.0.0 is an ABI break
|
||||||
* that a compile against the wrong header cannot survive quietly:
|
* that a compile against the wrong header cannot survive quietly:
|
||||||
*
|
*
|
||||||
* - `__akerr_last_ignored` became thread-local. `IGNORE` expands at *our* call
|
* - The context behind `IGNORE` became thread-local. `IGNORE` expands at *our*
|
||||||
* site, so our objects reference that symbol under whichever storage model
|
* call site, so our objects reference that storage under whichever model the
|
||||||
* the header on the include path declared.
|
* header on the include path declared. 2.0.2 went further and made it a
|
||||||
|
* per-translation-unit `static` snapshot named `akerr_last_ignored`, copied
|
||||||
|
* from the pool slot so the slot can be released; the old spelling
|
||||||
|
* `__akerr_last_ignored` was an `extern` pointer and no longer exists.
|
||||||
* - `akerr_next_error()` now returns a context that already holds a reference,
|
* - `akerr_next_error()` now returns a context that already holds a reference,
|
||||||
* and `ENSURE_ERROR_READY` no longer increments. Objects compiled against a
|
* and `ENSURE_ERROR_READY` no longer increments. Objects compiled against a
|
||||||
* 1.x header count every reference twice and never give a slot back.
|
* 1.x header count every reference twice and never give a slot back.
|
||||||
|
|||||||
@@ -252,6 +252,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.
|
||||||
|
|||||||
@@ -407,17 +407,22 @@ akerr_ErrorContext *akbasic_cmd_directory(akbasic_Runtime *obj, akbasic_ASTLeaf
|
|||||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||||
"NULL argument in DIRECTORY");
|
"NULL argument in DIRECTORY");
|
||||||
/*
|
/*
|
||||||
* Refused rather than half-built. Listing a directory needs opendir/readdir,
|
* Refused rather than half-built. This was blocked upstream: listing a
|
||||||
* which `libakstdlib` does not wrap -- and this project's rule is that a
|
* directory needs opendir/readdir, `libakstdlib` did not wrap them, and
|
||||||
* missing capability gets filed upstream rather than worked around here
|
* this project's rule is that a missing capability gets filed upstream
|
||||||
* (MAINTENANCE.md). Filed as libakstdlib issue #10.
|
* rather than worked around here (MAINTENANCE.md). That was libakstdlib
|
||||||
|
* issue #10, and it landed -- aksl_opendir, aksl_readdir, aksl_closedir
|
||||||
|
* and aksl_rewinddir all exist as of the revision this tree pins.
|
||||||
*
|
*
|
||||||
* The alternative was shelling out to `ls`, which a library has no business
|
* So the blocker is gone and only the work is left. Writing the verb needs
|
||||||
* doing, or calling readdir directly and stepping outside the error
|
* decisions this commit is not the place for: what a listing looks like on
|
||||||
* convention every other call in this file follows.
|
* a filesystem with no disk-image block counts, which of the Commodore
|
||||||
|
* wildcard forms to honour, and where the entries go. Tracked as akbasic
|
||||||
|
* issue #55; the refusal stays honest until then rather than growing a
|
||||||
|
* half-listing nobody specified.
|
||||||
*/
|
*/
|
||||||
FAIL_RETURN(errctx, AKBASIC_ERR_DEVICE,
|
FAIL_RETURN(errctx, AKBASIC_ERR_DEVICE,
|
||||||
"DIRECTORY is not implemented: libakstdlib has no directory-reading wrapper yet");
|
"DIRECTORY is not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ BSAVE/BLOAD -- */
|
/* ------------------------------------------------------------ BSAVE/BLOAD -- */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -244,10 +244,16 @@ static void test_no_drive_verbs(void)
|
|||||||
harness_stop();
|
harness_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DIRECTORY is refused for a different reason, and says which. */
|
/*
|
||||||
|
* DIRECTORY is refused for a different reason than the five above: not for
|
||||||
|
* want of a drive, but because it is unwritten. It used to name the missing
|
||||||
|
* libakstdlib wrapper; that wrapper landed, so naming it would be a lie.
|
||||||
|
*/
|
||||||
TEST_REQUIRE_OK(run_program("10 DIRECTORY\n"));
|
TEST_REQUIRE_OK(run_program("10 DIRECTORY\n"));
|
||||||
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "libakstdlib") != NULL,
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "not implemented") != NULL,
|
||||||
"DIRECTORY should name the missing wrapper, got \"%s\"", HARNESS_OUTPUT);
|
"DIRECTORY should say it is unwritten, got \"%s\"", HARNESS_OUTPUT);
|
||||||
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "libakstdlib") == NULL,
|
||||||
|
"DIRECTORY must not still blame libakstdlib, got \"%s\"", HARNESS_OUTPUT);
|
||||||
harness_stop();
|
harness_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -162,6 +162,26 @@ int main(void)
|
|||||||
expect_int("A# = INSTR(\"HELLO\", \"LL\")", 2);
|
expect_int("A# = INSTR(\"HELLO\", \"LL\")", 2);
|
||||||
expect_int("A# = INSTR(\"HELLO\", \"ZZ\")", -1);
|
expect_int("A# = INSTR(\"HELLO\", \"ZZ\")", -1);
|
||||||
|
|
||||||
|
/* RND auto-seeds from host time and follows the documented LCG. */
|
||||||
|
HARNESS_RUNTIME.rndseeded = false;
|
||||||
|
TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 12345));
|
||||||
|
expect_int("A# = RND(6)", 0);
|
||||||
|
expect_int("A# = RND(6)", 4);
|
||||||
|
expect_int("A# = RND(6)", 1);
|
||||||
|
expect_int("A# = RND(6)", 0);
|
||||||
|
expect_int("A# = RND(6)", 1);
|
||||||
|
expect_int("A# = RND(1)", 0);
|
||||||
|
TEST_REQUIRE_STATUS(eval_line("A# = RND(0)", &out), AKBASIC_ERR_VALUE);
|
||||||
|
TEST_REQUIRE_STATUS(eval_line("A# = RND(-1)", &out), AKBASIC_ERR_VALUE);
|
||||||
|
TEST_REQUIRE_STATUS(eval_line("A# = RND(\"x\")", &out), AKBASIC_ERR_TYPE);
|
||||||
|
|
||||||
|
/* ASC is the inverse of CHR for ASCII and non-ASCII code points. */
|
||||||
|
expect_int("A# = ASC(CHR(97))", 97);
|
||||||
|
expect_int("A# = ASC(\"A\")", 65);
|
||||||
|
expect_int("A# = ASC(CHR(8364))", 8364);
|
||||||
|
TEST_REQUIRE_STATUS(eval_line("A# = ASC(\"\")", &out), AKBASIC_ERR_BOUNDS);
|
||||||
|
TEST_REQUIRE_STATUS(eval_line("A# = ASC(65)", &out), AKBASIC_ERR_TYPE);
|
||||||
|
|
||||||
/* An unknown verb is diagnosed rather than silently ignored. */
|
/* An unknown verb is diagnosed rather than silently ignored. */
|
||||||
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
|
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
|
||||||
TEST_REQUIRE_STATUS(akbasic_runtime_evaluate(&HARNESS_RUNTIME, NULL, &out),
|
TEST_REQUIRE_STATUS(akbasic_runtime_evaluate(&HARNESS_RUNTIME, NULL, &out),
|
||||||
|
|||||||
Reference in New Issue
Block a user