Read the keyboard: implement GET, GETKEY and SCNCLR
The poll_key half of group E, against the akbasic_InputBackend record. The libakgl implementation behind it is akgl_controller_poll_key(), which drains a ring the library fills from SDL events the host pumps -- so the interpreter can answer "is there a key waiting" without owning an event loop, which is what goal 3 requires. GET and GETKEY differ in one way and it is the interesting one. GET takes whatever is there including nothing, and an empty buffer is success with the empty string rather than an error -- that is what happens on most iterations of every GET loop ever written, and upstream is explicit that its poll reports it the same way. GETKEY waits, and since the library may not block, waiting is spelled as holding the step loop: the verb sets a flag and akbasic_runtime_step() declines to advance until a key arrives. Every step still returns and a bounded run() still comes back, so a host keeps its frame rate; the program simply does not move past the GETKEY. The PLAY queue is serviced before that check on purpose -- music should keep playing while a program waits for a keypress. Withdrawing the input device while a GETKEY is holding releases it rather than wedging the script on a device that no longer exists. Both verbs accept an integer variable as well as a string one and give it the raw key code, which is what a program testing for cursor or function keys needs. No key is code zero, matching what a C128 reports. A float variable is refused: it is neither a character nor a code. SCNCLR goes through the text sink rather than a device, because the sink is where PRINT already goes and is the only thing that knows what a screen means for this host. The stdio sink treats it as a no-op; clearing a pipe means nothing. One thing worth knowing before writing any bounded-run test, and now commented in tests/input_verbs.c: source lines are stored indexed by line number and the cursor starts at zero, so a step is spent on each empty slot along the way. A program at line 10 needs eleven steps before it has run anything. 70/70 ctest, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,4 +39,44 @@ typedef struct akbasic_InputBackend
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*flush_keys)(struct akbasic_InputBackend *self);
|
||||
} akbasic_InputBackend;
|
||||
|
||||
/**
|
||||
* @brief The console input verbs' state, which lives on the runtime.
|
||||
*
|
||||
* `waiting` is what makes GETKEY work without blocking. On a C128, GETKEY sits
|
||||
* on the keyboard until somebody types; section 1.6 forbids that, so instead the
|
||||
* verb sets this and akbasic_runtime_step() declines to advance the program
|
||||
* until a key turns up. A host keeps its frame rate, a bounded
|
||||
* akbasic_runtime_run() still returns, and the program still does not move past
|
||||
* the GETKEY -- which is the part the program author cares about.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
bool waiting; /* a GETKEY is holding the program */
|
||||
char variable[64]; /* the variable it will assign the key to */
|
||||
bool numeric; /* true when that variable takes a code, not a letter */
|
||||
} akbasic_InputState;
|
||||
|
||||
/* --- Internal API: exposed for the step loop and the tests. --- */
|
||||
|
||||
struct akbasic_Runtime;
|
||||
|
||||
/**
|
||||
* @brief Answer whether a GETKEY is still holding, assigning the key if one came.
|
||||
*
|
||||
* Called from akbasic_runtime_step() before it executes anything. While
|
||||
* `*blocked` comes back true the step does nothing at all, so the program stays
|
||||
* exactly where the GETKEY left it.
|
||||
*
|
||||
* Taking the input device away while a GETKEY is holding releases the hold
|
||||
* rather than wedging the script forever -- a host is allowed to change its mind
|
||||
* about what it lends out.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param blocked Output destination populated by the function; true while a GETKEY is unsatisfied.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When either argument is NULL.
|
||||
* @throws AKBASIC_ERR_UNDEFINED When the variable the GETKEY named has gone out of scope.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_input_service(struct akbasic_Runtime *obj, bool *blocked);
|
||||
|
||||
#endif // _AKBASIC_INPUT_H_
|
||||
|
||||
@@ -111,6 +111,9 @@ typedef struct akbasic_Runtime
|
||||
*/
|
||||
akbasic_AudioState audio_state;
|
||||
|
||||
/* GETKEY's hold on the step loop. Same reasoning again: it is the program's. */
|
||||
akbasic_InputState input_state;
|
||||
|
||||
/*
|
||||
* The host's clock, in milliseconds, as of its last akbasic_runtime_settime()
|
||||
* call. The interpreter does not read a clock: it owns no loop and must not
|
||||
|
||||
Reference in New Issue
Block a user