Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m29s
akbasic CI Build / coverage (push) Failing after 3m40s
akbasic CI Build / sanitizers (push) Failing after 4m37s
akbasic CI Build / mutation_test (push) Failing after 3m35s
akbasic CI Build / akgl_build (push) Failing after 7m20s
Nothing in this interpreter mallocs; every pool is a fixed array sized by an AKBASIC_MAX_* constant, so sizeof(akbasic_Runtime) is a compile-time number and most of it was headroom nobody was using. Measured concurrent-use high-water marks off examples/breakout and examples/megademo -- the two most demanding programs this interpreter runs -- against each pool's ceiling: AKBASIC_MAX_ENVIRONMENTS 32 -> 12 (measured peak concurrency: 6-7) AKBASIC_MAX_FUNCTIONS 64 -> 8 (measured: 0, neither program uses DEF FN) AKBASIC_MAX_ARRAY_VALUES 4096 -> 2048 (measured peak: 1618 slots) AKBASIC_MAX_SOURCE_LINES 9999 -> 2048 (measured: ~1270-1496 non-blank lines) AKBASIC_SYMTAB_MAX_SLOTS 256 -> 172 (no caller ever requests more than 128) AKBASIC_SYMTAB_MAX_KEY 64 -> 24 (longest identifier measured: 11 chars) AKBASIC_MAX_LINE_LENGTH 256 -> 80 (Commodore BASIC's own line limit) AKBASIC_MAX_VARIABLES (128) is untouched on purpose: breakout alone reaches 121 of 128 concurrent named variables, so it has the least slack of any pool measured and is not a shrink candidate. akbasic_Variable.name shrinks from AKBASIC_MAX_STRING_LENGTH (256) to AKBASIC_SYMTAB_MAX_KEY: every variable name is registered with akbasic_symtab_set() right after this field is populated (akbasic_environment_create(), src/environment.c), and that call already refuses anything AKBASIC_SYMTAB_MAX_KEY characters or longer. The wider field was headroom nothing could ever put a byte into. Two defects surfaced while testing the line-length drop against the golden corpus, both fixed here because the 80-byte ceiling makes them routine rather than theoretical: - sourcepath (runtime.h) was borrowing AKBASIC_MAX_LINE_LENGTH by accident. It holds a directory, not a line of BASIC, and this checkout's own test paths are 81+ characters deep -- every golden test failed to load until this split into its own AKBASIC_MAX_SOURCE_PATH_LENGTH, backed by PATH_MAX the way libakerror already sizes its own path buffers. - src/sink_stdio.c's stdio_readline() called aksl_fgets() but never checked its own documented contract: a full buffer with no trailing newline means the line was longer than the buffer, and the unread remainder is still in the stream. Unchecked, the next readline() picks that remainder up as its own statement -- a real line silently becomes two wrong ones instead of a clean AKBASIC_ERR_BOUNDS refusal. At 256 bytes this was theoretical; at 80 it is not, so it now refuses loudly. tests/value_pool.c's test_pool_is_untouched_by_scopes() was pinned to the old 4x1024=4096 pool math (four max-size arrays proving nothing leaked); rewritten to 2x1024=2048 for the same proof against the new AKBASIC_MAX_ARRAY_VALUES. Known consequence, tracked in andrew/akbasic#32 rather than worked around here: two files in the protected tests/reference/ corpus (language/functions/mod.bas, language/flowcontrol/nestedforloopwaitingfor command.bas) have 82-character lines and cannot be shortened -- MAINTENANCE.md and CMakeLists.txt:585 are explicit that tests/reference/ is never edited to suit this interpreter. Twelve tests/language/ cases and one docs/18 line are in the same position but are this project's own content. Shipping 80 anyway, with the fallout tracked rather than hidden, was an explicit call on this PR rather than something decided here. Verified: cmake --build build-akgl && ctest --test-dir build-akgl, 97/112 (15 known failures, all AKBASIC_MAX_LINE_LENGTH-related, filed as #32). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
903 lines
42 KiB
C
903 lines
42 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 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_
|