akbasic_runtime_call_function()'s body loop drives process_line_run() directly, skipping the per-line prologue akbasic_runtime_step() provides. The call environment's value scratch therefore accumulated across the whole body, and any body past about ten real lines died with 'Maximum values per line reached' -- a limit that is supposed to be per line. The loop now runs the same prologue step() does. A body that died also left its call scopes active: nothing popped them, so a host absorbing script errors drained the twelve-slot environment pool after twelve dead calls. The loop now unwinds to the caller's environment on every exit path. New: akbasic_runtime_clear_error(), the missing half of host revival. A run's first BASIC-level error latches deliberately, and set_mode(RUN) alone cannot un-decide that; a host that absorbed the error calls this beside it. Both defects and the revival dance are pinned in tests/user_functions.c. Co-authored-by: andrew <andrew@aklabs.net> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
924 lines
43 KiB
C
924 lines
43 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/console.h>
|
|
#include <akbasic/data.h>
|
|
#include <akbasic/disk.h>
|
|
#include <akbasic/environment.h>
|
|
#include <akbasic/format.h>
|
|
#include <akbasic/grammar.h>
|
|
#include <akbasic/graphics.h>
|
|
#include <akbasic/input.h>
|
|
#include <akbasic/sink.h>
|
|
#include <akbasic/sprite.h>
|
|
#include <akbasic/structtype.h>
|
|
#include <akbasic/symtab.h>
|
|
#include <akbasic/types.h>
|
|
#include <akbasic/ui.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;
|
|
|
|
/**
|
|
* @brief One stored program line.
|
|
*
|
|
* `numbered` records whether the program itself wrote this line's number or the
|
|
* loader assigned one because the line arrived without. Both are real lines and
|
|
* both execute identically; the difference matters only to
|
|
* akbasic_runtime_check_targets(), which refuses `GOTO 100` when line 100 is a
|
|
* number nobody wrote.
|
|
*/
|
|
typedef struct
|
|
{
|
|
char code[AKBASIC_MAX_LINE_LENGTH];
|
|
int64_t lineno;
|
|
bool numbered;
|
|
} akbasic_SourceLine;
|
|
|
|
/**
|
|
* @brief What can interrupt a running program and send it into a handler.
|
|
*
|
|
* The numbering is COLLISION's own argument minus one, so `COLLISION 1` arms
|
|
* slot 0. The two collision types this interpreter refuses still hold slots:
|
|
* dropping them would make the arithmetic a lookup table for no gain, and a
|
|
* later release that implements them wants the same numbers.
|
|
*/
|
|
typedef enum
|
|
{
|
|
AKBASIC_INTERRUPT_SPRITE = 0, /** COLLISION 1 -- sprite met sprite */
|
|
AKBASIC_INTERRUPT_BACKGROUND, /** COLLISION 2 -- sprite met background; refused */
|
|
AKBASIC_INTERRUPT_LIGHTPEN, /** COLLISION 3 -- light pen; refused */
|
|
AKBASIC_INTERRUPT_ERROR, /** TRAP -- a BASIC error; group C, not yet armed by anything */
|
|
AKBASIC_MAX_INTERRUPTS
|
|
} akbasic_InterruptSource;
|
|
|
|
/**
|
|
* @brief One armed interrupt: where its handler is, and whether it is due.
|
|
*
|
|
* The target is held as a line number *or* a label name, and a label is resolved
|
|
* when the interrupt fires rather than when it is armed. A label may be re-filed
|
|
* by its own LABEL statement at any point, so resolving late is what lets the
|
|
* later spelling win.
|
|
*/
|
|
typedef struct
|
|
{
|
|
bool armed;
|
|
bool pending;
|
|
int64_t line; /* the numeric target; 0 when a label was given */
|
|
char label[AKBASIC_SYMTAB_MAX_KEY]; /* the label target; empty when a number was given */
|
|
} akbasic_Interrupt;
|
|
|
|
/** @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;
|
|
/*
|
|
* There is deliberately no environment here. It used to be owned by the
|
|
* funcdef and reset on every call, which made a function not re-entrant --
|
|
* two calls in one expression shared one slot, and recursion never came
|
|
* back. A call takes one from the pool now, exactly as GOSUB does.
|
|
*/
|
|
/* 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;
|
|
/* Every TYPE the program declared, prescanned like labels and DATA. */
|
|
akbasic_StructTypeTable structtypes;
|
|
|
|
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;
|
|
akbasic_SpriteBackend *sprites;
|
|
/*
|
|
* The fifth one, set on its own through akbasic_runtime_set_ui() rather than
|
|
* as a fifth argument to akbasic_runtime_set_devices(). Adding a parameter
|
|
* would have rewritten twenty-eight call sites to pass a NULL none of them
|
|
* cares about, in tests and documentation that are about something else.
|
|
*/
|
|
akbasic_UiBackend *ui;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* Which menus are up, and GETMENU's hold on the step loop. */
|
|
akbasic_UiState ui_state;
|
|
|
|
/*
|
|
* The eight sprites, their positions and their collision bits. Same
|
|
* reasoning as gfx and audio_state, and one more: RSPPOS and RSPRITE read
|
|
* this rather than asking the device, so they answer correctly with no
|
|
* device attached at all.
|
|
*/
|
|
akbasic_SpriteState sprite_state;
|
|
|
|
/* PUDEF's fill characters, which PRINT USING pads and punctuates with. */
|
|
akbasic_FormatState format_state;
|
|
|
|
/* SLEEP's and WAIT's hold on the step loop, and KEY's macros. */
|
|
akbasic_ConsoleState console_state;
|
|
|
|
/* Every DATA item in the program, and how far READ has got along it. */
|
|
akbasic_DataState data_state;
|
|
|
|
/* The open file channels DOPEN hands out. */
|
|
akbasic_DiskState disk_state;
|
|
|
|
/*
|
|
* Where the running program was loaded from, or empty when it did not come
|
|
* from a file -- a REPL session, or a host that handed over a string.
|
|
*
|
|
* Only one thing needs it today: SPRSAV resolving an image path that does
|
|
* not exist relative to the working directory, which is what makes a `.bas`
|
|
* beside its art work from anywhere. Group F's disk verbs will want the
|
|
* same, which is why it is on the runtime rather than in the sprite state.
|
|
*
|
|
* Sized to AKBASIC_MAX_SOURCE_PATH_LENGTH, not AKBASIC_MAX_LINE_LENGTH: a
|
|
* directory is a filesystem path, not a line of BASIC, and the two do not
|
|
* belong to the same budget.
|
|
*/
|
|
char sourcepath[AKBASIC_MAX_SOURCE_PATH_LENGTH];
|
|
|
|
/*
|
|
* The armed interrupts, and the environment the one currently running was
|
|
* entered through.
|
|
*
|
|
* `handlerenv` is doing two jobs at once. It is the "a handler is running"
|
|
* flag -- an interrupt does not interrupt an interrupt, which is what keeps a
|
|
* collision that persists across the handler from recursing until the
|
|
* environment pool is gone. And it is the identity RETURN compares against,
|
|
* so the flag clears on the RETURN that leaves *this* handler rather than on
|
|
* the first RETURN of any GOSUB the handler itself makes.
|
|
*/
|
|
akbasic_Interrupt interrupts[AKBASIC_MAX_INTERRUPTS];
|
|
akbasic_Environment *handlerenv;
|
|
|
|
/*
|
|
* The status code of the error being reported, for `ER#` to carry into a
|
|
* TRAP handler. Set by the reporting path, which is the only place that
|
|
* still has the error context; akbasic_runtime_error() sees a message and a
|
|
* class, both of which have already lost the number.
|
|
*/
|
|
int lasterrorstatus;
|
|
|
|
/*
|
|
* 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];
|
|
|
|
/*
|
|
* Set by the scanner when the line it just read began with a line number.
|
|
*
|
|
* It is what tells the REPL apart from a program. A line typed *with* a
|
|
* number is program text and is filed; a line typed *without* one is a
|
|
* direct-mode statement and runs now. Without this the REPL could only run
|
|
* the handful of verbs marked immediate, so `PRINT 2 + 2` at the prompt was
|
|
* silently stored rather than answered -- see TODO.md section 5.
|
|
*/
|
|
bool hadlinenumber;
|
|
|
|
/* 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.
|
|
* @param sprites Where SPRITE, MOVSPR and SPRSAV land; 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, akbasic_SpriteBackend *sprites);
|
|
|
|
/**
|
|
* @brief Attach the UI backend, where MENU, DIALOG, HUD and UISTYLE land.
|
|
*
|
|
* Separate from akbasic_runtime_set_devices() rather than a fifth argument to
|
|
* it, because that signature has twenty-eight call sites and none of them is
|
|
* about the UI. NULL withholds the capability, which is what the standalone
|
|
* stdio driver does and what every no-SDL build gets.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param ui Where MENU, DIALOG, HUD and UISTYLE land; 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_ui(akbasic_Runtime *obj, akbasic_UiBackend *ui);
|
|
|
|
/**
|
|
* @brief Tell the interpreter where the program it is running came from.
|
|
*
|
|
* An asset path a program writes -- `SPRSAV "ship.png", 1` -- is tried against
|
|
* the process working directory first and against this directory second, so a
|
|
* program stored beside its art works whether it was launched from its own
|
|
* directory or from somewhere else. Exactly what libakgl does for a sprite
|
|
* document naming its spritesheet.
|
|
*
|
|
* @p path is the program *file*, not its directory; the directory is taken from
|
|
* it. Passing NULL, or never calling this, leaves only the working directory,
|
|
* which is the right answer for a REPL session and for a host that handed the
|
|
* interpreter a string.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param path Path to the program file, or NULL for none.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When the path is longer than AKBASIC_MAX_SOURCE_PATH_LENGTH.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const char *path);
|
|
|
|
/**
|
|
* @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 Move every line onto a new number, rewriting every reference to one.
|
|
*
|
|
* Moving lines is a permutation of `source[]`. Rewriting `GOTO`, `GOSUB`, `RUN`,
|
|
* `RESTORE`, `TRAP` and `COLLISION` targets to match is the work, and doing it on
|
|
* source text means telling a line number apart from digits inside a string
|
|
* literal -- `PRINT "GOTO 10"` must survive.
|
|
*
|
|
* A target naming a line that does not exist is left alone rather than
|
|
* rewritten: `GOTO 9999` in a program with no line 9999 is already broken, and
|
|
* inventing a destination for it would hide that. A `GOTO` to a *label* needs no
|
|
* rewriting at all, which is the strongest argument for writing programs that
|
|
* way.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param newstart The number the first renumbered line takes.
|
|
* @param increment How far apart the new numbers are; must be positive.
|
|
* @param oldstart Renumber from this line on, leaving anything before it alone.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_VALUE When the increment is not positive, or a renumbered line would land on a kept one.
|
|
* @throws AKBASIC_ERR_BOUNDS When a new number would exceed AKBASIC_MAX_SOURCE_LINES, or a rewritten line would not fit.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int64_t increment, int64_t oldstart);
|
|
|
|
/**
|
|
* @brief Refuse a program that branches by number to a line it did not number.
|
|
*
|
|
* The fourth prescan, run beside the label, `DATA` and `TYPE` ones on every entry
|
|
* into AKBASIC_MODE_RUN. Since a loaded line may be given its number rather than
|
|
* carry one, `GOTO 100` in a script written without numbers finds the hundredth
|
|
* line and branches there -- plausible, silent and wrong. This says so before any
|
|
* line executes, which is the earliest it can be said and the only place that
|
|
* covers all four ways a program arrives.
|
|
*
|
|
* It walks the same `GOTO`, `GOSUB`, `RUN`, `RESTORE`, `TRAP`, `ON ... GOTO` and
|
|
* `COLLISION` targets akbasic_renumber() rewrites, sharing that walk rather than
|
|
* repeating it. A target naming an *empty* line is allowed through, for the same
|
|
* reason RENUMBER leaves one alone. A `GOTO` to a label never reaches here.
|
|
*
|
|
* @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.
|
|
* @throws AKBASIC_ERR_SYNTAX When a numeric target names a line the program did not number.
|
|
* @throws AKBASIC_ERR_BOUNDS When a line does not fit the walk's buffer.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_check_targets(akbasic_Runtime *obj);
|
|
|
|
/**
|
|
* @brief File every `LABEL` in the stored program before any of it runs.
|
|
*
|
|
* Without this a label exists only from the moment its `LABEL` statement
|
|
* executes, so `GOTO` and `GOSUB` reach backwards and never forwards -- and an
|
|
* interrupt handler, which by definition sits on a line normal flow does not
|
|
* fall into, could not be named by label at all. That is the case this exists
|
|
* for; forward `GOTO` is the improvement that comes with it.
|
|
*
|
|
* The scan is textual rather than a parse: `LABEL` at the start of a statement,
|
|
* outside a string literal, followed by an identifier. Parsing every line up
|
|
* front would raise on lines the program would never have reached, which is a
|
|
* worse trade than a scanner that understands one keyword.
|
|
*
|
|
* `LABEL` still executes normally and still files itself, so a label re-filed at
|
|
* run time wins. A name that appears twice therefore resolves to the last one in
|
|
* the source until one of them runs.
|
|
*
|
|
* Called by akbasic_runtime_set_mode() on every entry into AKBASIC_MODE_RUN,
|
|
* which is the one point `RUN`, `CONT`, akbasic_runtime_start() and the end of a
|
|
* RUNSTREAM load all pass through. A host has no reason to call it directly.
|
|
*
|
|
* @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 or the runtime has no environment.
|
|
* @throws AKBASIC_ERR_BOUNDS When the program holds more labels than AKBASIC_MAX_LABELS.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_scan_labels(akbasic_Runtime *obj);
|
|
|
|
/**
|
|
* @brief Point an interrupt source at a handler, replacing whatever it had.
|
|
*
|
|
* Exactly one of @p line and @p label carries the target. A label is stored by
|
|
* name and resolved when the interrupt fires; see #akbasic_Interrupt for why.
|
|
*
|
|
* Arming does not clear a pending event. A program that disarms and re-arms
|
|
* around a critical section still sees the collision that happened inside it,
|
|
* which is the behaviour that loses no events.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param source Which interrupt to arm.
|
|
* @param line Line number to enter, or 0 when @p label carries the target.
|
|
* @param label Label to enter, or NULL when @p line carries the target.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When `source` is out of range or the label is too long.
|
|
* @throws AKBASIC_ERR_VALUE When neither or both of `line` and `label` name a target.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_arm_interrupt(akbasic_Runtime *obj, akbasic_InterruptSource source, int64_t line, const char *label);
|
|
|
|
/**
|
|
* @brief Stop an interrupt source from entering a handler.
|
|
*
|
|
* Any event already pending on it is dropped: a program that has said it no
|
|
* longer cares should not be sent into a handler it has just taken down.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param source Which interrupt to disarm.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When `source` is out of range.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_disarm_interrupt(akbasic_Runtime *obj, akbasic_InterruptSource source);
|
|
|
|
/**
|
|
* @brief Record that an interrupt source fired.
|
|
*
|
|
* Cheap and safe to call every frame from a device backend, whether or not
|
|
* anything is armed: an unarmed source records nothing, so a host does not have
|
|
* to ask what the script has subscribed to. The handler is entered later, by
|
|
* akbasic_runtime_step(), at a line boundary -- never from inside the code that
|
|
* raised it.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param source Which interrupt fired.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When `source` is out of range.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_raise_interrupt(akbasic_Runtime *obj, akbasic_InterruptSource source);
|
|
|
|
/**
|
|
* @brief Enter the handler of the first pending interrupt, if one is due.
|
|
*
|
|
* Called by akbasic_runtime_step() between source lines, which is the only place
|
|
* it is safe: a handler entered mid-statement would return into the middle of a
|
|
* line, and the parser holds no state that could resume there. That granularity
|
|
* is the same one block skipping already works at -- see TODO.md.
|
|
*
|
|
* Entering a handler is a GOSUB the program did not write: a scope is pushed,
|
|
* its return line is the line that was about to run, and the handler's RETURN
|
|
* pops back to it. So a handler must end in RETURN, exactly as on a C128.
|
|
*
|
|
* Sources are tried in enum order, and only one is entered per call. Nothing is
|
|
* entered while a handler is already running.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param entered Output destination populated by the function; true when a handler was entered. May be NULL.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_UNDEFINED When the handler's label names nothing in the program.
|
|
* @throws AKBASIC_ERR_ENVIRONMENT When the environment pool is exhausted.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_service_interrupts(akbasic_Runtime *obj, bool *entered);
|
|
|
|
/**
|
|
* @brief Publish `ER#` and `EL#` into the global scope for a TRAP handler.
|
|
*
|
|
* A C128 spells these `ER` and `EL` as bare reserved names. This dialect has no
|
|
* bare variable names -- an identifier carries its type in a suffix, and a name
|
|
* without one is a label -- so they are ordinary global integers. See TODO.md
|
|
* section 5.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param status The status code of the error.
|
|
* @param line The line it was reported on.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When no variable slot is free.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_trap_set_error_variables(akbasic_Runtime *obj, int status, int64_t line);
|
|
|
|
/**
|
|
* @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 Forgive the last BASIC-level error, so a host may call again.
|
|
*
|
|
* A program run latches its first runtime error and ends -- deliberately, and
|
|
* a host cannot un-decide that with akbasic_runtime_set_mode() alone: the
|
|
* latch survives the mode change, every later line is skipped, and every
|
|
* later akbasic_runtime_call_function() answers a stale value after walking
|
|
* the whole source table doing nothing.
|
|
*
|
|
* A host that absorbed a script error -- reported through the sink, actor
|
|
* marked dumb, frame preserved -- calls this beside
|
|
* `akbasic_runtime_set_mode(obj, AKBASIC_MODE_RUN)` to put the runtime back
|
|
* in service. It is for hosts between calls, not for verbs during a run: a
|
|
* running program's first error still ends it, exactly once, with one 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_runtime_clear_error(akbasic_Runtime *obj);
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* **Line numbers are optional here.** A line that carries one is filed under it;
|
|
* a line that does not is filed one slot after the last line filed, so a script
|
|
* written entirely with `LABEL` and `GOTO NAME` needs no numbers at all. The two
|
|
* can be mixed: a numbered line moves the cursor, and the unnumbered lines after
|
|
* it continue from there. This is the rule RUNSTREAM and `DLOAD` follow too; only
|
|
* the prompt still requires a number, because that is the one place a number is
|
|
* what separates program text from a statement to run now.
|
|
*
|
|
* 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, its number is out of range, the program runs past AKBASIC_MAX_SOURCE_LINES, or an assigned number collides with a line already filed.
|
|
*/
|
|
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 Create the globals the runtime writes to, before anything needs them.
|
|
*
|
|
* `ER#` and `EL#`, which the `TRAP` dispatch sets. Creating a name costs a
|
|
* variable slot, and the dispatch runs at exactly the moment the program is
|
|
* already in trouble -- so they are made once, up front, where there is always
|
|
* room. `akbasic_runtime_init()` calls this; so does `CLR`, which empties the
|
|
* table it filled.
|
|
*
|
|
* @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.
|
|
* @throws AKBASIC_ERR_BOUNDS When no variable slot is free.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_reserve_globals(akbasic_Runtime *obj);
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
/**
|
|
* @brief Call a user-defined function with values a caller already has.
|
|
*
|
|
* The half of a call that is not parsing. akbasic_runtime_user_function()
|
|
* evaluates a parsed call site's arguments and then comes here; a **verb** that
|
|
* wants to hand a BASIC function four numbers starts here directly, which was
|
|
* not possible before -- the only entry point took an AST call site, so calling
|
|
* a function required having been parsed as an expression.
|
|
*
|
|
* The body runs the same way either way: a scope from the environment pool, as
|
|
* `GOSUB` takes, re-entrant, with recursion depth answering to
|
|
* #AKBASIC_MAX_ENVIRONMENTS. A multi-line definition re-enters the line loop
|
|
* synchronously and returns when its RETURN pops back out.
|
|
*
|
|
* @param obj The runtime.
|
|
* @param name The function's name, as `DEF` spelled it.
|
|
* @param args Values to bind, already evaluated. May be NULL when @p nargs is 0.
|
|
* @param nargs How many. Extra arguments beyond the definition's are ignored,
|
|
* which is what the AST path has always done.
|
|
* @param dest Receives the result, in the caller's scratch.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When @p obj, @p name or @p dest is NULL.
|
|
* @throws AKBASIC_ERR_UNDEFINED When no function of that name is defined.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_call_function(struct akbasic_Runtime *obj, const char *name, akbasic_Value **args, int nargs, akbasic_Value **dest);
|
|
|
|
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.
|
|
* @param numbered True when the program wrote this number, false when the loader assigned it.
|
|
* @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, bool numbered);
|
|
/**
|
|
* @brief File a freshly-scanned line, giving it a number if it arrived without one.
|
|
*
|
|
* The one place the optional-line-number rule lives, shared by all three loading
|
|
* paths -- akbasic_runtime_load(), RUNSTREAM and `DLOAD` -- so they cannot drift.
|
|
* Reads akbasic_Runtime::hadlinenumber, which akbasic_scanner_scan() has just set,
|
|
* and akbasic_Environment::lineno, which doubles as the loader's cursor.
|
|
*
|
|
* The prompt does not use this: a line typed without a number is direct mode, not
|
|
* a line waiting for one.
|
|
*
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
* @param code Source text, with any line number already stripped.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
* @throws AKBASIC_ERR_BOUNDS When the program runs past AKBASIC_MAX_SOURCE_LINES, or an assigned number collides with a line already filed.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_file_line(akbasic_Runtime *obj, 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_
|