Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 699ac9ab93 | |||
|
fac84acdaa
|
|||
|
0d6a817b7c
|
|||
|
78b7b55103
|
|||
|
c8d90c2743
|
|||
|
b434be1901
|
|||
|
330d731cfe
|
@@ -26,14 +26,42 @@ jobs:
|
|||||||
# skipped for the same reason: our CMakeLists declares akerror::akerror
|
# skipped for the same reason: our CMakeLists declares akerror::akerror
|
||||||
# first and libakstdlib guards on if(NOT TARGET ...).
|
# first and libakstdlib guards on if(NOT TARGET ...).
|
||||||
submodules: true
|
submodules: true
|
||||||
|
# moreutils is load-bearing, not incidental: it supplies errno(1), which
|
||||||
|
# deps/libakerror/scripts/generrno.sh shells out to at build time to
|
||||||
|
# generate its errno name table and to stamp AKERR_LAST_ERRNO_VALUE into
|
||||||
|
# the generated akerror.h.
|
||||||
|
#
|
||||||
|
# Its absence does not fail the build, which is what made this expensive to
|
||||||
|
# find. The script prints "errno: command not found" twice, emits an empty
|
||||||
|
# akerr_init_errno() so no errno ever gets a name, and substitutes an empty
|
||||||
|
# AKERR_LAST_ERRNO_VALUE -- so every code in libakerror's reserved band
|
||||||
|
# collapses from (134 + n) to ( + n), i.e. to n. AKERR_OUTOFBOUNDS is
|
||||||
|
# (AKERR_LAST_ERRNO_VALUE + 2), so it becomes literally 2, and ERR(2)
|
||||||
|
# answers "Out Of Bounds Error" where ENOENT's "No such file or directory"
|
||||||
|
# belongs. That is the docs_examples failure at docs/15-error-codes.md:105
|
||||||
|
# that this job carried on every run since #2.
|
||||||
- name: dependencies
|
- name: dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y cmake gcc
|
sudo apt-get install -y cmake gcc moreutils
|
||||||
|
# --parallel 2 rather than a bare --parallel, and that bound is measured
|
||||||
|
# rather than cautious. All five jobs in this file say runs-on:
|
||||||
|
# ubuntu-latest and land on the same runner, so a bare --parallel asks for
|
||||||
|
# nproc compilers *per job* and the runner is handed five times that at
|
||||||
|
# once. Run #30 never showed it because akgl_build and coverage were still
|
||||||
|
# dying early; run #31 was the first push on which all five did real work
|
||||||
|
# simultaneously, and the sanitizers job went from a 55-second link phase
|
||||||
|
# to a 14-minute one -- individual `Linking C executable` steps taking two
|
||||||
|
# minutes each, starting at the exact second akgl_build began its SDL tree
|
||||||
|
# -- before the runner's Docker daemon fell over outright and took the job
|
||||||
|
# with it. That is memory exhaustion, and the only lever on it is the
|
||||||
|
# number of concurrent compiler and linker processes. Applied at all four
|
||||||
|
# build sites for the same reason; mutation_test drives its own builds
|
||||||
|
# through the harness and is not bounded here.
|
||||||
- name: build
|
- name: build
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build
|
cmake -S . -B build
|
||||||
cmake --build build --parallel
|
cmake --build build --parallel 2
|
||||||
# The suite is 78 cases: 41 golden files byte-compared against the Go
|
# The suite is 78 cases: 41 golden files byte-compared against the Go
|
||||||
# reference's own corpus (checked in at tests/reference/, see its README),
|
# reference's own corpus (checked in at tests/reference/, see its README),
|
||||||
# 9 local golden cases for verbs the reference never implemented, 25 unit
|
# 9 local golden cases for verbs the reference never implemented, 25 unit
|
||||||
@@ -74,7 +102,7 @@ jobs:
|
|||||||
- name: dependencies
|
- name: dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y cmake gcc
|
sudo apt-get install -y cmake gcc moreutils
|
||||||
# The whole suite under ASan and UBSan, golden files included. This is the
|
# The whole suite under ASan and UBSan, golden files included. This is the
|
||||||
# gate libakstdlib's TODO.md section 1 calls its highest-value missing item
|
# gate libakstdlib's TODO.md section 1 calls its highest-value missing item
|
||||||
# -- worth having here from the start, because this library is all fixed
|
# -- worth having here from the start, because this library is all fixed
|
||||||
@@ -84,7 +112,7 @@ jobs:
|
|||||||
cmake -S . -B build-asan \
|
cmake -S . -B build-asan \
|
||||||
-DAKBASIC_SANITIZE=ON \
|
-DAKBASIC_SANITIZE=ON \
|
||||||
-DCMAKE_BUILD_TYPE=Debug
|
-DCMAKE_BUILD_TYPE=Debug
|
||||||
cmake --build build-asan --parallel
|
cmake --build build-asan --parallel 2
|
||||||
ctest --test-dir build-asan --output-on-failure
|
ctest --test-dir build-asan --output-on-failure
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
|
|
||||||
@@ -98,7 +126,7 @@ jobs:
|
|||||||
- name: dependencies
|
- name: dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y cmake gcc gcovr
|
sudo apt-get install -y cmake gcc gcovr moreutils
|
||||||
# The gate is a ratchet, not a target: src/ sits at 94.6% of lines and
|
# The gate is a ratchet, not a target: src/ sits at 94.6% of lines and
|
||||||
# 98.6% of functions, so 90 fails on a real regression (a test deleted, or
|
# 98.6% of functions, so 90 fails on a real regression (a test deleted, or
|
||||||
# new untested code added) without tripping over rounding.
|
# new untested code added) without tripping over rounding.
|
||||||
@@ -124,7 +152,7 @@ jobs:
|
|||||||
cmake -S . -B build-coverage \
|
cmake -S . -B build-coverage \
|
||||||
-DAKBASIC_COVERAGE=ON \
|
-DAKBASIC_COVERAGE=ON \
|
||||||
-DCMAKE_BUILD_TYPE=Debug
|
-DCMAKE_BUILD_TYPE=Debug
|
||||||
cmake --build build-coverage --parallel
|
cmake --build build-coverage --parallel 2
|
||||||
ctest --test-dir build-coverage --output-on-failure
|
ctest --test-dir build-coverage --output-on-failure
|
||||||
mkdir -p build-coverage/coverage
|
mkdir -p build-coverage/coverage
|
||||||
gcovr --root . --filter 'src/.*' \
|
gcovr --root . --filter 'src/.*' \
|
||||||
@@ -134,9 +162,21 @@ jobs:
|
|||||||
--fail-under-line 90
|
--fail-under-line 90
|
||||||
# Publish even when the threshold gate fails, so the uncovered lines are
|
# Publish even when the threshold gate fails, so the uncovered lines are
|
||||||
# visible -- each one is a missing test.
|
# visible -- each one is a missing test.
|
||||||
|
#
|
||||||
|
# @v3, not @v4, and that is Gitea rather than preference. @v4 bundles
|
||||||
|
# @actions/artifact v2, whose isGhes() treats any GITHUB_SERVER_URL that is
|
||||||
|
# not github.com as GitHub Enterprise Server and refuses outright:
|
||||||
|
# "GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+
|
||||||
|
# and download-artifact@v4+ are not currently supported on GHES". @v3 uses
|
||||||
|
# the older artifact API, which this forge does implement.
|
||||||
|
#
|
||||||
|
# This never showed before because the step had nothing to upload: gcovr
|
||||||
|
# was never reached, so the step warned "No files were found" and passed.
|
||||||
|
# Fixing the suite is what first gave it a real file to refuse. Same class
|
||||||
|
# of accommodation as the annotate_only flag on the junit reporter above.
|
||||||
- name: upload coverage reports
|
- name: upload coverage reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: code-coverage
|
name: code-coverage
|
||||||
path: build-coverage/coverage/
|
path: build-coverage/coverage/
|
||||||
@@ -161,22 +201,56 @@ jobs:
|
|||||||
# embedded it takes a find_package path instead, so our CMakeLists declares
|
# embedded it takes a find_package path instead, so our CMakeLists declares
|
||||||
# those targets first and needs the submodules present. Six of them, none
|
# those targets first and needs the submodules present. Six of them, none
|
||||||
# recursive. Filed upstream as libakgl API-gap item 5.
|
# recursive. Filed upstream as libakgl API-gap item 5.
|
||||||
|
#
|
||||||
|
# Eight, not six: deps/libccd and deps/clay were missing and are not
|
||||||
|
# optional. libakgl does not add_subdirectory either of them -- their own
|
||||||
|
# CMakeLists are unusable as subprojects -- it compiles them into itself,
|
||||||
|
# so nothing declares them and configuration dies late with "File
|
||||||
|
# deps/libccd/src/ccd/config.h.cmake.in does not exist" at
|
||||||
|
# deps/libakgl/CMakeLists.txt:282. clay is the same shape one step later:
|
||||||
|
# deps/clay/clay.h is on the include path and installed.
|
||||||
|
#
|
||||||
|
# deps/tg is a real submodule of libakgl and is deliberately not here --
|
||||||
|
# nothing in its CMakeLists references it. libakerror and libakstdlib are
|
||||||
|
# skipped for the reason the checkout note above gives.
|
||||||
- name: libakgl dependencies
|
- name: libakgl dependencies
|
||||||
run: |
|
run: |
|
||||||
git -C deps/libakgl submodule update --init \
|
git -C deps/libakgl submodule update --init \
|
||||||
deps/SDL deps/SDL_image deps/SDL_mixer deps/SDL_ttf \
|
deps/SDL deps/SDL_image deps/SDL_mixer deps/SDL_ttf \
|
||||||
deps/jansson deps/semver
|
deps/jansson deps/semver deps/libccd deps/clay
|
||||||
# libfreetype-dev and libharfbuzz-dev are load-bearing, not incidental.
|
# libfreetype-dev and libharfbuzz-dev are load-bearing, not incidental.
|
||||||
# SDL_ttf prefers the system copies -- it reports "Using system freetype
|
# SDL_ttf prefers the system copies -- it reports "Using system freetype
|
||||||
# library" and links libfreetype.so.6 -- and without them it would reach
|
# library" and links libfreetype.so.6 -- and without them it would reach
|
||||||
# for deps/SDL_ttf/external/freetype, which the checkout above
|
# for deps/SDL_ttf/external/freetype, which the checkout above
|
||||||
# deliberately does not clone. Installing two dev packages is much cheaper
|
# deliberately does not clone. Installing two dev packages is much cheaper
|
||||||
# than cloning freetype and harfbuzz.
|
# than cloning freetype and harfbuzz.
|
||||||
|
#
|
||||||
|
# The X11 dev packages are the other half, and they are what this job has
|
||||||
|
# been dying on since run #2 -- it has never once been green. SDL_X11 and
|
||||||
|
# its nine sub-options default ON on Linux, and CheckX11() in
|
||||||
|
# deps/SDL/cmake/sdlchecks.cmake calls SDL_missing_dependency() -- a hard
|
||||||
|
# CMake error, not a downgrade -- for any one of them whose header is
|
||||||
|
# absent. The runner image carries libx11-dev (X11/XKBlib.h resolved) but
|
||||||
|
# none of the extension packages, so configuration stopped at the first of
|
||||||
|
# the nine: "Couldn't find dependency package for XCURSOR".
|
||||||
|
#
|
||||||
|
# Turning the missing ones OFF would also configure, and is the wrong
|
||||||
|
# answer: the point of this job is that an AKGL build is a real SDL
|
||||||
|
# program, and a real SDL program on Linux builds the X11 backend. The
|
||||||
|
# tests still run headless under SDL_VIDEODRIVER=dummy -- see the env block
|
||||||
|
# below -- so this buys a faithful build, not a display.
|
||||||
|
#
|
||||||
|
# The list is SDL's own, from deps/SDL/docs/README-linux.md, reduced to the
|
||||||
|
# X11 entries: the audio, Wayland, KMSDRM and Vulkan backends all degrade
|
||||||
|
# to "not found" and skip themselves rather than erroring, so they cost
|
||||||
|
# nothing to leave out. Nine packages, all in noble.
|
||||||
- name: dependencies
|
- name: dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y cmake gcc g++ pkg-config \
|
sudo apt-get install -y cmake gcc g++ pkg-config moreutils \
|
||||||
libfreetype-dev libharfbuzz-dev
|
libfreetype-dev libharfbuzz-dev \
|
||||||
|
libx11-dev libxext-dev libxcursor-dev libxi-dev libxfixes-dev \
|
||||||
|
libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev
|
||||||
# The akgl-backed half: the text sink and the graphics, audio and input
|
# The akgl-backed half: the text sink and the graphics, audio and input
|
||||||
# backends, the standalone SDL frontend, and the two suites that drive
|
# backends, the standalone SDL frontend, and the two suites that drive
|
||||||
# them against a real software renderer and read the pixels back.
|
# them against a real software renderer and read the pixels back.
|
||||||
@@ -194,7 +268,7 @@ jobs:
|
|||||||
- name: build with libakgl
|
- name: build with libakgl
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B build-akgl -DAKBASIC_WITH_AKGL=ON
|
cmake -S . -B build-akgl -DAKBASIC_WITH_AKGL=ON
|
||||||
cmake --build build-akgl --parallel
|
cmake --build build-akgl --parallel 2
|
||||||
# Dummy video and audio drivers, set per test by CMakeLists rather than in
|
# Dummy video and audio drivers, set per test by CMakeLists rather than in
|
||||||
# this job's environment, because an AKGL build of `basic` is now an SDL
|
# this job's environment, because an AKGL build of `basic` is now an SDL
|
||||||
# program and the golden cases run *it*: forty-one real windows is not what
|
# program and the golden cases run *it*: forty-one real windows is not what
|
||||||
@@ -256,7 +330,7 @@ jobs:
|
|||||||
- name: dependencies
|
- name: dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y cmake gcc python3
|
sudo apt-get install -y cmake gcc python3 moreutils
|
||||||
# Verify the tests actually catch bugs: break the library many ways and
|
# Verify the tests actually catch bugs: break the library many ways and
|
||||||
# confirm the suite fails. This matters more here than in an ordinary C
|
# confirm the suite fails. This matters more here than in an ordinary C
|
||||||
# library, because the akerror control-flow macros expand at their call
|
# library, because the akerror control-flow macros expand at their call
|
||||||
|
|||||||
@@ -70,10 +70,14 @@ jobs:
|
|||||||
# corpus is part of what kills mutants and it is checked in now, so
|
# corpus is part of what kills mutants and it is checked in now, so
|
||||||
# that is libakerror and libakstdlib and nothing else.
|
# that is libakerror and libakstdlib and nothing else.
|
||||||
submodules: true
|
submodules: true
|
||||||
|
# moreutils for errno(1), which deps/libakerror's scripts/generrno.sh needs
|
||||||
|
# to generate its errno table. Without it the generated table is empty and
|
||||||
|
# every AKERR_* code collapses onto a low integer -- see the long note on
|
||||||
|
# ci.yaml's cmake_build job for what that breaks and how it presents.
|
||||||
- name: dependencies
|
- name: dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y cmake gcc python3
|
sudo apt-get install -y cmake gcc python3 moreutils
|
||||||
# The whole akbasic-owned src/ tree. ci.yaml runs a two-file subset on every
|
# The whole akbasic-owned src/ tree. ci.yaml runs a two-file subset on every
|
||||||
# push; this is the one that actually covers the interpreter.
|
# push; this is the one that actually covers the interpreter.
|
||||||
#
|
#
|
||||||
|
|||||||
2
deps/libakstdlib
vendored
2
deps/libakstdlib
vendored
Submodule deps/libakstdlib updated: 669b2b395f...2b79aca103
@@ -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. |
|
||||||
|
|||||||
@@ -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$ = ""
|
||||||
|
|||||||
@@ -156,18 +156,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_leaf(akbasic_Environm
|
|||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command);
|
||||||
/**
|
/**
|
||||||
* @brief True when this scope or any enclosing one is skipping forward.
|
* @brief Whether this scope or any enclosing one is skipping forward.
|
||||||
|
*
|
||||||
|
* The answer leaves through @p dest rather than the return value. Deciding it
|
||||||
|
* reads a recorded verb name, that read can fail, and a `bool` return has
|
||||||
|
* nowhere to report the failure -- so the signature changes rather than the
|
||||||
|
* error being swallowed. libakstdlib #38 is the ruling; `probe` in `symtab.c`
|
||||||
|
* and `loop_continues` in `runtime_structure.c` are the same shape.
|
||||||
|
*
|
||||||
* @param obj Scope to inspect; NULL is not waiting.
|
* @param obj Scope to inspect; NULL is not waiting.
|
||||||
* @return `true` when execution is currently suppressed.
|
* @param[out] dest `true` when execution is currently suppressed. Required.
|
||||||
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
|
* @throws AKERR_NULLPOINTER When dest is NULL.
|
||||||
*/
|
*/
|
||||||
bool akbasic_environment_is_waiting_for_any(akbasic_Environment *obj);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for_any(akbasic_Environment *obj, bool *dest);
|
||||||
/**
|
/**
|
||||||
* @brief True when this scope or an enclosing one is waiting for a given verb.
|
* @brief Whether this scope or an enclosing one is waiting for a given verb.
|
||||||
|
*
|
||||||
|
* Reports through @p dest for the same reason its sibling above does.
|
||||||
|
*
|
||||||
* @param obj Scope to inspect; NULL is not waiting.
|
* @param obj Scope to inspect; NULL is not waiting.
|
||||||
* @param command Verb to test for.
|
* @param command Verb to test for; NULL is not waiting.
|
||||||
* @return `true` when that verb is what execution is waiting on.
|
* @param[out] dest `true` when that verb is what execution is waiting on. Required.
|
||||||
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
|
* @throws AKERR_NULLPOINTER When dest is NULL.
|
||||||
*/
|
*/
|
||||||
bool akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command, bool *dest);
|
||||||
/**
|
/**
|
||||||
* @brief Clear a pending wait, searching the parent chain for it.
|
* @brief Clear a pending wait, searching the parent chain for it.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
18
src/data.c
18
src/data.c
@@ -8,7 +8,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
|
||||||
|
|
||||||
#include <akerror.h>
|
#include <akerror.h>
|
||||||
#include <akstdlib.h>
|
#include <akstdlib.h>
|
||||||
@@ -112,6 +111,7 @@ static akerr_ErrorContext *scan_line(akbasic_DataState *obj, const char *code, i
|
|||||||
const char *cursor = code;
|
const char *cursor = code;
|
||||||
bool statementstart = true;
|
bool statementstart = true;
|
||||||
bool instring = false;
|
bool instring = false;
|
||||||
|
int cmp = 0;
|
||||||
|
|
||||||
while ( *cursor != '\0' ) {
|
while ( *cursor != '\0' ) {
|
||||||
if ( instring ) {
|
if ( instring ) {
|
||||||
@@ -141,12 +141,14 @@ static akerr_ErrorContext *scan_line(akbasic_DataState *obj, const char *code, i
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ( statementstart && strncasecmp(cursor, "DATA", 4) == 0
|
if ( statementstart ) {
|
||||||
&& !isalnum((unsigned char)cursor[4]) ) {
|
PASS(errctx, aksl_strncasecmp(cursor, "DATA", 4, &cmp));
|
||||||
cursor += 4;
|
if ( cmp == 0 && !isalnum((unsigned char)cursor[4]) ) {
|
||||||
PASS(errctx, collect_items(obj, &cursor, lineno));
|
cursor += 4;
|
||||||
statementstart = false;
|
PASS(errctx, collect_items(obj, &cursor, lineno));
|
||||||
continue;
|
statementstart = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
statementstart = false;
|
statementstart = false;
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
@@ -184,7 +186,7 @@ akerr_ErrorContext *akbasic_data_next(akbasic_Runtime *obj, akbasic_Type type, a
|
|||||||
PASS(errctx, akbasic_value_zero(dest));
|
PASS(errctx, akbasic_value_zero(dest));
|
||||||
if ( type == AKBASIC_TYPE_STRING ) {
|
if ( type == AKBASIC_TYPE_STRING ) {
|
||||||
dest->valuetype = AKBASIC_TYPE_STRING;
|
dest->valuetype = AKBASIC_TYPE_STRING;
|
||||||
snprintf(dest->stringval, sizeof(dest->stringval), "%s", item->text);
|
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), item->text));
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||