Files
akbasic/include/akbasic/environment.h
Tachikoma fa52b2beca Document the public API in libakgl's Doxygen style
Adds a Doxyfile in the shape libakgl uses -- fifteen deliberate lines, including
WARN_IF_UNDOCUMENTED and WARN_AS_ERROR=FAIL_ON_WARNINGS -- and fills in the 70
public declarations that had no doc block, bringing include/akbasic to 114 of
114.

libakgl is the model rather than libakstdlib. Measured before starting: libakgl
documents 122 of 122 public declarations and libakstdlib 3 of 25, and
libakstdlib's Doxyfile is the unedited doxygen default, PROJECT_NAME = "My
Project" and an empty INPUT. So the house standard is libakgl's, down to the
boilerplate phrasing for the recurring parameters -- "Object to initialize,
inspect, or modify", "Output destination populated by the function", "`NULL` on
success, otherwise an error context owned by the caller".

Worth knowing what the gate actually gates. EXTRACT_ALL=YES suppresses
doxygen's undocumented-entity warnings, so the rule it enforces is that a
*partial* block is an error: document one @param and you must document them all.
Verified by deleting a @param and confirming a non-zero exit, then restoring it.
Full coverage is therefore a convention this commit adopts rather than something
the tool made me do.

Where a contract is non-obvious the block says so rather than restating the
signature: math_plus explains why it alone mutates its left operand, new_unary
notes that hanging the operand on .right is what makes the parser miscount a
negative literal argument, leaf_to_string warns that an assignment renders with
an empty operator, and stop_waiting records that a verb nobody is waiting for is
tolerated. Each cross-references its TODO.md section 6 item.

Also records in TODO.md that this repository has no CI, which libakgl and
libakstdlib both have -- so ctest, the sanitizer build, coverage and this new
doxygen gate are all run by hand today.

ctest 61/61; doxygen Doxyfile exits 0; no warnings under -Wall -Wextra.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-31 07:00:16 -04:00

202 lines
8.5 KiB
C

/**
* @file environment.h
* @brief Declares a scope and the per-line working state that rides with it.
*
* Ported from basicenvironment.go. An environment is both a variable scope and
* the in-flight state of whatever block structure is executing: IF, FOR, GOSUB
* and READ all park their bookkeeping here.
*
* The reference allocates one with new() per FOR and per GOSUB and never frees
* it. Here they come from a pool the runtime owns and are released on pop, so a
* long-running program exhausts a bounded resource and says so rather than
* leaking quietly.
*/
#ifndef _AKBASIC_ENVIRONMENT_H_
#define _AKBASIC_ENVIRONMENT_H_
#include <akerror.h>
#include <akbasic/grammar.h>
#include <akbasic/symtab.h>
#include <akbasic/types.h>
#include <akbasic/value.h>
#include <akbasic/variable.h>
struct akbasic_Runtime;
typedef struct akbasic_Environment
{
akbasic_SymbolTable variables; /** name -> akbasic_Variable * */
akbasic_SymbolTable functions; /** name -> akbasic_FunctionDef * */
akbasic_SymbolTable labels; /** name -> line number */
/* FOR state */
akbasic_ASTLeaf *forStepLeaf;
akbasic_Value forStepValue;
akbasic_ASTLeaf *forToLeaf;
akbasic_Value forToValue;
akbasic_Variable *forNextVariable;
/* Loop bounds */
int64_t loopFirstLine;
int64_t loopExitLine;
int64_t gosubReturnLine;
/* READ state. The identifier leaves are deep copies, so they need storage. */
int64_t readReturnLine;
akbasic_ASTLeaf *readIdentifierLeaves[AKBASIC_MAX_LEAVES];
int64_t readIdentifierIdx;
akbasic_ASTLeaf readLeafStorage[AKBASIC_MAX_LEAVES];
akbasic_LeafPool readLeafPool;
/*
* While this is set, no line executes until a COMMAND matching it is found.
* It is what keeps the body of a loop that should not run at all from
* running, given that the reference evaluates a loop's condition at the
* *bottom* of the structure. Any reimplementation has to reproduce it or
* restructure control flow deliberately.
*/
char waitingForCommand[AKBASIC_SYMTAB_MAX_KEY];
struct akbasic_Environment *parent;
struct akbasic_Runtime *runtime;
/* Runtime state */
int64_t lineno;
akbasic_Value values[AKBASIC_MAX_VALUES];
int nextvalue;
int64_t nextline;
akbasic_Value returnValue;
/* Parser state */
akbasic_Token tokens[AKBASIC_MAX_TOKENS];
int nexttoken;
int curtoken;
akbasic_ASTLeaf leaves[AKBASIC_MAX_LEAVES];
int nextleaf;
akbasic_Token *errorToken;
bool used; /** Pool bookkeeping */
} akbasic_Environment;
/**
* @brief Bring an environment up as a child of another, or as the root.
* @param obj Object to initialize, inspect, or modify.
* @param runtime The runtime that owns the pools this scope draws from.
* @param parent Enclosing scope, or NULL for the root.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` or `runtime` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_init(akbasic_Environment *obj, struct akbasic_Runtime *runtime, akbasic_Environment *parent);
/**
* @brief Reset the per-line value pool without touching variables or scope.
* @param obj Object to initialize, inspect, or modify.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_zero(akbasic_Environment *obj);
/**
* @brief Reset the per-line token and leaf pools and their cursors.
* @param obj Object to initialize, inspect, or modify.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_zero_parser(akbasic_Environment *obj);
/** @brief Take the next value from this environment's per-line pool. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_value(akbasic_Environment *obj, akbasic_Value **dest);
/** @brief Take the next leaf from this environment's per-line pool. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_leaf(akbasic_Environment *obj, akbasic_ASTLeaf **dest);
/**
* @brief Suppress execution until a given verb is reached.
*
* The reference panics on a second pending wait. This raises instead, but it is
* still a hard failure: two waits in one scope means the block structure is
* already corrupt.
*
* @param obj Object to initialize, inspect, or modify.
* @param command Verb to skip forward to, e.g. "NEXT" or "DATA".
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_STATE When this scope is already waiting for something.
* @throws AKBASIC_ERR_BOUNDS When the verb name is too long to record.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command);
/**
* @brief True when this scope or any enclosing one is skipping forward.
* @param obj Scope to inspect; NULL is not waiting.
* @return `true` when execution is currently suppressed.
*/
bool akbasic_environment_is_waiting_for_any(akbasic_Environment *obj);
/**
* @brief True when this scope or an enclosing one is waiting for a given verb.
* @param obj Scope to inspect; NULL is not waiting.
* @param command Verb to test for.
* @return `true` when that verb is what execution is waiting on.
*/
bool akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command);
/**
* @brief Clear a pending wait, searching the parent chain for it.
*
* A verb that is not being waited for is silently tolerated, matching the
* reference -- which ignores the argument entirely and clears unconditionally
* (TODO.md section 6 item 3).
*
* @param obj Object to initialize, inspect, or modify.
* @param command Verb whose wait should be cleared.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` or `command` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_stop_waiting(akbasic_Environment *obj, const char *command);
/**
* @brief Find a variable, creating it in the active environment if it is absent.
*
* Parents do not create variables on behalf of their children: only the
* runtime's currently active environment auto-creates. A lookup that misses in a
* non-active environment returns NULL through `dest` without error, matching the
* reference.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_get(akbasic_Environment *obj, const char *varname, akbasic_Variable **dest);
/**
* @brief Resolve a label to the line number it marks.
* @param obj Scope to search; the parent chain is walked.
* @param label Label name, which carries no type suffix.
* @param dest Output destination populated by the function.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_UNDEFINED When no enclosing scope defines the label.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_get_label(akbasic_Environment *obj, const char *label, int64_t *dest);
/**
* @brief Record a label against a line number.
*
* Labels are created only in the top-level scope, so one set inside a loop is
* still visible after it.
*
* @param obj Scope the request came from; the search walks up from here.
* @param label Label name.
* @param value Line number to record.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_ENVIRONMENT When no top-level scope is reachable.
* @throws AKBASIC_ERR_BOUNDS When the label table is full.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_set_label(akbasic_Environment *obj, const char *label, int64_t value);
/**
* @brief Find a user-defined function by name, case-insensitively.
* @param obj Scope to search; the parent chain is walked.
* @param fname Function name; folded to upper case before lookup.
* @param dest Output destination populated by the function; an akbasic_FunctionDef *.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_KEY When no enclosing scope defines the function.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_get_function(akbasic_Environment *obj, const char *fname, void **dest);
/** @brief Assign into the slot an lvalue leaf names, following any subscripts. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_assign(akbasic_Environment *obj, akbasic_ASTLeaf *lval, akbasic_Value *rval, akbasic_Value **dest);
#endif // _AKBASIC_ENVIRONMENT_H_