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>
195 lines
8.8 KiB
C
195 lines
8.8 KiB
C
/**
|
|
* @file parser.h
|
|
* @brief Declares the recursive-descent parser.
|
|
*
|
|
* The hierarchy is as-per "Commodore 128 Programmer's Reference Guide" page 23,
|
|
* copied verbatim from the reference because it is the specification:
|
|
*
|
|
* program -> line*
|
|
* line -> (line_number ( command | expression )) (immediate_command expression)
|
|
* command -> command (expression)
|
|
* expression -> logicalandor
|
|
* logicalandor -> logicalnot ( "OR" "AND" ) logicalnot
|
|
* logicalnot -> "NOT" relation
|
|
* relation -> subtraction* [ < <= = <> >= > ] subtraction*
|
|
* subtraction -> addition* "-" addition*
|
|
* addition -> multiplication* "+" multiplication*
|
|
* multiplication -> division* "*" division*
|
|
* division -> unary* "/" unary*
|
|
* unary -> "-" exponent
|
|
* primary -> IDENTIFIER | LITERAL_INT | LITERAL_FLOAT | LITERAL_STRING | "(" expression ")"
|
|
*/
|
|
|
|
#ifndef _AKBASIC_PARSER_H_
|
|
#define _AKBASIC_PARSER_H_
|
|
|
|
#include <akerror.h>
|
|
|
|
#include <akbasic/grammar.h>
|
|
#include <akbasic/runtime.h>
|
|
|
|
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.
|
|
*
|
|
* 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_
|