Compare commits
2 Commits
18b30b753b
...
23
| Author | SHA1 | Date | |
|---|---|---|---|
|
3342f2b569
|
|||
|
c5d13f00f6
|
@@ -282,7 +282,7 @@ jobs:
|
|||||||
# avoid the file: almost every survivor is in akbasic_audio_state_init,
|
# avoid the file: almost every survivor is in akbasic_audio_state_init,
|
||||||
# where nothing asserts that a freshly initialised audio state is actually
|
# where nothing asserts that a freshly initialised audio state is actually
|
||||||
# zeroed and defaulted -- the same gap this job's own history records
|
# zeroed and defaulted -- the same gap this job's own history records
|
||||||
# closing for src/symtab.c. Recorded in TODO.md; add the file back when it
|
# closing for src/symtab.c. That is issue #25; add the file back when it
|
||||||
# is closed.
|
# is closed.
|
||||||
#
|
#
|
||||||
# It was 73.1% before writing this job. The run's own findings closed the
|
# It was 73.1% before writing this job. The run's own findings closed the
|
||||||
@@ -297,7 +297,7 @@ jobs:
|
|||||||
#
|
#
|
||||||
# The 21 remaining survivors are listed in the published report. Most are
|
# The 21 remaining survivors are listed in the published report. Most are
|
||||||
# ICR mutants on loop and accumulator initialisers that a stronger
|
# ICR mutants on loop and accumulator initialisers that a stronger
|
||||||
# placement assertion would catch. Recorded in TODO.md.
|
# placement assertion would catch. Issue #25.
|
||||||
- name: mutation testing
|
- name: mutation testing
|
||||||
run: |
|
run: |
|
||||||
python3 scripts/mutation_test.py \
|
python3 scripts/mutation_test.py \
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ cmake_minimum_required(VERSION 3.10)
|
|||||||
# libakgl both use. It flows into the library SOVERSION and nothing else spells
|
# libakgl both use. It flows into the library SOVERSION and nothing else spells
|
||||||
# a version number.
|
# a version number.
|
||||||
#
|
#
|
||||||
# 0.x on purpose: TODO.md section 12 records eleven defects carried over from the
|
# 0.x on purpose: the language surface is not being promised yet. TODO.md §6
|
||||||
# Go reference that are deliberately reproduced and not yet fixed, so the
|
# records the defects inherited from the Go reference and what became of each;
|
||||||
# language surface is not being promised yet.
|
# the ones still open are in the issue tracker.
|
||||||
project(akbasic VERSION 0.1.0 LANGUAGES C)
|
project(akbasic VERSION 0.1.0 LANGUAGES C)
|
||||||
|
|
||||||
# Pre-1.0 the ABI may break on a minor bump, so the soname carries MAJOR.MINOR.
|
# Pre-1.0 the ABI may break on a minor bump, so the soname carries MAJOR.MINOR.
|
||||||
@@ -56,7 +56,7 @@ option(AKBASIC_SANITIZE "Build with ASan + UBSan" OFF
|
|||||||
# coverage build collides on the `coverage` target and fails to configure at all.
|
# coverage build collides on the `coverage` target and fails to configure at all.
|
||||||
# Rename the dependency's on the way past. Remove this once libakerror applies
|
# Rename the dependency's on the way past. Remove this once libakerror applies
|
||||||
# the same CMAKE_SOURCE_DIR test to `coverage` that it already applies to
|
# the same CMAKE_SOURCE_DIR test to `coverage` that it already applies to
|
||||||
# `mutation` -- filed in deps/libakstdlib/TODO.md section 2.3.
|
# `mutation` -- filed as libakerror issue #15.
|
||||||
#
|
#
|
||||||
# **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
|
||||||
@@ -276,7 +276,7 @@ endif()
|
|||||||
# AKBASIC_TESTS must exit 0
|
# AKBASIC_TESTS must exit 0
|
||||||
# AKBASIC_WILL_FAIL_TESTS abort by design
|
# AKBASIC_WILL_FAIL_TESTS abort by design
|
||||||
# AKBASIC_KNOWN_FAILING_TESTS assert the *correct* contract for a defect
|
# AKBASIC_KNOWN_FAILING_TESTS assert the *correct* contract for a defect
|
||||||
# recorded in TODO.md and are expected to fail.
|
# tracked as issues and are expected to fail.
|
||||||
# When one starts passing CTest reports
|
# When one starts passing CTest reports
|
||||||
# "unexpectedly passed" -- that is the cue to move
|
# "unexpectedly passed" -- that is the cue to move
|
||||||
# it into AKBASIC_TESTS along with the fix.
|
# it into AKBASIC_TESTS along with the fix.
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -2331,7 +2331,7 @@ gitignored is exactly what makes it invisible. **Keep build trees out of the sou
|
|||||||
and if a coverage number looks suspiciously unchanged, `find . -name '*.gcda'` before believing
|
and if a coverage number looks suspiciously unchanged, `find . -name '*.gcda'` before believing
|
||||||
it.
|
it.
|
||||||
|
|
||||||
Branch coverage reads 18.0% and is not a target, for the reason `libakgl/TODO.md` and
|
Branch coverage reads 18.0% and is not a target, for the reason `libakgl` and
|
||||||
`libakstdlib` both give: the akerror control-flow macros expand into large branch trees
|
`libakstdlib` both give: the akerror control-flow macros expand into large branch trees
|
||||||
at every call site, most of them unreachable in normal operation. Track line and function
|
at every call site, most of them unreachable in normal operation. Track line and function
|
||||||
coverage.
|
coverage.
|
||||||
|
|||||||
@@ -62,22 +62,28 @@ small number that is not a colour.
|
|||||||
|
|
||||||
### GRAPHIC
|
### GRAPHIC
|
||||||
|
|
||||||
`GRAPHIC mode` chooses a screen mode; `GRAPHIC CLR` clears it. Mode 0 is text and
|
`GRAPHIC mode [, clear] [, split]` chooses the display composition; `GRAPHIC CLR`
|
||||||
refuses to draw.
|
clears the saved graphics state. `GRAPHIC 0` displays the text plane. `GRAPHIC 1`
|
||||||
|
selects a full bitmap plane: `PRINT` still updates the retained text screen, but it is
|
||||||
|
not visible until text mode returns. `GRAPHIC 2` is the explicit split-screen form;
|
||||||
|
its optional third argument names the first text row, and its default leaves six text
|
||||||
|
rows at the bottom. `WINDOW` restricts the current text region; it does not make a
|
||||||
|
full bitmap visible.
|
||||||
|
|
||||||
Every picture in this chapter is generated by running the listing above it; see
|
Every picture in this chapter is generated by running the listing above it; see
|
||||||
`MAINTENANCE.md` if you are editing one.
|
`MAINTENANCE.md` if you are editing one.
|
||||||
|
|
||||||
### DRAW
|
### DRAW
|
||||||
|
|
||||||
```basic requires=akgl screenshot=draw
|
```basic requires=akgl screenshot=draw text=1
|
||||||
10 COLOR 1, 8
|
10 GRAPHIC 1, 1
|
||||||
20 DRAW 1, 20, 180 TO 90, 40 TO 160, 150 TO 230, 20 TO 300, 120
|
20 COLOR 1, 8
|
||||||
30 COLOR 2, 6
|
30 DRAW 1, 20, 180 TO 90, 40 TO 160, 150 TO 230, 20 TO 300, 120
|
||||||
40 DRAW 2, 20, 190 TO 300, 190
|
40 COLOR 2, 6
|
||||||
50 LOCATE 160, 100
|
50 DRAW 2, 20, 190 TO 300, 190
|
||||||
60 COLOR 3, 3
|
60 LOCATE 160, 100
|
||||||
70 DRAW 3
|
70 COLOR 3, 3
|
||||||
|
80 DRAW 3
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -88,14 +94,15 @@ picture — plots wherever `LOCATE` left the pixel cursor.
|
|||||||
|
|
||||||
### BOX
|
### BOX
|
||||||
|
|
||||||
```basic requires=akgl screenshot=box
|
```basic requires=akgl screenshot=box text=1
|
||||||
10 COLOR 1, 8
|
10 GRAPHIC 1, 1
|
||||||
20 BOX 1, 20, 30, 130, 140
|
20 COLOR 1, 8
|
||||||
30 COLOR 2, 6
|
30 BOX 1, 20, 30, 130, 140
|
||||||
40 BOX 2, 180, 30, 290, 140, 30
|
40 COLOR 2, 6
|
||||||
50 COLOR 3, 3
|
50 BOX 2, 180, 30, 290, 140, 30
|
||||||
60 LOCATE 300, 190
|
60 COLOR 3, 3
|
||||||
70 BOX 3, 20, 160
|
70 LOCATE 300, 190
|
||||||
|
80 BOX 3, 20, 160
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -110,13 +117,14 @@ have.
|
|||||||
|
|
||||||
### CIRCLE
|
### CIRCLE
|
||||||
|
|
||||||
```basic requires=akgl screenshot=circle
|
```basic requires=akgl screenshot=circle text=1
|
||||||
10 COLOR 1, 8
|
10 GRAPHIC 1, 1
|
||||||
20 CIRCLE 1, 80, 70, 60, 60
|
20 COLOR 1, 8
|
||||||
30 COLOR 2, 6
|
30 CIRCLE 1, 80, 70, 60, 60
|
||||||
40 CIRCLE 2, 230, 70, 75, 45
|
40 COLOR 2, 6
|
||||||
50 COLOR 3, 3
|
50 CIRCLE 2, 230, 70, 75, 45
|
||||||
60 CIRCLE 3, 160, 140, 130, 50, 90, 270
|
60 COLOR 3, 3
|
||||||
|
70 CIRCLE 3, 160, 140, 130, 50, 90, 270
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -129,14 +137,15 @@ degree increment, and a large increment is what turns a circle into a polygon.
|
|||||||
|
|
||||||
### PAINT
|
### PAINT
|
||||||
|
|
||||||
```basic requires=akgl screenshot=paint
|
```basic requires=akgl screenshot=paint text=1
|
||||||
10 COLOR 1, 8
|
10 GRAPHIC 1, 1
|
||||||
20 CIRCLE 1, 100, 100, 70, 70
|
20 COLOR 1, 8
|
||||||
30 BOX 1, 180, 50, 290, 150
|
30 CIRCLE 1, 100, 100, 70, 70
|
||||||
40 COLOR 2, 6
|
40 BOX 1, 180, 50, 290, 150
|
||||||
50 PAINT 2, 100, 100
|
50 COLOR 2, 6
|
||||||
60 COLOR 3, 3
|
60 PAINT 2, 100, 100
|
||||||
70 PAINT 3, 230, 100
|
70 COLOR 3, 3
|
||||||
|
80 PAINT 3, 230, 100
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -153,13 +162,14 @@ coordinates finishes.
|
|||||||
|
|
||||||
### SCALE
|
### SCALE
|
||||||
|
|
||||||
```basic requires=akgl screenshot=scale size=640x400
|
```basic requires=akgl screenshot=scale size=640x400 text=1
|
||||||
10 COLOR 1, 3
|
10 GRAPHIC 1, 1
|
||||||
20 BOX 1, 0, 0, 319, 199
|
20 COLOR 1, 3
|
||||||
30 SCALE 1, 319, 199
|
30 BOX 1, 0, 0, 319, 199
|
||||||
40 COLOR 2, 6
|
40 SCALE 1, 319, 199
|
||||||
50 BOX 2, 0, 0, 319, 199
|
50 COLOR 2, 6
|
||||||
60 DRAW 2, 0, 0 TO 319, 199
|
60 BOX 2, 0, 0, 319, 199
|
||||||
|
70 DRAW 2, 0, 0 TO 319, 199
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -212,15 +222,16 @@ parallel passes; see Chapter 13.
|
|||||||
|
|
||||||
`SSHAPE` copies a rectangle off the screen and `GSHAPE` stamps it back:
|
`SSHAPE` copies a rectangle off the screen and `GSHAPE` stamps it back:
|
||||||
|
|
||||||
```basic requires=akgl screenshot=shapes
|
```basic requires=akgl screenshot=shapes text=1
|
||||||
10 COLOR 1, 8
|
10 GRAPHIC 1, 1
|
||||||
20 CIRCLE 1, 40, 40, 30, 30
|
20 COLOR 1, 8
|
||||||
30 COLOR 2, 6
|
30 CIRCLE 1, 40, 40, 30, 30
|
||||||
40 PAINT 2, 40, 40
|
40 COLOR 2, 6
|
||||||
50 SSHAPE A$, 8, 8, 72, 72
|
50 PAINT 2, 40, 40
|
||||||
60 GSHAPE A$, 120, 20
|
60 SSHAPE A$, 8, 8, 72, 72
|
||||||
70 GSHAPE A$, 200, 60
|
70 GSHAPE A$, 120, 20
|
||||||
80 GSHAPE A$, 120, 120
|
80 GSHAPE A$, 200, 60
|
||||||
|
90 GSHAPE A$, 120, 120
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -244,19 +255,11 @@ does with one, but you cannot store it or measure it.
|
|||||||
text and the sprites, so a program draws its picture once and it is there on every frame
|
text and the sprites, so a program draws its picture once and it is there on every frame
|
||||||
after. It does not have to redraw it, and it does not have to capture it into a sprite.
|
after. It does not have to redraw it, and it does not have to capture it into a sprite.
|
||||||
|
|
||||||
What covers it is the text layer, which repaints every row it owns — opaque, every frame,
|
`GRAPHIC 1` changes that order: the full bitmap owns the display, while the text layer
|
||||||
for a reason `akbasic_sink_akgl_render()` explains — and by default it owns the whole
|
keeps its contents off-screen. Returning with `GRAPHIC 0` reveals what `PRINT` wrote.
|
||||||
window. `WINDOW` shrinks it:
|
Use `GRAPHIC 2, clear, split` when a program deliberately needs both planes; its text
|
||||||
|
rows begin at `split`. `WINDOW` can then narrow that text region further, but it is not
|
||||||
```basic norun
|
needed to make a full bitmap visible.
|
||||||
10 WINDOW 0, 0, 39, 1
|
|
||||||
20 GRAPHIC 1, 1
|
|
||||||
30 COLOR 1, 3
|
|
||||||
40 BOX 1, 20, 40, 300, 180
|
|
||||||
```
|
|
||||||
|
|
||||||
Two rows of text at the top, the rest of the window for drawing, and the box is still
|
|
||||||
there a thousand frames later.
|
|
||||||
|
|
||||||
**A redraw also has to fit inside one batch.** The host runs a fixed number of source
|
**A redraw also has to fit inside one batch.** The host runs a fixed number of source
|
||||||
lines and then presents, and presenting throws the drawing buffer away — so a run of
|
lines and then presents, and presenting throws the drawing buffer away — so a run of
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ typedef struct akbasic_TextSink
|
|||||||
akerr_ErrorContext AKERR_NOIGNORE *(*moveto)(struct akbasic_TextSink *self, int col, int row);
|
akerr_ErrorContext AKERR_NOIGNORE *(*moveto)(struct akbasic_TextSink *self, int col, int row);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *(*window)(struct akbasic_TextSink *self, int left, int top, int right, int bottom);
|
akerr_ErrorContext AKERR_NOIGNORE *(*window)(struct akbasic_TextSink *self, int left, int top, int right, int bottom);
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *(*grid)(struct akbasic_TextSink *self, int *columns, int *rows, int *cellw, int *cellh);
|
akerr_ErrorContext AKERR_NOIGNORE *(*grid)(struct akbasic_TextSink *self, int *columns, int *rows, int *cellw, int *cellh);
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *(*graphic)(struct akbasic_TextSink *self, int mode, int split);
|
||||||
} akbasic_TextSink;
|
} akbasic_TextSink;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -123,8 +124,8 @@ typedef struct akbasic_TextSink
|
|||||||
supplies its own and draws into a text layer. `readline` is expected to set `*eof` rather
|
supplies its own and draws into a text layer. `readline` is expected to set `*eof` rather
|
||||||
than block — that is how `INPUT` behaves sanely inside a frame.
|
than block — that is how `INPUT` behaves sanely inside a frame.
|
||||||
|
|
||||||
**The last three are optional and may be NULL**, which is how `CHAR`, `WINDOW` and
|
**The last four are optional and may be NULL**, which is how `CHAR`, `WINDOW`, `RWINDOW`
|
||||||
`RWINDOW` know to refuse by name rather than pretending. Supply `grid` if your text layer
|
and `GRAPHIC` know to refuse by name rather than pretending. Supply `grid` if your text layer
|
||||||
has a character cell: it is the only way a script can find out how big one is, and
|
has a character cell: it is the only way a script can find out how big one is, and
|
||||||
without it anything placing a character and a sprite at the same spot has to hardcode a
|
without it anything placing a character and a sprite at the same spot has to hardcode a
|
||||||
number measured against your font.
|
number measured against your font.
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ Three consequences follow, and all three are things people report as bugs:
|
|||||||
|
|
||||||
`akbasic_Value` carries its string **inline**, not behind a pointer, so a copy is a struct
|
`akbasic_Value` carries its string **inline**, not behind a pointer, so a copy is a struct
|
||||||
assignment with no allocator, no refcount and no lifetime question. It costs 256 bytes per
|
assignment with no allocator, no refcount and no lifetime question. It costs 256 bytes per
|
||||||
value; that is the trade, and it is recorded in `TODO.md`.
|
value; that is the trade, and it is recorded in `TODO.md` §6.
|
||||||
|
|
||||||
Type comes from the identifier's suffix and nothing else: `A#` integer, `A%` float, `A$`
|
Type comes from the identifier's suffix and nothing else: `A#` integer, `A%` float, `A$`
|
||||||
string, and a bare name with no suffix is a *label*. That is `akbasic_leaf_identifier_type()`,
|
string, and a bare name with no suffix is a *label*. That is `akbasic_leaf_identifier_type()`,
|
||||||
@@ -596,7 +596,7 @@ script's mistake into an error line are wrapped around *parsing* and *interpreta
|
|||||||
not around *scanning*. A line with more than 32 tokens therefore escapes as an
|
not around *scanning*. A line with more than 32 tokens therefore escapes as an
|
||||||
interpreter error — the driver prints a stack trace and exits 1, and an embedding host is
|
interpreter error — the driver prints a stack trace and exits 1, and an embedding host is
|
||||||
handed a context for what is really a script's mistake. It is the same shape as the
|
handed a context for what is really a script's mistake. It is the same shape as the
|
||||||
`VERIFY` defect above, on a path that fix did not cover. Filed in `TODO.md`; until it is
|
`VERIFY` defect above, on a path that fix did not cover. That is issue #4; until it is
|
||||||
closed, a host that cannot tolerate that should refuse over-long lines itself.
|
closed, a host that cannot tolerate that should refuse over-long lines itself.
|
||||||
|
|
||||||
## Devices, and how a capability is withheld
|
## Devices, and how a capability is withheld
|
||||||
@@ -808,7 +808,7 @@ by name when the pointer is `NULL`**, so an older host or a different backend ge
|
|||||||
error rather than silence.
|
error rather than silence.
|
||||||
|
|
||||||
If `libakgl` cannot supply what the verb needs, **do not work around it here**. File it in
|
If `libakgl` cannot supply what the verb needs, **do not work around it here**. File it in
|
||||||
`deps/libakgl/TODO.md`: what the BASIC verb requires, what the `akgl_*` entry point should
|
`libakgl`'s issue tracker: what the BASIC verb requires, what the `akgl_*` entry point should
|
||||||
look like, and what tests would cover it. Four gaps have gone upstream that way and all
|
look like, and what tests would cover it. Four gaps have gone upstream that way and all
|
||||||
four landed.
|
four landed.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 739 B After Width: | Height: | Size: 735 B |
@@ -189,6 +189,10 @@ typedef struct
|
|||||||
int columns; /* the character grid the cell size works out to */
|
int columns; /* the character grid the cell size works out to */
|
||||||
int rows;
|
int rows;
|
||||||
|
|
||||||
|
/* GRAPHIC chooses whether this text plane is visible and where it begins. */
|
||||||
|
int graphicmode;
|
||||||
|
int texttop; /* first screen row the current text region owns */
|
||||||
|
|
||||||
int cursorcol;
|
int cursorcol;
|
||||||
int cursorrow;
|
int cursorrow;
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,20 @@ typedef struct akbasic_TextSink
|
|||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *(*grid)(struct akbasic_TextSink *self, int *columns, int *rows, int *cellw, int *cellh);
|
akerr_ErrorContext AKERR_NOIGNORE *(*grid)(struct akbasic_TextSink *self, int *columns, int *rows, int *cellw, int *cellh);
|
||||||
|
/**
|
||||||
|
* Select how this sink's text plane participates in a GRAPHIC display mode.
|
||||||
|
*
|
||||||
|
* This is optional: a stream has no display plane to hide or split, so the
|
||||||
|
* stdio sink leaves it NULL. A graphical sink receives the mode after the
|
||||||
|
* runtime has validated it. @p split is the first text row for a split mode,
|
||||||
|
* or -1 when BASIC used the C128 default.
|
||||||
|
*
|
||||||
|
* @param self The sink.
|
||||||
|
* @param mode BASIC 7.0 GRAPHIC mode.
|
||||||
|
* @param split First text row for a split screen, or -1 for the default.
|
||||||
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
|
*/
|
||||||
|
akerr_ErrorContext AKERR_NOIGNORE *(*graphic)(struct akbasic_TextSink *self, int mode, int split);
|
||||||
} akbasic_TextSink;
|
} akbasic_TextSink;
|
||||||
|
|
||||||
/** @brief State for the stdio-backed sink. */
|
/** @brief State for the stdio-backed sink. */
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
* These are akgl_UiAnchor's five values and its numbering, so the adaptor maps
|
* These are akgl_UiAnchor's five values and its numbering, so the adaptor maps
|
||||||
* them across without a table. **There is deliberately no top-centre or
|
* them across without a table. **There is deliberately no top-centre or
|
||||||
* bottom-centre**, because libakgl has neither and this repository does not edit
|
* bottom-centre**, because libakgl has neither and this repository does not edit
|
||||||
* its dependencies to add one; see TODO.md.
|
* its dependencies to add one; see libakgl issues #79 and #80.
|
||||||
*/
|
*/
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* @brief Declares the strongly-typed BASIC value and its operators.
|
* @brief Declares the strongly-typed BASIC value and its operators.
|
||||||
*
|
*
|
||||||
* Ported from the reference's basicvalue.go. The arithmetic is reproduced
|
* Ported from the reference's basicvalue.go. The arithmetic is reproduced
|
||||||
* exactly, including the parts that look wrong -- see TODO.md section 12 for the
|
* exactly, except where a defect has since been fixed on its own merits -- see
|
||||||
* catalogue and the reason they are not fixed yet.
|
* TODO.md §6 for the catalogue and what became of each.
|
||||||
*
|
*
|
||||||
* **The left operand decides whether an operation is done in integers or in
|
* **The left operand decides whether an operation is done in integers or in
|
||||||
* floats**, and this is deliberate rather than an oversight: every operator
|
* floats**, and this is deliberate rather than an oversight: every operator
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ akerr_ErrorContext *akbasic_environment_stop_waiting(akbasic_Environment *obj, c
|
|||||||
"NULL argument in stop_waiting");
|
"NULL argument in stop_waiting");
|
||||||
/*
|
/*
|
||||||
* The reference ignores `command` and clears unconditionally, which lets an
|
* The reference ignores `command` and clears unconditionally, which lets an
|
||||||
* inner block clear an outer block's wait (TODO.md section 12 item 3). The
|
* inner block clear an outer block's wait (TODO.md §6 item 3). The
|
||||||
* argument is honoured here only to the extent of walking to the environment
|
* argument is honoured here only to the extent of walking to the environment
|
||||||
* that is actually waiting for it -- clearing the wrong one outright would
|
* that is actually waiting for it -- clearing the wrong one outright would
|
||||||
* change observable control flow, so the search stops at the first match and
|
* change observable control flow, so the search stops at the first match and
|
||||||
|
|||||||
@@ -1056,7 +1056,7 @@ akerr_ErrorContext *akbasic_runtime_call_function(akbasic_Runtime *obj, const ch
|
|||||||
* rule.** A multi-line function called at the REPL falls straight past this
|
* rule.** A multi-line function called at the REPL falls straight past this
|
||||||
* loop and returns whatever is in the caller's return slot -- zero -- so
|
* loop and returns whatever is in the caller's return slot -- zero -- so
|
||||||
* `PRINT TRIPLE(14)` answers "(UNDEFINED STRING REPRESENTATION FOR 0)" with
|
* `PRINT TRIPLE(14)` answers "(UNDEFINED STRING REPRESENTATION FOR 0)" with
|
||||||
* no error and no diagnostic. Recorded as TODO.md section 6 item 41.
|
* no error and no diagnostic. Issue #8.
|
||||||
*
|
*
|
||||||
* Widening it to `mode != AKBASIC_MODE_QUIT` is the obvious fix and is
|
* Widening it to `mode != AKBASIC_MODE_QUIT` is the obvious fix and is
|
||||||
* *wrong*: akbasic_runtime_process_line_run() does not advance a REPL-mode
|
* *wrong*: akbasic_runtime_process_line_run() does not advance a REPL-mode
|
||||||
|
|||||||
@@ -617,7 +617,7 @@ akerr_ErrorContext *akbasic_cmd_dload(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
|||||||
/*
|
/*
|
||||||
* aksl_fopen does not NULL-check pathname or mode and fopen(NULL, ...) is
|
* aksl_fopen does not NULL-check pathname or mode and fopen(NULL, ...) is
|
||||||
* undefined, so the name is validated here before it is handed over --
|
* undefined, so the name is validated here before it is handed over --
|
||||||
* deps/libakstdlib/TODO.md 2.2.2.
|
* libakstdlib's UPGRADING.md.
|
||||||
*/
|
*/
|
||||||
PASS(errctx, filename_argument(obj, expr, filename, sizeof(filename)));
|
PASS(errctx, filename_argument(obj, expr, filename, sizeof(filename)));
|
||||||
|
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ akerr_ErrorContext *akbasic_cmd_directory(akbasic_Runtime *obj, akbasic_ASTLeaf
|
|||||||
* Refused rather than half-built. Listing a directory needs opendir/readdir,
|
* Refused rather than half-built. Listing a directory needs opendir/readdir,
|
||||||
* which `libakstdlib` does not wrap -- and this project's rule is that a
|
* which `libakstdlib` does not wrap -- and this project's rule is that a
|
||||||
* missing capability gets filed upstream rather than worked around here
|
* missing capability gets filed upstream rather than worked around here
|
||||||
* (MAINTENANCE.md). Filed in deps/libakstdlib/TODO.md.
|
* (MAINTENANCE.md). Filed as libakstdlib issue #10.
|
||||||
*
|
*
|
||||||
* The alternative was shelling out to `ls`, which a library has no business
|
* The alternative was shelling out to `ls`, which a library has no business
|
||||||
* doing, or calling readdir directly and stepping outside the error
|
* doing, or calling readdir directly and stepping outside the error
|
||||||
|
|||||||
@@ -155,24 +155,24 @@ static akerr_ErrorContext *draw_line(akbasic_Runtime *obj, double x1, double y1,
|
|||||||
akerr_ErrorContext *akbasic_cmd_graphic(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
akerr_ErrorContext *akbasic_cmd_graphic(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
double args[2];
|
double args[3];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int mode = 0;
|
int mode = 0;
|
||||||
|
int split = -1;
|
||||||
akbasic_Color background;
|
akbasic_Color background;
|
||||||
|
|
||||||
(void)lval; (void)rval;
|
(void)lval; (void)rval;
|
||||||
PASS(errctx, require_graphics(obj, "GRAPHIC"));
|
PASS(errctx, require_graphics(obj, "GRAPHIC"));
|
||||||
PASS(errctx, akbasic_args_numbers(obj, expr, "GRAPHIC", args, 2, &count));
|
PASS(errctx, akbasic_args_numbers(obj, expr, "GRAPHIC", args, 3, &count));
|
||||||
FAIL_ZERO_RETURN(errctx, (count >= 1), AKBASIC_ERR_SYNTAX, "GRAPHIC expected a mode");
|
FAIL_ZERO_RETURN(errctx, (count >= 1), AKBASIC_ERR_SYNTAX, "GRAPHIC expected a mode");
|
||||||
|
|
||||||
mode = (int)args[0];
|
mode = (int)args[0];
|
||||||
/*
|
/*
|
||||||
* BASIC 7.0 numbers five modes plus a CLR. They differ in bitmap resolution
|
* BASIC 7.0 numbers five modes plus a CLR. They differ in bitmap resolution
|
||||||
* and in whether the bottom of the screen stays text, neither of which means
|
* and in whether the bottom of the screen stays text. Resolution belongs to
|
||||||
* anything against a host's renderer -- so the mode is recorded and only its
|
* the graphics backend, while the latter is a text-sink decision: an AKGL
|
||||||
* one observable consequence is honoured: mode 0 is text, and text mode does
|
* sink hides its text plane for a full bitmap and moves it below the split.
|
||||||
* not draw. Refusing an out-of-range mode still matters, because that is a
|
* A stream has no plane, so it simply leaves that optional entry point NULL.
|
||||||
* typo the program author can fix.
|
|
||||||
*/
|
*/
|
||||||
FAIL_ZERO_RETURN(errctx, (mode >= 0 && mode <= 5), AKBASIC_ERR_BOUNDS,
|
FAIL_ZERO_RETURN(errctx, (mode >= 0 && mode <= 5), AKBASIC_ERR_BOUNDS,
|
||||||
"GRAPHIC mode %d out of range (0 to 5)", mode);
|
"GRAPHIC mode %d out of range (0 to 5)", mode);
|
||||||
@@ -180,11 +180,22 @@ akerr_ErrorContext *akbasic_cmd_graphic(akbasic_Runtime *obj, akbasic_ASTLeaf *e
|
|||||||
/* GRAPHIC CLR: drop the saved shapes and go back to text. */
|
/* GRAPHIC CLR: drop the saved shapes and go back to text. */
|
||||||
PASS(errctx, obj->graphics->free_shapes(obj->graphics));
|
PASS(errctx, obj->graphics->free_shapes(obj->graphics));
|
||||||
PASS(errctx, akbasic_graphics_state_init(&obj->gfx));
|
PASS(errctx, akbasic_graphics_state_init(&obj->gfx));
|
||||||
|
if ( obj->sink != NULL && obj->sink->graphic != NULL ) {
|
||||||
|
PASS(errctx, obj->sink->graphic(obj->sink, 0, -1));
|
||||||
|
}
|
||||||
SUCCEED_TRUE(obj, dest);
|
SUCCEED_TRUE(obj, dest);
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( count >= 3 ) {
|
||||||
|
split = (int)args[2];
|
||||||
|
FAIL_ZERO_RETURN(errctx, (split >= 0 && split <= 25), AKBASIC_ERR_BOUNDS,
|
||||||
|
"GRAPHIC split %d out of range (0 to 25)", split);
|
||||||
|
}
|
||||||
obj->gfx.mode = mode;
|
obj->gfx.mode = mode;
|
||||||
|
if ( obj->sink != NULL && obj->sink->graphic != NULL ) {
|
||||||
|
PASS(errctx, obj->sink->graphic(obj->sink, mode, split));
|
||||||
|
}
|
||||||
if ( count >= 2 && args[1] != 0.0 ) {
|
if ( count >= 2 && args[1] != 0.0 ) {
|
||||||
PASS(errctx, akbasic_graphics_source_color(&obj->gfx, 0, &background));
|
PASS(errctx, akbasic_graphics_source_color(&obj->gfx, 0, &background));
|
||||||
PASS(errctx, obj->graphics->clear(obj->graphics, background));
|
PASS(errctx, obj->graphics->clear(obj->graphics, background));
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ static akerr_ErrorContext *sink_window(akbasic_TextSink *self, int left, int top
|
|||||||
"The sink has no character grid to window");
|
"The sink has no character grid to window");
|
||||||
|
|
||||||
maxcols = state->fullwidth / state->cellw;
|
maxcols = state->fullwidth / state->cellw;
|
||||||
maxrows = state->fullheight / state->cellh;
|
maxrows = (state->fullheight / state->cellh) - state->texttop;
|
||||||
if ( left < 0 ) { left = 0; }
|
if ( left < 0 ) { left = 0; }
|
||||||
if ( top < 0 ) { top = 0; }
|
if ( top < 0 ) { top = 0; }
|
||||||
if ( right >= maxcols ) { right = maxcols - 1; }
|
if ( right >= maxcols ) { right = maxcols - 1; }
|
||||||
@@ -484,7 +484,7 @@ static akerr_ErrorContext *sink_window(akbasic_TextSink *self, int left, int top
|
|||||||
"WINDOW asks for no cells at all");
|
"WINDOW asks for no cells at all");
|
||||||
|
|
||||||
state->x = state->fullx + (left * state->cellw);
|
state->x = state->fullx + (left * state->cellw);
|
||||||
state->y = state->fully + (top * state->cellh);
|
state->y = state->fully + ((state->texttop + top) * state->cellh);
|
||||||
state->columns = (right - left) + 1;
|
state->columns = (right - left) + 1;
|
||||||
state->rows = (bottom - top) + 1;
|
state->rows = (bottom - top) + 1;
|
||||||
state->width = state->columns * state->cellw;
|
state->width = state->columns * state->cellw;
|
||||||
@@ -494,6 +494,78 @@ static akerr_ErrorContext *sink_window(akbasic_TextSink *self, int left, int top
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Apply GRAPHIC's text-plane half without teaching the runtime about SDL.
|
||||||
|
*
|
||||||
|
* Modes 1 and 3 are full bitmaps, so the retained text grid is hidden. Modes 2
|
||||||
|
* and 4 have a bitmap above and text below. A C128 gives an omitted split its
|
||||||
|
* bottom six rows; an explicit zero means all text, and the host clamps a C128
|
||||||
|
* row number to however many measured cells its own window has.
|
||||||
|
*/
|
||||||
|
static akerr_ErrorContext *sink_graphic(akbasic_TextSink *self, int mode, int split)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
akbasic_AkglSink *state = NULL;
|
||||||
|
int maxcols = 0;
|
||||||
|
int maxrows = 0;
|
||||||
|
|
||||||
|
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL sink in graphic");
|
||||||
|
state = (akbasic_AkglSink *)self->self;
|
||||||
|
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER, "akgl sink has no state");
|
||||||
|
maxcols = state->fullwidth / state->cellw;
|
||||||
|
maxrows = state->fullheight / state->cellh;
|
||||||
|
|
||||||
|
state->graphicmode = mode;
|
||||||
|
if ( mode == 0 ) {
|
||||||
|
/* Text mode owns the whole screen again, not the last split's rows. */
|
||||||
|
state->texttop = 0;
|
||||||
|
state->x = state->fullx;
|
||||||
|
state->y = state->fully;
|
||||||
|
state->width = state->fullwidth;
|
||||||
|
state->height = state->fullheight;
|
||||||
|
state->columns = maxcols;
|
||||||
|
state->rows = maxrows;
|
||||||
|
if ( state->columns > SINK_MAX_COLUMNS - 1 ) {
|
||||||
|
state->columns = SINK_MAX_COLUMNS - 1;
|
||||||
|
}
|
||||||
|
if ( state->rows > SINK_MAX_ROWS ) {
|
||||||
|
state->rows = SINK_MAX_ROWS;
|
||||||
|
}
|
||||||
|
state->cursorcol = 0;
|
||||||
|
state->cursorrow = 0;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
if ( mode != 2 && mode != 4 ) {
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
if ( split < 0 ) {
|
||||||
|
split = maxrows - 6;
|
||||||
|
}
|
||||||
|
if ( split < 0 ) { split = 0; }
|
||||||
|
if ( split >= maxrows ) {
|
||||||
|
/* A split below the final row is C128 all-bitmap mode, not a zero-row grid. */
|
||||||
|
state->graphicmode = 1;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->texttop = split;
|
||||||
|
state->x = state->fullx;
|
||||||
|
state->y = state->fully + (split * state->cellh);
|
||||||
|
state->width = state->fullwidth;
|
||||||
|
state->height = (maxrows - split) * state->cellh;
|
||||||
|
state->columns = maxcols;
|
||||||
|
state->rows = maxrows - split;
|
||||||
|
if ( state->columns > SINK_MAX_COLUMNS - 1 ) {
|
||||||
|
state->columns = SINK_MAX_COLUMNS - 1;
|
||||||
|
}
|
||||||
|
if ( state->rows > SINK_MAX_ROWS ) {
|
||||||
|
state->rows = SINK_MAX_ROWS;
|
||||||
|
}
|
||||||
|
state->cursorcol = 0;
|
||||||
|
state->cursorrow = 0;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSink *state, akgl_RenderBackend *renderer, TTF_Font *font, int w, int h)
|
akerr_ErrorContext *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSink *state, akgl_RenderBackend *renderer, TTF_Font *font, int w, int h)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -554,6 +626,8 @@ akerr_ErrorContext *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSi
|
|||||||
state->cellh = cellh;
|
state->cellh = cellh;
|
||||||
state->columns = w / cellw;
|
state->columns = w / cellw;
|
||||||
state->rows = h / cellh;
|
state->rows = h / cellh;
|
||||||
|
state->graphicmode = 0;
|
||||||
|
state->texttop = 0;
|
||||||
if ( state->columns > SINK_MAX_COLUMNS - 1 ) {
|
if ( state->columns > SINK_MAX_COLUMNS - 1 ) {
|
||||||
state->columns = SINK_MAX_COLUMNS - 1;
|
state->columns = SINK_MAX_COLUMNS - 1;
|
||||||
}
|
}
|
||||||
@@ -569,6 +643,7 @@ akerr_ErrorContext *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSi
|
|||||||
obj->moveto = sink_moveto;
|
obj->moveto = sink_moveto;
|
||||||
obj->window = sink_window;
|
obj->window = sink_window;
|
||||||
obj->grid = sink_grid;
|
obj->grid = sink_grid;
|
||||||
|
obj->graphic = sink_graphic;
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,6 +659,11 @@ akerr_ErrorContext *akbasic_sink_akgl_render(akbasic_TextSink *obj)
|
|||||||
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
|
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
|
||||||
"akgl sink has no state");
|
"akgl sink has no state");
|
||||||
|
|
||||||
|
/* A full bitmap owns every display row; the text buffer remains intact. */
|
||||||
|
if ( state->graphicmode == 1 || state->graphicmode == 3 ) {
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* **Repaint every row of the text area, every frame.** Not just the rows
|
* **Repaint every row of the text area, every frame.** Not just the rows
|
||||||
* that changed -- that was tried, with a `drawn[]` array marking which rows
|
* that changed -- that was tried, with a `drawn[]` array marking which rows
|
||||||
|
|||||||
@@ -109,5 +109,6 @@ akerr_ErrorContext *akbasic_sink_init_stdio(akbasic_TextSink *obj, akbasic_Stdio
|
|||||||
obj->moveto = NULL;
|
obj->moveto = NULL;
|
||||||
obj->window = NULL;
|
obj->window = NULL;
|
||||||
obj->grid = NULL;
|
obj->grid = NULL;
|
||||||
|
obj->graphic = NULL;
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,22 @@ static akerr_ErrorContext *tee_window(akbasic_TextSink *self, int left, int top,
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Forward GRAPHIC's display-mode decision to the half with a display. */
|
||||||
|
static akerr_ErrorContext *tee_graphic(akbasic_TextSink *self, int mode, int split)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
akbasic_TeeSink *state = NULL;
|
||||||
|
|
||||||
|
PASS(errctx, tee_state(self, &state));
|
||||||
|
if ( state->primary != NULL && state->primary->graphic != NULL ) {
|
||||||
|
PASS(errctx, state->primary->graphic(state->primary, mode, split));
|
||||||
|
}
|
||||||
|
if ( state->mirror != NULL && state->mirror->graphic != NULL ) {
|
||||||
|
PASS(errctx, state->mirror->graphic(state->mirror, mode, split));
|
||||||
|
}
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Report the grid of whichever half has one.
|
* @brief Report the grid of whichever half has one.
|
||||||
*
|
*
|
||||||
@@ -202,6 +218,7 @@ akerr_ErrorContext *akbasic_sink_init_tee(akbasic_TextSink *obj, akbasic_TeeSink
|
|||||||
obj->moveto = NULL;
|
obj->moveto = NULL;
|
||||||
obj->window = NULL;
|
obj->window = NULL;
|
||||||
obj->grid = NULL;
|
obj->grid = NULL;
|
||||||
|
obj->graphic = NULL;
|
||||||
/*
|
/*
|
||||||
* Offered only when a half can actually do it, so CHAR's refusal against a
|
* Offered only when a half can actually do it, so CHAR's refusal against a
|
||||||
* stdio-only driver still reads correctly through a tee.
|
* stdio-only driver still reads correctly through a tee.
|
||||||
@@ -221,5 +238,9 @@ akerr_ErrorContext *akbasic_sink_init_tee(akbasic_TextSink *obj, akbasic_TeeSink
|
|||||||
(mirror != NULL && mirror->grid != NULL) ) {
|
(mirror != NULL && mirror->grid != NULL) ) {
|
||||||
obj->grid = tee_grid;
|
obj->grid = tee_grid;
|
||||||
}
|
}
|
||||||
|
if ( (primary != NULL && primary->graphic != NULL) ||
|
||||||
|
(mirror != NULL && mirror->graphic != NULL) ) {
|
||||||
|
obj->graphic = tee_graphic;
|
||||||
|
}
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* Probe for `key`. On success *slot points at either the slot holding the key or
|
* Probe for `key`. On success *slot points at either the slot holding the key or
|
||||||
* the first free slot it could occupy; *found says which. Uses
|
* the first free slot it could occupy; *found says which. Uses
|
||||||
* aksl_strhash_djb2 rather than a private hash. That wrapper sign-extends char,
|
* aksl_strhash_djb2 rather than a private hash. That wrapper sign-extends char,
|
||||||
* so a high-bit byte hashes wrong (deps/libakstdlib/TODO.md 1.6) -- harmless
|
* so a high-bit byte hashes wrong (libakstdlib's UPGRADING.md) -- harmless
|
||||||
* here because BASIC identifiers are 7-bit ASCII, and it would only ever cost
|
* here because BASIC identifiers are 7-bit ASCII, and it would only ever cost
|
||||||
* probe efficiency, never correctness, since the key comparison is a strcmp.
|
* probe efficiency, never correctness, since the key comparison is a strcmp.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1173,6 +1173,78 @@ static akerr_ErrorContext AKERR_NOIGNORE *test_drawing_layer_persists(void)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GRAPHIC selects whether the AKGL text plane covers the drawing layer.
|
||||||
|
*
|
||||||
|
* `PRINT` always writes into the retained grid. In a full bitmap mode the grid
|
||||||
|
* is simply not composited; returning to mode zero reveals it. Split mode moves
|
||||||
|
* the text region below the requested row, leaving the drawing visible above.
|
||||||
|
*/
|
||||||
|
static akerr_ErrorContext AKERR_NOIGNORE *test_graphic_text_composition(void)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
SDL_Surface *shot = NULL;
|
||||||
|
int fullrows = 0;
|
||||||
|
|
||||||
|
PASS(errctx, akbasic_sink_init_akgl(&AKGLSINK, &AKGLSINKSTATE, akgl_renderer, font,
|
||||||
|
TARGET_SIZE, TARGET_SIZE));
|
||||||
|
PASS(errctx, akbasic_runtime_init(&RUNTIME, &AKGLSINK));
|
||||||
|
PASS(errctx, akbasic_graphics_init_akgl(&GRAPHICS, &GRAPHICSSTATE, akgl_renderer));
|
||||||
|
PASS(errctx, akbasic_runtime_set_devices(&RUNTIME, &GRAPHICS, NULL, NULL, NULL));
|
||||||
|
PASS(errctx, akbasic_runtime_load(&RUNTIME,
|
||||||
|
"10 GRAPHIC 1, 1\n"
|
||||||
|
"20 COLOR 1, 3\n"
|
||||||
|
"30 DRAW 1, 40, 40\n"
|
||||||
|
"40 PRINT \"HIDDEN\"\n"));
|
||||||
|
PASS(errctx, akbasic_runtime_start(&RUNTIME, AKBASIC_MODE_RUN));
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_begin(&GRAPHICS));
|
||||||
|
PASS(errctx, akbasic_runtime_run(&RUNTIME, 0));
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_end(&GRAPHICS));
|
||||||
|
TEST_REQUIRE_INT(AKGLSINKSTATE.graphicmode, 1);
|
||||||
|
TEST_REQUIRE_STR(AKGLSINKSTATE.text[0], "HIDDEN");
|
||||||
|
|
||||||
|
PASS(errctx, clear_target());
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
|
||||||
|
PASS(errctx, akbasic_sink_akgl_render(&AKGLSINK));
|
||||||
|
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||||
|
TEST_REQUIRE(shot != NULL, "could not read the full-bitmap frame back");
|
||||||
|
TEST_REQUIRE(pixel_is(shot, 40, 40, 0x88, 0x39, 0x32),
|
||||||
|
"GRAPHIC 1 must show DRAW even after PRINT wrote the hidden text grid");
|
||||||
|
SDL_DestroySurface(shot);
|
||||||
|
|
||||||
|
PASS(errctx, AKGLSINK.graphic(&AKGLSINK, 0, -1));
|
||||||
|
PASS(errctx, clear_target());
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
|
||||||
|
PASS(errctx, akbasic_sink_akgl_render(&AKGLSINK));
|
||||||
|
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||||
|
TEST_REQUIRE(shot != NULL, "could not read the text frame back");
|
||||||
|
TEST_REQUIRE(!pixel_is(shot, 40, 40, 0x88, 0x39, 0x32),
|
||||||
|
"GRAPHIC 0 must put the retained text plane back over the drawing");
|
||||||
|
SDL_DestroySurface(shot);
|
||||||
|
|
||||||
|
PASS(errctx, AKGLSINK.clear(&AKGLSINK));
|
||||||
|
fullrows = TARGET_SIZE / AKGLSINKSTATE.cellh;
|
||||||
|
PASS(errctx, AKGLSINK.graphic(&AKGLSINK, 2, 3));
|
||||||
|
TEST_REQUIRE_INT(AKGLSINKSTATE.y, 3 * AKGLSINKSTATE.cellh);
|
||||||
|
TEST_REQUIRE_INT(AKGLSINKSTATE.rows, fullrows - 3);
|
||||||
|
PASS(errctx, AKGLSINK.writeln(&AKGLSINK, "BOTTOM"));
|
||||||
|
PASS(errctx, clear_target());
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
|
||||||
|
PASS(errctx, akbasic_sink_akgl_render(&AKGLSINK));
|
||||||
|
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||||
|
TEST_REQUIRE(shot != NULL, "could not read the split-screen frame back");
|
||||||
|
TEST_REQUIRE(pixel_is(shot, 40, 40, 0x88, 0x39, 0x32),
|
||||||
|
"GRAPHIC 2 must leave the graphics region above its text rows visible");
|
||||||
|
SDL_DestroySurface(shot);
|
||||||
|
|
||||||
|
PASS(errctx, AKGLSINK.graphic(&AKGLSINK, 0, -1));
|
||||||
|
TEST_REQUIRE_INT(AKGLSINKSTATE.texttop, 0);
|
||||||
|
TEST_REQUIRE_INT(AKGLSINKSTATE.rows, fullrows);
|
||||||
|
|
||||||
|
akbasic_graphics_akgl_shutdown(&GRAPHICS);
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The contact says which way to push out and how far.
|
* @brief The contact says which way to push out and how far.
|
||||||
*
|
*
|
||||||
@@ -1339,6 +1411,7 @@ int main(void)
|
|||||||
CATCH(errctx, test_static_geometry());
|
CATCH(errctx, test_static_geometry());
|
||||||
CATCH(errctx, test_contact_geometry());
|
CATCH(errctx, test_contact_geometry());
|
||||||
CATCH(errctx, test_drawing_layer_persists());
|
CATCH(errctx, test_drawing_layer_persists());
|
||||||
|
CATCH(errctx, test_graphic_text_composition());
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
if ( font != NULL ) {
|
if ( font != NULL ) {
|
||||||
TTF_CloseFont(font);
|
TTF_CloseFont(font);
|
||||||
|
|||||||
@@ -34,6 +34,22 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_program(const char *source)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief The values delivered through GRAPHIC's optional text-plane hook. */
|
||||||
|
static int GRAPHIC_CALLS = 0;
|
||||||
|
static int GRAPHIC_MODE = 0;
|
||||||
|
static int GRAPHIC_SPLIT = 0;
|
||||||
|
|
||||||
|
static akerr_ErrorContext AKERR_NOIGNORE *record_graphic(akbasic_TextSink *self, int mode, int split)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
|
||||||
|
(void)self;
|
||||||
|
GRAPHIC_CALLS += 1;
|
||||||
|
GRAPHIC_MODE = mode;
|
||||||
|
GRAPHIC_SPLIT = split;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
/** @brief The white of palette index 2, which most of these draw with. */
|
/** @brief The white of palette index 2, which most of these draw with. */
|
||||||
#define WHITE "#ffffff"
|
#define WHITE "#ffffff"
|
||||||
/** @brief The red of palette index 3. */
|
/** @brief The red of palette index 3. */
|
||||||
@@ -323,6 +339,21 @@ static void test_graphic(void)
|
|||||||
TEST_REQUIRE_STR(MOCK.log, "");
|
TEST_REQUIRE_STR(MOCK.log, "");
|
||||||
harness_stop();
|
harness_stop();
|
||||||
|
|
||||||
|
/* The third argument reaches a graphical text sink as the first text row. */
|
||||||
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||||||
|
mock_devices_init();
|
||||||
|
GRAPHIC_CALLS = 0;
|
||||||
|
HARNESS_SINK.graphic = record_graphic;
|
||||||
|
TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS,
|
||||||
|
&MOCK_AUDIO, &MOCK_INPUT, NULL));
|
||||||
|
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 GRAPHIC 2, 1, 19\n"));
|
||||||
|
TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
||||||
|
TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
|
||||||
|
TEST_REQUIRE_INT(GRAPHIC_CALLS, 1);
|
||||||
|
TEST_REQUIRE_INT(GRAPHIC_MODE, 2);
|
||||||
|
TEST_REQUIRE_INT(GRAPHIC_SPLIT, 19);
|
||||||
|
harness_stop();
|
||||||
|
|
||||||
/* GRAPHIC CLR drops the saved shapes and resets the whole state. */
|
/* GRAPHIC CLR drops the saved shapes and resets the whole state. */
|
||||||
TEST_REQUIRE_OK(run_program("10 COLOR 1, 3\n20 GRAPHIC 5\n"));
|
TEST_REQUIRE_OK(run_program("10 COLOR 1, 3\n20 GRAPHIC 5\n"));
|
||||||
TEST_REQUIRE_STR(MOCK.log, "freeshapes\n");
|
TEST_REQUIRE_STR(MOCK.log, "freeshapes\n");
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ static akerr_ErrorContext AKERR_NOIGNORE *broken_clear(akbasic_TextSink *self)
|
|||||||
static int MOVETOS = 0;
|
static int MOVETOS = 0;
|
||||||
static int WINDOWS = 0;
|
static int WINDOWS = 0;
|
||||||
static int WINDOW_ARGS[4] = { 0, 0, 0, 0 };
|
static int WINDOW_ARGS[4] = { 0, 0, 0, 0 };
|
||||||
|
static int GRAPHICS = 0;
|
||||||
|
static int GRAPHIC_MODE = 0;
|
||||||
|
static int GRAPHIC_SPLIT = 0;
|
||||||
|
|
||||||
static akerr_ErrorContext AKERR_NOIGNORE *counting_moveto(akbasic_TextSink *self, int col, int row)
|
static akerr_ErrorContext AKERR_NOIGNORE *counting_moveto(akbasic_TextSink *self, int col, int row)
|
||||||
{
|
{
|
||||||
@@ -70,6 +73,17 @@ static akerr_ErrorContext AKERR_NOIGNORE *counting_window(akbasic_TextSink *self
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static akerr_ErrorContext AKERR_NOIGNORE *counting_graphic(akbasic_TextSink *self, int mode, int split)
|
||||||
|
{
|
||||||
|
PREPARE_ERROR(errctx);
|
||||||
|
|
||||||
|
(void)self;
|
||||||
|
GRAPHICS += 1;
|
||||||
|
GRAPHIC_MODE = mode;
|
||||||
|
GRAPHIC_SPLIT = split;
|
||||||
|
SUCCEED_RETURN(errctx);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
akbasic_TextSink primary;
|
akbasic_TextSink primary;
|
||||||
@@ -163,11 +177,15 @@ int main(void)
|
|||||||
gridded.clear = broken_clear;
|
gridded.clear = broken_clear;
|
||||||
gridded.moveto = counting_moveto;
|
gridded.moveto = counting_moveto;
|
||||||
gridded.window = counting_window;
|
gridded.window = counting_window;
|
||||||
|
gridded.grid = NULL;
|
||||||
|
gridded.graphic = counting_graphic;
|
||||||
MOVETOS = 0;
|
MOVETOS = 0;
|
||||||
WINDOWS = 0;
|
WINDOWS = 0;
|
||||||
|
GRAPHICS = 0;
|
||||||
TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &gridded, NULL));
|
TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &gridded, NULL));
|
||||||
TEST_REQUIRE(tee.moveto != NULL, "a tee with a gridded half must offer moveto");
|
TEST_REQUIRE(tee.moveto != NULL, "a tee with a gridded half must offer moveto");
|
||||||
TEST_REQUIRE(tee.window != NULL, "a tee with a gridded half must offer window");
|
TEST_REQUIRE(tee.window != NULL, "a tee with a gridded half must offer window");
|
||||||
|
TEST_REQUIRE(tee.graphic != NULL, "a tee with a graphical half must offer graphic");
|
||||||
TEST_REQUIRE_OK(tee.moveto(&tee, 3, 4));
|
TEST_REQUIRE_OK(tee.moveto(&tee, 3, 4));
|
||||||
TEST_REQUIRE_OK(tee.window(&tee, 0, 20, 39, 24));
|
TEST_REQUIRE_OK(tee.window(&tee, 0, 20, 39, 24));
|
||||||
TEST_REQUIRE_INT(MOVETOS, 1);
|
TEST_REQUIRE_INT(MOVETOS, 1);
|
||||||
@@ -176,6 +194,10 @@ int main(void)
|
|||||||
TEST_REQUIRE_INT(WINDOW_ARGS[1], 20);
|
TEST_REQUIRE_INT(WINDOW_ARGS[1], 20);
|
||||||
TEST_REQUIRE_INT(WINDOW_ARGS[2], 39);
|
TEST_REQUIRE_INT(WINDOW_ARGS[2], 39);
|
||||||
TEST_REQUIRE_INT(WINDOW_ARGS[3], 24);
|
TEST_REQUIRE_INT(WINDOW_ARGS[3], 24);
|
||||||
|
TEST_REQUIRE_OK(tee.graphic(&tee, 2, 19));
|
||||||
|
TEST_REQUIRE_INT(GRAPHICS, 1);
|
||||||
|
TEST_REQUIRE_INT(GRAPHIC_MODE, 2);
|
||||||
|
TEST_REQUIRE_INT(GRAPHIC_SPLIT, 19);
|
||||||
|
|
||||||
/* The gridded half being first is wired the same way round. */
|
/* The gridded half being first is wired the same way round. */
|
||||||
WINDOWS = 0;
|
WINDOWS = 0;
|
||||||
@@ -193,6 +215,10 @@ int main(void)
|
|||||||
broken.writeln = broken_write;
|
broken.writeln = broken_write;
|
||||||
broken.readline = NULL;
|
broken.readline = NULL;
|
||||||
broken.clear = broken_clear;
|
broken.clear = broken_clear;
|
||||||
|
broken.moveto = NULL;
|
||||||
|
broken.window = NULL;
|
||||||
|
broken.grid = NULL;
|
||||||
|
broken.graphic = NULL;
|
||||||
TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &broken, NULL));
|
TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &broken, NULL));
|
||||||
TEST_REQUIRE_STATUS(tee.write(&tee, "X"), AKERR_IO);
|
TEST_REQUIRE_STATUS(tee.write(&tee, "X"), AKERR_IO);
|
||||||
TEST_REQUIRE_STATUS(tee.writeln(&tee, "X"), AKERR_IO);
|
TEST_REQUIRE_STATUS(tee.writeln(&tee, "X"), AKERR_IO);
|
||||||
|
|||||||
@@ -197,16 +197,17 @@ static akerr_ErrorContext AKERR_NOIGNORE *draw_program(const char *outpath, int
|
|||||||
}
|
}
|
||||||
PASS(errctx, akbasic_runtime_load(&RUNTIME, SOURCE));
|
PASS(errctx, akbasic_runtime_load(&RUNTIME, SOURCE));
|
||||||
PASS(errctx, akbasic_runtime_start(&RUNTIME, AKBASIC_MODE_RUN));
|
PASS(errctx, akbasic_runtime_start(&RUNTIME, AKBASIC_MODE_RUN));
|
||||||
|
/* The same layer bracket and composition order the standalone frontend uses. */
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_begin(&GRAPHICS));
|
||||||
PASS(errctx, akbasic_runtime_run(&RUNTIME, 0));
|
PASS(errctx, akbasic_runtime_run(&RUNTIME, 0));
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_end(&GRAPHICS));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The drawing verbs are immediate and are already on the target. The text
|
* The drawing layer, text grid and sprites all redraw from retained state.
|
||||||
* grid and the sprites are not: both are redrawn from state the interpreter
|
* Keep this in the frontend's order so a documentation image exercises the
|
||||||
* keeps, which in the standalone frontend happens once a frame. There is no
|
* same GRAPHIC composition users run rather than a convenient direct path.
|
||||||
* frame here, so this is it -- and the order is the frontend's, grid first
|
|
||||||
* and sprites over it, because that ordering is what a program written
|
|
||||||
* against the real host will have assumed.
|
|
||||||
*/
|
*/
|
||||||
|
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
|
||||||
if ( grid ) {
|
if ( grid ) {
|
||||||
PASS(errctx, akbasic_sink_akgl_render(&SINK));
|
PASS(errctx, akbasic_sink_akgl_render(&SINK));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user