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>
313 lines
13 KiB
C
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_
|