Merge branch 'main' into 12
Some checks failed
akbasic CI Build / cmake_build (push) Successful in 3m30s
akbasic CI Build / sanitizers (push) Successful in 4m53s
akbasic CI Build / coverage (push) Successful in 3m55s
akbasic CI Build / akgl_build (push) Has been cancelled
akbasic CI Build / mutation_test (push) Has been cancelled
Some checks failed
akbasic CI Build / cmake_build (push) Successful in 3m30s
akbasic CI Build / sanitizers (push) Successful in 4m53s
akbasic CI Build / coverage (push) Successful in 3m55s
akbasic CI Build / akgl_build (push) Has been cancelled
akbasic CI Build / mutation_test (push) Has been cancelled
This commit is contained in:
@@ -296,7 +296,7 @@ typedef struct
|
||||
*
|
||||
* Claimed up front rather than per scan for two reasons. The pool is shared
|
||||
* with whatever host this interpreter is embedded in -- a game with its own
|
||||
* shaped actors draws from the same #AKGL_MAX_HEAP_COLLISION_PROXY -- so
|
||||
* shaped actors draws from the same `AKGL_MAX_HEAP_COLLISION_PROXY` -- so
|
||||
* running out is a real possibility, and it should be an init-time failure
|
||||
* naming the pool rather than a collision scan that starts refusing halfway
|
||||
* through a game. And a proxy carries a *copy* of its shape, so there is
|
||||
|
||||
@@ -72,6 +72,37 @@ typedef struct akbasic_Environment
|
||||
*/
|
||||
bool exiting;
|
||||
|
||||
/*
|
||||
* Generator state (GEN / EMIT / FOR EACH / DO EACH).
|
||||
*
|
||||
* `isGenerator` is set on the environment a GEN call pushes -- the one
|
||||
* whose body is actually running the GEN's lines, as opposed to the loop's
|
||||
* own environment. `generatorFn` records *which* GEN it is running, so a
|
||||
* FOR EACH/DO EACH that would invoke a GEN currently running higher up the
|
||||
* parent chain (self-recursion) can be told apart from one invoking it
|
||||
* fresh, or invoking a sibling instance of the same GEN sitting detached in
|
||||
* someone else's `forGeneratorEnv`. It carries an akbasic_FunctionDef *, kept
|
||||
* as void * for the same reason akbasic_environment_get_function() does:
|
||||
* runtime.h includes this header, not the other way around.
|
||||
*/
|
||||
bool isGenerator;
|
||||
void *generatorFn;
|
||||
/**
|
||||
* Set on a FOR EACH or DO EACH loop's own environment, distinguishing it
|
||||
* from a plain FOR/DO for verbs that need to know which kind of loop this
|
||||
* is -- EXIT, NEXT and LOOP all read it.
|
||||
*/
|
||||
bool isEachLoop;
|
||||
/**
|
||||
* The generator environment a FOR EACH/DO EACH loop is suspended on
|
||||
* between iterations -- alive, detached from the step loop, but not
|
||||
* released, so its own `nextline` still says where to resume. NULL means
|
||||
* either "not an EACH loop" or "the generator is exhausted": both leave
|
||||
* nothing to resume, and by the time either becomes true the loop
|
||||
* environment itself is on its way out too.
|
||||
*/
|
||||
struct akbasic_Environment *forGeneratorEnv;
|
||||
|
||||
int64_t gosubReturnLine;
|
||||
|
||||
/* READ state. The identifier leaves are deep copies, so they need storage. */
|
||||
@@ -246,6 +277,16 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_collect_subscripts(akbasi
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_create(akbasic_Environment *obj, const char *varname, akbasic_Variable **dest);
|
||||
|
||||
/**
|
||||
* @brief Create a variable slot without allocating value storage.
|
||||
*
|
||||
* Used when the caller knows the variable's representation before its first
|
||||
* initialization, such as a structure parameter. The caller must initialize
|
||||
* the variable before evaluating it.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_create_empty(akbasic_Environment *obj, const char *varname,
|
||||
akbasic_Variable **dest);
|
||||
|
||||
/**
|
||||
* @brief Resolve a label to the line number it marks.
|
||||
* @param obj Scope to search; the parent chain is walked.
|
||||
|
||||
@@ -12,9 +12,12 @@
|
||||
* libakerror 2.0.0 is the floor, raised from 1.0.0 because 2.0.0 is an ABI break
|
||||
* that a compile against the wrong header cannot survive quietly:
|
||||
*
|
||||
* - `__akerr_last_ignored` became thread-local. `IGNORE` expands at *our* call
|
||||
* site, so our objects reference that symbol under whichever storage model
|
||||
* the header on the include path declared.
|
||||
* - The context behind `IGNORE` became thread-local. `IGNORE` expands at *our*
|
||||
* call site, so our objects reference that storage under whichever model the
|
||||
* header on the include path declared. 2.0.2 went further and made it a
|
||||
* per-translation-unit `static` snapshot named `akerr_last_ignored`, copied
|
||||
* from the pool slot so the slot can be released; the old spelling
|
||||
* `__akerr_last_ignored` was an `extern` pointer and no longer exists.
|
||||
* - `akerr_next_error()` now returns a context that already holds a reference,
|
||||
* and `ENSURE_ERROR_READY` no longer increments. Objects compiled against a
|
||||
* 1.x header count every reference twice and never give a slot back.
|
||||
|
||||
@@ -102,6 +102,15 @@ typedef struct
|
||||
akbasic_ASTLeaf *arglist;
|
||||
akbasic_ASTLeaf *expression;
|
||||
int64_t lineno;
|
||||
/*
|
||||
* Set by akbasic_parse_gen(), left false by akbasic_parse_def(). GEN and
|
||||
* DEF share this table (TODO.md's namespace decision for generators), so
|
||||
* this is what lets akbasic_runtime_call_function() refuse to run a GEN
|
||||
* called like an ordinary function -- cleanly, before anything is pushed,
|
||||
* rather than relying on EMIT to fail deep inside a call whose BASIC-level
|
||||
* error a caller driving its own step loop would not see raised.
|
||||
*/
|
||||
bool isGenerator;
|
||||
/*
|
||||
* 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 --
|
||||
@@ -258,6 +267,11 @@ typedef struct akbasic_Runtime
|
||||
*/
|
||||
int64_t timems;
|
||||
|
||||
/* RND's lazy seed state. The flag distinguishes an unseeded run from a
|
||||
* legitimate LCG state of zero. */
|
||||
int64_t rndseed;
|
||||
bool rndseeded;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
@@ -596,6 +610,115 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_environment(akbasic_Runti
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_prev_environment(akbasic_Runtime *obj);
|
||||
|
||||
/**
|
||||
* @brief Return control to the active scope's parent, without releasing it.
|
||||
*
|
||||
* The half of akbasic_runtime_prev_environment() that pops; the other half,
|
||||
* akbasic_runtime_release_environment(), gives the scope's variables and its
|
||||
* pool slot back. Split for EMIT: a generator suspended between iterations
|
||||
* has to keep existing -- its own `nextline` is where NEXT resumes it -- so
|
||||
* detaching without releasing is what lets `obj->environment` move on to the
|
||||
* loop while the generator's scope stays alive, reachable through
|
||||
* `forGeneratorEnv`.
|
||||
*
|
||||
* @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_detach_environment(akbasic_Runtime *obj);
|
||||
/**
|
||||
* @brief Give a scope's variables and its pool slot back.
|
||||
*
|
||||
* The other half of akbasic_runtime_prev_environment(): unlike that function,
|
||||
* @p env need not be `obj->environment` -- a generator environment sitting
|
||||
* detached in some loop's `forGeneratorEnv` is released this way once it is
|
||||
* exhausted or abandoned, without disturbing whatever scope is active now.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param env The scope to release; must not be NULL and must not be the root.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When `env` is NULL.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_release_environment(akbasic_Runtime *obj, akbasic_Environment *env);
|
||||
|
||||
/**
|
||||
* @brief Pop and release every scope from the active one up to @p target.
|
||||
*
|
||||
* The shared teardown for every path that abandons part of the environment
|
||||
* chain at once instead of popping it a verb at a time: the error unwinds in
|
||||
* akbasic_runtime_pump_generator() and akbasic_runtime_call_function(). Each
|
||||
* popped scope's suspended generator (`forGeneratorEnv`), if it still holds
|
||||
* one, is released through akbasic_runtime_release_generator() -- a suspended
|
||||
* generator is a *child* of its loop scope, so no walk up the parent chain
|
||||
* would ever reach it.
|
||||
*
|
||||
* Stops without error at the root if @p target is not on the chain: callers
|
||||
* are already cleaning up after a failure, and releasing everything is the
|
||||
* least-wrong answer to a target that has gone missing.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param target The scope to stop at; it is left active and untouched.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When @p target is NULL.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_unwind_to_environment(akbasic_Runtime *obj, akbasic_Environment *target);
|
||||
|
||||
/**
|
||||
* @brief Push a GEN's environment, bind its parameters and run it to its first EMIT.
|
||||
*
|
||||
* Shared by the EACH branches of `FOR` and `DO`: both look up the same kind of
|
||||
* call expression (an `AKBASIC_LEAF_FUNCTION` leaf naming a GEN), guard
|
||||
* against invoking a GEN that is already running higher up this same parent
|
||||
* chain, evaluate the call's arguments in the caller's scope, bind them into a
|
||||
* fresh environment the way a function call does, and run that environment
|
||||
* until EMIT detaches it or END GEN ends it with nothing emitted.
|
||||
*
|
||||
* @p loopenv is left in the state a caller checks afterwards:
|
||||
* `loopenv->forGeneratorEnv` is the live generator environment when
|
||||
* something was emitted, or NULL when the GEN produced nothing at all.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param loopenv The FOR EACH/DO EACH loop's own environment; becomes the new
|
||||
* generator environment's parent.
|
||||
* @param callexpr The generator call, e.g. `ROOMOBJECTS(CURROOM%)`.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKBASIC_ERR_STATE When the named GEN is already running higher up
|
||||
* this same parent chain.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_generator_invoke(akbasic_Runtime *obj, akbasic_Environment *loopenv, akbasic_ASTLeaf *callexpr);
|
||||
/**
|
||||
* @brief Run a suspended generator until it next detaches.
|
||||
*
|
||||
* Shared by the EACH branches of `NEXT` and `LOOP`: `obj->environment` is
|
||||
* expected to already be the generator environment to resume (a caller sets
|
||||
* that from `loopenv->forGeneratorEnv` before calling), and this drives the
|
||||
* step loop until either EMIT detaches it back to @p loopenv with another
|
||||
* value, or END GEN really ends it -- in which case it releases the
|
||||
* generator environment itself and clears `loopenv->forGeneratorEnv`.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param loopenv The FOR EACH/DO EACH loop's own environment, and the pump's
|
||||
* stopping point.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_pump_generator(akbasic_Runtime *obj, akbasic_Environment *loopenv);
|
||||
|
||||
/**
|
||||
* @brief Release a live, abandoned generator, wherever EMIT left it suspended.
|
||||
*
|
||||
* `env` is expected to be a loop's `forGeneratorEnv` -- the resume point, not
|
||||
* necessarily the GEN's own call frame, since EMIT may have run several
|
||||
* levels below it inside a FOR/DO/GOSUB the body wrote. Walks up from there,
|
||||
* releasing every environment through the call frame itself inclusive, so
|
||||
* nothing above the resume point is left behind.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param env The suspended resume point to release.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When `env` is NULL.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_release_generator(akbasic_Runtime *obj, akbasic_Environment *env);
|
||||
|
||||
/**
|
||||
* @brief Report a BASIC error on the current line, in the reference's format.
|
||||
*
|
||||
@@ -833,13 +956,6 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_reserve_globals(akbasic_Runti
|
||||
* @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.
|
||||
*
|
||||
@@ -866,6 +982,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_variable(akbasic_Runtime
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_call_function(struct akbasic_Runtime *obj, const char *name, akbasic_Value **args, int nargs, akbasic_Value **dest);
|
||||
|
||||
/**
|
||||
* @brief Take an unused function definition from the runtime's pool.
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKBASIC_ERR_BOUNDS When every function slot is in use.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_function(akbasic_Runtime *obj, akbasic_FunctionDef **dest);
|
||||
/**
|
||||
* @brief File one already-scanned source line under its line number.
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
* with headroom. The ceiling matters because these come out of libakgl's
|
||||
* collision proxy pool, which is shared with whatever host this interpreter is
|
||||
* embedded in: eight sprites plus sixty-four solids is seventy-two of
|
||||
* #AKGL_MAX_HEAP_COLLISION_PROXY, and the rest is the host's.
|
||||
* `AKGL_MAX_HEAP_COLLISION_PROXY`, and the rest is the host's.
|
||||
*/
|
||||
#ifndef AKBASIC_MAX_SOLIDS
|
||||
#define AKBASIC_MAX_SOLIDS 64
|
||||
@@ -117,7 +117,7 @@ typedef struct
|
||||
int speed; /* clockwise from vertical, and 0-15 */
|
||||
|
||||
/**
|
||||
* SPRHIT's collision shape: one of the #AKBASIC_SHAPE_* kinds, and a
|
||||
* SPRHIT's collision shape: one of the `AKBASIC_SHAPE_*` kinds, and a
|
||||
* rectangle measured from the sprite's top-left corner.
|
||||
*
|
||||
* `shapeexplicit` is what separates "the program asked for the whole frame"
|
||||
|
||||
Reference in New Issue
Block a user