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>
74 lines
3.5 KiB
C
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_
|