NEW, CLR, CONT, SWAP, TRON, TROFF and HELP. None is in the Go reference, so what each means here is recorded beside it. Testing CONT turned up a hang that predates this: the runtime's error class is deliberately sticky, and with run_finished_mode REPL every later step re-entered REPL and printed READY -- overwriting the QUIT that end of input had just set. An interactive session that hit one runtime error printed READY forever instead of exiting. RESTORE and RENUMBER are deferred with reasons; RESTORE turns out to need a DATA pointer this port does not have, which is a defect in its own right. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
488 lines
21 KiB
C
488 lines
21 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/audio.h>
|
|
#include <akbasic/environment.h>
|
|
#include <akbasic/grammar.h>
|
|
#include <akbasic/graphics.h>
|
|
#include <akbasic/input.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;
|
|
|
|
/*
|
|
* The device backends, any of which may be NULL. The standalone driver
|
|
* supplies none of them, so a PRINT-only program must keep working; every
|
|
* verb that needs one refuses with AKBASIC_ERR_DEVICE instead.
|
|
*/
|
|
akbasic_GraphicsBackend *graphics;
|
|
akbasic_AudioBackend *audio;
|
|
akbasic_InputBackend *input;
|
|
|
|
/*
|
|
* The graphics verbs' own state -- mode, color-source bindings, pixel cursor
|
|
* and SCALE. It lives here rather than on the backend because it is BASIC's
|
|
* state, not the device's: a host that swaps one renderer for another does
|
|
* not expect the program's COLOR settings to go with it.
|
|
*/
|
|
akbasic_GraphicsState gfx;
|
|
|
|
/*
|
|
* The sound verbs' state, including the PLAY queue. Same reasoning as gfx:
|
|
* TEMPO, the ENVELOPE presets and the current octave are the program's, not
|
|
* the device's.
|
|
*/
|
|
akbasic_AudioState audio_state;
|
|
|
|
/* GETKEY's hold on the step loop. Same reasoning again: it is the program's. */
|
|
akbasic_InputState input_state;
|
|
|
|
/*
|
|
* The host's clock, in milliseconds, as of its last akbasic_runtime_settime()
|
|
* call. The interpreter does not read a clock: it owns no loop and must not
|
|
* block, so the caller that owns the frame owns the time. libakgl does the
|
|
* same thing -- akgl_actor_logic_changeframe() takes curtimems as an
|
|
* argument rather than asking the system for it.
|
|
*
|
|
* Left at zero, every duration expires on the step after it starts. That is
|
|
* wrong but never a hang, which is the right way for it to fail.
|
|
*/
|
|
int64_t timems;
|
|
|
|
/*
|
|
* Set by a branch that has decided the remaining statements on its line
|
|
* belong to the arm it did not take, and cleared at the top of every line.
|
|
*
|
|
* This exists because a line can hold several statements and BASIC 7.0
|
|
* scopes everything after THEN to the condition -- `IF C THEN A : B` runs
|
|
* neither A nor B when C is false. The statement loop is what knows where a
|
|
* line ends, so the branch raises a flag and the loop acts on it. See the
|
|
* BRANCH case in akbasic_runtime_evaluate() for the exact rule, which is not
|
|
* quite "skip when false".
|
|
*/
|
|
bool skiprestofline;
|
|
|
|
/*
|
|
* TRON/TROFF. When set, every line prints its number in brackets before it
|
|
* runs, inline and with no newline, which is what a C128 does: a traced
|
|
* program's output reads `[10][20]HELLO`.
|
|
*/
|
|
bool trace;
|
|
|
|
/*
|
|
* CONT's two pieces. `stopped` says a STOP actually happened, so CONT can
|
|
* refuse rather than silently starting a program that was never running;
|
|
* `stoppedline` is where to pick up, taken before STOP hands control back to
|
|
* the REPL and the environment's line counters move on.
|
|
*/
|
|
bool stopped;
|
|
int64_t stoppedline;
|
|
|
|
/*
|
|
* The line the last BASIC-visible error was reported on, and 0 if there has
|
|
* not been one. HELP re-displays it, which is the whole of what HELP does.
|
|
*/
|
|
int64_t errorline;
|
|
|
|
/* 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 Attach the device backends a host is willing to lend the interpreter.
|
|
*
|
|
* Any argument may be NULL, which is how a host withholds a capability: a script
|
|
* given no audio backend gets an error from SOUND rather than silence. Call it
|
|
* after akbasic_runtime_init() and before akbasic_runtime_start(); calling it
|
|
* again mid-run is allowed and takes effect on the next verb.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param graphics Where DRAW, BOX, CIRCLE and PAINT land; may be NULL.
|
|
* @param audio Where SOUND, PLAY and VOL land; may be NULL.
|
|
* @param input Where GET and GETKEY read; may be NULL.
|
|
* @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_devices(akbasic_Runtime *obj, akbasic_GraphicsBackend *graphics, akbasic_AudioBackend *audio, akbasic_InputBackend *input);
|
|
|
|
/**
|
|
* @brief Tell the interpreter what time the host thinks it is.
|
|
*
|
|
* SOUND durations, PLAY note lengths and TEMPO are all time-based, and section
|
|
* 1.6 forbids the library blocking or reading a clock of its own. A host calls
|
|
* this once a frame before akbasic_runtime_run(); the standalone driver calls it
|
|
* from a monotonic clock each step.
|
|
*
|
|
* Time is only ever compared, never differenced against a wall clock, so any
|
|
* monotonic millisecond source will do. It is not required to advance, and a
|
|
* host that never calls this leaves it at zero -- durations then expire
|
|
* immediately, which is audible but never a hang.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param timems The host's current time in milliseconds.
|
|
* @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_settime(akbasic_Runtime *obj, int64_t timems);
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/**
|
|
* @brief Find or create a variable in the script's outermost scope.
|
|
*
|
|
* **This is the entry point a host exchanging values with a script should use.**
|
|
* akbasic_environment_get() lands a new variable in whatever scope is active,
|
|
* and a script suspended part-way through a bounded akbasic_runtime_run() is
|
|
* usually inside a `FOR` or `GOSUB` body -- so the script reads the value
|
|
* correctly inside the loop and gets `0` immediately after it, with nothing
|
|
* raised anywhere. Reaching for the root by hand does not help either: that
|
|
* function only auto-creates in the *active* environment, so with a child active
|
|
* it returns NULL through `dest` without raising and an unchecked host
|
|
* dereferences it.
|
|
*
|
|
* Here the root is found by walking `obj->environment` to the environment with
|
|
* no parent, and the variable is created there unconditionally. That is the
|
|
* right answer inside a user function's scope too: a funcdef's environment is
|
|
* initialized with the caller's as its parent, so the walk terminates at the
|
|
* same root.
|
|
*
|
|
* Seeding before akbasic_runtime_start() and reading after the script stops both
|
|
* still work through this, so a host has no reason to use anything else.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param name Variable name including its type suffix -- `A#`, `B%`, `C$`.
|
|
* @param dest Output destination populated by the function; never NULL on success.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When any argument is NULL, or the runtime has no environment.
|
|
* @throws AKBASIC_ERR_BOUNDS When the name is too long, or no variable slot is free.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_global(akbasic_Runtime *obj, const char *name, akbasic_Variable **dest);
|
|
|
|
/* --- 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_
|