Port onto libakstdlib 2b79aca and convert the eight bool predicates
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m27s
akbasic CI Build / coverage (push) Failing after 3m44s
akbasic CI Build / sanitizers (push) Failing after 4m43s
akbasic CI Build / mutation_test (push) Failing after 3m45s
akbasic CI Build / akgl_build (push) Failing after 4m51s

akbasic's src/ now calls libakstdlib 313 times and raw libc 7 -- 2.2%
bypassed, against 86.4% on the same tree before this. The submodule bump
669b2b3 -> 2b79aca needed no source change of its own: the release is
drop-in for what akbasic already used.

Seven of the eight sites the earlier port left on raw libc change their own
signature rather than swallowing an error, per andrew's ruling on
libakstdlib#38. word_is, the is_waiting_for pair, the scanner's is_at_end,
peek, peek_next and match_next_char, format.c's overflow, and sink_akgl's
scroll/newline/putchar_at/echo_line/edit_key chain all return an
akerr_ErrorContext * and hand the answer back through an out parameter.
is_waiting_for and is_waiting_for_any are a public header change; every
call site that used one as a term in a condition hoists it into a
statement first.

verb_compare is the eighth and stays on strcmp. bsearch(3) fixes the
comparator's signature, so there is no out parameter to report through --
which is what libakstdlib#38 concluded. It carries a comment saying so and
saying why the bypass is safe there.

Six snprintf sites stay raw because they want truncation as an answer
rather than an error, and aksl_snprintf cannot express that until
libakstdlib#34 hands the required length back. Each of the six says so at
the site. Two of them, in host.c, are a latent defect rather than a
decision: a host type name over 31 characters truncates silently and two
sharing a prefix then collide, where structtype.c refuses the same case.

DLOAD leaked a file descriptor. Its read loop sat inside an ATTEMPT and the
PASS in it returned past CLEANUP, so a scan error left the file open.
Hoisting the loop into its own helper to convert fgets fixes it.

Refs libakstdlib#26, libakstdlib#38

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-03 15:41:49 -04:00
parent 330d731cfe
commit d219f80777
40 changed files with 1211 additions and 584 deletions

View File

@@ -6,10 +6,9 @@
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/parser.h>
@@ -28,7 +27,7 @@ akerr_ErrorContext *akbasic_runtime_new_variable(akbasic_Runtime *obj, akbasic_V
"NULL argument in new_variable");
for ( i = 0; i < AKBASIC_MAX_VARIABLES; i++ ) {
if ( !obj->variables[i].used ) {
memset(&obj->variables[i], 0, sizeof(obj->variables[i]));
PASS(errctx, aksl_memset(&obj->variables[i], 0, sizeof(obj->variables[i])));
obj->variables[i].used = true;
/*
* Not zero: zero is a valid structure type index, so a memset alone
@@ -102,7 +101,7 @@ akerr_ErrorContext *akbasic_runtime_new_function(akbasic_Runtime *obj, akbasic_F
"NULL argument in new_function");
for ( i = 0; i < AKBASIC_MAX_FUNCTIONS; i++ ) {
if ( !obj->functions[i].used ) {
memset(&obj->functions[i], 0, sizeof(obj->functions[i]));
PASS(errctx, aksl_memset(&obj->functions[i], 0, sizeof(obj->functions[i])));
obj->functions[i].used = true;
obj->functions[i].leafpool.next = 0;
obj->functions[i].leafpool.capacity = AKBASIC_MAX_LEAVES * 2;
@@ -215,7 +214,7 @@ akerr_ErrorContext *akbasic_runtime_init(akbasic_Runtime *obj, akbasic_TextSink
*/
PASS(errctx, akbasic_error_register());
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->sink = sink;
obj->environment = NULL;
obj->autoLineNumber = 0;
@@ -279,7 +278,7 @@ akerr_ErrorContext *akbasic_runtime_set_ui(akbasic_Runtime *obj, akbasic_UiBacke
akerr_ErrorContext *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const char *path)
{
PREPARE_ERROR(errctx);
const char *slash = NULL;
char *slash = NULL;
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in set_source_path");
@@ -292,18 +291,18 @@ akerr_ErrorContext *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const
* would do it but it is allowed to modify its argument and two of the three
* libcs this has to build on disagree about which one they implement.
*/
slash = strrchr(path, '/');
PASS(errctx, aksl_strrchr(path, '/', &slash));
length = (slash == NULL ? 0 : (size_t)(slash - path));
if ( length == 0 ) {
/* Either no directory at all, or the root. */
strncpy(obj->sourcepath, (slash == NULL ? "." : "/"), sizeof(obj->sourcepath) - 1);
obj->sourcepath[sizeof(obj->sourcepath) - 1] = '\0';
PASS(errctx, aksl_strcpy(obj->sourcepath, sizeof(obj->sourcepath),
(slash == NULL ? "." : "/")));
SUCCEED_RETURN(errctx);
}
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->sourcepath)), AKBASIC_ERR_BOUNDS,
"Program path of %zu characters exceeds the %d character limit",
length, AKBASIC_MAX_LINE_LENGTH - 1);
memcpy(obj->sourcepath, path, length);
PASS(errctx, aksl_memcpy(obj->sourcepath, path, length));
obj->sourcepath[length] = '\0';
SUCCEED_RETURN(errctx);
}
@@ -393,6 +392,16 @@ akerr_ErrorContext *akbasic_runtime_error(akbasic_Runtime *obj, akbasic_ErrorCla
* The format, the trailing \n inside the string, and the second newline
* writeln adds are all part of the acceptance contract --
* tests/language/array_outofbounds.txt ends in 0a 0a. See TODO.md 1.8.
*
* **Raw snprintf on purpose, and this is the site where it matters most.**
* `line` is 512 bytes; `message` arrives from an akerr_ErrorContext, whose
* own buffer is AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH -- 12384. An ordinary
* long diagnostic therefore truncates, and truncating a report is correct
* here: this is the one function that tells the user *what went wrong*, and
* aksl_snprintf would turn a long message into a second, different failure
* that replaces the first. A report may be shortened; it may not be lost.
* libakstdlib #34 is the issue tracking the contract that would let a caller
* ask for the length instead.
*/
snprintf(line, sizeof(line), "? %" PRId64 " : %s %s\n",
obj->environment->lineno, errclass_to_string(errclass), message);
@@ -469,7 +478,15 @@ akerr_ErrorContext *akbasic_runtime_set_mode(akbasic_Runtime *obj, int mode)
} PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
snprintf(message, sizeof(message), "%s", errctx->message);
/*
* Ignored rather than propagated: `errctx` is the error being
* handled, so a PASS here would overwrite it with the copy's own
* failure and the next line would read a released context. Same
* reason the two calls below are ignored. aksl_strcpy empties the
* destination before it copies, so a refusal leaves an empty
* message rather than an uninitialised one.
*/
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
obj->lasterrorstatus = errctx->status;
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
IGNORE(akbasic_runtime_set_mode(obj, obj->run_finished_mode));
@@ -492,7 +509,14 @@ static akerr_ErrorContext *report_and_reraise(akbasic_Runtime *obj, akerr_ErrorC
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
int status = cause->status;
snprintf(message, sizeof(message), "%s", cause->message);
/*
* Ignored rather than passed: this function's whole contract is that the
* program's own error is the one that leaves, so a failure to copy the
* message must not become the error that gets raised. aksl_strcpy empties
* the destination first, so a refusal reports an empty message rather than
* an uninitialised one.
*/
IGNORE(aksl_strcpy(message, sizeof(message), cause->message));
/* What ER# reports, if a TRAP is armed. Recorded before the context goes. */
obj->lasterrorstatus = status;
cause->handled = true;
@@ -675,6 +699,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
akbasic_Value *rval = NULL;
akbasic_Value *scratch = NULL;
const akbasic_Verb *verb = NULL;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in evaluate");
@@ -724,9 +749,11 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
* reaches the end of this line. Arming the wait is what makes a
* multi-line IF possible at all; BEND clears it.
*/
if ( notaken != NULL && notaken->leaftype == AKBASIC_LEAF_COMMAND &&
strcmp(notaken->identifier, "BEGIN") == 0 ) {
PASS(errctx, akbasic_environment_wait_for_command(obj->environment, "BEND"));
if ( notaken != NULL && notaken->leaftype == AKBASIC_LEAF_COMMAND ) {
PASS(errctx, aksl_strcmp(notaken->identifier, "BEGIN", &cmp));
if ( cmp == 0 ) {
PASS(errctx, akbasic_environment_wait_for_command(obj->environment, "BEND"));
}
}
if ( taken ) {
PASS(errctx, akbasic_runtime_evaluate(obj, expr->left, dest));
@@ -765,7 +792,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
case AKBASIC_LEAF_LITERAL_STRING:
lval->valuetype = AKBASIC_TYPE_STRING;
memcpy(lval->stringval, expr->literal_string, sizeof(lval->stringval));
PASS(errctx, aksl_memcpy(lval->stringval, expr->literal_string, sizeof(lval->stringval)));
SUCCEED_RETURN(errctx);
case AKBASIC_LEAF_UNARY:
@@ -816,6 +843,10 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
bool waiting = false;
bool matchesverb = false;
bool waitingbend = false;
FAIL_ZERO_RETURN(errctx, (obj != NULL && expr != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in interpret");
@@ -824,9 +855,21 @@ akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTL
* verb. This is what keeps a zero-iteration FOR body from executing, given
* that the loop condition is evaluated at the bottom of the structure.
*/
if ( akbasic_environment_is_waiting_for_any(obj->environment) ) {
if ( expr->leaftype != AKBASIC_LEAF_COMMAND ||
!akbasic_environment_is_waiting_for(obj->environment, expr->identifier) ) {
PASS(errctx, akbasic_environment_is_waiting_for_any(obj->environment, &waiting));
if ( waiting ) {
/*
* Hoisted out of the condition it used to be a term in: the test reads a
* recorded verb name, that read can fail, and the old `bool` return had
* nowhere to report it. `matchesverb` stays false unless the leaf really
* is a command whose name is what the scope is waiting for, which is what
* the `||` short-circuit used to say. See libakstdlib #38.
*/
matchesverb = false;
if ( expr->leaftype == AKBASIC_LEAF_COMMAND ) {
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment,
expr->identifier, &matchesverb));
}
if ( !matchesverb ) {
/*
* **A skipped loop has already pushed its scope, and this is where it
* comes back.**
@@ -860,11 +903,18 @@ akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTL
*/
if ( expr->leaftype == AKBASIC_LEAF_COMMAND &&
obj->environment->loopFirstLine != 0 &&
obj->environment->waitingForCommand[0] == '\0' &&
akbasic_environment_is_waiting_for(obj->environment, "BEND") &&
(strcmp(expr->identifier, "FOR") == 0 ||
strcmp(expr->identifier, "DO") == 0) ) {
PASS(errctx, akbasic_runtime_prev_environment(obj));
obj->environment->waitingForCommand[0] == '\0' ) {
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "BEND",
&waitingbend));
if ( waitingbend ) {
PASS(errctx, aksl_strcmp(expr->identifier, "FOR", &cmp));
if ( cmp != 0 ) {
PASS(errctx, aksl_strcmp(expr->identifier, "DO", &cmp));
}
if ( cmp == 0 ) {
PASS(errctx, akbasic_runtime_prev_environment(obj));
}
}
}
*dest = &obj->staticTrueValue;
SUCCEED_RETURN(errctx);
@@ -1125,15 +1175,18 @@ int64_t akbasic_runtime_find_previous_lineno(akbasic_Runtime *obj)
akerr_ErrorContext *akbasic_runtime_store_line(akbasic_Runtime *obj, int64_t lineno, const char *code, bool numbered)
{
PREPARE_ERROR(errctx);
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (lineno >= 0 && lineno < AKBASIC_MAX_SOURCE_LINES),
AKBASIC_ERR_BOUNDS,
"Line number %" PRId64 " is outside 0..%d",
lineno, AKBASIC_MAX_SOURCE_LINES - 1);
FAIL_ZERO_RETURN(errctx, (strlen(code) < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(code, &length));
FAIL_ZERO_RETURN(errctx, (length < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
"Source line exceeds the %d character limit", AKBASIC_MAX_LINE_LENGTH - 1);
strncpy(obj->source[lineno].code, code, AKBASIC_MAX_LINE_LENGTH - 1);
obj->source[lineno].code[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
/* aksl_strcpy always terminates and refuses rather than truncates; the
length check above is what makes the refusal unreachable. */
PASS(errctx, aksl_strcpy(obj->source[lineno].code, sizeof(obj->source[lineno].code), code));
obj->source[lineno].lineno = lineno;
obj->source[lineno].numbered = numbered;
SUCCEED_RETURN(errctx);
@@ -1242,11 +1295,12 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
akbasic_ASTLeaf *leaf = NULL;
akbasic_Value *value = NULL;
akbasic_Parser parser;
int written = 0;
bool eof = false;
if ( obj->autoLineNumber > 0 ) {
snprintf(prompt, sizeof(prompt), "%" PRId64 " ",
obj->environment->lineno + obj->autoLineNumber);
PASS(errctx, aksl_snprintf(&written, prompt, sizeof(prompt), "%" PRId64 " ",
obj->environment->lineno + obj->autoLineNumber));
PASS(errctx, akbasic_runtime_write(obj, prompt));
}
@@ -1272,7 +1326,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
} PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
snprintf(message, sizeof(message), "%s", errctx->message);
/* Ignored, not passed: `errctx` is the error being handled here. */
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
obj->lasterrorstatus = errctx->status;
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
} FINISH(errctx, false);
@@ -1337,14 +1392,14 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
akbasic_ASTLeaf *leaf = NULL;
akbasic_Value *value = NULL;
akbasic_Parser parser;
int written = 0;
if ( obj->environment->nextline >= AKBASIC_MAX_SOURCE_LINES ) {
PASS(errctx, akbasic_runtime_set_mode(obj, obj->run_finished_mode));
SUCCEED_RETURN(errctx);
}
strncpy(line, obj->source[obj->environment->nextline].code, sizeof(line) - 1);
line[sizeof(line) - 1] = '\0';
PASS(errctx, aksl_strcpy(line, sizeof(line), obj->source[obj->environment->nextline].code));
obj->environment->lineno = obj->environment->nextline;
obj->environment->nextline += 1;
if ( line[0] == '\0' ) {
@@ -1358,7 +1413,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
*/
if ( obj->trace ) {
char tracemark[32];
snprintf(tracemark, sizeof(tracemark), "[%" PRId64 "]", obj->environment->lineno);
PASS(errctx, aksl_snprintf(&written, tracemark, sizeof(tracemark), "[%" PRId64 "]",
obj->environment->lineno));
PASS(errctx, akbasic_runtime_write(obj, tracemark));
}
@@ -1373,7 +1429,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
} PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
snprintf(message, sizeof(message), "%s", errctx->message);
/* Ignored, not passed: `errctx` is the error being handled here. */
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
/*
* What ER# reports, recorded before the context goes -- the same line
* report_and_reraise() carries for a runtime error, and it was missing
@@ -1442,6 +1499,7 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
{
PREPARE_ERROR(errctx);
const char *cursor = code;
int cmp = 0;
bool statementstart = true;
bool instring = false;
@@ -1479,31 +1537,33 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
}
continue;
}
if ( statementstart && strncasecmp(cursor, "LABEL", 5) == 0
&& !isalnum((unsigned char)cursor[5]) ) {
char name[AKBASIC_SYMTAB_MAX_KEY];
size_t used = 0;
if ( statementstart ) {
PASS(errctx, aksl_strncasecmp(cursor, "LABEL", 5, &cmp));
if ( cmp == 0 && !isalnum((unsigned char)cursor[5]) ) {
char name[AKBASIC_SYMTAB_MAX_KEY];
size_t used = 0;
cursor += 5;
while ( isspace((unsigned char)*cursor) ) {
cursor += 1;
cursor += 5;
while ( isspace((unsigned char)*cursor) ) {
cursor += 1;
}
/*
* Copied as written. Verbs are case-insensitive in this dialect and
* identifiers are not, so folding the name here would file a label
* under a spelling `LABEL` itself never uses.
*/
while ( isalnum((unsigned char)*cursor) && used < sizeof(name) - 1 ) {
name[used] = *cursor;
used += 1;
cursor += 1;
}
name[used] = '\0';
if ( used > 0 ) {
PASS(errctx, akbasic_symtab_set(&root->labels, name, NULL, lineno));
}
statementstart = false;
continue;
}
/*
* Copied as written. Verbs are case-insensitive in this dialect and
* identifiers are not, so folding the name here would file a label
* under a spelling `LABEL` itself never uses.
*/
while ( isalnum((unsigned char)*cursor) && used < sizeof(name) - 1 ) {
name[used] = *cursor;
used += 1;
cursor += 1;
}
name[used] = '\0';
if ( used > 0 ) {
PASS(errctx, akbasic_symtab_set(&root->labels, name, NULL, lineno));
}
statementstart = false;
continue;
}
statementstart = false;
cursor += 1;
@@ -1536,6 +1596,7 @@ akerr_ErrorContext *akbasic_runtime_arm_interrupt(akbasic_Runtime *obj, akbasic_
{
PREPARE_ERROR(errctx);
akbasic_Interrupt *slot = NULL;
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in arm_interrupt");
FAIL_ZERO_RETURN(errctx, (source >= 0 && source < AKBASIC_MAX_INTERRUPTS),
@@ -1550,11 +1611,13 @@ akerr_ErrorContext *akbasic_runtime_arm_interrupt(akbasic_Runtime *obj, akbasic_
slot->line = line;
slot->label[0] = '\0';
if ( label != NULL && label[0] != '\0' ) {
FAIL_ZERO_RETURN(errctx, (strlen(label) < sizeof(slot->label)), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(label, &length));
FAIL_ZERO_RETURN(errctx, (length < sizeof(slot->label)), AKBASIC_ERR_BOUNDS,
"Handler label \"%s\" exceeds the %zu character limit",
label, sizeof(slot->label) - 1);
strncpy(slot->label, label, sizeof(slot->label) - 1);
slot->label[sizeof(slot->label) - 1] = '\0';
/* aksl_strcpy always terminates and refuses rather than truncates; the
length check above is what makes the refusal unreachable. */
PASS(errctx, aksl_strcpy(slot->label, sizeof(slot->label), label));
}
SUCCEED_RETURN(errctx);
}
@@ -1567,7 +1630,7 @@ akerr_ErrorContext *akbasic_runtime_disarm_interrupt(akbasic_Runtime *obj, akbas
FAIL_ZERO_RETURN(errctx, (source >= 0 && source < AKBASIC_MAX_INTERRUPTS),
AKBASIC_ERR_BOUNDS, "Interrupt source %d is outside 0..%d",
(int)source, AKBASIC_MAX_INTERRUPTS - 1);
memset(&obj->interrupts[source], 0, sizeof(obj->interrupts[source]));
PASS(errctx, aksl_memset(&obj->interrupts[source], 0, sizeof(obj->interrupts[source])));
SUCCEED_RETURN(errctx);
}
@@ -1682,6 +1745,7 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
char scanned[AKBASIC_MAX_LINE_LENGTH];
const char *cursor = NULL;
const char *eol = NULL;
char *match = NULL;
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in load");
@@ -1696,9 +1760,11 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
obj->environment->lineno = 0;
for ( cursor = source; *cursor != '\0'; cursor = (*eol == '\0' ? eol : eol + 1) ) {
eol = strchr(cursor, '\n');
PASS(errctx, aksl_strchr(cursor, '\n', &match));
eol = match;
if ( eol == NULL ) {
eol = cursor + strlen(cursor);
PASS(errctx, aksl_strlen(cursor, &length));
eol = cursor + length;
}
length = (size_t)(eol - cursor);
if ( length > 0 && cursor[length - 1] == '\r' ) {
@@ -1707,7 +1773,7 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
FAIL_ZERO_RETURN(errctx, (length < sizeof(line)), AKBASIC_ERR_BOUNDS,
"Source line of %zu characters exceeds the %d character limit",
length, AKBASIC_MAX_LINE_LENGTH - 1);
memcpy(line, cursor, length);
PASS(errctx, aksl_memcpy(line, cursor, length));
line[length] = '\0';
if ( line[0] == '\0' ) {
continue;
@@ -1815,7 +1881,8 @@ akerr_ErrorContext *akbasic_runtime_step(akbasic_Runtime *obj)
} PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
snprintf(message, sizeof(message), "%s", errctx->message);
/* Ignored, not passed: `errctx` is the error being handled here. */
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_RUNTIME, message));
} FINISH(errctx, false);
if ( obj->errclass != AKBASIC_ERRCLASS_NONE ) {