Files
akbasic/include/akbasic/input.h
Tachikoma 9eb85b42e5 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>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 08:35:05 -04:00

83 lines
3.3 KiB
C

/**
* @file input.h
* @brief Declares the input backend: where GET and GETKEY read from.
*
* Same reasoning as graphics.h and audio.h. The libakgl implementation is
* akgl_controller_poll_key(), which drains a keystroke ring the library fills
* from SDL events the *host* pumps -- so the interpreter answers "is there a key
* waiting" without owning the event loop, which is what goal 3 requires.
*
* One thing an embedding host should know: that ring is process-global and the
* host's own control maps read the same events. An interpreter sitting in a
* GET loop drains keystrokes the game will then never see. A host that cares
* either withholds the input backend or supplies a filtered one of its own.
*/
#ifndef _AKBASIC_INPUT_H_
#define _AKBASIC_INPUT_H_
#include <akerror.h>
#include <akbasic/types.h>
/**
* @brief Where the console input verbs read.
*/
typedef struct akbasic_InputBackend
{
void *self;
/**
* Take the next buffered keystroke without waiting for one.
*
* An empty buffer is success with `*available` false, never an error: GET on
* an empty buffer yields the empty string, which is ordinary BASIC and
* happens on most iterations of any GET loop.
*/
akerr_ErrorContext AKERR_NOIGNORE *(*poll_key)(struct akbasic_InputBackend *self, int *keycode, bool *available);
/** Discard everything buffered. */
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_