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
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:
197
src/runtime.c
197
src/runtime.c
@@ -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 ) {
|
||||
|
||||
Reference in New Issue
Block a user