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>
343 lines
14 KiB
C
343 lines
14 KiB
C
/**
|
|
* @file runtime.h
|
|
* @brief Declares the interpreter: source, pools, scanner, parser and evaluator.
|
|
*
|
|
* Ported from basicruntime.go, minus SDL and minus the for{} loop that owned the
|
|
* process. The reference's run() does not return until MODE_QUIT; here
|
|
* akbasic_runtime_step() executes exactly one iteration of that loop and returns,
|
|
* and akbasic_runtime_run() bounds it. A host game must be able to step or bound
|
|
* execution rather than surrender control, so the loop belongs to the caller.
|
|
*
|
|
* Nothing in this library terminates the process. Errors propagate out as
|
|
* akerr_ErrorContext * for the host to handle; FINISH_NORETURN belongs only to
|
|
* the driver's main().
|
|
*/
|
|
|
|
#ifndef _AKBASIC_RUNTIME_H_
|
|
#define _AKBASIC_RUNTIME_H_
|
|
|
|
#include <akerror.h>
|
|
|
|
#include <akbasic/environment.h>
|
|
#include <akbasic/grammar.h>
|
|
#include <akbasic/sink.h>
|
|
#include <akbasic/types.h>
|
|
#include <akbasic/value.h>
|
|
#include <akbasic/variable.h>
|
|
|
|
/** @brief The BASIC-visible error classes, used to build the "? n : CLASS msg" line. */
|
|
typedef enum
|
|
{
|
|
AKBASIC_ERRCLASS_NONE = 0,
|
|
AKBASIC_ERRCLASS_IO,
|
|
AKBASIC_ERRCLASS_PARSE,
|
|
AKBASIC_ERRCLASS_SYNTAX,
|
|
AKBASIC_ERRCLASS_RUNTIME
|
|
} akbasic_ErrorClass;
|
|
|
|
typedef struct
|
|
{
|
|
char code[AKBASIC_MAX_LINE_LENGTH];
|
|
int64_t lineno;
|
|
} akbasic_SourceLine;
|
|
|
|
/** @brief A user-defined subroutine or single-expression function. */
|
|
typedef struct
|
|
{
|
|
char name[AKBASIC_SYMTAB_MAX_KEY];
|
|
akbasic_ASTLeaf *arglist;
|
|
akbasic_ASTLeaf *expression;
|
|
int64_t lineno;
|
|
akbasic_Environment *environment;
|
|
/* Deep copies of the arglist and expression need storage that outlives the line. */
|
|
akbasic_ASTLeaf leafstorage[AKBASIC_MAX_LEAVES * 2];
|
|
akbasic_LeafPool leafpool;
|
|
bool used;
|
|
} akbasic_FunctionDef;
|
|
|
|
typedef struct akbasic_Runtime
|
|
{
|
|
akbasic_SourceLine source[AKBASIC_MAX_SOURCE_LINES];
|
|
|
|
/* Pools. Nothing here is malloc'd; everything is drawn from and returned. */
|
|
akbasic_Environment environments[AKBASIC_MAX_ENVIRONMENTS];
|
|
akbasic_Variable variables[AKBASIC_MAX_VARIABLES];
|
|
akbasic_FunctionDef functions[AKBASIC_MAX_FUNCTIONS];
|
|
akbasic_ValuePool valuepool;
|
|
|
|
akbasic_Value staticTrueValue;
|
|
akbasic_Value staticFalseValue;
|
|
|
|
int mode;
|
|
int run_finished_mode;
|
|
akbasic_ErrorClass errclass;
|
|
int64_t autoLineNumber;
|
|
|
|
/*
|
|
* When false, evaluating an identifier yields the live value rather than a
|
|
* clone. POKE and POINTER need the address of the real storage. The
|
|
* reference declares this on both the runtime and the environment and only
|
|
* ever reads the runtime's; the environment copy is dropped here.
|
|
*/
|
|
bool eval_clone_identifiers;
|
|
|
|
akbasic_Environment *environment;
|
|
akbasic_TextSink *sink;
|
|
|
|
/* REPL line assembly */
|
|
char userline[AKBASIC_MAX_LINE_LENGTH];
|
|
|
|
/* Scanner state */
|
|
char line[AKBASIC_MAX_LINE_LENGTH];
|
|
int current;
|
|
int start;
|
|
akbasic_TokenType tokentype;
|
|
bool hasError;
|
|
|
|
bool inputEof;
|
|
} akbasic_Runtime;
|
|
|
|
/**
|
|
* @brief Bring a runtime up: register status codes, build the root environment.
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param sink Where output goes and input comes from; required.
|
|
*/
|
|
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.
|
|
* @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.
|
|
*
|
|
* Writes `? <line> : <CLASS> <message>` 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 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().
|
|
* @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.
|
|
* @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 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.
|
|
* @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.
|
|
*
|
|
* 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);
|
|
|
|
/**
|
|
* @brief Execute exactly one iteration of the reference's run() loop.
|
|
*
|
|
* 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 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);
|
|
|
|
/**
|
|
* @brief Load a whole program from memory, filing each line under its line number.
|
|
*
|
|
* The entry point for an embedding host, which usually already holds its script
|
|
* as a string and wants the sink reserved for output. The alternative --
|
|
* AKBASIC_MODE_RUNSTREAM -- reads the program through the sink's readline, which
|
|
* works for a file-backed driver but forces a game to point its output device at
|
|
* its source text.
|
|
*
|
|
* Lines are separated by `\n`; a `\r` before it is tolerated. Blank lines are
|
|
* skipped. A line with no line number is filed under the last one seen, matching
|
|
* what the scanner does in RUNSTREAM mode. This does not run anything: follow it
|
|
* with akbasic_runtime_start(obj, AKBASIC_MODE_RUN).
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param source Whole program text; must not be NULL.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` or `source` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When a line is longer than AKBASIC_MAX_LINE_LENGTH or its number is out of range.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_load(akbasic_Runtime *obj, const char *source);
|
|
|
|
/* --- 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_
|