Files
akbasic/include/akbasic/grammar.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

313 lines
13 KiB
C

/**
* @file grammar.h
* @brief Declares the token and AST leaf types the scanner and parser share.
*
* The reference splits these across basicscanner.go (BasicTokenType),
* basicparser.go (BasicToken) and basicgrammar.go (BasicASTLeafType,
* BasicASTLeaf). They are one header here because a leaf's operator *is* a token
* type and separating them buys nothing in C. The numeric values are preserved
* from the reference so a debugging session against either implementation reads
* the same.
*/
#ifndef _AKBASIC_GRAMMAR_H_
#define _AKBASIC_GRAMMAR_H_
#include <akerror.h>
#include <akbasic/types.h>
typedef enum
{
AKBASIC_TOK_UNDEFINED = 0,
AKBASIC_TOK_EQUAL, /* 1 */
AKBASIC_TOK_LESS_THAN, /* 2 */
AKBASIC_TOK_LESS_THAN_EQUAL, /* 3 */
AKBASIC_TOK_GREATER_THAN, /* 4 */
AKBASIC_TOK_GREATER_THAN_EQUAL, /* 5 */
AKBASIC_TOK_COMMA, /* 6 */
AKBASIC_TOK_HASH, /* 7 */
AKBASIC_TOK_NOT_EQUAL, /* 8 */
AKBASIC_TOK_LEFT_PAREN, /* 9 */
AKBASIC_TOK_RIGHT_PAREN, /* 10 */
AKBASIC_TOK_PLUS, /* 11 */
AKBASIC_TOK_MINUS, /* 12 */
AKBASIC_TOK_LEFT_SLASH, /* 13 */
AKBASIC_TOK_STAR, /* 14 */
AKBASIC_TOK_CARAT, /* 15 */
AKBASIC_TOK_LITERAL_STRING, /* 16 */
AKBASIC_TOK_LITERAL_INT, /* 17 */
AKBASIC_TOK_LITERAL_FLOAT, /* 18 */
AKBASIC_TOK_IDENTIFIER, /* 19 */
AKBASIC_TOK_IDENTIFIER_STRING, /* 20 */
AKBASIC_TOK_IDENTIFIER_FLOAT, /* 21 */
AKBASIC_TOK_IDENTIFIER_INT, /* 22 */
AKBASIC_TOK_COLON, /* 23 */
AKBASIC_TOK_AND, /* 24 */
AKBASIC_TOK_NOT, /* 25 */
AKBASIC_TOK_OR, /* 26 */
AKBASIC_TOK_REM, /* 27 */
AKBASIC_TOK_EOL, /* 28 */
AKBASIC_TOK_EOF, /* 29 */
AKBASIC_TOK_LINE_NUMBER, /* 30 -- an integer literal in token position 0 */
AKBASIC_TOK_COMMAND, /* 31 */
AKBASIC_TOK_COMMAND_IMMEDIATE, /* 32 */
AKBASIC_TOK_FUNCTION, /* 33 */
AKBASIC_TOK_ASSIGNMENT, /* 34 */
AKBASIC_TOK_LEFT_SQUAREBRACKET, /* 35 */
AKBASIC_TOK_RIGHT_SQUAREBRACKET, /* 36 */
AKBASIC_TOK_ARRAY_SUBSCRIPT, /* 37 */
AKBASIC_TOK_FUNCTION_ARGUMENT, /* 38 */
AKBASIC_TOK_ATSYMBOL, /* 39 */
AKBASIC_TOK_IDENTIFIER_STRUCT /* 40 */
} akbasic_TokenType;
typedef enum
{
AKBASIC_LEAF_UNDEFINED = 0,
AKBASIC_LEAF_LITERAL_INT, /* 1 */
AKBASIC_LEAF_LITERAL_FLOAT, /* 2 */
AKBASIC_LEAF_LITERAL_STRING, /* 3 */
AKBASIC_LEAF_IDENTIFIER, /* 4 */
AKBASIC_LEAF_IDENTIFIER_INT, /* 5 */
AKBASIC_LEAF_IDENTIFIER_FLOAT, /* 6 */
AKBASIC_LEAF_IDENTIFIER_STRING, /* 7 */
AKBASIC_LEAF_UNARY, /* 8 */
AKBASIC_LEAF_BINARY, /* 9 */
AKBASIC_LEAF_GROUPING, /* 10 */
AKBASIC_LEAF_EQUALITY, /* 11 */
AKBASIC_LEAF_COMPARISON, /* 12 */
AKBASIC_LEAF_TERM, /* 13 */
AKBASIC_LEAF_PRIMARY, /* 14 */
AKBASIC_LEAF_COMMAND, /* 15 */
AKBASIC_LEAF_COMMAND_IMMEDIATE, /* 16 */
AKBASIC_LEAF_FUNCTION, /* 17 */
AKBASIC_LEAF_BRANCH, /* 18 */
AKBASIC_LEAF_ARGUMENTLIST, /* 19 */
AKBASIC_LEAF_IDENTIFIER_STRUCT /* 20 */
} akbasic_LeafType;
typedef struct
{
akbasic_TokenType tokentype;
int64_t lineno;
char lexeme[AKBASIC_MAX_LINE_LENGTH];
} akbasic_Token;
typedef struct akbasic_ASTLeaf
{
akbasic_LeafType leaftype;
int64_t literal_int;
char literal_string[AKBASIC_MAX_STRING_LENGTH];
double literal_float;
char identifier[AKBASIC_MAX_STRING_LENGTH];
akbasic_TokenType operator_;
struct akbasic_ASTLeaf *parent;
struct akbasic_ASTLeaf *left;
struct akbasic_ASTLeaf *right;
struct akbasic_ASTLeaf *expr;
} akbasic_ASTLeaf;
/**
* @brief A pool of leaves a deep clone can draw from.
*
* The reference's BasicASTLeaf.clone() recurses and allocates. Here the caller
* supplies storage and a deep clone that would exceed it fails rather than
* growing without bound.
*/
typedef struct
{
int next;
int capacity;
akbasic_ASTLeaf *leaves;
} akbasic_LeafPool;
/**
* @brief Reset a token to the undefined state.
* @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_token_init(akbasic_Token *obj);
/**
* @brief Reset a leaf, clearing every payload and link.
* @param obj Object to initialize, inspect, or modify.
* @param leaftype Type to stamp on the leaf.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_init(akbasic_ASTLeaf *obj, akbasic_LeafType leaftype);
/**
* @brief Deep-copy a leaf and everything hanging off it into pool storage.
* @param self Source leaf.
* @param pool Storage the copies are drawn from.
* @param dest Output destination populated by the function.
* @throws AKBASIC_ERR_BOUNDS When the pool runs out of leaves.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_clone(akbasic_ASTLeaf *self, akbasic_LeafPool *pool, akbasic_ASTLeaf **dest);
/**
* @brief The first argument of a function-call leaf.
* @param self Leaf to inspect.
* @return The first argument, or NULL when the leaf carries no argument list.
*/
akbasic_ASTLeaf *akbasic_leaf_first_argument(akbasic_ASTLeaf *self);
/**
* @brief The first subscript of an array-reference leaf.
* @param self Leaf to inspect.
* @return The first subscript, or NULL when the leaf carries no subscript list.
*/
akbasic_ASTLeaf *akbasic_leaf_first_subscript(akbasic_ASTLeaf *self);
/**
* @brief True when a leaf is any of the four identifier kinds.
* @param self Leaf to inspect; NULL is not an identifier.
* @return `true` when the leaf names a variable or a label.
*/
bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self);
/**
* @brief True when a leaf is an integer, float or string literal.
* @param self Leaf to inspect; NULL is not a literal.
* @return `true` when the leaf carries a literal value.
*/
bool akbasic_leaf_is_literal(akbasic_ASTLeaf *self);
/**
* @brief Build a comparison leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param left Left operand.
* @param op Comparison operator; must be one of the six relational tokens.
* @param right Right operand.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `left` or `right` is NULL.
* @throws AKBASIC_ERR_SYNTAX When `op` is not a comparison operator.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_comparison(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *left, akbasic_TokenType op, akbasic_ASTLeaf *right);
/**
* @brief Build a binary-operator leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param left Left operand.
* @param op Operator token.
* @param right Right operand.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `left` or `right` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_binary(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *left, akbasic_TokenType op, akbasic_ASTLeaf *right);
/**
* @brief Build a unary-operator leaf.
*
* The operand hangs off `.right`, which is why the arity counter in the parser
* miscounts a negative literal argument -- see TODO.md section 6 item 13.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param op Operator token.
* @param right Operand.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `right` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_unary(akbasic_ASTLeaf *obj, akbasic_TokenType op, akbasic_ASTLeaf *right);
/**
* @brief Build a function-call leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param fname Function name as written; case is preserved for the error message.
* @param right Argument list, or NULL.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_function(akbasic_ASTLeaf *obj, const char *fname, akbasic_ASTLeaf *right);
/**
* @brief Build a verb leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param cmdname Verb name as written.
* @param right The verb's rval, or NULL for a verb that takes none.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_command(akbasic_ASTLeaf *obj, const char *cmdname, akbasic_ASTLeaf *right);
/**
* @brief Build a leaf for a verb a REPL may run without a line number.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param cmdname Verb name as written.
* @param right The verb's rval, or NULL.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_immediate_command(akbasic_ASTLeaf *obj, const char *cmdname, akbasic_ASTLeaf *right);
/**
* @brief Build a branch leaf, which is what IF parses to.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param expr Condition, evaluated for a BASIC boolean.
* @param trueleaf Taken when the condition is true.
* @param falseleaf Taken when it is false; may be NULL, as ELSE is optional.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `expr` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_branch(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *expr, akbasic_ASTLeaf *trueleaf, akbasic_ASTLeaf *falseleaf);
/**
* @brief Build a parenthesised-expression leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param expr The grouped expression.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `expr` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_grouping(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *expr);
/**
* @brief Build an integer literal leaf from its lexeme.
*
* A `0x` prefix selects base 16 and a bare leading `0` selects base 8, so `010`
* is 8 and `08` will not parse. Commodore BASIC has no octal literals; this is
* reproduced from the reference and filed as TODO.md section 6 item 10.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param lexeme Digits as the scanner captured them.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the lexeme is empty or not a valid number in its base.
* @throws ERANGE When the value does not fit in an int64_t.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const char *lexeme);
/**
* @brief Build a float literal leaf from its lexeme.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param lexeme Digits as the scanner captured them.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the lexeme is not a valid float.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_literal_float(akbasic_ASTLeaf *obj, const char *lexeme);
/**
* @brief Build a string literal leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param lexeme String contents, without the surrounding quotes.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the literal exceeds the length limit.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_literal_string(akbasic_ASTLeaf *obj, const char *lexeme);
/**
* @brief Build an identifier leaf.
* @param obj Leaf to initialize; drawn from a pool by the caller.
* @param leaftype Which identifier type the suffix selected.
* @param lexeme Identifier name, including its type suffix.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_identifier(akbasic_ASTLeaf *obj, akbasic_LeafType leaftype, const char *lexeme);
/**
* @brief Render a leaf the way the reference's toString() does.
*
* Prefix form, so `1 + 2` reads `(+ 1 2)`. Note that an assignment renders with
* an empty operator -- the reference's operator table has a case for `=` meaning
* equality but none for assignment -- so `A# = 1` reads `( A# 1)`.
*
* @param self Leaf to render.
* @param dest Output destination populated by the function.
* @param len Size of `dest`.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `self` or `dest` is NULL.
* @throws AKBASIC_ERR_BOUNDS When `len` is zero.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, size_t len);
#endif // _AKBASIC_GRAMMAR_H_