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>
202 lines
8.5 KiB
C
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_
|