diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..e596a31 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,14 @@ +PROJECT_NAME = libakbasic +PROJECT_BRIEF = "Commodore/Dartmouth BASIC interpreter, embeddable in C" +OUTPUT_DIRECTORY = build/docs +INPUT = include/akbasic src examples +FILE_PATTERNS = *.h *.c +RECURSIVE = YES +EXTRACT_ALL = YES +EXTRACT_STATIC = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_AS_ERROR = FAIL_ON_WARNINGS +GENERATE_HTML = YES +GENERATE_LATEX = NO +QUIET = YES diff --git a/README.md b/README.md index e3954e8..8927a09 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,17 @@ cmake --build build --parallel # The test suite: unit tests plus the Go version's own corpus, byte-compared ctest --test-dir build --output-on-failure + +# API documentation, into build/docs/html +doxygen Doxyfile ``` +The `Doxyfile` is configured the way `libakgl`'s is, including +`WARN_AS_ERROR = FAIL_ON_WARNINGS` — a doc block that documents some of a function's +parameters but not all of them fails the run, so `doxygen Doxyfile` is a gate rather than a +convenience. Every one of the 114 public declarations under `include/akbasic/` carries a +`@brief`, a `@param` per parameter, a `@return` and its `@throws`. + # Why rewrite it in C? Three reasons, in the order they matter. diff --git a/TODO.md b/TODO.md index 3a48942..43b9283 100644 --- a/TODO.md +++ b/TODO.md @@ -620,6 +620,7 @@ entire test corpus and passes clean under ASan and UBSan. | Line coverage | 92.3% (2823/3058) — above the 90% gate | | Function coverage | 96.9% (221/228) | | Warnings | none under `-Wall -Wextra` | +| `doxygen Doxyfile` | clean; 114/114 public declarations documented | Branch coverage reads 17.3% and is not a target, for the reason `libakgl/TODO.md` and `libakstdlib/TODO.md` both give: the akerror control-flow macros expand into large branch trees @@ -634,6 +635,11 @@ Dependency baseline: | `deps/libakstdlib` | 0.1.0 | soname `libakstdlib.so.0.1`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. Its `aksl_ato*` family is banned here — see §1.9. | | `deps/libakgl` | 0.1.0 | Migrated to the 1.0.0 registry and owns 256–260. Not yet linked: `AKBASIC_WITH_AKGL` defaults OFF and `src/sink_akgl.c` does not exist. | +**This repository has no CI.** `libakgl` and `libakstdlib` both carry +`.gitea/workflows/ci.yaml`; akbasic does not, so every gate here — ctest, the sanitizer build, +coverage, and `doxygen Doxyfile` with `WARN_AS_ERROR` — is currently run by hand. Copying +`deps/libakgl/.gitea/workflows/ci.yaml` and adjusting the option prefixes is the whole job. + What remains, in priority order: 1. **§3 — the akgl text sink.** Blocked on `libakgl` having no text-measurement call; that gap diff --git a/include/akbasic/environment.h b/include/akbasic/environment.h index 0251b66..0d0a331 100644 --- a/include/akbasic/environment.h +++ b/include/akbasic/environment.h @@ -81,8 +81,28 @@ typedef struct akbasic_Environment 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. */ @@ -91,9 +111,45 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_value(akbasic_Environ /** @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); /** @@ -106,8 +162,37 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_stop_waiting(akbasic_Envi */ 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. */ diff --git a/include/akbasic/grammar.h b/include/akbasic/grammar.h index 6967f59..0817896 100644 --- a/include/akbasic/grammar.h +++ b/include/akbasic/grammar.h @@ -122,8 +122,21 @@ typedef struct 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); /** @@ -135,29 +148,165 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_init(akbasic_ASTLeaf *obj, akbas */ 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, or NULL. */ +/** + * @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, or NULL. */ +/** + * @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 as the reference's toString() does, for tests and LIST. */ +/** + * @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_ diff --git a/include/akbasic/parser.h b/include/akbasic/parser.h index 4f6864e..8aa80b8 100644 --- a/include/akbasic/parser.h +++ b/include/akbasic/parser.h @@ -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_ diff --git a/include/akbasic/runtime.h b/include/akbasic/runtime.h index 168ffd2..382fd75 100644 --- a/include/akbasic/runtime.h +++ b/include/akbasic/runtime.h @@ -104,32 +104,120 @@ typedef struct akbasic_Runtime */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_init(akbasic_Runtime *obj, akbasic_TextSink *sink); -/** @brief Reset the per-line state without disturbing the program or variables. */ +/** + * @brief Reset the per-line state without disturbing the program or variables. + * @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_runtime_zero(akbasic_Runtime *obj); +/** + * @brief Push a new scope, taking one from the pool. + * @param obj Object to initialize, inspect, or modify. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_ENVIRONMENT When the environment pool is exhausted. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_environment(akbasic_Runtime *obj); +/** + * @brief Pop the active scope and release it back to the pool. + * + * The reference never releases, which its garbage collector papers over. Here + * the pool is finite, so an unreleased scope shows up as exhaustion later. + * + * @param obj Object to initialize, inspect, or modify. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_ENVIRONMENT When the active scope is the root. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_prev_environment(akbasic_Runtime *obj); -/** @brief Report a BASIC error on the current line, in the reference's format. */ +/** + * @brief Report a BASIC error on the current line, in the reference's format. + * + * Writes `? : ` through the sink. The message is expected + * to end in a newline and the sink adds another, which is why an error line in + * the golden corpus is followed by a blank one -- that is the contract, not an + * accident. + * + * @param obj Object to initialize, inspect, or modify. + * @param errclass Which error class to name in the line. + * @param message Text to append; conventionally ends in a newline. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `obj` or `message` is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_error(akbasic_Runtime *obj, akbasic_ErrorClass errclass, const char *message); -/** @brief Write text, mirroring the reference's Write(). */ +/** + * @brief Write text through the sink, mirroring the reference's Write(). + * @param obj Object to initialize, inspect, or modify. + * @param text Text to emit. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `obj` or `text` is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_write(akbasic_Runtime *obj, const char *text); -/** @brief Write text and a newline, mirroring the reference's Println(). */ +/** + * @brief Write text and a newline, mirroring the reference's Println(). + * @param obj Object to initialize, inspect, or modify. + * @param text Text to emit. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `obj` or `text` is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_println(akbasic_Runtime *obj, const char *text); +/** + * @brief Change the execution mode, announcing READY when entering the REPL. + * @param obj Object to initialize, inspect, or modify. + * @param mode One of the AKBASIC_MODE_* values. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `obj` is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_mode(akbasic_Runtime *obj, int mode); -/** @brief Evaluate one AST leaf, drawing scratch values from the environment. */ +/** + * @brief Evaluate one AST leaf, drawing scratch values from the environment. + * @param obj Object to initialize, inspect, or modify. + * @param expr Leaf to evaluate. + * @param dest Output destination populated by the function; points into the per-line value pool. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + * @throws AKBASIC_ERR_BOUNDS When the per-line value pool is exhausted. + * @throws AKBASIC_ERR_UNDEFINED When the leaf names a verb, function or label that does not exist. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest); -/** @brief Evaluate a leaf only when it is a command a REPL may run immediately. */ +/** + * @brief Evaluate a leaf only when it is a verb a REPL may run without a line number. + * @param obj Object to initialize, inspect, or modify. + * @param expr Leaf to consider. + * @param dest Output destination populated by the function; NULL when the leaf was not immediate. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_interpret_immediate(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest); -/** @brief Evaluate a leaf unless the environment is skipping forward to a verb. */ +/** + * @brief Evaluate a leaf unless the environment is skipping forward to a verb. + * @param obj Object to initialize, inspect, or modify. + * @param expr Leaf to evaluate. + * @param dest Output destination populated by the function. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest); -/** @brief Call a user-defined function or subroutine. */ +/** + * @brief Call a user-defined function or subroutine. + * + * A single-expression DEF evaluates and returns. A multi-line one hands control + * to its own scope and runs until RETURN pops back out, which is why this can + * execute an arbitrary number of source lines. + * + * @param obj Object to initialize, inspect, or modify. + * @param expr The call leaf, carrying the function name and its arguments. + * @param dest Output destination populated by the function. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_KEY When no such function is defined. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_user_function(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest); /** @@ -137,16 +225,38 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_user_function(akbasic_Runtime * * One call reads or runs at most one source line. It never blocks beyond a * single readline on the sink, and it always returns. + * + * @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_runtime_step(akbasic_Runtime *obj); /** * @brief Step until the runtime quits or `maxsteps` steps have elapsed. + * + * This is the entry point an embedding host calls once per frame. A bounded + * budget is what keeps a script with an infinite loop from taking the host's + * frame rate with it. + * + * @param obj Object to initialize, inspect, or modify. * @param maxsteps Step ceiling; zero or negative means unbounded. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `obj` is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_run(akbasic_Runtime *obj, int maxsteps); -/** @brief Point the runtime at an input stream and choose a starting mode. */ +/** + * @brief Choose a starting mode and decide what happens when a program ends. + * + * Starting in AKBASIC_MODE_REPL returns to the REPL when a program finishes; + * anything else quits. + * + * @param obj Object to initialize, inspect, or modify. + * @param mode One of the AKBASIC_MODE_* values. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `obj` is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_start(akbasic_Runtime *obj, int mode); /** @@ -173,12 +283,60 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_load(akbasic_Runtime *obj, co /* --- Internal API: exposed for the scanner, parser and verb handlers only. --- */ +/** + * @brief Take an unused variable from the runtime's 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 every variable slot is in use. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_variable(akbasic_Runtime *obj, akbasic_Variable **dest); +/** + * @brief Take an unused function definition from the runtime's 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 every function slot is in use. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_function(akbasic_Runtime *obj, akbasic_FunctionDef **dest); +/** + * @brief File one already-scanned source line under its line number. + * + * Hosts should prefer akbasic_runtime_load(), which scans as well. + * + * @param obj Object to initialize, inspect, or modify. + * @param lineno Line number to file it under. + * @param code Source text, with any line number already stripped. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_BOUNDS When the number or the line length is out of range. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_store_line(akbasic_Runtime *obj, int64_t lineno, const char *code); +/** + * @brief Execute the next line of the stored program. + * @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_runtime_process_line_run(akbasic_Runtime *obj); +/** + * @brief Read one line from the sink and file it, without executing it. + * @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_runtime_process_line_runstream(akbasic_Runtime *obj); +/** + * @brief Read one line from the sink and either run it or file it. + * @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_runtime_process_line_repl(akbasic_Runtime *obj); +/** + * @brief The nearest non-empty line before the current one. + * @param obj Runtime to inspect. + * @return That line's number, or the current one when there is nothing before it. + */ int64_t akbasic_runtime_find_previous_lineno(akbasic_Runtime *obj); #endif // _AKBASIC_RUNTIME_H_ diff --git a/include/akbasic/value.h b/include/akbasic/value.h index 726b0ad..66fa835 100644 --- a/include/akbasic/value.h +++ b/include/akbasic/value.h @@ -76,26 +76,196 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_set_bool(akbasic_Value *obj, bo /** @brief True when the value is a boolean holding AKBASIC_TRUE. */ bool akbasic_value_is_true(akbasic_Value *self); +/** + * @brief Negate a numeric value. + * + * Negates both the integer and float payloads, so it is correct whichever one + * the value is carrying. + * @param self Operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When `self` is a string. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_invert(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief One's complement of an integer value. + * @param self Operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When `self` is not an integer. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_not(akbasic_Value *self, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Shift an integer value left. + * + * Refuses a count outside 0..63. Go defines the result for any count; in C it + * would be undefined behaviour, so this is a deliberate deviation. + * @param self Operand; must be an integer. + * @param bits Number of bit positions to shift by; must be 0..63. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When `self` is not an integer. + * @throws AKBASIC_ERR_VALUE When `bits` is outside 0..63. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_shift_left(akbasic_Value *self, int64_t bits, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Shift an integer value right. + * @param self Operand; must be an integer. + * @param bits Number of bit positions to shift by; must be 0..63. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When `self` is not an integer. + * @throws AKBASIC_ERR_VALUE When `bits` is outside 0..63. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_shift_right(akbasic_Value *self, int64_t bits, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Bitwise AND of two integer values. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When `self` is not an integer. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_and(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Bitwise OR of two integer values. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When `self` is not an integer. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_or(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Bitwise exclusive OR of two integer values. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When either operand is not an integer. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_bitwise_xor(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Add two values, or concatenate when the left operand is a string. + * + * The one operator that does not always clone: when `self` is mutable the result + * is written into `self` and `*dest` is set to it rather than to `scratch`. + * CommandNEXT's loop increment depends on that, so it is not an oversight -- + * see TODO.md section 6 item 4. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When the operand types have no defined addition. + * @throws AKBASIC_ERR_VALUE When a concatenated string exceeds the length limit. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Subtract one numeric value from another. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When either operand is a string. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_minus(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Multiply two numeric values, or repeat a string. + * + * A string left operand is repeated `rval->intval` times, which is what makes + * `" " * 5` work. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When both operands are strings. + * @throws AKBASIC_ERR_VALUE When the multiplier is negative or the result would overflow the length limit. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Divide one numeric value by another. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_TYPE When either operand is a string. + * @throws AKBASIC_ERR_VALUE On integer division by zero. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_math_divide(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Compare two values for less-than, yielding a BASIC boolean. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Compare two values for less-than-or-equal, yielding a BASIC boolean. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Compare two values for greater-than, yielding a BASIC boolean. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_greater_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Compare two values for greater-than-or-equal, yielding a BASIC boolean. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_greater_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Compare two values for equality, yielding a BASIC boolean. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); +/** + * @brief Compare two values for inequality, yielding a BASIC boolean. + * @param self Left operand, and the value whose type selects the operation. + * @param rval Right operand. + * @param scratch A value drawn from the caller's pool for the result to land in. + * @param dest Output destination populated by the function; set to `scratch`. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When any argument is NULL. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest); #endif // _AKBASIC_VALUE_H_ diff --git a/include/akbasic/variable.h b/include/akbasic/variable.h index 26689b9..606091c 100644 --- a/include/akbasic/variable.h +++ b/include/akbasic/variable.h @@ -41,7 +41,12 @@ typedef struct */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_init(akbasic_Variable *obj, akbasic_ValuePool *pool, int64_t *sizes, int sizecount); -/** @brief Mark the variable undefined and mutable without touching its storage. */ +/** + * @brief Mark the variable undefined and mutable without touching its storage. + * @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_variable_zero(akbasic_Variable *obj); /** @@ -55,11 +60,49 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_zero(akbasic_Variable *obj); */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_get_subscript(akbasic_Variable *obj, int64_t *subscripts, int subscriptcount, akbasic_Value **dest); -/** @brief Copy a value into the slot a subscript list addresses. */ +/** + * @brief Copy a value into the slot a subscript list addresses. + * @param obj Object to initialize, inspect, or modify. + * @param value Source value; cloned into the slot, not aliased. + * @param subscripts Index per dimension. + * @param subscriptcount Number of indices supplied; must equal the dimension count. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `value` is NULL. + * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_subscript(akbasic_Variable *obj, akbasic_Value *value, int64_t *subscripts, int subscriptcount); +/** + * @brief Store an integer into the slot a subscript list addresses. + * @param obj Object to initialize, inspect, or modify. + * @param value Value to store. + * @param subscripts Index per dimension. + * @param subscriptcount Number of indices supplied; must equal the dimension count. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_integer(akbasic_Variable *obj, int64_t value, int64_t *subscripts, int subscriptcount); +/** + * @brief Store a float into the slot a subscript list addresses. + * @param obj Object to initialize, inspect, or modify. + * @param value Value to store. + * @param subscripts Index per dimension. + * @param subscriptcount Number of indices supplied; must equal the dimension count. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_float(akbasic_Variable *obj, double value, int64_t *subscripts, int subscriptcount); +/** + * @brief Store a string into the slot a subscript list addresses. + * @param obj Object to initialize, inspect, or modify. + * @param value Value to store. + * @param subscripts Index per dimension. + * @param subscriptcount Number of indices supplied; must equal the dimension count. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER When `value` is NULL. + * @throws AKBASIC_ERR_VALUE When the string exceeds the length limit. + * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range. + */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_string(akbasic_Variable *obj, const char *value, int64_t *subscripts, int subscriptcount); #endif // _AKBASIC_VARIABLE_H_