Compare commits
2 Commits
16
...
1e514f679b
| Author | SHA1 | Date | |
|---|---|---|---|
|
1e514f679b
|
|||
|
17af2d406c
|
@@ -9,7 +9,6 @@ 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. |
|
||||||
@@ -30,7 +29,6 @@ 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,48 +1005,20 @@ IF NUDGE# = 1 THEN GOSUB UNSTICK
|
|||||||
LABEL UNSTICK
|
LABEL UNSTICK
|
||||||
NUDGE# = 0
|
NUDGE# = 0
|
||||||
STALL# = 0
|
STALL# = 0
|
||||||
BVX# = (RND(4) * 3) - 6
|
RMAX# = 4
|
||||||
|
GOSUB RANDOM
|
||||||
|
BVX# = (RND# * 3) - 6
|
||||||
IF BVX# = 0 THEN BVX# = 3
|
IF BVX# = 0 THEN BVX# = 3
|
||||||
RETURN
|
RETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
### Random numbers are built in
|
### You have to write your own random numbers
|
||||||
|
|
||||||
There is no `INT`, `SQR` or `TIMER` in this dialect, but
|
**There is no `RND` in this dialect**, and no `INT`, `SQR`, `ASC` or `TIMER` either. A
|
||||||
`RND(n)` returns an integer from zero through `n - 1`. It seeds itself
|
linear congruential generator is nine tokens and does the job. Put the number of possible
|
||||||
from the host clock the first time it is called, so a program only needs the bound:
|
answers in `RMAX#` and read the result from `RND#`:
|
||||||
|
|
||||||
```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
|
||||||
@@ -1063,11 +1035,43 @@ RND# = MOD((SEED# / 65536), RMAX#)
|
|||||||
RETURN
|
RETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
The multiplication stays inside a 64-bit integer for any seed below 2147483648. The
|
```output
|
||||||
answer is taken from the middle bits because the low bits of a power-of-two modulus
|
ROLL 1
|
||||||
barely change from one call to the next. This used to be required; it is now built in.
|
ROLL 5
|
||||||
|
ROLL 2
|
||||||
|
ROLL 1
|
||||||
|
ROLL 2
|
||||||
|
```
|
||||||
|
|
||||||
</details>
|
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
|
||||||
|
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:
|
||||||
@@ -1418,7 +1422,9 @@ PX# = PX# + D#
|
|||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
LABEL DEMOAIM
|
LABEL DEMOAIM
|
||||||
DOFF# = RND(81) - 40
|
RMAX# = 81
|
||||||
|
GOSUB RANDOM
|
||||||
|
DOFF# = RND# - 40
|
||||||
RETURN
|
RETURN
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1495,7 +1501,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
|
||||||
RND(n) seeds itself from the host clock
|
SEED# = TI#
|
||||||
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
|
||||||
@@ -1570,7 +1576,10 @@ 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$ = ""
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ This is a tour of the interpreter's edges, on purpose:
|
|||||||
counterpoint: a genuine 24x21 `SPRSAV` type-in sprite, 63 bytes, which is
|
counterpoint: a genuine 24x21 `SPRSAV` type-in sprite, 63 bytes, which is
|
||||||
everything `DATA` has room to say. The paint is the reveal, and the exit is a
|
everything `DATA` has room to say. The paint is the reveal, and the exit is a
|
||||||
stride-63 column dissolve — 63 is coprime to 160, so the walk hits every
|
stride-63 column dissolve — 63 is coprime to 160, so the walk hits every
|
||||||
column once and looks random while carrying no state.
|
column once and looks random while carrying no state. The strings come in
|
||||||
|
64-character chunks because a source line is 80 columns, like the machines
|
||||||
|
this pretends to be — the decoder carries its cursor from one `IM$` entry to
|
||||||
|
the next, and the generator refuses to cut inside a run.
|
||||||
- **And then the picture is video.** Six delta frames loop the floor grid toward
|
- **And then the picture is video.** Six delta frames loop the floor grid toward
|
||||||
you and crawl the sun's slice pattern — full motion video, 1.3 KB total, about
|
you and crawl the sun's slice pattern — full motion video, 1.3 KB total, about
|
||||||
220 bytes a frame. A delta re-encodes only the rows that changed: an `R`
|
220 bytes a frame. A delta re-encodes only the rows that changed: an `R`
|
||||||
@@ -82,6 +85,10 @@ This is a tour of the interpreter's edges, on purpose:
|
|||||||
batch a bar of noise drums. Noise is real: `ENVELOPE`'s sixth argument is a
|
batch a bar of noise drums. Noise is real: `ENVELOPE`'s sixth argument is a
|
||||||
waveform, 0 to 3, which Chapter 7 forgot to mention. Voice 1 carries the tune so
|
waveform, 0 to 3, which Chapter 7 forgot to mention. Voice 1 carries the tune so
|
||||||
the collision blips (voice 2) and scene sweeps (voice 3) never steal its channel.
|
the collision blips (voice 2) and scene sweeps (voice 3) never steal its channel.
|
||||||
|
A batch is four `PLAY` statements, one bar each — the 80-column line limit will
|
||||||
|
not hold four bars in one string, and it does not need to: the parser's voice,
|
||||||
|
envelope and duration state persists across statements and every `PLAY` appends
|
||||||
|
to the same queue, so four bars queue exactly as one line would.
|
||||||
|
|
||||||
It also probes for its hardware like a proper boot loader, with one `TRAP` per
|
It also probes for its hardware like a proper boot loader, with one `TRAP` per
|
||||||
device: no graphics refuses by name and exits, no audio mutes the soundtrack, no
|
device: no graphics refuses by name and exits, no audio mutes the soundtrack, no
|
||||||
|
|||||||
@@ -461,38 +461,78 @@ BS% = W# / 160
|
|||||||
IF BS% < 1 THEN BS% = 1
|
IF BS% < 1 THEN BS% = 1
|
||||||
REM ---- PICTURE-BEGIN (generated by vaporwave.py; do not
|
REM ---- PICTURE-BEGIN (generated by vaporwave.py; do not
|
||||||
REM ---- hand-edit -- rerun the script to change the picture)
|
REM ---- hand-edit -- rerun the script to change the picture)
|
||||||
DIM IM$(16)
|
DIM IM$(56)
|
||||||
DIM VA#(6)
|
DIM VA#(6)
|
||||||
DIM VB#(6)
|
DIM VB#(6)
|
||||||
NS# = 9
|
NS# = 32
|
||||||
VA#(0) = 9
|
VA#(0) = 32
|
||||||
VB#(0) = 9
|
VB#(0) = 35
|
||||||
VA#(1) = 10
|
VA#(1) = 36
|
||||||
VB#(1) = 10
|
VB#(1) = 39
|
||||||
VA#(2) = 11
|
VA#(2) = 40
|
||||||
VB#(2) = 12
|
VB#(2) = 43
|
||||||
VA#(3) = 13
|
VA#(3) = 44
|
||||||
VB#(3) = 13
|
VB#(3) = 47
|
||||||
VA#(4) = 14
|
VA#(4) = 48
|
||||||
VB#(4) = 14
|
VB#(4) = 51
|
||||||
VA#(5) = 15
|
VA#(5) = 52
|
||||||
VB#(5) = 15
|
VB#(5) = 55
|
||||||
IM$(0) = "G9G9G9G9GPGHEHG9GTEHG9GTEHGPGPEHG9GTEHG9GTEHGHGXEHGTBAG8EHG9GTEHG5EHGPEHG5EHGPEHG5EHGAPAG3EHGPEHG5EHGPEHGXGHEHG5EHGPEHG5EHGFBAGIEHGPGPEHGPEHGHEHGPEHGPEHGHEHGPEHGHEHGPEHGPEHGHEHGBPAGMEHGPEHGHEHGPEHGHEHGPEHGPEHGHEHGPEHGPEHGHEHGPEHGHEPGHEHGPEH"
|
IM$(0) = "G9G9G9G9GPGHEHG9GTEHG9GTEHGPGPEHG9GTEHG9GTEHGHGXEHGTBAG8EHG9GTEH"
|
||||||
IM$(1) = "GHEPGHEHGPEHGHEPGHEHGHEGBAEHGHEHGPEHGHEPGHEHGPEHGHEHGPEHGHEPGHEHGPEHGHEPGHEHGPECPAEDGHEPGHEHGHEPGHEPGHEHGHEPGHEPGHEHGHEPGHEHGHEPGHEPGHECBAEDGHEPGHEPGHEHGHEPGHEHGHEPGHEPGHEHGHEPGHEPEPGHEPGHE5GHEPGHE5GHEHEXGHEPGHEEPAEZGHEPGHE5GHE5GHEPGHE5GHEP"
|
IM$(1) = "G5EHGPEHG5EHGPEHG5EHGAPAG3EHGPEHG5EHGPEHGXGHEHG5EHGPEHG5EHGFBAGI"
|
||||||
IM$(2) = "GHE5GHE9ETGHE9ETGHEXEHGHE9ETGHHAE9ESGHEPEPGBBAGEE9EMHOE9ETGHEHE9E7HUE9E6E9E4H0E9E3E9E3H2E9EHBAETEHKHE9ELH6E9ECKHEPEPKHE9EBH9HAE9EIKHEHEXKHE2H9HCE9EPKHE5KHEPKDH9HEKCEPKHE5KHE5KHEKH9HGEBKHEPKHEXEHKHE5KHEBH9HIEIKHEPKHEPEPKHEPKHEHKAH9HKKHEHKHEP"
|
IM$(2) = "EHGPGPEHGPEHGHEHGPEHGPEHGHEHGPEHGHEHGPEHGPEHGHEHGBPAGMEHGPEHGHEH"
|
||||||
IM$(3) = "KHEHKHEPKHEPKHH9HMEGKHEHKHEPKHEHKHEPKHEPH9HMEOKHEHKHEPKHEHKPEHKHEGH9HOKFEPKHEHKPEHKHEHKPEHKGH9HOEFKHEPKHEHKHEPKHEHKPEFH9HQKEEHKHEPKHEHKHEPKHEHKMH9HSKLEHKHEPKHEHKPEHKHEHKEH9HSEDKPEHKPEHKHEHKPEHKHEEH9HSKDEHKPEHKPKPEHKPEHKDH9HUECKHEHKPEHKHKXEH"
|
IM$(3) = "GPEHGHEHGPEHGPEHGHEHGPEHGPEHGHEHGPEHGHEPGHEHGPEHGHEPGHEHGPEHGHEP"
|
||||||
IM$(4) = "KPEDH9HUKCEHK5EHK5EHKLH9HUKKEHK5EHK5EHKCH9HWEBKPEHKXKHEHK8I9IWKZEHKPKPEHK0I9IWK7EHKHKXEHKSI9IWK9KFEHK9KOI9IWK9KNK9KOI9IWK9KNK9KOI9IWK9KNKPIHKZI9IYK6IHKHKXIHKSI9IWK9KFIHK5IHKKI9IWK9KNIHK5IHKCI9IYKPIHKXKHIHK5IHKPIHK5IHKPIHKPKPIHK0I9IWKJIHKPIH"
|
IM$(4) = "GHEHGHEGBAEHGHEHGPEHGHEPGHEHGPEHGHEHGPEHGHEPGHEHGPEHGHEPGHEHGPEC"
|
||||||
IM$(5) = "KHIHKPIHKPI9IZKBIHKHIHKPIHKHIHKPIHKKI9IWKJIHKHIHKPIHKHIHKPIHKDI9IXKPIHKHIHKHKHIHKHIPKHI9IYKCIHKPIHKHIHKPIHKHIPKHIHKPIHKHIPKHIHKPIHKHIHKPIHKHI9I9IHKHIHKPIHKHIPKHIHKHIEC9CSKDIPKHIPKHIHKHIPKHIHKEC9CSIDKHIPKHIPIPKHIPKHIFC9CQKEIHKHIPKHIHIXKHIPKG"
|
IM$(5) = "PAEDGHEPGHEHGHEPGHEPGHEHGHEPGHEPGHEHGHEPGHEHGHEPGHEPGHECBAEDGHEP"
|
||||||
IM$(6) = "C9COIFKHI5KHI5KHIPKHI5KHIPKHI5KHI5KHIPKHI5KHIPKHIXIHKHI9IDC9CMI4KHIPIPKHI6C9CKI9IDKHIHIXKHIZC9CII9IMKHI9IWC9CGI9IVI9I9I9I9IPI9I9I9I9IPQ9Q9Q9Q9QPE9E9E9E9EPE9E9E9E9EPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9"
|
IM$(6) = "GHEPGHEHGHEPGHEHGHEPGHEPGHEHGHEPGHEPEPGHEPGHE5GHEPGHE5GHEHEXGHEP"
|
||||||
IM$(7) = "Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9"
|
IM$(7) = "GHEEPAEZGHEPGHE5GHE5GHEPGHE5GHEPGHE5GHE9ETGHE9ETGHEXEHGHE9ETGHHA"
|
||||||
IM$(8) = "Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QP"
|
IM$(8) = "E9ESGHEPEPGBBAGEE9EMHOE9ETGHEHE9E7HUE9E6E9E4H0E9E3E9E3H2E9EHBAET"
|
||||||
IM$(9) = "RBRQ9QOKMQHK5RBSQ9QTI9IRRBXQ9QTKPQHKHQPKERBYQ9QPI9IHRB3Q9QSKAI5KHIJRB5Q9QTC9CMRB9Q9QWI9IGRCBQ9QYC9CCRCIA9A9A9A9APRCJE9E9E9E9EPRCNA9A9A9A9APRCOE9E9E9E9EPRCUA9A9A9A9APRCVE9E9E9E9EPRC5A9A9A9A9APRC7E9E9E9E9EP"
|
IM$(9) = "EHKHE9ELH6E9ECKHEPEPKHE9EBH9HAE9EIKHEHEXKHE2H9HCE9EPKHE5KHEPKDH9"
|
||||||
IM$(10) = "RBQQ9QOK9KIQHKFRBRQ9QOI9IQRBWQ9QPKLQHKHQHKPRBXQ9QTI9QLIERB2Q9QRIBKHIPKHIPKCRB4Q9QSC9CORB8Q9QVI3KHIGRCAQ9QXC9CERCJA9A9A9A9APRCKE9E9E9E9EPRCOA9A9A9A9APRCPE9E9E9E9EPRCVA9A9A9A9APRCXE9E9E9E9EPRC7A9A9A9A9APRDAE9E9E9E9EP"
|
IM$(10) = "HEKCEPKHE5KHE5KHEKH9HGEBKHEPKHEXEHKHE5KHEBH9HIEIKHEPKHEPEPKHEPKH"
|
||||||
IM$(11) = "RBPQ9QOK9KAQHKNRBQQ9QOI9IWRBVQ9QOKEQHKHQHKPQHKFRBWQ9QPI9IPRB1Q9QQKCIPKHIPKHIDRB3Q9QSC9CORB7Q9QUIWKHIPRB9Q9QWC9CGRCEA9A9A9A9APRCFE9E9E9E9EPRCGA9A9A9A9APRCHE9E9E9E9EPRCPA9A9A9A9APRCQE9E9E9E9EPRCXA9A9A9A9APRCZE9E9E9E9EPRDAA9A9A9A9APRDCE9E9E9E9"
|
IM$(11) = "EHKAH9HKKHEHKHEPKHEHKHEPKHEPKHH9HMEGKHEHKHEPKHEHKHEPKHEPH9HMEOKH"
|
||||||
IM$(12) = "EP"
|
IM$(12) = "EHKHEPKHEHKPEHKHEGH9HOKFEPKHEHKPEHKHEHKPEHKGH9HOEFKHEPKHEHKHEPKH"
|
||||||
IM$(13) = "RBOQ9QNK3QHKWRBPQ9QOI9IWRBUQ9QTKHQHKPQHKNRBVQ9QOI9IWRB0Q9QQIKKHIPKHIHKDRB1Q9QQC9CSRB2Q9QRC9CQRB6Q9QTIPKHIYRB8Q9QVC9CIRCKA9A9A9A9APRCLE9E9E9E9EPRCQA9A9A9A9APRCRE9E9E9E9EPRCZA9A9A9A9APRC1E9E9E9E9EPRDCA9A9A9A9APRDFE9E9E9E9EP"
|
IM$(13) = "EHKPEFH9HQKEEHKHEPKHEHKHEPKHEHKMH9HSKLEHKHEPKHEHKPEHKHEHKEH9HSED"
|
||||||
IM$(14) = "RBOQ9QNI9IYRBTQ9QOKEQHKPQHKVRBUQ9QTI9IRRBZQ9QTKHQHKPQHKHRB0Q9QQC9CSRB5Q9QTIHKHI5KARB7Q9QUC9CKRCBQ9QYI9ICRCHA9A9A9A9APRCIE9E9E9E9EPRCLA9A9A9A9APRCME9E9E9E9EPRCRA9A9A9A9APRCTE9E9E9E9EPRC1A9A9A9A9APRC3E9E9E9E9EPRDFA9A9A9A9APRDIE9E9E9E9EP"
|
IM$(14) = "KPEHKPEHKHEHKPEHKHEEH9HSKDEHKPEHKPKPEHKPEHKDH9HUECKHEHKPEHKHKXEH"
|
||||||
IM$(15) = "RBSQ9QTKPQHK3RBTQ9QOI9IWRBYQ9QPKDQHKPQHKHRBZQ9QTI9ILRB4Q9QSIAKHI5KHIBRB6Q9QTC9CMRCAQ9QXI9IERCEE9E9E9E9EPRCFA9A9A9A9APRCGE9E9E9E9EPRCMA9A9A9A9APRCNE9E9E9E9EPRCTA9A9A9A9APRCUE9E9E9E9EPRC3A9A9A9A9APRC5E9E9E9E9EPRDIA9A9A9A9AP"
|
IM$(15) = "KPEDH9HUKCEHK5EHK5EHKLH9HUKKEHK5EHK5EHKCH9HWEBKPEHKXKHEHK8I9IWKZ"
|
||||||
|
IM$(16) = "EHKPKPEHK0I9IWK7EHKHKXEHKSI9IWK9KFEHK9KOI9IWK9KNK9KOI9IWK9KNK9KO"
|
||||||
|
IM$(17) = "I9IWK9KNKPIHKZI9IYK6IHKHKXIHKSI9IWK9KFIHK5IHKKI9IWK9KNIHK5IHKCI9"
|
||||||
|
IM$(18) = "IYKPIHKXKHIHK5IHKPIHK5IHKPIHKPKPIHK0I9IWKJIHKPIHKHIHKPIHKPI9IZKB"
|
||||||
|
IM$(19) = "IHKHIHKPIHKHIHKPIHKKI9IWKJIHKHIHKPIHKHIHKPIHKDI9IXKPIHKHIHKHKHIH"
|
||||||
|
IM$(20) = "KHIPKHI9IYKCIHKPIHKHIHKPIHKHIPKHIHKPIHKHIPKHIHKPIHKHIHKPIHKHI9I9"
|
||||||
|
IM$(21) = "IHKHIHKPIHKHIPKHIHKHIEC9CSKDIPKHIPKHIHKHIPKHIHKEC9CSIDKHIPKHIPIP"
|
||||||
|
IM$(22) = "KHIPKHIFC9CQKEIHKHIPKHIHIXKHIPKGC9COIFKHI5KHI5KHIPKHI5KHIPKHI5KH"
|
||||||
|
IM$(23) = "I5KHIPKHI5KHIPKHIXIHKHI9IDC9CMI4KHIPIPKHI6C9CKI9IDKHIHIXKHIZC9CI"
|
||||||
|
IM$(24) = "I9IMKHI9IWC9CGI9IVI9I9I9I9IPI9I9I9I9IPQ9Q9Q9Q9QPE9E9E9E9EPE9E9E9"
|
||||||
|
IM$(25) = "E9EPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QP"
|
||||||
|
IM$(26) = "Q9Q9Q9Q9QPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9"
|
||||||
|
IM$(27) = "Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9"
|
||||||
|
IM$(28) = "QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9"
|
||||||
|
IM$(29) = "Q9Q9Q9QPE9E9E9E9EPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9"
|
||||||
|
IM$(30) = "Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QP"
|
||||||
|
IM$(31) = "Q9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QPQ9Q9Q9Q9QP"
|
||||||
|
IM$(32) = "RBRQ9QOKMQHK5RBSQ9QTI9IRRBXQ9QTKPQHKHQPKERBYQ9QPI9IHRB3Q9QSKAI5"
|
||||||
|
IM$(33) = "KHIJRB5Q9QTC9CMRB9Q9QWI9IGRCBQ9QYC9CCRCIA9A9A9A9APRCJE9E9E9E9EP"
|
||||||
|
IM$(34) = "RCNA9A9A9A9APRCOE9E9E9E9EPRCUA9A9A9A9APRCVE9E9E9E9EPRC5A9A9A9A9"
|
||||||
|
IM$(35) = "APRC7E9E9E9E9EP"
|
||||||
|
IM$(36) = "RBQQ9QOK9KIQHKFRBRQ9QOI9IQRBWQ9QPKLQHKHQHKPRBXQ9QTI9QLIERB2Q9QR"
|
||||||
|
IM$(37) = "IBKHIPKHIPKCRB4Q9QSC9CORB8Q9QVI3KHIGRCAQ9QXC9CERCJA9A9A9A9APRCK"
|
||||||
|
IM$(38) = "E9E9E9E9EPRCOA9A9A9A9APRCPE9E9E9E9EPRCVA9A9A9A9APRCXE9E9E9E9EP"
|
||||||
|
IM$(39) = "RC7A9A9A9A9APRDAE9E9E9E9EP"
|
||||||
|
IM$(40) = "RBPQ9QOK9KAQHKNRBQQ9QOI9IWRBVQ9QOKEQHKHQHKPQHKFRBWQ9QPI9IPRB1Q9"
|
||||||
|
IM$(41) = "QQKCIPKHIPKHIDRB3Q9QSC9CORB7Q9QUIWKHIPRB9Q9QWC9CGRCEA9A9A9A9AP"
|
||||||
|
IM$(42) = "RCFE9E9E9E9EPRCGA9A9A9A9APRCHE9E9E9E9EPRCPA9A9A9A9APRCQE9E9E9E9"
|
||||||
|
IM$(43) = "EPRCXA9A9A9A9APRCZE9E9E9E9EPRDAA9A9A9A9APRDCE9E9E9E9EP"
|
||||||
|
IM$(44) = "RBOQ9QNK3QHKWRBPQ9QOI9IWRBUQ9QTKHQHKPQHKNRBVQ9QOI9IWRB0Q9QQIKKH"
|
||||||
|
IM$(45) = "IPKHIHKDRB1Q9QQC9CSRB2Q9QRC9CQRB6Q9QTIPKHIYRB8Q9QVC9CIRCKA9A9A9"
|
||||||
|
IM$(46) = "A9APRCLE9E9E9E9EPRCQA9A9A9A9APRCRE9E9E9E9EPRCZA9A9A9A9APRC1E9E9"
|
||||||
|
IM$(47) = "E9E9EPRDCA9A9A9A9APRDFE9E9E9E9EP"
|
||||||
|
IM$(48) = "RBOQ9QNI9IYRBTQ9QOKEQHKPQHKVRBUQ9QTI9IRRBZQ9QTKHQHKPQHKHRB0Q9QQ"
|
||||||
|
IM$(49) = "C9CSRB5Q9QTIHKHI5KARB7Q9QUC9CKRCBQ9QYI9ICRCHA9A9A9A9APRCIE9E9E9"
|
||||||
|
IM$(50) = "E9EPRCLA9A9A9A9APRCME9E9E9E9EPRCRA9A9A9A9APRCTE9E9E9E9EPRC1A9A9"
|
||||||
|
IM$(51) = "A9A9APRC3E9E9E9E9EPRDFA9A9A9A9APRDIE9E9E9E9EP"
|
||||||
|
IM$(52) = "RBSQ9QTKPQHK3RBTQ9QOI9IWRBYQ9QPKDQHKPQHKHRBZQ9QTI9ILRB4Q9QSIAKH"
|
||||||
|
IM$(53) = "I5KHIBRB6Q9QTC9CMRCAQ9QXI9IERCEE9E9E9E9EPRCFA9A9A9A9APRCGE9E9E9"
|
||||||
|
IM$(54) = "E9EPRCMA9A9A9A9APRCNE9E9E9E9EPRCTA9A9A9A9APRCUE9E9E9E9EPRC3A9A9"
|
||||||
|
IM$(55) = "A9A9APRC5E9E9E9E9EPRDIA9A9A9A9AP"
|
||||||
REM ---- PICTURE-END
|
REM ---- PICTURE-END
|
||||||
SS# = 0
|
SS# = 0
|
||||||
SE# = NS# - 1
|
SE# = NS# - 1
|
||||||
@@ -1317,13 +1357,25 @@ TB# = TB# + 1
|
|||||||
IF TB# > 2 THEN TB# = 0
|
IF TB# > 2 THEN TB# = 0
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
|
REM A batch is four PLAY statements, one bar each: the parser's V, T,
|
||||||
|
REM U and duration state persists across statements and every PLAY
|
||||||
|
REM appends to the same queue, so four bars queue exactly as one long
|
||||||
|
REM string would -- which the 80-column line limit no longer allows.
|
||||||
|
REM Each bar restates the prefix anyway, so a bar dropped by QFULL
|
||||||
|
REM never leaves the next one playing with drum-kit state.
|
||||||
LABEL TUNEA
|
LABEL TUNEA
|
||||||
PLAY "V1T3U9S O1AO2AO3AO2AO3AO4CEAO1AO2AO4CEAO5CO4AE O1FO2FO3FO2FO3FAO4CFO1FO2FO3AO4CFAO5CO4A O1CO2CO3CO2CO3CEGO4CO1CO2CO3EGO4CEGO5C O1GO2GO3GO2GO3GBO4DGO1GO2GO3BO4DGBO5DO4B"
|
PLAY "V1T3U9S O1AO2AO3AO2AO3AO4CEAO1AO2AO4CEAO5CO4AE"
|
||||||
|
PLAY "V1T3U9S O1FO2FO3FO2FO3FAO4CFO1FO2FO3AO4CFAO5CO4A"
|
||||||
|
PLAY "V1T3U9S O1CO2CO3CO2CO3CEGO4CO1CO2CO3EGO4CEGO5C"
|
||||||
|
PLAY "V1T3U9S O1GO2GO3GO2GO3GBO4DGO1GO2GO3BO4DGBO5DO4B"
|
||||||
MT# = TI# + 270
|
MT# = TI# + 270
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
LABEL TUNEB
|
LABEL TUNEB
|
||||||
PLAY "V1T3U9S O1AO2AO3AO2AO3AO4CEAO1AO2AO4CEAO5CEO4A O1FO2FO3FO2FO3FAO4CFO1FO2FO3AO4CFAO5CO4A O1DO2DO3DO2DO3DFAO4DO1DO2DO3FAO4DFAO5D O1EO2EO3EO2EO3E#GBO4EO1EO2EO3#GBO4E#GBO5E"
|
PLAY "V1T3U9S O1AO2AO3AO2AO3AO4CEAO1AO2AO4CEAO5CEO4A"
|
||||||
|
PLAY "V1T3U9S O1FO2FO3FO2FO3FAO4CFO1FO2FO3AO4CFAO5CO4A"
|
||||||
|
PLAY "V1T3U9S O1DO2DO3DO2DO3DFAO4DO1DO2DO3FAO4DFAO5D"
|
||||||
|
PLAY "V1T3U9S O1EO2EO3EO2EO3E#GBO4EO1EO2EO3#GBO4E#GBO5E"
|
||||||
MT# = TI# + 270
|
MT# = TI# + 270
|
||||||
RETURN
|
RETURN
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,14 @@ SKIP = "Q"
|
|||||||
ROWREC = "R"
|
ROWREC = "R"
|
||||||
LENCH = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
LENCH = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
MAXRUN = len(LENCH)
|
MAXRUN = len(LENCH)
|
||||||
PAYLOAD = 240
|
|
||||||
|
# The interpreter reads source through an 80-byte line buffer and refuses
|
||||||
|
# any line that fills it (AKBASIC_MAX_LINE_LENGTH, sink_stdio.c), so a
|
||||||
|
# stored line is at most 78 characters plus its newline. 'IM$(NN) = "' and
|
||||||
|
# the closing quote spend 12 of those; 64 keeps the emitted lines under
|
||||||
|
# the ceiling with margin to spare while the index stays two digits.
|
||||||
|
PAYLOAD = 64
|
||||||
|
MAXLINE = 78
|
||||||
|
|
||||||
SUN_CX, SUN_CY, SUN_R = 80, 50, 30
|
SUN_CX, SUN_CY, SUN_R = 80, 50, 30
|
||||||
HORIZON = 74
|
HORIZON = 74
|
||||||
@@ -206,13 +213,32 @@ def encode_delta(prev, cur):
|
|||||||
|
|
||||||
|
|
||||||
def chop(blob):
|
def chop(blob):
|
||||||
return [blob[i:i + PAYLOAD] for i in range(0, len(blob), PAYLOAD)]
|
"""Split a stream into strings of at most PAYLOAD characters, cutting
|
||||||
|
only between records. The decoder reads a record's tail characters
|
||||||
|
with MID on the string it is walking, so a run (two characters) or a
|
||||||
|
row record (three) that straddled two IM$ entries would decode as
|
||||||
|
garbage; DRAWSTREAM only carries the cursor, never a partial record."""
|
||||||
|
out, cur = [], ""
|
||||||
|
p = 0
|
||||||
|
while p < len(blob):
|
||||||
|
n = 3 if blob[p] == ROWREC else 2
|
||||||
|
if len(cur) + n > PAYLOAD:
|
||||||
|
out.append(cur)
|
||||||
|
cur = ""
|
||||||
|
cur += blob[p:p + n]
|
||||||
|
p += n
|
||||||
|
if cur:
|
||||||
|
out.append(cur)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def simulate(raster, blob):
|
def simulate(raster, blob, x=0, y=0):
|
||||||
"""Apply one encoded stream to a raster exactly the way the BASIC
|
"""Apply one encoded stream to a raster exactly the way the BASIC
|
||||||
decoder does, skips-draw-nothing and all."""
|
decoder does, skips-draw-nothing and all. The cursor comes in and
|
||||||
x = y = p = 0
|
goes back out because DRAWSTREAM carries it from one IM$ entry to
|
||||||
|
the next -- decoding the chopped strings one at a time with the
|
||||||
|
cursor threaded through is exactly what the demo will execute."""
|
||||||
|
p = 0
|
||||||
while p < len(blob):
|
while p < len(blob):
|
||||||
c = blob[p]
|
c = blob[p]
|
||||||
if c == ROWREC:
|
if c == ROWREC:
|
||||||
@@ -230,20 +256,31 @@ def simulate(raster, blob):
|
|||||||
x = 0
|
x = 0
|
||||||
y += 1
|
y += 1
|
||||||
p += 2
|
p += 2
|
||||||
|
return raster, x, y
|
||||||
|
|
||||||
|
|
||||||
|
def simulate_lines(raster, lines):
|
||||||
|
"""One stream as its chopped strings, cursor carried across the
|
||||||
|
boundaries the way DRAWSTREAM carries X# and Y#."""
|
||||||
|
x = y = 0
|
||||||
|
for line in lines:
|
||||||
|
raster, x, y = simulate(raster, line, x, y)
|
||||||
return raster
|
return raster
|
||||||
|
|
||||||
|
|
||||||
def verify(frames, base_blob, delta_blobs):
|
def verify(frames, base_lines, delta_line_groups):
|
||||||
"""The base must reproduce frame 0 exactly, and each delta must
|
"""The base must reproduce frame 0 exactly, and each delta must
|
||||||
carry the raster exactly to the next frame. A skip leaves the cell
|
carry the raster exactly to the next frame. A skip leaves the cell
|
||||||
the encoder promised was already right, so equality is total and
|
the encoder promised was already right, so equality is total and
|
||||||
any difference at all is an encoder bug."""
|
any difference at all is an encoder bug. This decodes the CHOPPED
|
||||||
|
strings, not the blobs, so a chop that split a record would fail
|
||||||
|
here instead of corrupting the screen."""
|
||||||
raster = [[1] * W for _ in range(H)]
|
raster = [[1] * W for _ in range(H)]
|
||||||
raster = simulate(raster, base_blob)
|
raster = simulate_lines(raster, base_lines)
|
||||||
assert raster == frames[0], "base stream does not reproduce frame 0"
|
assert raster == frames[0], "base stream does not reproduce frame 0"
|
||||||
for i, blob in enumerate(delta_blobs):
|
for i, lines in enumerate(delta_line_groups):
|
||||||
want = frames[(i + 1) % PHASES]
|
want = frames[(i + 1) % PHASES]
|
||||||
raster = simulate(raster, blob)
|
raster = simulate_lines(raster, lines)
|
||||||
assert raster == want, "delta %d does not reproduce its frame" % i
|
assert raster == want, "delta %d does not reproduce its frame" % i
|
||||||
|
|
||||||
|
|
||||||
@@ -261,6 +298,9 @@ def emit_block(base_lines, delta_ranges, all_lines):
|
|||||||
for i, s in enumerate(all_lines):
|
for i, s in enumerate(all_lines):
|
||||||
out.append('IM$(%d) = "%s"' % (i, s))
|
out.append('IM$(%d) = "%s"' % (i, s))
|
||||||
out.append("REM ---- PICTURE-END")
|
out.append("REM ---- PICTURE-END")
|
||||||
|
for line in out:
|
||||||
|
assert len(line) <= MAXLINE, "emitted line over %d chars: %r" % (
|
||||||
|
MAXLINE, line)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -303,15 +343,15 @@ def main():
|
|||||||
base_lines = chop(base_blob)
|
base_lines = chop(base_blob)
|
||||||
all_lines = list(base_lines)
|
all_lines = list(base_lines)
|
||||||
delta_ranges = []
|
delta_ranges = []
|
||||||
delta_blobs = []
|
delta_line_groups = []
|
||||||
for i in range(PHASES):
|
for i in range(PHASES):
|
||||||
blob = encode_delta(frames[i], frames[(i + 1) % PHASES])
|
blob = encode_delta(frames[i], frames[(i + 1) % PHASES])
|
||||||
delta_blobs.append(blob)
|
|
||||||
lines = chop(blob)
|
lines = chop(blob)
|
||||||
|
delta_line_groups.append(lines)
|
||||||
delta_ranges.append((len(all_lines), len(all_lines) + len(lines) - 1))
|
delta_ranges.append((len(all_lines), len(all_lines) + len(lines) - 1))
|
||||||
all_lines.extend(lines)
|
all_lines.extend(lines)
|
||||||
verify(frames, base_blob, delta_blobs)
|
verify(frames, base_lines, delta_line_groups)
|
||||||
dbytes = sum(len(b) for b in delta_blobs)
|
dbytes = sum(len(l) for g in delta_line_groups for l in g)
|
||||||
print("base %d bytes in %d strings; video %d bytes in %d strings; "
|
print("base %d bytes in %d strings; video %d bytes in %d strings; "
|
||||||
"%d strings total" %
|
"%d strings total" %
|
||||||
(sum(len(s) for s in base_lines), len(base_lines), dbytes,
|
(sum(len(s) for s in base_lines), len(base_lines), dbytes,
|
||||||
|
|||||||
@@ -211,8 +211,12 @@ typedef struct akbasic_Runtime
|
|||||||
* not exist relative to the working directory, which is what makes a `.bas`
|
* not exist relative to the working directory, which is what makes a `.bas`
|
||||||
* beside its art work from anywhere. Group F's disk verbs will want the
|
* beside its art work from anywhere. Group F's disk verbs will want the
|
||||||
* same, which is why it is on the runtime rather than in the sprite state.
|
* same, which is why it is on the runtime rather than in the sprite state.
|
||||||
|
*
|
||||||
|
* Sized to AKBASIC_MAX_SOURCE_PATH_LENGTH, not AKBASIC_MAX_LINE_LENGTH: a
|
||||||
|
* directory is a filesystem path, not a line of BASIC, and the two do not
|
||||||
|
* belong to the same budget.
|
||||||
*/
|
*/
|
||||||
char sourcepath[AKBASIC_MAX_LINE_LENGTH];
|
char sourcepath[AKBASIC_MAX_SOURCE_PATH_LENGTH];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The armed interrupts, and the environment the one currently running was
|
* The armed interrupts, and the environment the one currently running was
|
||||||
@@ -248,11 +252,6 @@ 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.
|
||||||
@@ -370,7 +369,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_ui(akbasic_Runtime *obj,
|
|||||||
* @param path Path to the program file, or NULL for none.
|
* @param path Path to the program file, or NULL for none.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
||||||
* @throws AKBASIC_ERR_BOUNDS When the path is longer than AKBASIC_MAX_LINE_LENGTH.
|
* @throws AKBASIC_ERR_BOUNDS When the path is longer than AKBASIC_MAX_SOURCE_PATH_LENGTH.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const char *path);
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const char *path);
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,17 @@
|
|||||||
* serves variables, functions and labels. Capacity is a member; the table is
|
* serves variables, functions and labels. Capacity is a member; the table is
|
||||||
* kept below a 75% load factor by construction because `capacity` counts slots
|
* kept below a 75% load factor by construction because `capacity` counts slots
|
||||||
* and the caller's logical maximum is smaller.
|
* and the caller's logical maximum is smaller.
|
||||||
|
*
|
||||||
|
* 172 rather than the old 256: no caller passes akbasic_symtab_init() anything
|
||||||
|
* larger than AKBASIC_MAX_VARIABLES (128), and 172 keeps that under the 75%
|
||||||
|
* load factor the comment above promises (128 / 0.75 = 170.7).
|
||||||
|
*
|
||||||
|
* AKBASIC_SYMTAB_MAX_KEY 24 rather than 64: the longest identifier across
|
||||||
|
* examples/breakout and examples/megademo -- variables, DIMmed arrays, labels
|
||||||
|
* and DEF FN names alike -- is 11 characters (TITLESCREEN, CLEARPOWERS).
|
||||||
*/
|
*/
|
||||||
#define AKBASIC_SYMTAB_MAX_SLOTS 256
|
#define AKBASIC_SYMTAB_MAX_SLOTS 172
|
||||||
#define AKBASIC_SYMTAB_MAX_KEY 64
|
#define AKBASIC_SYMTAB_MAX_KEY 24
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#ifndef _AKBASIC_TYPES_H_
|
#ifndef _AKBASIC_TYPES_H_
|
||||||
#define _AKBASIC_TYPES_H_
|
#define _AKBASIC_TYPES_H_
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@@ -27,15 +28,39 @@
|
|||||||
#define AKBASIC_MAX_VALUES 64
|
#define AKBASIC_MAX_VALUES 64
|
||||||
#define AKBASIC_MAX_VARIABLES 128
|
#define AKBASIC_MAX_VARIABLES 128
|
||||||
|
|
||||||
/* Whole-runtime pools */
|
/*
|
||||||
#define AKBASIC_MAX_SOURCE_LINES 9999
|
* Whole-runtime pools.
|
||||||
#define AKBASIC_MAX_LINE_LENGTH 256
|
*
|
||||||
|
* AKBASIC_MAX_SOURCE_LINES, AKBASIC_MAX_LINE_LENGTH, AKBASIC_MAX_ARRAY_VALUES,
|
||||||
|
* AKBASIC_MAX_ENVIRONMENTS and AKBASIC_MAX_FUNCTIONS were cut from their
|
||||||
|
* original values against measurements taken off examples/breakout and
|
||||||
|
* examples/megademo, the two most demanding programs this interpreter runs --
|
||||||
|
* see the memory-footprint discussion this commit's PR body links. Each is
|
||||||
|
* sized at roughly 1.5-2x the peak the reference corpus actually reaches, not
|
||||||
|
* at the peak itself.
|
||||||
|
*
|
||||||
|
* AKBASIC_MAX_LINE_LENGTH in particular follows Commodore BASIC's own 80-column
|
||||||
|
* line limit rather than a measurement, which is why sink_stdio.c now refuses a
|
||||||
|
* line that fills the buffer with no terminator instead of silently truncating
|
||||||
|
* it: at 256 bytes that failure mode was theoretical, and at 80 it is not.
|
||||||
|
*
|
||||||
|
* AKBASIC_MAX_SOURCE_PATH_LENGTH is its own constant rather than a reuse of
|
||||||
|
* AKBASIC_MAX_LINE_LENGTH, which is where it used to come from. `sourcepath`
|
||||||
|
* (runtime.h) holds a directory, not a line of BASIC, and the two ideas do not
|
||||||
|
* scale together: shrinking the line limit to 80 broke every golden test in
|
||||||
|
* this checkout, because this repository's own working directory is deeper
|
||||||
|
* than that. PATH_MAX is the actual bound a filesystem path is subject to, so
|
||||||
|
* it is the one this borrows.
|
||||||
|
*/
|
||||||
|
#define AKBASIC_MAX_SOURCE_LINES 2048
|
||||||
|
#define AKBASIC_MAX_LINE_LENGTH 80 /* Commodore BASIC's own line limit */
|
||||||
|
#define AKBASIC_MAX_SOURCE_PATH_LENGTH PATH_MAX
|
||||||
#define AKBASIC_MAX_ARRAY_DEPTH 64 /* dimensions per array */
|
#define AKBASIC_MAX_ARRAY_DEPTH 64 /* dimensions per array */
|
||||||
#define AKBASIC_MAX_ARRAY_ELEMENTS 1024 /* elements in one array */
|
#define AKBASIC_MAX_ARRAY_ELEMENTS 1024 /* elements in one array */
|
||||||
#define AKBASIC_MAX_ARRAY_VALUES 4096 /* array elements across all variables */
|
#define AKBASIC_MAX_ARRAY_VALUES 2048 /* array elements across all variables */
|
||||||
#define AKBASIC_MAX_STRING_LENGTH 256 /* see TODO.md 1.2 */
|
#define AKBASIC_MAX_STRING_LENGTH 256 /* see TODO.md 1.2 */
|
||||||
#define AKBASIC_MAX_ENVIRONMENTS 32 /* new: Go allocated these unbounded */
|
#define AKBASIC_MAX_ENVIRONMENTS 12 /* new: Go allocated these unbounded */
|
||||||
#define AKBASIC_MAX_FUNCTIONS 64 /* new: Go used an unbounded map */
|
#define AKBASIC_MAX_FUNCTIONS 8 /* new: Go used an unbounded map */
|
||||||
#define AKBASIC_MAX_LABELS 64 /* new: Go used an unbounded map */
|
#define AKBASIC_MAX_LABELS 64 /* new: Go used an unbounded map */
|
||||||
/*
|
/*
|
||||||
* Leaves a DO/LOOP condition may use. Its own small pool rather than a second
|
* Leaves a DO/LOOP condition may use. Its own small pool rather than a second
|
||||||
|
|||||||
@@ -13,12 +13,22 @@
|
|||||||
|
|
||||||
#include <akerror.h>
|
#include <akerror.h>
|
||||||
|
|
||||||
|
#include <akbasic/symtab.h>
|
||||||
#include <akbasic/types.h>
|
#include <akbasic/types.h>
|
||||||
#include <akbasic/value.h>
|
#include <akbasic/value.h>
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char name[AKBASIC_MAX_STRING_LENGTH];
|
/*
|
||||||
|
* Sized to AKBASIC_SYMTAB_MAX_KEY, not AKBASIC_MAX_STRING_LENGTH: this name
|
||||||
|
* only ever gets here by surviving akbasic_symtab_set() first
|
||||||
|
* (akbasic_environment_create() calls it right after this field is
|
||||||
|
* populated), and that call refuses anything AKBASIC_SYMTAB_MAX_KEY
|
||||||
|
* characters or longer with AKBASIC_ERR_BOUNDS. A variable whose name did
|
||||||
|
* not fit could never exist, so the wider buffer was 232 bytes of headroom
|
||||||
|
* nothing could ever put a byte into.
|
||||||
|
*/
|
||||||
|
char name[AKBASIC_SYMTAB_MAX_KEY];
|
||||||
akbasic_Type valuetype;
|
akbasic_Type valuetype;
|
||||||
akbasic_Value *values; /** The pool, or `inlinevalue` for a scalar */
|
akbasic_Value *values; /** The pool, or `inlinevalue` for a scalar */
|
||||||
int valuecount;
|
int valuecount;
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ akerr_ErrorContext *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const
|
|||||||
}
|
}
|
||||||
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->sourcepath)), AKBASIC_ERR_BOUNDS,
|
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->sourcepath)), AKBASIC_ERR_BOUNDS,
|
||||||
"Program path of %zu characters exceeds the %d character limit",
|
"Program path of %zu characters exceeds the %d character limit",
|
||||||
length, AKBASIC_MAX_LINE_LENGTH - 1);
|
length, AKBASIC_MAX_SOURCE_PATH_LENGTH - 1);
|
||||||
PASS(errctx, aksl_memcpy(obj->sourcepath, path, length));
|
PASS(errctx, aksl_memcpy(obj->sourcepath, path, length));
|
||||||
obj->sourcepath[length] = '\0';
|
obj->sourcepath[length] = '\0';
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
|
|||||||
@@ -183,69 +183,6 @@ 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);
|
||||||
|
|||||||
@@ -74,6 +74,19 @@ static akerr_ErrorContext *stdio_readline(akbasic_TextSink *self, char *dest, si
|
|||||||
if ( *eof ) {
|
if ( *eof ) {
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* aksl_fgets(3)'s own contract: a full buffer with no trailing newline is
|
||||||
|
* how a caller spots a line longer than the buffer, because the rest of it
|
||||||
|
* is still sitting unread in the stream. Refusing here is what makes that
|
||||||
|
* true -- without it, the unread remainder is picked up by the *next*
|
||||||
|
* readline() as if it were its own statement, which does not fail, it just
|
||||||
|
* runs the wrong program. AKBASIC_MAX_LINE_LENGTH is small enough now that
|
||||||
|
* this is not a hypothetical: examples/breakout's own longest line used to
|
||||||
|
* clear the old 256-byte ceiling by more than half.
|
||||||
|
*/
|
||||||
|
FAIL_NONZERO_RETURN(errctx, (used == len - 1 && dest[used - 1] != '\n' && dest[used - 1] != '\r'),
|
||||||
|
AKBASIC_ERR_BOUNDS,
|
||||||
|
"Source line exceeds the %zu character limit", len - 1);
|
||||||
/*
|
/*
|
||||||
* Strip the line terminator. The scanner treats \r and \n as end-of-line
|
* Strip the line terminator. The scanner treats \r and \n as end-of-line
|
||||||
* anyway, but leaving them on would make a stored source line differ from
|
* anyway, but leaving them on would make a stored source line differ from
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ 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 },
|
||||||
@@ -147,7 +146,6 @@ 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,7 +140,6 @@ 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);
|
||||||
@@ -155,7 +154,6 @@ 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);
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
10 PRINT "97 : " + ASC("a")
|
|
||||||
20 PRINT "65 : " + ASC("A")
|
|
||||||
30 PRINT "64 : " + ASC("@")
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
97 : 97
|
|
||||||
65 : 65
|
|
||||||
64 : 64
|
|
||||||
@@ -162,26 +162,6 @@ 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),
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ static void test_scoped_loop_counter_costs_nothing(void)
|
|||||||
/**
|
/**
|
||||||
* @brief The pool is genuinely untouched, not merely large enough.
|
* @brief The pool is genuinely untouched, not merely large enough.
|
||||||
*
|
*
|
||||||
* Two hundred scope entries and then the pool's *entire* width -- four arrays,
|
* Two hundred scope entries and then the pool's *entire* width -- two arrays,
|
||||||
* because AKBASIC_MAX_ARRAY_ELEMENTS caps any one of them at 1024 while the pool
|
* because AKBASIC_MAX_ARRAY_ELEMENTS caps any one of them at 1024 while the pool
|
||||||
* holds 4096. One leaked slot and the fourth `DIM` has nowhere to go, which
|
* holds 2048. One leaked slot and the second `DIM` has nowhere to go, which
|
||||||
* makes this the sharpest of the three and by far the cheapest: the two above
|
* makes this the sharpest of the three and by far the cheapest: the two above
|
||||||
* prove a program finishes, this proves nothing at all was spent. The long ones
|
* prove a program finishes, this proves nothing at all was spent. The long ones
|
||||||
* stay because they are the shape the defect was found in.
|
* stay because they are the shape the defect was found in.
|
||||||
@@ -111,10 +111,8 @@ static void test_pool_is_untouched_by_scopes(void)
|
|||||||
"30 NEXT T#\n"
|
"30 NEXT T#\n"
|
||||||
"40 DIM A#(1024)\n"
|
"40 DIM A#(1024)\n"
|
||||||
"50 DIM B#(1024)\n"
|
"50 DIM B#(1024)\n"
|
||||||
"60 DIM C#(1024)\n"
|
"70 B#(1023) = 7\n"
|
||||||
"70 DIM D#(1024)\n"
|
"90 PRINT B#(1023)\n"
|
||||||
"80 D#(1023) = 7\n"
|
|
||||||
"90 PRINT D#(1023)\n"
|
|
||||||
"100 END\n"
|
"100 END\n"
|
||||||
"110 LABEL SUBA\n"
|
"110 LABEL SUBA\n"
|
||||||
"120 LOC# = 1\n"
|
"120 LOC# = 1\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user