Files
akbasic/include/akbasic/error.h
Tachikoma f269a97c06 Reach hardware through backend records, and take the time from the host
Groups G, I and E are unblocked but cannot be written yet: the core library is
free of SDL and builds with no libakgl present, so a graphics verb cannot call
akgl_draw_* and a sound verb cannot call akgl_audio_*. This adds what they call
instead.

Three records of function pointers -- akbasic_GraphicsBackend, _AudioBackend and
_InputBackend -- in the same shape as akbasic_TextSink, and the same shape
libakgl uses for akgl_RenderBackend. akbasic_runtime_set_devices() attaches any
subset; all three may be NULL and that is the standalone driver's normal state,
so a runtime with no backends still comes up and still prints. A verb that needs
one it was not given raises the new AKBASIC_ERR_DEVICE rather than dereferencing
a NULL vtable.

Two decisions worth stating. The graphics record has no circle entry point:
BASIC 7.0's CIRCLE takes two radii, an arc range, a rotation and a degree
increment, which makes it a polygon by definition, so it will be built from line
calls rather than from akgl_draw_circle. And coordinates are double rather than
an integer pixel address, because SCALE makes them fractional and rounding at
each verb rather than once at the backend accumulates drift along a polyline.

akbasic_runtime_settime() is how SOUND, PLAY and TEMPO get a clock without the
library reading one. Section 1.6 forbids blocking or owning a loop, so the caller
that owns the frame owns the time -- which is what libakgl already does, since
akgl_actor_logic_changeframe takes curtimems as an argument. Left unset it is
zero and every duration expires immediately: audible, but never a hang.

AKBASIC_ERR_LAST is a sentinel rather than a status, so tests/error_codes.c walks
every code looking for an unnamed one without anybody remembering to widen the
loop when a code is added.

tests/mockdevice.h records every backend call as a formatted line. The graphics
and audio verbs emit nothing a golden file can compare, so that log is where
their assertions have to live -- and since it needs no SDL, the whole of groups
G, I and E stays testable in the default build.

62/62 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:07:23 -04:00

74 lines
3.5 KiB
C

/**
* @file error.h
* @brief Declares akbasic's status codes and the registry claim for them.
*/
#ifndef _AKBASIC_ERROR_H_
#define _AKBASIC_ERROR_H_
#include <akerror.h>
/*
* libakerror 1.0.0 is the floor. That release moved the status-name table into a
* private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the
* registry entry points raise akerr_ErrorContext * instead of returning int, and
* the library gained an soname -- so a translation unit that pairs this header
* with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem.
*
* libakerror publishes no version macro, so this feature-tests on
* AKERR_FIRST_CONSUMER_STATUS, which that release introduced. Same guard
* libakstdlib and libakgl already carry.
*/
#ifndef AKERR_FIRST_CONSUMER_STATUS
#error "libakbasic requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
#endif
/*
* libakerror reserves 0-255 for the host's errno values and its own AKERR_*
* codes. libakgl claims 256-260. akbasic claims 512-767, leaving 261-511 as
* headroom for libakgl to grow into -- see CLAUDE.md for the coordinated range
* map, which is the only coordination there is: libakerror can enumerate its
* consumers no better than we can.
*
* These are absolute constants, never offsets from AKERR_LAST_ERRNO_VALUE, so a
* libc that grows an errno cannot move them. An enum rather than a chain of
* #defines so the values stay compile-time integer constants -- HANDLE expands
* to case labels, which require that.
*
* Add a code here and you must name it in akbasic_error_register(), or it prints
* as "Unknown Error" in every stack trace that carries it.
*/
#define AKBASIC_OWNER "akbasic"
enum {
AKBASIC_ERR_BASE = 512, /** Start of akbasic's reserved status range */
AKBASIC_ERR_SYNTAX = AKBASIC_ERR_BASE,
/** Parse-time grammar violation */
AKBASIC_ERR_TYPE, /** Incompatible types in an operation */
AKBASIC_ERR_UNDEFINED, /** Reference to an undefined verb, function or label */
AKBASIC_ERR_BOUNDS, /** Array subscript or pool index out of range */
AKBASIC_ERR_ENVIRONMENT, /** Environment pool exhausted or orphaned environment */
AKBASIC_ERR_VALUE, /** A value was malformed, truncated or unconvertible */
AKBASIC_ERR_STATE, /** A verb ran outside the block structure it requires */
AKBASIC_ERR_DEVICE, /** A verb needs a graphics, audio or input backend the runtime has not been given, or one that cannot do what was asked */
AKBASIC_ERR_LAST, /** One past the last real code. Not a status; tests/error_codes.c walks up to it so a new code is checked for a name without anybody remembering to widen the loop. */
AKBASIC_ERR_LIMIT = AKBASIC_ERR_BASE + 256
};
/**
* @brief Claim the akbasic status band and register a name for every code in it.
*
* Reserves the whole 256-value range in one call -- libakerror treats only an
* identical reservation as a repeat, so a subset or superset raises -- and then
* names each code through the owned entry point. Call it before anything else in
* the library. Repeating the call is a no-op.
*
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_STATUS_RANGE_OVERLAP When another component already owns part of the band.
* @throws AKERR_STATUS_RANGE_FULL When libakerror has no reservation slots left.
* @throws AKERR_STATUS_NAME_FULL When libakerror's name registry is full.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_error_register(void);
#endif // _AKBASIC_ERROR_H_