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>
This commit is contained in:
2026-07-31 07:00:16 -04:00
parent e51b9a1694
commit 134ade9392
9 changed files with 783 additions and 15 deletions

View File

@@ -33,28 +33,162 @@ typedef struct akbasic_Parser
akbasic_Runtime *runtime;
} akbasic_Parser;
/**
* @brief Bind a parser to a runtime.
* @param obj Object to initialize, inspect, or modify.
* @param runtime The runtime whose active environment holds the token stream.
* @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_parser_init(akbasic_Parser *obj, akbasic_Runtime *runtime);
/**
* @brief Reset the token and leaf cursors for a fresh line.
* @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_parser_zero(akbasic_Parser *obj);
/** @brief Parse one statement from the token stream. */
/**
* @brief Parse one statement from the token stream.
*
* A line can hold more than one statement, so callers loop on
* akbasic_parser_is_at_end().
*
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When the tokens do not parse.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_parse(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief True when the token stream for this line is spent.
* @param obj Parser to inspect; a NULL or unbound parser is at the end.
* @return `true` when there is nothing left to parse.
*/
bool akbasic_parser_is_at_end(akbasic_Parser *obj);
/* --- Internal API: the grammar rules, reachable from src/parser_commands.c. --- */
/**
* @brief Grammar rule: a verb and its rval, or an assignment.
*
* A verb with its own parse path in the dispatch table delegates to it; anything
* else takes an expression as its rval, and a line that is not a verb at all
* falls through to assignment().
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function; the leaf this rule produced.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
* @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_command(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief Grammar rule: `IDENTIFIER = expression`, or a bare expression.
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function; the leaf this rule produced.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
* @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_assignment(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief Grammar rule: the top of the expression chain.
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function; the leaf this rule produced.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
* @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_expression(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief Grammar rule: a comparison between two subtractions.
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function; the leaf this rule produced.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
* @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_relation(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief Grammar rule: a literal, an identifier, or a parenthesised expression.
*
* An identifier with a type suffix also consumes an array subscript list when
* one follows it.
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function; the leaf this rule produced.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
* @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_primary(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief Grammar rule: comma-separated expressions joined through `.right`.
*
* With `requireparens` false and no opening paren this still builds a list,
* which is how DATA and READ take a bare comma-separated series.
*
* @param obj Object to initialize, inspect, or modify.
* @param arglisttype Marks the list as function arguments or array subscripts.
* @param requireparens When true, parentheses are mandatory and unbalanced ones are an error.
* @param dest Output destination populated by the function; NULL when parens were required and absent.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When `arglisttype` is wrong or a paren is unbalanced.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_argument_list(akbasic_Parser *obj, akbasic_TokenType arglisttype, bool requireparens, akbasic_ASTLeaf **dest);
/**
* @brief Take the next leaf from the active environment's per-line pool.
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_BOUNDS When the line has already used all its leaves.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_new_leaf(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
* @brief Consume the next token when it is any of the given types.
* @param obj Object to initialize, inspect, or modify.
* @param types Token types to accept.
* @param count How many entries `types` holds.
* @return `true` when a token was consumed.
*/
bool akbasic_parser_match(akbasic_Parser *obj, const akbasic_TokenType *types, int count);
/**
* @brief Consume the next token when it is of one given type.
* @param obj Object to initialize, inspect, or modify.
* @param type Token type to accept.
* @return `true` when a token was consumed.
*/
bool akbasic_parser_match1(akbasic_Parser *obj, akbasic_TokenType type);
/**
* @brief The next token, without consuming it.
* @param obj Parser to inspect.
* @return The next token, or NULL at the end of the line.
*/
akbasic_Token *akbasic_parser_peek(akbasic_Parser *obj);
/**
* @brief The token just consumed.
* @param obj Object to initialize, inspect, or modify.
* @param dest Output destination populated by the function.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_SYNTAX When nothing has been consumed yet.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_previous(akbasic_Parser *obj, akbasic_Token **dest);
/**
* @brief Raise a parse error naming the token the parser stopped on.
*
* Always returns an error; it never succeeds. The reference also dumps a stack
* trace here, which is not reproduced -- an interpreter library does not print
* the host's stack, and the akerr trace already carries it.
*
* @param obj Object to initialize, inspect, or modify.
* @param message What was expected, appended to the position.
* @return An error context owned by the caller; never `NULL`.
* @throws AKBASIC_ERR_SYNTAX Always.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_error(akbasic_Parser *obj, const char *message);
#endif // _AKBASIC_PARSER_H_