Port onto libakstdlib 2b79aca and convert the eight bool predicates (libakstdlib#26) #31

Open
tachikoma wants to merge 4 commits from libakstdlib-26 into main
40 changed files with 1211 additions and 584 deletions
Showing only changes of commit d219f80777 - Show all commits

View File

@@ -156,18 +156,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_leaf(akbasic_Environm
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command);
/**
* @brief True when this scope or any enclosing one is skipping forward.
* @brief Whether this scope or any enclosing one is skipping forward.
*
* The answer leaves through @p dest rather than the return value. Deciding it
* reads a recorded verb name, that read can fail, and a `bool` return has
* nowhere to report the failure -- so the signature changes rather than the
* error being swallowed. libakstdlib #38 is the ruling; `probe` in `symtab.c`
* and `loop_continues` in `runtime_structure.c` are the same shape.
*
* @param obj Scope to inspect; NULL is not waiting.
* @return `true` when execution is currently suppressed.
* @param[out] dest `true` when execution is currently suppressed. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When dest is NULL.
*/
bool akbasic_environment_is_waiting_for_any(akbasic_Environment *obj);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for_any(akbasic_Environment *obj, bool *dest);
/**
* @brief True when this scope or an enclosing one is waiting for a given verb.
* @brief Whether this scope or an enclosing one is waiting for a given verb.
*
* Reports through @p dest for the same reason its sibling above does.
*
* @param obj Scope to inspect; NULL is not waiting.
* @param command Verb to test for.
* @return `true` when that verb is what execution is waiting on.
* @param command Verb to test for; NULL is not waiting.
* @param[out] dest `true` when that verb is what execution is waiting on. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When dest is NULL.
*/
bool akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command, bool *dest);
/**
* @brief Clear a pending wait, searching the parent chain for it.
*

View File

@@ -8,7 +8,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <akerror.h>
#include <akstdlib.h>
@@ -112,6 +111,7 @@ static akerr_ErrorContext *scan_line(akbasic_DataState *obj, const char *code, i
const char *cursor = code;
bool statementstart = true;
bool instring = false;
int cmp = 0;
while ( *cursor != '\0' ) {
if ( instring ) {
@@ -141,13 +141,15 @@ static akerr_ErrorContext *scan_line(akbasic_DataState *obj, const char *code, i
}
continue;
}
if ( statementstart && strncasecmp(cursor, "DATA", 4) == 0
&& !isalnum((unsigned char)cursor[4]) ) {
if ( statementstart ) {
PASS(errctx, aksl_strncasecmp(cursor, "DATA", 4, &cmp));
if ( cmp == 0 && !isalnum((unsigned char)cursor[4]) ) {
cursor += 4;
PASS(errctx, collect_items(obj, &cursor, lineno));
statementstart = false;
continue;
}
}
statementstart = false;
cursor += 1;
}
@@ -184,7 +186,7 @@ akerr_ErrorContext *akbasic_data_next(akbasic_Runtime *obj, akbasic_Type type, a
PASS(errctx, akbasic_value_zero(dest));
if ( type == AKBASIC_TYPE_STRING ) {
dest->valuetype = AKBASIC_TYPE_STRING;
snprintf(dest->stringval, sizeof(dest->stringval), "%s", item->text);
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), item->text));
SUCCEED_RETURN(errctx);
}

View File

@@ -7,6 +7,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/environment.h>
#include <akbasic/error.h>
@@ -39,7 +40,7 @@ akerr_ErrorContext *akbasic_environment_init(akbasic_Environment *obj, akbasic_R
obj->readIdentifierIdx = 0;
obj->waitingForCommand[0] = '\0';
obj->errorToken = NULL;
memset(obj->readIdentifierLeaves, 0, sizeof(obj->readIdentifierLeaves));
PASS(errctx, aksl_memset(obj->readIdentifierLeaves, 0, sizeof(obj->readIdentifierLeaves)));
obj->doLeafPool.next = 0;
obj->doLeafPool.capacity = AKBASIC_MAX_CONDITION_LEAVES;
obj->doLeafPool.leaves = obj->doLeafStorage;
@@ -123,6 +124,7 @@ akerr_ErrorContext *akbasic_environment_new_leaf(akbasic_Environment *obj, akbas
akerr_ErrorContext *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command)
{
PREPARE_ERROR(errctx);
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER,
"NULL argument in wait_for_command");
@@ -135,38 +137,56 @@ akerr_ErrorContext *akbasic_environment_wait_for_command(akbasic_Environment *ob
FAIL_NONZERO_RETURN(errctx, (obj->waitingForCommand[0] != '\0'), AKBASIC_ERR_STATE,
"Can't wait on multiple commands in the same environment : %s",
obj->waitingForCommand);
FAIL_ZERO_RETURN(errctx, (strlen(command) < sizeof(obj->waitingForCommand)),
PASS(errctx, aksl_strlen(command, &length));
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->waitingForCommand)),
AKBASIC_ERR_BOUNDS, "Command name '%s' is too long to wait on", command);
strncpy(obj->waitingForCommand, command, sizeof(obj->waitingForCommand) - 1);
obj->waitingForCommand[sizeof(obj->waitingForCommand) - 1] = '\0';
PASS(errctx, aksl_strcpy(obj->waitingForCommand, sizeof(obj->waitingForCommand), command));
SUCCEED_RETURN(errctx);
}
bool akbasic_environment_is_waiting_for_any(akbasic_Environment *obj)
akerr_ErrorContext *akbasic_environment_is_waiting_for_any(akbasic_Environment *obj, bool *dest)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
"NULL destination in is_waiting_for_any");
*dest = false;
if ( obj == NULL ) {
return false;
SUCCEED_RETURN(errctx);
}
if ( obj->waitingForCommand[0] != '\0' ) {
return true;
*dest = true;
SUCCEED_RETURN(errctx);
}
return akbasic_environment_is_waiting_for_any(obj->parent);
/* The recursive tail becomes a PASS; dest carries the answer back up. */
PASS(errctx, akbasic_environment_is_waiting_for_any(obj->parent, dest));
SUCCEED_RETURN(errctx);
}
bool akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command)
akerr_ErrorContext *akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command, bool *dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
"NULL destination in is_waiting_for");
*dest = false;
if ( obj == NULL || command == NULL ) {
return false;
SUCCEED_RETURN(errctx);
}
if ( strcmp(obj->waitingForCommand, command) == 0 ) {
return true;
PASS(errctx, aksl_strcmp(obj->waitingForCommand, command, &cmp));
if ( cmp == 0 ) {
*dest = true;
SUCCEED_RETURN(errctx);
}
return akbasic_environment_is_waiting_for(obj->parent, command);
PASS(errctx, akbasic_environment_is_waiting_for(obj->parent, command, dest));
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_environment_stop_waiting(akbasic_Environment *obj, const char *command)
{
PREPARE_ERROR(errctx);
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER,
"NULL argument in stop_waiting");
@@ -179,7 +199,8 @@ akerr_ErrorContext *akbasic_environment_stop_waiting(akbasic_Environment *obj, c
* a miss is silently tolerated, exactly as today.
*/
while ( obj != NULL ) {
if ( strcmp(obj->waitingForCommand, command) == 0 ) {
PASS(errctx, aksl_strcmp(obj->waitingForCommand, command, &cmp));
if ( cmp == 0 ) {
obj->waitingForCommand[0] = '\0';
SUCCEED_RETURN(errctx);
}
@@ -197,7 +218,7 @@ akerr_ErrorContext *akbasic_environment_get_function(akbasic_Environment *obj, c
FAIL_ZERO_RETURN(errctx, (obj != NULL && fname != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in get_function");
len = strlen(fname);
PASS(errctx, aksl_strlen(fname, &len));
FAIL_ZERO_RETURN(errctx, (len < sizeof(upper)), AKERR_KEY, "Function '%s' is not defined", fname);
for ( i = 0; i < len; i++ ) {
char c = fname[i];
@@ -291,6 +312,7 @@ akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const c
akbasic_Variable *variable = NULL;
int64_t sizes[1] = { 1 };
void *slot = NULL;
size_t namelen = 0;
akerr_ErrorContext *found = NULL;
FAIL_ZERO_RETURN(errctx, (obj != NULL && varname != NULL && dest != NULL), AKERR_NULLPOINTER,
@@ -312,10 +334,10 @@ akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const c
IGNORE(akerr_release_error(found));
PASS(errctx, akbasic_runtime_new_variable(obj->runtime, &variable));
FAIL_ZERO_RETURN(errctx, (strlen(varname) < sizeof(variable->name)), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(varname, &namelen));
FAIL_ZERO_RETURN(errctx, (namelen < sizeof(variable->name)), AKBASIC_ERR_BOUNDS,
"Variable name '%s' is too long", varname);
strncpy(variable->name, varname, sizeof(variable->name) - 1);
variable->name[sizeof(variable->name) - 1] = '\0';
PASS(errctx, aksl_strcpy(variable->name, sizeof(variable->name), varname));
variable->valuetype = AKBASIC_TYPE_UNDEFINED;
variable->mutable_ = true;
PASS(errctx, akbasic_variable_init(variable, &obj->runtime->valuepool, sizes, 1));

View File

@@ -12,6 +12,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/format.h>
@@ -36,7 +37,7 @@ akerr_ErrorContext *akbasic_format_state_init(akbasic_FormatState *obj)
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL format state in init");
memcpy(obj->chars, PUDEF_DEFAULTS, sizeof(obj->chars));
PASS(errctx, aksl_memcpy(obj->chars, PUDEF_DEFAULTS, sizeof(obj->chars)));
SUCCEED_RETURN(errctx);
}
@@ -161,11 +162,20 @@ static void measure_numeric(const char *field, size_t length, int *before, int *
}
}
/** @brief Fill @p dest with @p width copies of `*`, the overflow marker. */
static void overflow(char *dest, size_t width)
/**
* @brief Fill @p dest with @p width copies of `*`, the overflow marker.
*
* Returns a context rather than `void` because the fill can fail and there was
* nowhere to say so. Both call sites are already inside `render_numeric`, which
* returns one. See libakstdlib #38.
*/
static akerr_ErrorContext *overflow(char *dest, size_t width)
{
memset(dest, '*', width);
PREPARE_ERROR(errctx);
PASS(errctx, aksl_memset(dest, '*', width));
dest[width] = '\0';
SUCCEED_RETURN(errctx);
}
/**
@@ -191,15 +201,21 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
bool leadsign = false;
bool trailsign = false;
bool negative = (number < 0.0);
int written = 0;
measure_numeric(field, length, &before, &after, &commas, &dollar, &leadsign, &trailsign);
FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS,
"PRINT USING field of %zu characters does not fit", length);
snprintf(digits, sizeof(digits), "%.*f", after, (negative ? -number : number));
PASS(errctx, aksl_snprintf(&written, digits, sizeof(digits), "%.*f",
after, (negative ? -number : number)));
point = strchr(digits, '.');
intlen = (point != NULL ? (size_t)(point - digits) : strlen(digits));
PASS(errctx, aksl_strchr(digits, '.', &point));
if ( point != NULL ) {
intlen = (size_t)(point - digits);
} else {
PASS(errctx, aksl_strlen(digits, &intlen));
}
/* Group the integer part, if the field asked for separators. */
used = 0;
@@ -213,7 +229,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
used += 1;
}
} else {
memcpy(grouped, digits, intlen);
PASS(errctx, aksl_memcpy(grouped, digits, intlen));
used = intlen;
}
grouped[used] = '\0';
@@ -226,7 +242,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
* printing wider than asked, which would misalign every later column.
*/
if ( (int)intlen > before ) {
overflow(dest, length);
PASS(errctx, overflow(dest, length));
SUCCEED_RETURN(errctx);
}
@@ -252,13 +268,13 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
used += 1;
}
}
memcpy(dest + used, grouped, (size_t)group);
PASS(errctx, aksl_memcpy(dest + used, grouped, (size_t)group));
used += (size_t)group;
if ( after > 0 ) {
dest[used] = obj->chars[AKBASIC_PUDEF_POINT];
used += 1;
memcpy(dest + used, (point != NULL ? point + 1 : ""), (size_t)after);
PASS(errctx, aksl_memcpy(dest + used, (point != NULL ? point + 1 : ""), (size_t)after));
used += (size_t)after;
}
if ( trailsign ) {
@@ -271,7 +287,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
* because a printed -5 that reads as 5 is worse than a row of stars.
*/
if ( negative && !leadsign && !trailsign ) {
overflow(dest, length);
PASS(errctx, overflow(dest, length));
SUCCEED_RETURN(errctx);
}
dest[used] = '\0';
@@ -283,26 +299,27 @@ static akerr_ErrorContext *render_string(const char *field, size_t length, const
char *dest, size_t len)
{
PREPARE_ERROR(errctx);
size_t textlen = strlen(text);
size_t textlen = 0;
size_t pad = 0;
PASS(errctx, aksl_strlen(text, &textlen));
FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS,
"PRINT USING field of %zu characters does not fit", length);
if ( textlen > length ) {
/* Truncated, not starred: BASIC 7.0 cuts a string to its field. */
memcpy(dest, text, length);
PASS(errctx, aksl_memcpy(dest, text, length));
dest[length] = '\0';
SUCCEED_RETURN(errctx);
}
memset(dest, ' ', length);
PASS(errctx, aksl_memset(dest, ' ', length));
dest[length] = '\0';
if ( field[0] == '=' ) {
pad = (length - textlen) / 2;
} else {
pad = length - textlen;
}
memcpy(dest + pad, text, textlen);
PASS(errctx, aksl_memcpy(dest + pad, text, textlen));
SUCCEED_RETURN(errctx);
}
@@ -313,6 +330,9 @@ akerr_ErrorContext *akbasic_format_using(akbasic_FormatState *obj, const char *f
size_t start = 0;
size_t length = 0;
size_t used = 0;
size_t renderedlen = 0;
size_t formatlen = 0;
int written = 0;
bool numeric = false;
FAIL_ZERO_RETURN(errctx, (obj != NULL && format != NULL && value != NULL && dest != NULL),
@@ -343,12 +363,15 @@ akerr_ErrorContext *akbasic_format_using(akbasic_FormatState *obj, const char *f
}
/* Literal text before the field, the field, then literal text after it. */
used = strlen(rendered) + strlen(format) - length;
PASS(errctx, aksl_strlen(rendered, &renderedlen));
PASS(errctx, aksl_strlen(format, &formatlen));
used = renderedlen + formatlen - length;
FAIL_ZERO_RETURN(errctx, (used + 1 <= len), AKBASIC_ERR_BOUNDS,
"PRINT USING result of %zu characters does not fit", used);
memcpy(dest, format, start);
memcpy(dest + start, rendered, strlen(rendered));
snprintf(dest + start + strlen(rendered), len - start - strlen(rendered),
"%s", format + start + length);
PASS(errctx, aksl_memcpy(dest, format, start));
PASS(errctx, aksl_memcpy(dest + start, rendered, renderedlen));
/* The check above already proved the tail fits, so this cannot truncate. */
PASS(errctx, aksl_snprintf(&written, dest + start + renderedlen,
len - start - renderedlen, "%s", format + start + length));
SUCCEED_RETURN(errctx);
}

View File

@@ -23,9 +23,9 @@
*/
#include <stddef.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akgl/controller.h>
#include <akgl/error.h>
@@ -55,7 +55,7 @@ akerr_ErrorContext *akbasic_frontend_akgl_init(akbasic_AkglFrontend *obj, const
FAIL_ZERO_RETURN(errctx, (w > 0 && h > 0 && fontsize > 0), AKBASIC_ERR_VALUE,
"A %dx%d window at %d points is not a window", w, h, fontsize);
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->width = w;
obj->height = h;

View File

@@ -17,14 +17,19 @@
static akerr_ErrorContext *copy_bounded(char *dest, const char *src, const char *what)
{
PREPARE_ERROR(errctx);
size_t srclen = 0;
FAIL_ZERO_RETURN(errctx, (src != NULL), AKERR_NULLPOINTER, "NULL %s", what);
FAIL_ZERO_RETURN(errctx, (strlen(src) < AKBASIC_MAX_STRING_LENGTH),
PASS(errctx, aksl_strlen(src, &srclen));
FAIL_ZERO_RETURN(errctx, (srclen < AKBASIC_MAX_STRING_LENGTH),
AKBASIC_ERR_VALUE,
"%s of %zu characters exceeds the %d character limit",
what, strlen(src), AKBASIC_MAX_STRING_LENGTH - 1);
strncpy(dest, src, AKBASIC_MAX_STRING_LENGTH - 1);
dest[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
what, srclen, AKBASIC_MAX_STRING_LENGTH - 1);
/* aksl_strcpy always terminates and refuses rather than truncates, which is
the behaviour this site already wanted. The length check above stays
because it names the limit in the message. Every destination is one of the
leaf's AKBASIC_MAX_STRING_LENGTH buffers, which is what sizes the copy. */
PASS(errctx, aksl_strcpy(dest, AKBASIC_MAX_STRING_LENGTH, src));
SUCCEED_RETURN(errctx);
}
@@ -90,8 +95,8 @@ static akerr_ErrorContext *clone_into(akbasic_ASTLeaf *self, akbasic_LeafPool *p
copy->parent = self->parent;
copy->literal_int = self->literal_int;
copy->literal_float = self->literal_float;
memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string));
memcpy(copy->identifier, self->identifier, sizeof(copy->identifier));
PASS(errctx, aksl_memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string)));
PASS(errctx, aksl_memcpy(copy->identifier, self->identifier, sizeof(copy->identifier)));
copy->operator_ = self->operator_;
PASS(errctx, clone_into(self->left, pool, &copy->left));
@@ -289,7 +294,9 @@ akerr_ErrorContext *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const cha
{
PREPARE_ERROR(errctx);
long long value = 0;
size_t lexlen = 0;
int base = 10;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_literal_int");
FAIL_ZERO_RETURN(errctx, (lexeme != NULL), AKERR_NULLPOINTER, "NULL lexeme in new_literal_int");
@@ -302,7 +309,9 @@ akerr_ErrorContext *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const cha
* Commodore BASIC has no octal literals, and a leading zero in a listing is
* padding, not a radix.
*/
if ( strlen(lexeme) > 2 && strncmp(lexeme, "0x", 2) == 0 ) {
PASS(errctx, aksl_strlen(lexeme, &lexlen));
PASS(errctx, aksl_strncmp(lexeme, "0x", 2, &cmp));
if ( lexlen > 2 && cmp == 0 ) {
base = 16;
}
PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_LITERAL_INT));
@@ -388,6 +397,7 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
PREPARE_ERROR(errctx);
char sub1[AKBASIC_MAX_STRING_LENGTH];
char sub2[AKBASIC_MAX_STRING_LENGTH];
int written = 0;
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL leaf in to_string");
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in to_string");
@@ -395,40 +405,42 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
switch ( self->leaftype ) {
case AKBASIC_LEAF_LITERAL_INT:
snprintf(dest, len, "%" PRId64, self->literal_int);
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, self->literal_int));
break;
case AKBASIC_LEAF_LITERAL_FLOAT:
snprintf(dest, len, "%f", self->literal_float);
PASS(errctx, aksl_snprintf(&written, dest, len, "%f", self->literal_float));
break;
case AKBASIC_LEAF_LITERAL_STRING:
snprintf(dest, len, "%s", self->literal_string);
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->literal_string));
break;
case AKBASIC_LEAF_IDENTIFIER_INT:
case AKBASIC_LEAF_IDENTIFIER_FLOAT:
case AKBASIC_LEAF_IDENTIFIER_STRING:
case AKBASIC_LEAF_IDENTIFIER:
snprintf(dest, len, "%s", self->identifier);
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->identifier));
break;
case AKBASIC_LEAF_IDENTIFIER_STRUCT:
snprintf(dest, len, "%s", self->identifier);
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->identifier));
break;
case AKBASIC_LEAF_FIELD:
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
snprintf(dest, len, "%s%s%s", sub1,
(self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier);
PASS(errctx, aksl_snprintf(&written, dest, len, "%s%s%s", sub1,
(self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier));
break;
case AKBASIC_LEAF_UNARY:
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
snprintf(dest, len, "(%s %s)", operator_to_str(self->operator_), sub1);
PASS(errctx, aksl_snprintf(&written, dest, len, "(%s %s)",
operator_to_str(self->operator_), sub1));
break;
case AKBASIC_LEAF_BINARY:
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
PASS(errctx, akbasic_leaf_to_string(self->right, sub2, sizeof(sub2)));
snprintf(dest, len, "(%s %s %s)", operator_to_str(self->operator_), sub1, sub2);
PASS(errctx, aksl_snprintf(&written, dest, len, "(%s %s %s)",
operator_to_str(self->operator_), sub1, sub2));
break;
case AKBASIC_LEAF_GROUPING:
PASS(errctx, akbasic_leaf_to_string(self->expr, sub1, sizeof(sub1)));
snprintf(dest, len, "(group %s)", sub1);
PASS(errctx, aksl_snprintf(&written, dest, len, "(group %s)", sub1));
break;
case AKBASIC_LEAF_COMMAND:
case AKBASIC_LEAF_COMMAND_IMMEDIATE:
@@ -437,10 +449,10 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
* The reference falls through to Go's %+v struct dump here, which has no
* useful C equivalent. Print something a test can assert on instead.
*/
snprintf(dest, len, "(%s)", self->identifier);
PASS(errctx, aksl_snprintf(&written, dest, len, "(%s)", self->identifier));
break;
default:
snprintf(dest, len, "(leaf %d)", (int)self->leaftype);
PASS(errctx, aksl_snprintf(&written, dest, len, "(leaf %d)", (int)self->leaftype));
break;
}
SUCCEED_RETURN(errctx);

View File

@@ -12,9 +12,8 @@
* reaching for a global, which is exactly what goal 3 needs.
*/
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akgl/draw.h>
#include <akgl/error.h>
@@ -246,7 +245,7 @@ akerr_ErrorContext *akbasic_graphics_init_akgl(akbasic_GraphicsBackend *obj, akb
"NULL renderer in graphics_init_akgl: the host creates it, not this");
PASS(errctx, akgl_error_init());
memset(state, 0, sizeof(*state));
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer;
obj->self = state;

View File

@@ -19,9 +19,9 @@
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/host.h>
@@ -89,8 +89,14 @@ akerr_ErrorContext *akbasic_host_read_field(akbasic_Runtime *obj, akbasic_Struct
* left it full: a BASIC string is inline and fixed, so there is nothing
* to alias and nothing that can outlive the read.
*/
/*
* Raw snprintf: the host's bytes need not be terminated, and `%.*s`
* bounded by hostwidth is what reads at most that many of them. A field
* wider than the destination truncates, which is the documented contract
* for reading a host string into a fixed BASIC one. libakstdlib #34.
*/
snprintf(text, sizeof(text), "%.*s", (int)field->hostwidth, at);
snprintf(dest->stringval, sizeof(dest->stringval), "%s", text);
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), text));
break;
default:
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE,
@@ -115,6 +121,7 @@ akerr_ErrorContext *akbasic_host_write_field(akbasic_Runtime *obj, akbasic_Struc
char *at = (char *)hostbase + field->hostoffset;
int64_t n = 0;
double d = 0.0;
size_t len = 0;
(void)obj;
FAIL_ZERO_RETURN(errctx, (hostbase != NULL && src != NULL), AKERR_NULLPOINTER,
@@ -166,12 +173,13 @@ akerr_ErrorContext *akbasic_host_write_field(akbasic_Runtime *obj, akbasic_Struc
* eight of them. BASIC strings have no width, so this is the one place
* the two models genuinely disagree and the disagreement is reported.
*/
FAIL_ZERO_RETURN(errctx, (strlen(src->stringval) < field->hostwidth),
PASS(errctx, aksl_strlen(src->stringval, &len));
FAIL_ZERO_RETURN(errctx, (len < field->hostwidth),
AKBASIC_ERR_VALUE,
"A string of %zu characters does not fit in %s, which holds %zu",
strlen(src->stringval), field->name, field->hostwidth - 1);
memset(at, 0, field->hostwidth);
memcpy(at, src->stringval, strlen(src->stringval));
len, field->name, field->hostwidth - 1);
PASS(errctx, aksl_memset(at, 0, field->hostwidth));
PASS(errctx, aksl_memcpy(at, src->stringval, len));
break;
default:
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE,
@@ -226,7 +234,16 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas
type->name, type->fieldcount, AKBASIC_MAX_STRUCT_FIELDS);
dest = &obj->structtypes.types[obj->structtypes.count];
memset(dest, 0, sizeof(*dest));
PASS(errctx, aksl_memset(dest, 0, sizeof(*dest)));
/*
* Raw snprintf, and a latent defect rather than a settled decision: a host
* type name over 31 characters truncates silently here, and two that share a
* 31-character prefix then collide in akbasic_structtype_find. scan_names()
* in structtype.c already refuses the same case for a script-declared type
* with an explicit limit message, so the two paths disagree. Converting this
* to aksl_strcpy is the fix and it is a behaviour change on a public
* registration call, so it wants its own issue rather than this port.
*/
snprintf(dest->name, sizeof(dest->name), "%s", type->name);
dest->used = true;
dest->ishost = true;
@@ -241,7 +258,7 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas
FAIL_ZERO_RETURN(errctx, (src->name != NULL), AKERR_NULLPOINTER,
"%s field %d has no name", type->name, i);
len = strlen(src->name);
PASS(errctx, aksl_strlen(src->name, &len));
/*
* The suffix has to agree with the C type. A host that writes "HP%" over
* an int32_t has said two different things about one field, and the
@@ -253,6 +270,7 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas
"%s.%s must end in '%c' for the C type it describes",
type->name, src->name, suffix_for(src->kind));
/* Same silent truncation as the type name above, and the same fix. */
snprintf(field->name, sizeof(field->name), "%s", src->name);
field->hostkind = src->kind;
field->hostoffset = src->offset;
@@ -296,7 +314,7 @@ akerr_ErrorContext *akbasic_host_bind(akbasic_Runtime *obj, const char *name,
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && typename_ != NULL && instance != NULL),
AKERR_NULLPOINTER, "NULL argument in host bind");
len = strlen(name);
PASS(errctx, aksl_strlen(name, &len));
FAIL_ZERO_RETURN(errctx, (len >= 2 && name[len - 1] == '@'), AKBASIC_ERR_VALUE,
"A structure variable's name ends in '@', so \"%s\" cannot be bound", name);
PASS(errctx, akbasic_structtype_find(&obj->structtypes, typename_, &typeindex));

View File

@@ -157,6 +157,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_akgl(FILE *program, const char *pa
const char *fontpath = getenv("AKBASIC_FONT");
FILE *input = program;
int mode = AKBASIC_MODE_RUNSTREAM;
int fd = 0;
if ( fontpath == NULL ) {
fontpath = AKBASIC_FONT_PATH;
@@ -169,7 +170,8 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_akgl(FILE *program, const char *pa
* in an AKGL build, and the golden corpus can be driven through this
* binary as well as through the stdio one.
*/
input = (isatty(fileno(stdin)) ? NULL : stdin);
PASS(errctx, aksl_fileno(stdin, &fd));
input = (isatty(fd) ? NULL : stdin);
mode = AKBASIC_MODE_REPL;
}

View File

@@ -10,9 +10,9 @@
*/
#include <inttypes.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/parser.h>
@@ -206,8 +206,7 @@ akerr_ErrorContext *akbasic_parser_command(akbasic_Parser *obj, akbasic_ASTLeaf
PASS(errctx, akbasic_parser_previous(obj, &operator_));
optype = operator_->tokentype;
strncpy(opname, operator_->lexeme, sizeof(opname) - 1);
opname[sizeof(opname) - 1] = '\0';
PASS(errctx, aksl_strcpy(opname, sizeof(opname), operator_->lexeme));
/* Does this verb need its own parse path? */
PASS(errctx, akbasic_verb_lookup(opname, &verb));
@@ -596,8 +595,7 @@ static akerr_ErrorContext *function_call(akbasic_Parser *obj, akbasic_ASTLeaf **
}
PASS(errctx, akbasic_parser_previous(obj, &operator_));
strncpy(fname, operator_->lexeme, sizeof(fname) - 1);
fname[sizeof(fname) - 1] = '\0';
PASS(errctx, aksl_strcpy(fname, sizeof(fname), operator_->lexeme));
PASS(errctx, akbasic_verb_lookup(fname, &verb));
if ( verb != NULL && verb->tokentype == AKBASIC_TOK_FUNCTION ) {

View File

@@ -10,10 +10,9 @@
*/
#include <inttypes.h>
#include <string.h>
#include <strings.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/parser.h>
@@ -78,10 +77,12 @@ akerr_ErrorContext *akbasic_parse_graphic(akbasic_Parser *parser, akbasic_ASTLea
akbasic_ASTLeaf *arglist = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *peeked = NULL;
int cmp = 0;
peeked = akbasic_parser_peek(parser);
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND &&
strcmp(peeked->lexeme, "CLR") == 0 ) {
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
PASS(errctx, aksl_strcmp(peeked->lexeme, "CLR", &cmp));
if ( cmp == 0 ) {
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
@@ -93,6 +94,7 @@ akerr_ErrorContext *akbasic_parse_graphic(akbasic_Parser *parser, akbasic_ASTLea
*dest = expr;
SUCCEED_RETURN(errctx);
}
}
PASS(errctx, akbasic_parse_arglist(parser, dest));
SUCCEED_RETURN(errctx);
}
@@ -105,6 +107,7 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
akbasic_ASTLeaf *tail = NULL;
akbasic_Token *peeked = NULL;
akbasic_Token *operator_ = NULL;
int cmp = 0;
/*
* DRAW source, x1,y1 TO x2,y2 TO x3,y3 -- a polyline, with TO between pairs
@@ -125,9 +128,11 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
for ( ;; ) {
peeked = akbasic_parser_peek(parser);
if ( peeked == NULL ||
peeked->tokentype != AKBASIC_TOK_COMMAND ||
strcmp(peeked->lexeme, "TO") != 0 ) {
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ) {
break;
}
PASS(errctx, aksl_strcmp(peeked->lexeme, "TO", &cmp));
if ( cmp != 0 ) {
break;
}
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
@@ -259,6 +264,7 @@ static akerr_ErrorContext *parse_loop_condition(akbasic_Parser *parser, akbasic_
{
PREPARE_ERROR(errctx);
akbasic_Token *peeked = NULL;
int cmp = 0;
*condition = NULL;
*kind = AKBASIC_LOOPCOND_NONE;
@@ -267,13 +273,16 @@ static akerr_ErrorContext *parse_loop_condition(akbasic_Parser *parser, akbasic_
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ) {
SUCCEED_RETURN(errctx);
}
if ( strcmp(peeked->lexeme, "WHILE") == 0 ) {
PASS(errctx, aksl_strcmp(peeked->lexeme, "WHILE", &cmp));
if ( cmp == 0 ) {
*kind = AKBASIC_LOOPCOND_WHILE;
} else if ( strcmp(peeked->lexeme, "UNTIL") == 0 ) {
*kind = AKBASIC_LOOPCOND_UNTIL;
} else {
PASS(errctx, aksl_strcmp(peeked->lexeme, "UNTIL", &cmp));
if ( cmp != 0 ) {
SUCCEED_RETURN(errctx);
}
*kind = AKBASIC_LOOPCOND_UNTIL;
}
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
/* A loop condition is a condition, so a lone `=` in it is an equality test. */
@@ -372,6 +381,7 @@ akerr_ErrorContext *akbasic_parse_on(akbasic_Parser *parser, akbasic_ASTLeaf **d
akbasic_ASTLeaf *expr = NULL;
akbasic_ASTLeaf *tail = NULL;
akbasic_Token *operator_ = NULL;
int cmp = 0;
bool gosub = false;
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
@@ -389,10 +399,12 @@ akerr_ErrorContext *akbasic_parse_on(akbasic_Parser *parser, akbasic_ASTLeaf **d
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Expected GOTO or GOSUB after ON (expression)");
PASS(errctx, akbasic_parser_previous(parser, &operator_));
if ( strcmp(operator_->lexeme, "GOSUB") == 0 ) {
PASS(errctx, aksl_strcmp(operator_->lexeme, "GOSUB", &cmp));
if ( cmp == 0 ) {
gosub = true;
} else {
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "GOTO"), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcmp(operator_->lexeme, "GOTO", &cmp));
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
"Expected GOTO or GOSUB after ON (expression)");
}
marker->literal_int = (gosub ? 1 : 0);
@@ -429,14 +441,20 @@ akerr_ErrorContext *akbasic_parse_resume(akbasic_Parser *parser, akbasic_ASTLeaf
akbasic_ASTLeaf *expr = NULL;
akbasic_ASTLeaf *target = NULL;
akbasic_Token *peeked = NULL;
int cmp = 0;
peeked = akbasic_parser_peek(parser);
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND &&
strcmp(peeked->lexeme, "NEXT") == 0 ) {
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
PASS(errctx, aksl_strcmp(peeked->lexeme, "NEXT", &cmp));
if ( cmp == 0 ) {
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
PASS(errctx, akbasic_parser_new_leaf(parser, &target));
PASS(errctx, akbasic_leaf_new_command(target, "NEXT", NULL));
} else if ( peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
}
}
/* The other two forms, taken only when the word was not NEXT -- which is
what a target already built says. */
if ( target == NULL && peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
PASS(errctx, akbasic_parser_expression(parser, &target));
}
@@ -464,6 +482,8 @@ akerr_ErrorContext *akbasic_parse_print(akbasic_Parser *parser, akbasic_ASTLeaf
akbasic_ASTLeaf *arglist = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_Token *peeked = NULL;
int cmp = 0;
bool matched = false;
peeked = akbasic_parser_peek(parser);
@@ -499,8 +519,11 @@ akerr_ErrorContext *akbasic_parse_print(akbasic_Parser *parser, akbasic_ASTLeaf
SUCCEED_RETURN(errctx);
}
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ||
strcmp(peeked->lexeme, "USING") != 0 ) {
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
PASS(errctx, aksl_strcmp(peeked->lexeme, "USING", &cmp));
matched = (cmp == 0);
}
if ( !matched ) {
/* The ordinary PRINT: one expression, or none at all. */
if ( peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
PASS(errctx, akbasic_parser_expression(parser, &right));
@@ -627,12 +650,14 @@ akerr_ErrorContext *akbasic_parse_point(akbasic_Parser *parser, akbasic_ASTLeaf
akbasic_ASTLeaf *arglist = NULL;
akbasic_ASTLeaf *command = NULL;
akbasic_Token *word = NULL;
int cmp = 0;
PASS(errctx, akbasic_parser_expression(parser, &pointer));
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected POINT <pointer> AT <structure>");
PASS(errctx, akbasic_parser_previous(parser, &word));
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AT") == 0), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcasecmp(word->lexeme, "AT", &cmp));
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
"Expected AT after POINT <pointer>, not %s", word->lexeme);
PASS(errctx, akbasic_parser_expression(parser, &target));
@@ -668,6 +693,7 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *command = NULL;
akbasic_Token *word = NULL;
int cmp = 0;
PASS(errctx, parse_verb_with_identifier(parser, "DIM", dest));
command = *dest;
@@ -676,13 +702,15 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_previous(parser, &word));
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AS") == 0), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcasecmp(word->lexeme, "AS", &cmp));
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
"Expected AS after DIM %s, not %s", command->right->identifier, word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
PASS(errctx, akbasic_parser_previous(parser, &word));
if ( strcasecmp(word->lexeme, "PTR") == 0 ) {
PASS(errctx, aksl_strcasecmp(word->lexeme, "PTR", &cmp));
if ( cmp == 0 ) {
command->literal_int = 1;
/*
* `TO` is already a verb -- FOR ... TO owns it -- so it arrives as a
@@ -694,13 +722,14 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
PASS(errctx, akbasic_parser_previous(parser, &word));
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "TO") == 0), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcasecmp(word->lexeme, "TO", &cmp));
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
"Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
PASS(errctx, akbasic_parser_previous(parser, &word));
}
snprintf(command->literal_string, sizeof(command->literal_string), "%s", word->lexeme);
PASS(errctx, aksl_strcpy(command->literal_string, sizeof(command->literal_string), word->lexeme));
SUCCEED_RETURN(errctx);
}
@@ -738,6 +767,7 @@ static akerr_ErrorContext *parse_def_parameters(akbasic_Parser *parser, akbasic_
akbasic_ASTLeaf *param = NULL;
akbasic_ASTLeaf *tail = NULL;
akbasic_Token *word = NULL;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_LEFT_PAREN),
AKBASIC_ERR_SYNTAX, "Expected an argument list after DEF <name>");
@@ -762,25 +792,28 @@ static akerr_ErrorContext *parse_def_parameters(akbasic_Parser *parser, akbasic_
"%s must name its type: %s AS TYPENAME, or %s AS PTR TO TYPENAME",
param->identifier, param->identifier, param->identifier);
PASS(errctx, akbasic_parser_previous(parser, &word));
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AS") == 0), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcasecmp(word->lexeme, "AS", &cmp));
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
"Expected AS after %s, not %s", param->identifier, word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
PASS(errctx, akbasic_parser_previous(parser, &word));
if ( strcasecmp(word->lexeme, "PTR") == 0 ) {
PASS(errctx, aksl_strcasecmp(word->lexeme, "PTR", &cmp));
if ( cmp == 0 ) {
param->literal_int = 1;
/* `TO` is already a verb -- FOR ... TO owns it -- so it arrives
as a command token rather than an identifier. */
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
PASS(errctx, akbasic_parser_previous(parser, &word));
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "TO") == 0), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcasecmp(word->lexeme, "TO", &cmp));
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
"Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
PASS(errctx, akbasic_parser_previous(parser, &word));
}
snprintf(param->literal_string, sizeof(param->literal_string), "%s", word->lexeme);
PASS(errctx, aksl_strcpy(param->literal_string, sizeof(param->literal_string), word->lexeme));
}
if ( tail == NULL ) {
@@ -806,6 +839,7 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
akbasic_ASTLeaf *expression = NULL;
akbasic_ASTLeaf *command = NULL;
akbasic_FunctionDef *fndef = NULL;
size_t namelen = 0;
size_t i = 0;
PASS(errctx, akbasic_parser_primary(parser, &identifier));
@@ -817,13 +851,14 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
PASS(errctx, akbasic_runtime_new_function(runtime, &fndef));
/* Uppercase the name: verbs and function names are case-insensitive. */
FAIL_ZERO_RETURN(errctx, (strlen(identifier->identifier) < sizeof(fndef->name)),
PASS(errctx, aksl_strlen(identifier->identifier, &namelen));
FAIL_ZERO_RETURN(errctx, (namelen < sizeof(fndef->name)),
AKBASIC_ERR_BOUNDS, "Function name '%s' is too long", identifier->identifier);
for ( i = 0; i < strlen(identifier->identifier); i++ ) {
for ( i = 0; i < namelen; i++ ) {
char c = identifier->identifier[i];
fndef->name[i] = (char)((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c);
}
fndef->name[strlen(identifier->identifier)] = '\0';
fndef->name[namelen] = '\0';
if ( akbasic_parser_match1(parser, AKBASIC_TOK_ASSIGNMENT) ) {
PASS(errctx, akbasic_parser_expression(parser, &expression));
@@ -866,13 +901,15 @@ akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **
akbasic_Environment *parent = runtime->environment;
akbasic_Environment *newenv = NULL;
int64_t firstline = 0;
int cmp = 0;
PASS(errctx, akbasic_parser_assignment(parser, &assignment));
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX,
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
PASS(errctx, akbasic_parser_previous(parser, &operator_));
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "TO"), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcmp(operator_->lexeme, "TO", &cmp));
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
FAIL_ZERO_RETURN(errctx,
(assignment != NULL && akbasic_leaf_is_identifier(assignment->left)),
@@ -894,7 +931,8 @@ akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
PASS(errctx, akbasic_parser_previous(parser, &operator_));
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "STEP"), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcmp(operator_->lexeme, "STEP", &cmp));
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
PASS(errctx, akbasic_parser_expression(parser, &newenv->forStepLeaf));
} else {
@@ -907,7 +945,8 @@ akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **
}
/* A NEXT already being awaited means this is an inner loop over the same variable. */
if ( strcmp(parent->waitingForCommand, "NEXT") == 0 ) {
PASS(errctx, aksl_strcmp(parent->waitingForCommand, "NEXT", &cmp));
if ( cmp == 0 ) {
newenv->forNextVariable = parent->forNextVariable;
}
newenv->loopFirstLine = firstline;
@@ -1038,6 +1077,7 @@ akerr_ErrorContext *akbasic_parse_if(akbasic_Parser *parser, akbasic_ASTLeaf **d
akbasic_ASTLeaf *else_command = NULL;
akbasic_ASTLeaf *branch = NULL;
akbasic_Token *operator_ = NULL;
int cmp = 0;
/*
* Everything from here to the THEN is a condition, so a lone `=` in it is an
@@ -1057,14 +1097,16 @@ akerr_ErrorContext *akbasic_parse_if(akbasic_Parser *parser, akbasic_ASTLeaf **d
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Incomplete IF statement");
PASS(errctx, akbasic_parser_previous(parser, &operator_));
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "THEN"), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcmp(operator_->lexeme, "THEN", &cmp));
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
"Expected IF ... THEN");
PASS(errctx, akbasic_parser_command(parser, &then_command));
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
PASS(errctx, akbasic_parser_previous(parser, &operator_));
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "ELSE"), AKBASIC_ERR_SYNTAX,
PASS(errctx, aksl_strcmp(operator_->lexeme, "ELSE", &cmp));
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
"Expected IF ... THEN ... ELSE ...");
PASS(errctx, akbasic_parser_command(parser, &else_command));
}

View File

@@ -15,9 +15,9 @@
*/
#include <ctype.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/audio.h>
#include <akbasic/error.h>
@@ -96,7 +96,7 @@ akerr_ErrorContext *akbasic_play_parse(akbasic_Runtime *obj, const char *notes)
FAIL_ZERO_RETURN(errctx, (obj != NULL && notes != NULL), AKERR_NULLPOINTER,
"NULL argument in play_parse");
audio = &obj->audio_state;
len = strlen(notes);
PASS(errctx, aksl_strlen(notes, &len));
/*
* A loop, so no CATCH and no _BREAK macros in here -- they expand to a C

View File

@@ -27,10 +27,9 @@
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/runtime.h>
@@ -103,7 +102,7 @@ static akerr_ErrorContext *rewrite_targets(akbasic_TargetWalk *walk, const char
for ( ;; ) {
char replacement[32];
int64_t line = 0;
int written = 0;
size_t written = 0;
while ( *p == ' ' || *p == '\t' ) {
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS,
@@ -126,9 +125,16 @@ static akerr_ErrorContext *rewrite_targets(akbasic_TargetWalk *walk, const char
p += 1;
}
PASS(errctx, walk->visit(walk, line, replacement, sizeof(replacement)));
written = snprintf(out, (size_t)(limit - out), "%s", replacement);
/*
* The fit is decided before the copy rather than read back out of a
* return value afterwards: aksl_strcpy refuses rather than truncates,
* so the bounds check has to come first to keep the diagnosis this
* function already gives.
*/
PASS(errctx, aksl_strlen(replacement, &written));
FAIL_ZERO_RETURN(errctx, (written > 0 && out + written < limit), AKBASIC_ERR_BOUNDS,
"RENUMBER: a rewritten line does not fit");
PASS(errctx, aksl_strcpy(out, (size_t)(limit - out), replacement));
out += written;
/*
@@ -176,6 +182,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
const char *limit = dest + len - 1;
bool statementstart = true;
bool instring = false;
int cmp = 0;
int i = 0;
while ( *p != '\0' ) {
@@ -227,12 +234,12 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
* name in the language to reach it.
*/
for ( i = 0; i < BRANCH_VERB_COUNT; i++ ) {
verblen = strlen(BRANCH_VERBS[i]);
PASS(errctx, aksl_strlen(BRANCH_VERBS[i], &verblen));
if ( p > code && isalnum((unsigned char)p[-1]) ) {
break;
}
if ( strncasecmp(p, BRANCH_VERBS[i], verblen) == 0 &&
!isalnum((unsigned char)p[verblen]) ) {
PASS(errctx, aksl_strncasecmp(p, BRANCH_VERBS[i], verblen, &cmp));
if ( cmp == 0 && !isalnum((unsigned char)p[verblen]) ) {
matched = true;
break;
}
@@ -247,7 +254,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
if ( matched ) {
FAIL_ZERO_RETURN(errctx, (out + verblen < (size_t)(limit - dest) + dest),
AKBASIC_ERR_BOUNDS, "RENUMBER: a rewritten line does not fit");
memcpy(out, p, verblen);
PASS(errctx, aksl_memcpy(out, p, verblen));
out += verblen;
p += verblen;
PASS(errctx, rewrite_targets(walk, &p, &out, limit));
@@ -260,9 +267,10 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
* cannot be pointed at it directly. Copy the type and the comma, then
* rewrite what follows.
*/
if ( strncasecmp(p, "COLLISION", 9) == 0 && !isalnum((unsigned char)p[9])
PASS(errctx, aksl_strncasecmp(p, "COLLISION", 9, &cmp));
if ( cmp == 0 && !isalnum((unsigned char)p[9])
&& !(p > code && isalnum((unsigned char)p[-1])) ) {
memcpy(out, p, 9);
PASS(errctx, aksl_memcpy(out, p, 9));
out += 9;
p += 9;
while ( *p != '\0' && *p != ',' && *p != ':' ) {
@@ -299,8 +307,9 @@ static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t targ
{
PREPARE_ERROR(errctx);
const int64_t *map = (const int64_t *)walk->self;
int written = 0;
snprintf(dest, len, "%" PRId64, mapped(map, target));
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, mapped(map, target)));
SUCCEED_RETURN(errctx);
}
@@ -347,7 +356,7 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
next += increment;
}
memset(rewritten, 0, sizeof(rewritten));
PASS(errctx, aksl_memset(rewritten, 0, sizeof(rewritten)));
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
int64_t target = 0;
@@ -371,7 +380,7 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
rewritten[target].numbered = true;
}
memcpy(obj->source, rewritten, sizeof(obj->source));
PASS(errctx, aksl_memcpy(obj->source, rewritten, sizeof(obj->source)));
SUCCEED_RETURN(errctx);
}
@@ -402,8 +411,9 @@ static akerr_ErrorContext *visit_check(akbasic_TargetWalk *walk, int64_t target,
{
PREPARE_ERROR(errctx);
CheckState *state = (CheckState *)walk->self;
int written = 0;
snprintf(dest, len, "%" PRId64, target);
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, target));
if ( target >= 0 && target < AKBASIC_MAX_SOURCE_LINES ) {
FAIL_NONZERO_RETURN(errctx,
(state->runtime->source[target].code[0] != '\0'

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,10 +749,12 @@ 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 ) {
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));
SUCCEED_RETURN(errctx);
@@ -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,12 +903,19 @@ 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) ) {
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,8 +1537,9 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
}
continue;
}
if ( statementstart && strncasecmp(cursor, "LABEL", 5) == 0
&& !isalnum((unsigned char)cursor[5]) ) {
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;
@@ -1505,6 +1564,7 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
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 ) {

View File

@@ -9,7 +9,6 @@
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
@@ -96,7 +95,7 @@ akerr_ErrorContext *akbasic_cmd_print(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
*/
if ( (*dest)->valuetype == AKBASIC_TYPE_STRUCT || (*dest)->valuetype == AKBASIC_TYPE_POINTER ) {
if ( (*dest)->structbase == NULL ) {
snprintf(rendered, sizeof(rendered), "NOTHING");
PASS(errctx, aksl_strcpy(rendered, sizeof(rendered), "NOTHING"));
} else {
PASS(errctx, akbasic_struct_to_string(obj, (*dest)->structtype, (*dest)->structbase,
0, rendered, sizeof(rendered)));
@@ -149,13 +148,15 @@ akerr_ErrorContext *akbasic_cmd_return(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
{
PREPARE_ERROR(errctx);
akbasic_Value *result = NULL;
bool waiting = false;
(void)lval; (void)rval;
/*
* A RETURN reached while skipping forward to one is the end of a DEF body,
* not a subroutine return. Stop waiting and carry on.
*/
if ( akbasic_environment_is_waiting_for(obj->environment, "RETURN") ) {
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "RETURN", &waiting));
if ( waiting ) {
PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "RETURN"));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
@@ -445,6 +446,7 @@ akerr_ErrorContext *akbasic_cmd_input(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
char rendered[AKBASIC_MAX_STRING_LENGTH];
char buffer[AKBASIC_MAX_LINE_LENGTH];
long long converted = 0;
size_t len = 0;
bool eof = false;
(void)lval; (void)rval;
@@ -479,10 +481,11 @@ akerr_ErrorContext *akbasic_cmd_input(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
break;
default:
entered->valuetype = AKBASIC_TYPE_STRING;
FAIL_ZERO_RETURN(errctx, (strlen(buffer) < AKBASIC_MAX_STRING_LENGTH), AKBASIC_ERR_VALUE,
PASS(errctx, aksl_strlen(buffer, &len));
FAIL_ZERO_RETURN(errctx, (len < AKBASIC_MAX_STRING_LENGTH), AKBASIC_ERR_VALUE,
"Input line exceeds the %d character limit", AKBASIC_MAX_STRING_LENGTH - 1);
strncpy(entered->stringval, buffer, AKBASIC_MAX_STRING_LENGTH - 1);
entered->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
/* aksl_strcpy always terminates, so the explicit terminator is gone. */
PASS(errctx, aksl_strcpy(entered->stringval, sizeof(entered->stringval), buffer));
break;
}
PASS(errctx, akbasic_environment_assign(obj->environment, identifier, entered, &unused));
@@ -539,6 +542,7 @@ akerr_ErrorContext *akbasic_cmd_list(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
int64_t startidx = 0;
int64_t endidx = 0;
int64_t i = 0;
int written = 0;
(void)lval; (void)rval;
PASS(errctx, parse_line_range(obj, expr, &startidx, &endidx));
@@ -552,7 +556,8 @@ akerr_ErrorContext *akbasic_cmd_list(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
if ( obj->source[i].code[0] == '\0' ) {
continue;
}
snprintf(line, sizeof(line), "%" PRId64 " %s", i, obj->source[i].code);
PASS(errctx, aksl_snprintf(&written, line, sizeof(line), "%" PRId64 " %s",
i, obj->source[i].code));
PASS(errctx, akbasic_runtime_println(obj, line));
}
SUCCEED_TRUE(obj, dest);
@@ -588,6 +593,7 @@ static akerr_ErrorContext *filename_argument(akbasic_Runtime *obj, akbasic_ASTLe
{
PREPARE_ERROR(errctx);
akbasic_Value *value = NULL;
size_t namelen = 0;
FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX,
"Expected a filename");
@@ -596,10 +602,107 @@ static akerr_ErrorContext *filename_argument(akbasic_Runtime *obj, akbasic_ASTLe
"Expected a filename string");
FAIL_ZERO_RETURN(errctx, (value->stringval[0] != '\0'), AKBASIC_ERR_VALUE,
"Filename must not be empty");
FAIL_ZERO_RETURN(errctx, (strlen(value->stringval) < len), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(value->stringval, &namelen));
FAIL_ZERO_RETURN(errctx, (namelen < len), AKBASIC_ERR_BOUNDS,
"Filename exceeds the %zu character limit", len - 1);
strncpy(dest, value->stringval, len - 1);
dest[len - 1] = '\0';
/* aksl_strcpy always terminates, so the explicit terminator is gone. */
PASS(errctx, aksl_strcpy(dest, len, value->stringval));
SUCCEED_RETURN(errctx);
}
/**
* @brief Read one line, reporting end of stream rather than raising it.
*
* aksl_fgets raises AKERR_EOF where fgets(3) returned NULL, so the end of an
* ordinary file arrives as an error and has to be handled. It is handled here,
* in a function whose ATTEMPT block contains no loop at all, because a CATCH
* inside a loop expands to a break that leaves the loop rather than the
* ATTEMPT. The caller reads @p eof instead.
*/
static akerr_ErrorContext *dload_read_line(FILE *fp, char *buffer, size_t len, bool *eof)
{
PREPARE_ERROR(errctx);
size_t used = 0;
*eof = false;
ATTEMPT {
CATCH(errctx, aksl_fgets(buffer, len, fp, &used));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_EOF) {
*eof = true;
used = 0;
} FINISH(errctx, true);
while ( used > 0 && (buffer[used - 1] == '\n' || buffer[used - 1] == '\r') ) {
buffer[used - 1] = '\0';
used -= 1;
}
SUCCEED_RETURN(errctx);
}
/**
* @brief Read a whole program in, one line at a time.
*
* Hoisted out of akbasic_cmd_dload()'s ATTEMPT block because a loop inside one
* can use neither CATCH -- which breaks the loop -- nor PASS, which returns
* past CLEANUP and leaves the file open. Out here PASS is correct, and the
* caller CATCHes this one call.
*/
static akerr_ErrorContext *dload_read_program(akbasic_Runtime *obj, FILE *fp,
char *buffer, size_t buflen,
char *scanned, size_t scanlen)
{
PREPARE_ERROR(errctx);
bool eof = false;
for ( ;; ) {
PASS(errctx, dload_read_line(fp, buffer, buflen, &eof));
if ( eof ) {
break;
}
if ( buffer[0] == '\0' ) {
continue;
}
PASS(errctx, akbasic_scanner_scan(obj, buffer, scanned, scanlen));
PASS(errctx, akbasic_runtime_file_line(obj, scanned));
}
SUCCEED_RETURN(errctx);
}
/**
* @brief Write every stored line out, numbered.
*
* Hoisted out of akbasic_cmd_dsave()'s ATTEMPT block for the same reason
* dload_read_program() is.
*/
static akerr_ErrorContext *dsave_write_program(akbasic_Runtime *obj, FILE *fp, char *line, size_t len)
{
PREPARE_ERROR(errctx);
size_t written = 0;
int64_t i = 0;
int count = 0;
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
if ( obj->source[i].code[0] == '\0' ) {
continue;
}
/*
* aksl_snprintf's count is the length it wrote, and it treats
* truncation as an error rather than reporting a length it did not
* write -- so it is what goes to fwrite, and the strlen that used to
* recompute it is gone.
*/
PASS(errctx, aksl_snprintf(&count, line, len, "%" PRId64 " %s\n", i, obj->source[i].code));
/*
* The written count is required by libakstdlib 0.2.0 and discarded
* here on purpose: a short write is no longer something a caller has
* to notice for itself, because that release made it an AKERR_IO
* rather than a silent success. Before it, a DSAVE onto a full disk
* reported nothing.
*/
PASS(errctx, aksl_fwrite(line, 1, (size_t)count, fp, &written));
}
SUCCEED_RETURN(errctx);
}
@@ -610,7 +713,6 @@ akerr_ErrorContext *akbasic_cmd_dload(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
char buffer[AKBASIC_MAX_LINE_LENGTH];
char scanned[AKBASIC_MAX_LINE_LENGTH];
FILE *fp = NULL;
size_t used = 0;
int64_t i = 0;
(void)lval; (void)rval;
@@ -632,23 +734,14 @@ akerr_ErrorContext *akbasic_cmd_dload(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
ATTEMPT {
CATCH(errctx, aksl_fopen(filename, "r", &fp));
while ( fgets(buffer, sizeof(buffer), fp) != NULL ) {
used = strlen(buffer);
while ( used > 0 && (buffer[used - 1] == '\n' || buffer[used - 1] == '\r') ) {
buffer[used - 1] = '\0';
used -= 1;
}
if ( buffer[0] == '\0' ) {
continue;
}
/*
* PASS inside the loop, never CATCH: CATCH expands to a break, which
* would leave this loop rather than the ATTEMPT and let the rest of
* the block run with an error pending.
* The read is one CATCH of one call, because the loop it used to be
* lives in dload_read_program() now: inside this block a loop can use
* neither CATCH nor PASS without either escaping the loop or returning
* past the fclose below.
*/
PASS(errctx, akbasic_scanner_scan(obj, buffer, scanned, sizeof(scanned)));
PASS(errctx, akbasic_runtime_file_line(obj, scanned));
}
CATCH(errctx, dload_read_program(obj, fp, buffer, sizeof(buffer),
scanned, sizeof(scanned)));
} CLEANUP {
if ( fp != NULL ) {
IGNORE(aksl_fclose(fp));
@@ -676,30 +769,13 @@ akerr_ErrorContext *akbasic_cmd_dsave(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
char filename[AKBASIC_MAX_STRING_LENGTH];
char line[AKBASIC_MAX_LINE_LENGTH * 2];
FILE *fp = NULL;
int64_t i = 0;
int count = 0;
size_t written = 0;
(void)lval; (void)rval;
PASS(errctx, filename_argument(obj, expr, filename, sizeof(filename)));
ATTEMPT {
CATCH(errctx, aksl_fopen(filename, "w", &fp));
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
if ( obj->source[i].code[0] == '\0' ) {
continue;
}
snprintf(line, sizeof(line), "%" PRId64 " %s\n", i, obj->source[i].code);
/*
* The written count is required by libakstdlib 0.2.0 and discarded
* here on purpose: a short write is no longer something a caller has
* to notice for itself, because that release made it an AKERR_IO
* rather than a silent success. Before it, a DSAVE onto a full disk
* reported nothing.
*/
PASS(errctx, aksl_fwrite(line, 1, strlen(line), fp, &written));
count += 1;
}
CATCH(errctx, dsave_write_program(obj, fp, line, sizeof(line)));
} CLEANUP {
if ( fp != NULL ) {
IGNORE(aksl_fclose(fp));
@@ -807,6 +883,7 @@ akerr_ErrorContext *akbasic_cmd_next(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
akbasic_Value *updated = NULL;
akbasic_Value scratch;
int64_t zerosubscript[1] = { 0 };
int cmp = 0;
bool met = false;
(void)lval; (void)rval;
@@ -842,7 +919,9 @@ akerr_ErrorContext *akbasic_cmd_next(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
* A NEXT for someone else's loop variable: this environment is done, hand
* the line back to the parent and pop. That is how nested loops unwind.
*/
if ( strcmp(expr->right->identifier, obj->environment->forNextVariable->name) != 0 ) {
PASS(errctx, aksl_strcmp(expr->right->identifier,
obj->environment->forNextVariable->name, &cmp));
if ( cmp != 0 ) {
FAIL_ZERO_RETURN(errctx, (obj->environment->parent != NULL), AKBASIC_ERR_ENVIRONMENT,
"NEXT in an orphaned environment");
obj->environment->parent->nextline = obj->environment->nextline;
@@ -951,7 +1030,8 @@ akerr_ErrorContext *akbasic_cmd_read(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
PASS(errctx, akbasic_leaf_init(&literal, AKBASIC_LEAF_LITERAL_INT));
if ( value.valuetype == AKBASIC_TYPE_STRING ) {
literal.leaftype = AKBASIC_LEAF_LITERAL_STRING;
snprintf(literal.literal_string, sizeof(literal.literal_string), "%s", value.stringval);
PASS(errctx, aksl_strcpy(literal.literal_string, sizeof(literal.literal_string),
value.stringval));
} else if ( value.valuetype == AKBASIC_TYPE_FLOAT ) {
literal.leaftype = AKBASIC_LEAF_LITERAL_FLOAT;
literal.literal_float = value.floatval;

View File

@@ -15,6 +15,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h>
#include <akbasic/error.h>
@@ -114,6 +115,7 @@ akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
/* Room for the longest macro plus `KEY n, ""` around it. */
char line[AKBASIC_MAX_STRING_LENGTH + 32];
int64_t number = 0;
int written = 0;
int i = 0;
(void)lval; (void)rval;
@@ -124,8 +126,8 @@ akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
if ( arg == NULL ) {
/* Bare KEY lists the definitions, which is what a C128 does. */
for ( i = 0; i < AKBASIC_MAX_FUNCTION_KEYS; i++ ) {
snprintf(line, sizeof(line), "KEY %d, \"%s\"",
i + 1, obj->console_state.keys[i]);
PASS(errctx, aksl_snprintf(&written, line, sizeof(line), "KEY %d, \"%s\"",
i + 1, obj->console_state.keys[i]));
PASS(errctx, akbasic_runtime_println(obj, line));
}
SUCCEED_TRUE(obj, dest);
@@ -146,8 +148,8 @@ akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
"KEY expected a string");
snprintf(obj->console_state.keys[number - 1],
sizeof(obj->console_state.keys[0]), "%s", value->stringval);
PASS(errctx, aksl_snprintf(&written, obj->console_state.keys[number - 1],
sizeof(obj->console_state.keys[0]), "%s", value->stringval));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
@@ -253,7 +255,7 @@ akerr_ErrorContext *akbasic_console_state_init(akbasic_ConsoleState *obj)
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL console state in init");
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
SUCCEED_RETURN(errctx);
}
@@ -302,6 +304,7 @@ akerr_ErrorContext *akbasic_console_update_clock(akbasic_Runtime *obj)
int64_t jiffies = 0;
int64_t seconds = 0;
char text[16];
int written = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in update_clock");
if ( obj->environment == NULL ) {
@@ -324,8 +327,9 @@ akerr_ErrorContext *akbasic_console_update_clock(akbasic_Runtime *obj)
PASS(errctx, akbasic_variable_set_integer(variable, jiffies, zerosubscript, 1));
seconds = obj->timems / 1000;
snprintf(text, sizeof(text), "%02" PRId64 "%02" PRId64 "%02" PRId64,
(seconds / 3600) % 24, (seconds / 60) % 60, seconds % 60);
PASS(errctx, aksl_snprintf(&written, text, sizeof(text),
"%02" PRId64 "%02" PRId64 "%02" PRId64,
(seconds / 3600) % 24, (seconds / 60) % 60, seconds % 60));
PASS(errctx, akbasic_runtime_global(obj, "TI$", &variable));
PASS(errctx, akbasic_variable_set_string(variable, text, zerosubscript, 1));
SUCCEED_RETURN(errctx);

View File

@@ -22,7 +22,6 @@
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
@@ -44,7 +43,7 @@ akerr_ErrorContext *akbasic_disk_state_init(akbasic_DiskState *obj)
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL disk state in init");
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
SUCCEED_RETURN(errctx);
}
@@ -103,7 +102,12 @@ static akerr_ErrorContext *string_arg(akbasic_Runtime *obj, akbasic_ASTLeaf *arg
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
"%s expected a file name", verb);
snprintf(dest, len, "%s", value->stringval);
/*
* A plain copy, so aksl_strcpy rather than aksl_snprintf: it refuses rather
* than truncates, and every caller here hands in a buffer the size of a
* string value, so a name that did not fit could only ever be a bug.
*/
PASS(errctx, aksl_strcpy(dest, len, value->stringval));
SUCCEED_RETURN(errctx);
}
@@ -157,7 +161,7 @@ static akerr_ErrorContext *open_file(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
}
PASS(errctx, aksl_fopen(name, mode, &channel->fp));
snprintf(channel->name, sizeof(channel->name), "%s", name);
PASS(errctx, aksl_strcpy(channel->name, sizeof(channel->name), name));
channel->writing = writing;
SUCCEED_RETURN(errctx);
}
@@ -265,13 +269,24 @@ akerr_ErrorContext *akbasic_cmd_record(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
akerr_ErrorContext *akbasic_cmd_scratch(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
akerr_ErrorContext *failure = NULL;
char name[AKBASIC_MAX_STRING_LENGTH];
(void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in SCRATCH");
PASS(errctx, string_arg(obj, akbasic_leaf_first_argument(expr), "SCRATCH", name, sizeof(name)));
FAIL_ZERO_RETURN(errctx, (remove(name) == 0), AKERR_IO,
"SCRATCH could not delete \"%s\"", name);
/*
* Caught and restated rather than passed. aksl_remove raises the bare errno,
* and "No such file or directory" on its own does not say which file or
* which verb wanted it -- so the wrapper's error is retired here and the
* message the program sees keeps naming both.
*/
failure = aksl_remove(name);
if ( failure != NULL ) {
failure->handled = true;
IGNORE(akerr_release_error(failure));
FAIL_RETURN(errctx, AKERR_IO, "SCRATCH could not delete \"%s\"", name);
}
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
@@ -279,6 +294,7 @@ akerr_ErrorContext *akbasic_cmd_scratch(akbasic_Runtime *obj, akbasic_ASTLeaf *e
akerr_ErrorContext *akbasic_cmd_rename(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
akerr_ErrorContext *failure = NULL;
akbasic_ASTLeaf *arg = NULL;
char from[AKBASIC_MAX_STRING_LENGTH];
char to[AKBASIC_MAX_STRING_LENGTH];
@@ -288,8 +304,13 @@ akerr_ErrorContext *akbasic_cmd_rename(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
arg = akbasic_leaf_first_argument(expr);
PASS(errctx, string_arg(obj, arg, "RENAME", from, sizeof(from)));
PASS(errctx, string_arg(obj, (arg != NULL ? arg->next : NULL), "RENAME", to, sizeof(to)));
FAIL_ZERO_RETURN(errctx, (rename(from, to) == 0), AKERR_IO,
"RENAME could not rename \"%s\" to \"%s\"", from, to);
/* Caught and restated, for the reason SCRATCH gives: the message names both files. */
failure = aksl_rename(from, to);
if ( failure != NULL ) {
failure->handled = true;
IGNORE(akerr_release_error(failure));
FAIL_RETURN(errctx, AKERR_IO, "RENAME could not rename \"%s\" to \"%s\"", from, to);
}
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
@@ -486,6 +507,7 @@ akerr_ErrorContext *akbasic_disk_write(akbasic_Runtime *obj, int64_t number, con
{
PREPARE_ERROR(errctx);
akbasic_Channel *channel = NULL;
size_t length = 0;
size_t put = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && text != NULL), AKERR_NULLPOINTER,
@@ -493,7 +515,8 @@ akerr_ErrorContext *akbasic_disk_write(akbasic_Runtime *obj, int64_t number, con
PASS(errctx, open_channel(obj, number, &channel));
FAIL_ZERO_RETURN(errctx, channel->writing, AKBASIC_ERR_STATE,
"Channel %" PRId64 " was opened for reading", number);
PASS(errctx, aksl_fwrite(text, 1, strlen(text), channel->fp, &put));
PASS(errctx, aksl_strlen(text, &length));
PASS(errctx, aksl_fwrite(text, 1, length, channel->fp, &put));
PASS(errctx, aksl_fwrite("\n", 1, 1, channel->fp, &put));
SUCCEED_RETURN(errctx);
}
@@ -525,7 +548,7 @@ akerr_ErrorContext *akbasic_disk_readline(akbasic_Runtime *obj, int64_t number,
*eof = true;
SUCCEED_RETURN(errctx);
}
newline = strchr(dest, '\n');
PASS(errctx, aksl_strchr(dest, '\n', &newline));
if ( newline != NULL ) {
*newline = '\0';
}
@@ -582,7 +605,7 @@ akerr_ErrorContext *akbasic_cmd_input_channel(akbasic_Runtime *obj, akbasic_ASTL
* variable reading an empty line gets zero, the same as a C128.
*/
PASS(errctx, akbasic_leaf_init(&literal, AKBASIC_LEAF_LITERAL_STRING));
snprintf(literal.literal_string, sizeof(literal.literal_string), "%s", text);
PASS(errctx, aksl_strcpy(literal.literal_string, sizeof(literal.literal_string), text));
if ( akbasic_leaf_identifier_type(identifier) == AKBASIC_TYPE_INTEGER ) {
long long converted = 0;
@@ -610,13 +633,69 @@ akerr_ErrorContext *akbasic_cmd_input_channel(akbasic_Runtime *obj, akbasic_ASTL
/* ---------------------------------------------------------------- VERIFY -- */
/**
* @brief Count the lines of @p fp that differ from the program in memory.
*
* Its own function so that the comparison can be reached with a single CATCH.
* Written inline it would be a loop inside VERIFY's ATTEMPT, where neither form
* is available: CATCH expands to a break that would escape only the loop, and
* PASS returns past the CLEANUP that closes the file. One call is neither, so
* the wrappers below can PASS in the ordinary way.
*/
static akerr_ErrorContext *verify_lines(akbasic_Runtime *obj, FILE *fp, int64_t *dest)
{
PREPARE_ERROR(errctx);
akerr_ErrorContext *got = NULL;
char line[AKBASIC_MAX_LINE_LENGTH];
const char *filetext = NULL;
char *newline = NULL;
int64_t lineno = 0;
size_t length = 0;
int cmp = 0;
*dest = 0;
for ( lineno = 0; lineno < AKBASIC_MAX_SOURCE_LINES; lineno++ ) {
if ( obj->source[lineno].code[0] == '\0' ) {
continue;
}
got = aksl_fgets(line, sizeof(line), fp, &length);
if ( got != NULL ) {
got->handled = true;
IGNORE(akerr_release_error(got));
*dest += 1;
break;
}
PASS(errctx, aksl_strchr(line, '\n', &newline));
if ( newline != NULL ) {
*newline = '\0';
}
/*
* The stored line has had its number stripped, so the file's copy is
* compared from past its own number.
*/
filetext = line;
while ( *filetext == ' ' ) {
filetext += 1;
}
while ( *filetext >= '0' && *filetext <= '9' ) {
filetext += 1;
}
while ( *filetext == ' ' ) {
filetext += 1;
}
PASS(errctx, aksl_strcmp(filetext, obj->source[lineno].code, &cmp));
if ( cmp != 0 ) {
*dest += 1;
}
}
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_cmd_dverify(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
FILE *fp = NULL;
char name[AKBASIC_MAX_STRING_LENGTH];
char line[AKBASIC_MAX_LINE_LENGTH];
int64_t lineno = 0;
int64_t mismatch = 0;
(void)lval; (void)rval;
@@ -632,47 +711,7 @@ akerr_ErrorContext *akbasic_cmd_dverify(akbasic_Runtime *obj, akbasic_ASTLeaf *e
*/
PASS(errctx, aksl_fopen(name, "r", &fp));
ATTEMPT {
for ( lineno = 0; lineno < AKBASIC_MAX_SOURCE_LINES; lineno++ ) {
size_t length = 0;
akerr_ErrorContext *got = NULL;
char *newline = NULL;
if ( obj->source[lineno].code[0] == '\0' ) {
continue;
}
got = aksl_fgets(line, sizeof(line), fp, &length);
if ( got != NULL ) {
got->handled = true;
IGNORE(akerr_release_error(got));
mismatch += 1;
break;
}
newline = strchr(line, '\n');
if ( newline != NULL ) {
*newline = '\0';
}
/*
* The stored line has had its number stripped, so the file's copy is
* compared from past its own number. PASS rather than CATCH: this is
* a loop.
*/
{
const char *filetext = line;
while ( *filetext == ' ' ) {
filetext += 1;
}
while ( *filetext >= '0' && *filetext <= '9' ) {
filetext += 1;
}
while ( *filetext == ' ' ) {
filetext += 1;
}
if ( strcmp(filetext, obj->source[lineno].code) != 0 ) {
mismatch += 1;
}
}
}
CATCH(errctx, verify_lines(obj, fp, &mismatch));
} CLEANUP {
IGNORE(aksl_fclose(fp));
} PROCESS(errctx) {

View File

@@ -14,7 +14,6 @@
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
@@ -189,13 +188,19 @@ akerr_ErrorContext *akbasic_fn_hex(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
PREPARE_ERROR(errctx);
akbasic_Value *arg = NULL;
akbasic_Value *out = NULL;
int written = 0;
(void)lval; (void)rval;
PASS(errctx, first_arg(obj, expr, "HEX", NULL, &arg, &out));
FAIL_NONZERO_RETURN(errctx, (arg->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
"HEX expected an integer");
out->valuetype = AKBASIC_TYPE_STRING;
snprintf(out->stringval, sizeof(out->stringval), "%" PRIx64, arg->intval);
/*
* aksl_snprintf refuses to truncate, which costs nothing here: sixteen hex
* digits is the longest an int64_t can be and a string value holds far more.
*/
PASS(errctx, aksl_snprintf(&written, out->stringval, sizeof(out->stringval),
"%" PRIx64, arg->intval));
*dest = out;
SUCCEED_RETURN(errctx);
}
@@ -270,6 +275,7 @@ akerr_ErrorContext *akbasic_fn_len(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
akbasic_Value *strval = NULL;
akbasic_Value *out = NULL;
akbasic_Variable *variable = NULL;
size_t length = 0;
(void)lval; (void)rval;
/*
@@ -291,7 +297,8 @@ akerr_ErrorContext *akbasic_fn_len(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
if ( argleaf->leaftype == AKBASIC_LEAF_LITERAL_STRING ||
argleaf->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ) {
PASS(errctx, akbasic_runtime_evaluate(obj, argleaf, &strval));
out->intval = (int64_t)strlen(strval->stringval);
PASS(errctx, aksl_strlen(strval->stringval, &length));
out->intval = (int64_t)length;
} else {
PASS(errctx, akbasic_environment_get(obj->environment, argleaf->identifier, &variable));
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED,
@@ -338,7 +345,8 @@ akerr_ErrorContext *akbasic_fn_instr(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_INTEGER;
hit = strstr(haystack->stringval, needle->stringval);
/* Not finding it is a successful NULL, which is exactly the -1 answer below. */
PASS(errctx, aksl_strstr(haystack->stringval, needle->stringval, &hit));
out->intval = (hit == NULL ? -1 : (int64_t)(hit - haystack->stringval));
*dest = out;
SUCCEED_RETURN(errctx);
@@ -360,7 +368,7 @@ akerr_ErrorContext *akbasic_fn_left(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
"LEFT expected a string");
FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
"LEFT expected an integer count");
sourcelen = strlen(source->stringval);
PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
take = count->intval;
if ( take < 0 ) {
take = 0;
@@ -371,7 +379,7 @@ akerr_ErrorContext *akbasic_fn_left(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_STRING;
memcpy(out->stringval, source->stringval, (size_t)take);
PASS(errctx, aksl_memcpy(out->stringval, source->stringval, (size_t)take));
out->stringval[take] = '\0';
*dest = out;
SUCCEED_RETURN(errctx);
@@ -393,7 +401,7 @@ akerr_ErrorContext *akbasic_fn_right(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
"RIGHT expected a string");
FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
"RIGHT expected an integer count");
sourcelen = strlen(source->stringval);
PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
take = count->intval;
if ( take < 0 ) {
take = 0;
@@ -404,7 +412,8 @@ akerr_ErrorContext *akbasic_fn_right(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_STRING;
memcpy(out->stringval, source->stringval + (sourcelen - (size_t)take), (size_t)take);
PASS(errctx, aksl_memcpy(out->stringval, source->stringval + (sourcelen - (size_t)take),
(size_t)take));
out->stringval[take] = '\0';
*dest = out;
SUCCEED_RETURN(errctx);
@@ -432,7 +441,7 @@ akerr_ErrorContext *akbasic_fn_mid(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
lengthval->valuetype == AKBASIC_TYPE_INTEGER),
AKBASIC_ERR_TYPE, "MID expected integer start and length");
sourcelen = strlen(source->stringval);
PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
start = startval->intval;
length = lengthval->intval;
FAIL_ZERO_RETURN(errctx, (start >= 0 && (size_t)start <= sourcelen), AKBASIC_ERR_BOUNDS,
@@ -446,7 +455,7 @@ akerr_ErrorContext *akbasic_fn_mid(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_STRING;
memcpy(out->stringval, source->stringval + start, (size_t)length);
PASS(errctx, aksl_memcpy(out->stringval, source->stringval + start, (size_t)length));
out->stringval[length] = '\0';
*dest = out;
SUCCEED_RETURN(errctx);

View File

@@ -709,6 +709,7 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
char encoded[AKBASIC_MAX_STRING_LENGTH];
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
int subscriptcount = 0;
int written = 0;
(void)lval; (void)rval;
PASS(errctx, require_graphics(obj, "SSHAPE"));
@@ -742,7 +743,7 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
* BASIC ever does with it; what it can no longer do is save it to disk or
* take its LEN and get a size. TODO.md section 5.
*/
snprintf(encoded, sizeof(encoded), "%s%d", SHAPE_PREFIX, handle);
PASS(errctx, aksl_snprintf(&written, encoded, sizeof(encoded), "%s%d", SHAPE_PREFIX, handle));
PASS(errctx, akbasic_variable_set_string(variable, encoded, subscripts, subscriptcount));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
@@ -762,7 +763,9 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
double x = 0.0;
double y = 0.0;
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
size_t prefixlen = 0;
int subscriptcount = 0;
int cmp = 0;
(void)lval; (void)rval;
PASS(errctx, require_graphics(obj, "GSHAPE"));
@@ -782,11 +785,11 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
y = (count >= 2) ? coords[1] : obj->gfx.y;
PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &stored));
FAIL_ZERO_RETURN(errctx,
(strncmp(stored->stringval, SHAPE_PREFIX, strlen(SHAPE_PREFIX)) == 0),
AKBASIC_ERR_VALUE,
PASS(errctx, aksl_strlen(SHAPE_PREFIX, &prefixlen));
PASS(errctx, aksl_strncmp(stored->stringval, SHAPE_PREFIX, prefixlen, &cmp));
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_VALUE,
"GSHAPE was given a string that did not come from SSHAPE");
PASS(errctx, aksl_atoll(stored->stringval + strlen(SHAPE_PREFIX), &converted));
PASS(errctx, aksl_atoll(stored->stringval + prefixlen, &converted));
handle = (int64_t)converted;
scale_point(&obj->gfx, &x, &y);

View File

@@ -13,10 +13,9 @@
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h>
#include <akbasic/error.h>
@@ -322,13 +321,13 @@ akerr_ErrorContext *akbasic_cmd_swap(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
* exactly this reason. The names stay put: they are what the symbol table
* points at.
*/
memcpy(namea, a->name, sizeof(namea));
memcpy(nameb, b->name, sizeof(nameb));
memcpy(&swap, a, sizeof(swap));
memcpy(a, b, sizeof(*a));
memcpy(b, &swap, sizeof(*b));
memcpy(a->name, namea, sizeof(a->name));
memcpy(b->name, nameb, sizeof(b->name));
PASS(errctx, aksl_memcpy(namea, a->name, sizeof(namea)));
PASS(errctx, aksl_memcpy(nameb, b->name, sizeof(nameb)));
PASS(errctx, aksl_memcpy(&swap, a, sizeof(swap)));
PASS(errctx, aksl_memcpy(a, b, sizeof(*a)));
PASS(errctx, aksl_memcpy(b, &swap, sizeof(*b)));
PASS(errctx, aksl_memcpy(a->name, namea, sizeof(a->name)));
PASS(errctx, aksl_memcpy(b->name, nameb, sizeof(b->name)));
/*
* A scalar's storage is *inside* the record, so the pointer that came over
* with it names the other variable's inline slot -- which now holds this
@@ -352,6 +351,7 @@ akerr_ErrorContext *akbasic_cmd_help(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
{
PREPARE_ERROR(errctx);
char line[AKBASIC_MAX_LINE_LENGTH * 2];
int written = 0;
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in HELP");
@@ -369,8 +369,14 @@ akerr_ErrorContext *akbasic_cmd_help(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
snprintf(line, sizeof(line), "%" PRId64 " %s",
obj->errorline, obj->source[obj->errorline].code);
/*
* aksl_snprintf treats truncation as an error, and this destination is twice
* AKBASIC_MAX_LINE_LENGTH against a stored line that is at most one of them
* plus a line number, so the fit is a property of the buffer rather than
* something to check for.
*/
PASS(errctx, aksl_snprintf(&written, line, sizeof(line), "%" PRId64 " %s",
obj->errorline, obj->source[obj->errorline].code));
PASS(errctx, akbasic_runtime_println(obj, line));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);

View File

@@ -14,9 +14,8 @@
* as sitting on the keyboard. See TODO.md section 5.
*/
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/input.h>
@@ -152,9 +151,8 @@ akerr_ErrorContext *akbasic_cmd_getkey(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
}
obj->input_state.waiting = true;
obj->input_state.numeric = numeric;
strncpy(obj->input_state.variable, arg->identifier,
sizeof(obj->input_state.variable) - 1);
obj->input_state.variable[sizeof(obj->input_state.variable) - 1] = '\0';
PASS(errctx, aksl_strcpy(obj->input_state.variable,
sizeof(obj->input_state.variable), arg->identifier));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}

View File

@@ -14,9 +14,9 @@
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h>
#include <akbasic/error.h>
@@ -69,7 +69,8 @@ static akerr_ErrorContext *copy_bytes(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
* POKE and PEEK. A BASIC integer here is a real address, so a wrong one is a
* segmentation fault rather than an error message. See TODO.md section 5.
*/
memmove((void *)(uintptr_t)args[2], (const void *)(uintptr_t)args[1], (size_t)length);
PASS(errctx, aksl_memmove((void *)(uintptr_t)args[2], (const void *)(uintptr_t)args[1],
(size_t)length));
SUCCEED_RETURN(errctx);
}

View File

@@ -31,6 +31,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h>
#include <akbasic/error.h>
@@ -403,6 +404,9 @@ static akerr_ErrorContext *define_from_leaf(akbasic_Runtime *obj, akbasic_ASTLea
akbasic_Color fg;
akbasic_Color bg;
uint8_t pattern[AKBASIC_SPRITE_PATTERN_BYTES];
size_t prefixlen = 0;
int handle = 0;
int cmp = 0;
if ( arg->leaftype == AKBASIC_LEAF_IDENTIFIER_INT &&
akbasic_leaf_first_subscript(arg) == NULL ) {
@@ -428,11 +432,17 @@ static akerr_ErrorContext *define_from_leaf(akbasic_Runtime *obj, akbasic_ASTLea
FAIL_ZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
"SPRSAV expected a sprite number, an image path, a saved shape or an integer array");
if ( strncmp(value->stringval, SHAPE_PREFIX, strlen(SHAPE_PREFIX)) == 0 ) {
int handle = atoi(value->stringval + strlen(SHAPE_PREFIX));
PASS(errctx, aksl_strlen(SHAPE_PREFIX, &prefixlen));
PASS(errctx, aksl_strncmp(value->stringval, SHAPE_PREFIX, prefixlen, &cmp));
if ( cmp == 0 ) {
FAIL_ZERO_RETURN(errctx, (obj->sprites->define_shape != NULL), AKBASIC_ERR_DEVICE,
"This sprite device cannot take a saved shape");
/*
* aksl_atoi refuses a slot number that is not one, where atoi(3) answered
* a silent 0 -- so `SPRSAV "SHAPE:", 1` is now told what is wrong with it
* rather than being handed shape 0.
*/
PASS(errctx, aksl_atoi(value->stringval + prefixlen, &handle));
PASS(errctx, obj->sprites->define_shape(obj->sprites, index + 1, handle));
SUCCEED_RETURN(errctx);
}
@@ -779,8 +789,9 @@ akerr_ErrorContext *akbasic_cmd_solid(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
if ( !obj->sprite_state.solids[i].active ) {
continue;
}
memset(&obj->sprite_state.solids[i], 0, sizeof(obj->sprite_state.solids[i]));
/* PASS rather than CATCH: this is a loop. */
PASS(errctx, aksl_memset(&obj->sprite_state.solids[i], 0,
sizeof(obj->sprite_state.solids[i])));
PASS(errctx, obj->sprites->solid(obj->sprites, i + 1, false, 0.0, 0.0, 0.0, 0.0));
}
SUCCEED_RETURN(errctx);
@@ -792,7 +803,7 @@ akerr_ErrorContext *akbasic_cmd_solid(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
solid = &obj->sprite_state.solids[id - 1];
if ( count == 1 ) {
memset(solid, 0, sizeof(*solid));
PASS(errctx, aksl_memset(solid, 0, sizeof(*solid)));
PASS(errctx, obj->sprites->solid(obj->sprites, id, false, 0.0, 0.0, 0.0, 0.0));
SUCCEED_RETURN(errctx);
}

View File

@@ -28,6 +28,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/runtime.h>
@@ -194,7 +195,11 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
PREPARE_ERROR(errctx);
char rendered[AKBASIC_MAX_STRING_LENGTH];
akbasic_StructType *type = NULL;
const char *separator = NULL;
size_t used = 0;
size_t seplen = 0;
size_t namelen = 0;
size_t renderedlen = 0;
int written = 0;
int i = 0;
@@ -204,6 +209,20 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
AKBASIC_ERR_BOUNDS, "Structure type index %d is out of range", typeindex);
type = &obj->structtypes.types[typeindex];
/*
* These two stay on raw snprintf on purpose, and are two of the six such
* sites left in src/. Both want truncation as an *answer*: `dest` and `len`
* come from the caller, this renders a value for display, and a name too long
* for the buffer should print short rather than fail the whole PRINT. The
* second one reads the return value to detect it -- on overflow `used` lands
* past `len`, which is exactly what makes the field loop below and the
* closing ")" both skip.
*
* aksl_snprintf treats truncation as AKERR_OUTOFBOUNDS and its `count` is 0
* on that path, so it can express neither the tolerance nor the detection.
* libakstdlib #34 is the issue that would give `count` the required length
* back; until it lands there is nothing to convert these to.
*/
if ( depth >= AKBASIC_MAX_STRUCT_DEPTH ) {
snprintf(dest, len, "%s(...)", type->name);
SUCCEED_RETURN(errctx);
@@ -222,7 +241,7 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
break;
case AKBASIC_FIELD_POINTER:
if ( slot->structbase == NULL ) {
snprintf(rendered, sizeof(rendered), "NOTHING");
PASS(errctx, aksl_snprintf(&written, rendered, sizeof(rendered), "NOTHING"));
} else {
PASS(errctx, akbasic_struct_to_string(obj, slot->structtype, slot->structbase,
depth + 1, rendered, sizeof(rendered)));
@@ -232,15 +251,24 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
PASS(errctx, akbasic_value_to_string(slot, rendered, sizeof(rendered)));
break;
}
written = snprintf(dest + used, len - used, "%s%s=%s",
(i == 0 ? "" : ", "), field->name, rendered);
if ( written < 0 || (size_t)written >= len - used ) {
separator = (i == 0 ? "" : ", ");
PASS(errctx, aksl_strlen(separator, &seplen));
PASS(errctx, aksl_strlen(field->name, &namelen));
PASS(errctx, aksl_strlen(rendered, &renderedlen));
/*
* A render that does not fit ends the list rather than failing it, so the
* fit is decided here -- aksl_snprintf treats truncation as an error, and
* the one byte counted beyond the three lengths is the `=`.
*/
if ( used + seplen + namelen + 1 + renderedlen >= len ) {
break;
}
PASS(errctx, aksl_snprintf(&written, dest + used, len - used, "%s%s=%s",
separator, field->name, rendered));
used += (size_t)written;
}
if ( used + 1 < len ) {
snprintf(dest + used, len - used, ")");
PASS(errctx, aksl_snprintf(&written, dest + used, len - used, ")"));
}
SUCCEED_RETURN(errctx);
}

View File

@@ -173,6 +173,7 @@ akerr_ErrorContext *akbasic_cmd_begin(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
akerr_ErrorContext *akbasic_cmd_bend(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
bool waiting = false;
(void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
@@ -182,7 +183,8 @@ akerr_ErrorContext *akbasic_cmd_bend(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
* to do, or it is the BEND a skipped block was skipping to, in which case
* stopping the skip is the whole job.
*/
if ( akbasic_environment_is_waiting_for(obj->environment, "BEND") ) {
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "BEND", &waiting));
if ( waiting ) {
PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "BEND"));
}
SUCCEED_TRUE(obj, dest);

View File

@@ -16,10 +16,9 @@
*/
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h>
#include <akbasic/error.h>
@@ -107,6 +106,8 @@ akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
akbasic_Variable *variable = NULL;
int64_t zerosubscript[1] = { 0 };
int64_t resumeline = 0;
int cmp = 0;
bool isnext = false;
(void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
@@ -124,6 +125,10 @@ akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
resumeline = value->intval;
target = (expr != NULL ? expr->right : NULL);
if ( target != NULL && target->leaftype == AKBASIC_LEAF_COMMAND ) {
PASS(errctx, aksl_strcmp(target->identifier, "NEXT", &cmp));
isnext = (cmp == 0);
}
if ( target == NULL ) {
/*
* Bare RESUME retries the line that failed. That is a loop unless the
@@ -131,8 +136,7 @@ akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
* is exactly what the verb is for.
*/
obj->environment->parent->nextline = resumeline;
} else if ( target->leaftype == AKBASIC_LEAF_COMMAND &&
strcmp(target->identifier, "NEXT") == 0 ) {
} else if ( isnext ) {
/* RESUME NEXT carries on at the line after the one that failed. */
obj->environment->parent->nextline = resumeline + 1;
} else {
@@ -182,8 +186,8 @@ akerr_ErrorContext *akbasic_fn_err(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
* that ever decides otherwise.
*/
name = akerr_name_for_status((int)status, NULL);
snprintf(out->stringval, sizeof(out->stringval), "%s",
(name != NULL ? name : "Unknown Error"));
PASS(errctx, aksl_strcpy(out->stringval, sizeof(out->stringval),
(name != NULL ? name : "Unknown Error")));
*dest = out;
SUCCEED_RETURN(errctx);
}

View File

@@ -19,9 +19,9 @@
*/
#include <inttypes.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h>
#include <akbasic/error.h>
@@ -153,8 +153,7 @@ static akerr_ErrorContext *nth_string(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
FAIL_ZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
"%s expected a string in argument %d", verb, n + 1);
strncpy(dest, value->stringval, len - 1);
dest[len - 1] = '\0';
PASS(errctx, aksl_strcpy(dest, len, value->stringval));
SUCCEED_RETURN(errctx);
}
@@ -259,8 +258,7 @@ static akerr_ErrorContext *target_variable(akbasic_Runtime *obj, akbasic_ASTLeaf
PASS(errctx, akbasic_environment_get(obj->environment, arg->identifier, dest));
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKBASIC_ERR_UNDEFINED,
"GETMENU could not reach the variable %s", arg->identifier);
strncpy(name, arg->identifier, len - 1);
name[len - 1] = '\0';
PASS(errctx, aksl_strcpy(name, len, arg->identifier));
SUCCEED_RETURN(errctx);
}
@@ -309,8 +307,7 @@ akerr_ErrorContext *akbasic_cmd_getmenu(akbasic_Runtime *obj, akbasic_ASTLeaf *e
*/
obj->ui_state.waiting = true;
obj->ui_state.waitmenu = slot;
strncpy(obj->ui_state.variable, name, sizeof(obj->ui_state.variable) - 1);
obj->ui_state.variable[sizeof(obj->ui_state.variable) - 1] = '\0';
PASS(errctx, aksl_strcpy(obj->ui_state.variable, sizeof(obj->ui_state.variable), name));
SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx);
}
@@ -487,7 +484,7 @@ akerr_ErrorContext *akbasic_ui_state_init(akbasic_UiState *obj)
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
"NULL argument in ui_state_init");
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
SUCCEED_RETURN(errctx);
}

View File

@@ -26,27 +26,67 @@ akerr_ErrorContext *akbasic_scanner_zero(akbasic_Runtime *obj)
SUCCEED_RETURN(errctx);
}
static bool is_at_end(akbasic_Runtime *obj)
/**
* @brief Is the cursor past the end of the line?
*
* The answer leaves through @p dest rather than the return value because
* measuring the line can fail, and a `bool` has nowhere to put that. Same shape
* as symtab.c's `probe`, and for the same reason. See libakstdlib #38.
*/
static akerr_ErrorContext *is_at_end(akbasic_Runtime *obj, bool *dest)
{
return (obj->current >= (int)strlen(obj->line));
PREPARE_ERROR(errctx);
size_t linelen = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in is_at_end");
PASS(errctx, aksl_strlen(obj->line, &linelen));
*dest = (obj->current >= (int)linelen);
SUCCEED_RETURN(errctx);
}
static bool peek(akbasic_Runtime *obj, char *dest)
/**
* @brief The character under the cursor.
* @param[out] dest The character. Untouched when there is none.
* @param[out] got Whether there was one. The old `bool` return.
*/
static akerr_ErrorContext *peek(akbasic_Runtime *obj, char *dest, bool *got)
{
if ( is_at_end(obj) ) {
return false;
PREPARE_ERROR(errctx);
bool atend = false;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL && got != NULL), AKERR_NULLPOINTER,
"NULL argument in peek");
PASS(errctx, is_at_end(obj, &atend));
if ( atend ) {
*got = false;
SUCCEED_RETURN(errctx);
}
*dest = obj->line[obj->current];
return true;
*got = true;
SUCCEED_RETURN(errctx);
}
static bool peek_next(akbasic_Runtime *obj, char *dest)
/**
* @brief The character one past the cursor.
* @param[out] dest The character. Untouched when there is none.
* @param[out] got Whether there was one. The old `bool` return.
*/
static akerr_ErrorContext *peek_next(akbasic_Runtime *obj, char *dest, bool *got)
{
if ( (obj->current + 1) >= (int)strlen(obj->line) ) {
return false;
PREPARE_ERROR(errctx);
size_t linelen = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL && got != NULL), AKERR_NULLPOINTER,
"NULL argument in peek_next");
PASS(errctx, aksl_strlen(obj->line, &linelen));
if ( (obj->current + 1) >= (int)linelen ) {
*got = false;
SUCCEED_RETURN(errctx);
}
*dest = obj->line[obj->current + 1];
return true;
*got = true;
SUCCEED_RETURN(errctx);
}
/*
@@ -58,9 +98,12 @@ static bool peek_next(akbasic_Runtime *obj, char *dest)
static akerr_ErrorContext *get_lexeme(akbasic_Runtime *obj, char *dest, size_t len)
{
PREPARE_ERROR(errctx);
int linelen = (int)strlen(obj->line);
size_t measured = 0;
int linelen = 0;
int span = 0;
PASS(errctx, aksl_strlen(obj->line, &measured));
linelen = (int)measured;
if ( obj->current == linelen ) {
span = linelen - obj->start;
} else if ( obj->start == obj->current ) {
@@ -74,7 +117,7 @@ static akerr_ErrorContext *get_lexeme(akbasic_Runtime *obj, char *dest, size_t l
}
FAIL_ZERO_RETURN(errctx, (span >= 0 && (size_t)span < len), AKBASIC_ERR_BOUNDS,
"Lexeme of %d characters exceeds the %zu character limit", span, len - 1);
memcpy(dest, obj->line + obj->start, (size_t)span);
PASS(errctx, aksl_memcpy(dest, obj->line + obj->start, (size_t)span));
dest[span] = '\0';
SUCCEED_RETURN(errctx);
}
@@ -83,26 +126,39 @@ static akerr_ErrorContext *add_token(akbasic_Runtime *obj, akbasic_TokenType tok
{
PREPARE_ERROR(errctx);
akbasic_Environment *env = obj->environment;
size_t lexemelen = 0;
FAIL_ZERO_RETURN(errctx, (env->nexttoken < AKBASIC_MAX_TOKENS), AKBASIC_ERR_BOUNDS,
"Line %" PRId64 " has more than %d tokens",
env->lineno, AKBASIC_MAX_TOKENS);
FAIL_ZERO_RETURN(errctx, (strlen(lexeme) < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(lexeme, &lexemelen));
FAIL_ZERO_RETURN(errctx, (lexemelen < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
"Token lexeme exceeds the %d character limit", AKBASIC_MAX_LINE_LENGTH - 1);
env->tokens[env->nexttoken].tokentype = token;
env->tokens[env->nexttoken].lineno = env->lineno;
strncpy(env->tokens[env->nexttoken].lexeme, lexeme, AKBASIC_MAX_LINE_LENGTH - 1);
env->tokens[env->nexttoken].lexeme[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
PASS(errctx, aksl_strcpy(env->tokens[env->nexttoken].lexeme,
sizeof(env->tokens[env->nexttoken].lexeme), lexeme));
env->nexttoken += 1;
SUCCEED_RETURN(errctx);
}
/* Consume one more character when it matches, choosing between two token types. */
static bool match_next_char(akbasic_Runtime *obj, char cm, akbasic_TokenType truetype, akbasic_TokenType falsetype)
/**
* @brief Consume one more character when it matches, choosing between two token types.
* @param[out] matched Whether the character was consumed. The old `bool` return.
*
* On the chain below `peek`, so it reports the same way. See libakstdlib #38.
*/
static akerr_ErrorContext *match_next_char(akbasic_Runtime *obj, char cm, akbasic_TokenType truetype,
akbasic_TokenType falsetype, bool *matched)
{
PREPARE_ERROR(errctx);
char nc = '\0';
bool got = false;
if ( !peek(obj, &nc) ) {
FAIL_ZERO_RETURN(errctx, (obj != NULL && matched != NULL), AKERR_NULLPOINTER,
"NULL argument in match_next_char");
PASS(errctx, peek(obj, &nc, &got));
if ( !got ) {
/*
* Nothing left to peek at, so the operator is whatever it is on its
* own. The reference returns here *without* setting a type
@@ -112,24 +168,34 @@ static bool match_next_char(akbasic_Runtime *obj, char cm, akbasic_TokenType tru
* TODO.md section 6 item 14.
*/
obj->tokentype = falsetype;
return false;
*matched = false;
SUCCEED_RETURN(errctx);
}
if ( nc == cm ) {
obj->current += 1;
obj->tokentype = truetype;
return true;
*matched = true;
SUCCEED_RETURN(errctx);
}
obj->tokentype = falsetype;
return false;
*matched = false;
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *match_string(akbasic_Runtime *obj)
{
PREPARE_ERROR(errctx);
char c = '\0';
bool atend = false;
bool got = false;
while ( !is_at_end(obj) ) {
if ( !peek(obj, &c) ) {
for ( ;; ) {
PASS(errctx, is_at_end(obj, &atend));
if ( atend ) {
break;
}
PASS(errctx, peek(obj, &c, &got));
if ( !got ) {
PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE,
"UNTERMINATED STRING LITERAL\n"));
obj->hasError = true;
@@ -154,12 +220,19 @@ static akerr_ErrorContext *match_number(akbasic_Runtime *obj)
int64_t lineno = 0;
long long converted = 0;
bool hex = false;
bool atend = false;
bool got = false;
obj->tokentype = AKBASIC_TOK_LITERAL_INT;
while ( !is_at_end(obj) ) {
(void)peek(obj, &c);
for ( ;; ) {
PASS(errctx, is_at_end(obj, &atend));
if ( atend ) {
break;
}
PASS(errctx, peek(obj, &c, &got));
if ( c == '.' ) {
if ( !peek_next(obj, &nc) || !isdigit((unsigned char)nc) ) {
PASS(errctx, peek_next(obj, &nc, &got));
if ( !got || !isdigit((unsigned char)nc) ) {
PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE,
"INVALID FLOATING POINT LITERAL\n"));
obj->hasError = true;
@@ -200,13 +273,17 @@ static akerr_ErrorContext *match_number(akbasic_Runtime *obj)
} CLEANUP {
} PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) {
char message[AKBASIC_MAX_LINE_LENGTH + 32];
snprintf(message, sizeof(message), "INTEGER CONVERSION ON '%s'", lexeme);
char message[AKBASIC_MAX_LINE_LENGTH + 32] = "INTEGER CONVERSION";
int written = 0;
/*
* Reporting can itself fail if the sink is broken. Nothing useful
* remains to be done about that here, so record the flag and let the
* next operation surface it.
* Reporting can itself fail if the sink is broken, and so can
* formatting the message. Neither leaves anything useful to do here --
* this block is already handling an error -- so both are ignored, the
* message keeps the initialiser above if the format fails, and the flag
* lets the next operation surface it.
*/
IGNORE(aksl_snprintf(&written, message, sizeof(message),
"INTEGER CONVERSION ON '%s'", lexeme));
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
obj->hasError = true;
} FINISH(errctx, false);
@@ -230,10 +307,16 @@ static akerr_ErrorContext *match_identifier(akbasic_Runtime *obj)
bool userfunction = false;
char c = '\0';
size_t used = 0;
bool atend = false;
bool got = false;
obj->tokentype = AKBASIC_TOK_IDENTIFIER;
while ( !is_at_end(obj) ) {
(void)peek(obj, &c);
for ( ;; ) {
PASS(errctx, is_at_end(obj, &atend));
if ( atend ) {
break;
}
PASS(errctx, peek(obj, &c, &got));
if ( isdigit((unsigned char)c) || isalpha((unsigned char)c) ) {
obj->current += 1;
continue;
@@ -270,9 +353,8 @@ static akerr_ErrorContext *match_identifier(akbasic_Runtime *obj)
* in variable name" branch below is dead code, and `PRINT$ = 1` is quietly
* accepted as an ordinary string variable. TODO.md section 6 item 16.
*/
strncpy(basename, lexeme, sizeof(basename) - 1);
basename[sizeof(basename) - 1] = '\0';
used = strlen(basename);
PASS(errctx, aksl_strcpy(basename, sizeof(basename), lexeme));
PASS(errctx, aksl_strlen(basename, &used));
if ( obj->tokentype != AKBASIC_TOK_IDENTIFIER && used > 0 ) {
basename[used - 1] = '\0';
}
@@ -311,15 +393,18 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
char lexeme[AKBASIC_MAX_LINE_LENGTH];
char c = '\0';
bool done = false;
bool atend = false;
bool matched = false;
size_t linelen = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in scan");
FAIL_ZERO_RETURN(errctx, (line != NULL), AKERR_NULLPOINTER, "NULL line in scan");
FAIL_ZERO_RETURN(errctx, (strlen(line) < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(line, &linelen));
FAIL_ZERO_RETURN(errctx, (linelen < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
"Source line of %zu characters exceeds the %d character limit",
strlen(line), AKBASIC_MAX_LINE_LENGTH - 1);
linelen, AKBASIC_MAX_LINE_LENGTH - 1);
strncpy(obj->line, line, AKBASIC_MAX_LINE_LENGTH - 1);
obj->line[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
PASS(errctx, aksl_strcpy(obj->line, sizeof(obj->line), line));
PASS(errctx, akbasic_environment_zero_parser(obj->environment));
obj->current = 0;
obj->start = 0;
@@ -333,7 +418,11 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
*/
obj->hadlinenumber = false;
while ( !is_at_end(obj) && !done ) {
for ( ;; ) {
PASS(errctx, is_at_end(obj, &atend));
if ( atend || done ) {
break;
}
c = obj->line[obj->current];
obj->current += 1;
@@ -350,7 +439,7 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
* no spelling this makes ambiguous.
*/
case '-':
(void)match_next_char(obj, '>', AKBASIC_TOK_ARROW, AKBASIC_TOK_MINUS);
PASS(errctx, match_next_char(obj, '>', AKBASIC_TOK_ARROW, AKBASIC_TOK_MINUS, &matched));
break;
case '/': obj->tokentype = AKBASIC_TOK_LEFT_SLASH; break;
case '*': obj->tokentype = AKBASIC_TOK_STAR; break;
@@ -373,15 +462,19 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
case '[': obj->tokentype = AKBASIC_TOK_LEFT_SQUAREBRACKET; break;
case ']': obj->tokentype = AKBASIC_TOK_RIGHT_SQUAREBRACKET; break;
case '=':
(void)match_next_char(obj, '=', AKBASIC_TOK_EQUAL, AKBASIC_TOK_ASSIGNMENT);
PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_EQUAL, AKBASIC_TOK_ASSIGNMENT, &matched));
break;
case '<':
if ( !match_next_char(obj, '=', AKBASIC_TOK_LESS_THAN_EQUAL, AKBASIC_TOK_LESS_THAN) ) {
(void)match_next_char(obj, '>', AKBASIC_TOK_NOT_EQUAL, AKBASIC_TOK_LESS_THAN);
PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_LESS_THAN_EQUAL,
AKBASIC_TOK_LESS_THAN, &matched));
if ( !matched ) {
PASS(errctx, match_next_char(obj, '>', AKBASIC_TOK_NOT_EQUAL,
AKBASIC_TOK_LESS_THAN, &matched));
}
break;
case '>':
(void)match_next_char(obj, '=', AKBASIC_TOK_GREATER_THAN_EQUAL, AKBASIC_TOK_GREATER_THAN);
PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_GREATER_THAN_EQUAL,
AKBASIC_TOK_GREATER_THAN, &matched));
break;
case '"':
obj->start = obj->current;
@@ -402,7 +495,10 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
PASS(errctx, match_identifier(obj));
} else {
char message[AKBASIC_MAX_LINE_LENGTH];
snprintf(message, sizeof(message), "UNKNOWN TOKEN %c\n", c);
int written = 0;
PASS(errctx, aksl_snprintf(&written, message, sizeof(message),
"UNKNOWN TOKEN %c\n", c));
PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
obj->hasError = true;
obj->start = obj->current;
@@ -426,10 +522,13 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
* stores *that* as the program text.
*/
int skip = obj->current;
size_t tail = 0;
while ( obj->line[skip] == ' ' ) {
skip += 1;
}
memmove(obj->line, obj->line + skip, strlen(obj->line + skip) + 1);
PASS(errctx, aksl_strlen(obj->line + skip, &tail));
PASS(errctx, aksl_memmove(obj->line, obj->line + skip, tail + 1));
obj->current = 0;
} else {
PASS(errctx, get_lexeme(obj, lexeme, sizeof(lexeme)));
@@ -445,10 +544,10 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
}
if ( dest != NULL ) {
FAIL_ZERO_RETURN(errctx, (strlen(obj->line) < len), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(obj->line, &linelen));
FAIL_ZERO_RETURN(errctx, (linelen < len), AKBASIC_ERR_BOUNDS,
"Scanned line does not fit the caller's buffer");
strncpy(dest, obj->line, len - 1);
dest[len - 1] = '\0';
PASS(errctx, aksl_strcpy(dest, len, obj->line));
}
SUCCEED_RETURN(errctx);
}

View File

@@ -15,9 +15,8 @@
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akgl/controller.h>
#include <akgl/draw.h>
@@ -32,15 +31,24 @@
/** @brief Bytes per row, including the terminator. */
#define SINK_MAX_COLUMNS 256
/** @brief Scroll the grid up by one row, dropping the top one. */
static void scroll(akbasic_AkglSink *state)
/**
* @brief Scroll the grid up by one row, dropping the top one.
*
* Returns a context rather than `void` because the row moves can fail and there
* was nowhere to say so. Everything on this chain -- scroll, newline,
* putchar_at, echo_line, edit_key -- returns one for the same reason, and the
* chain already ends at sink_write, sink_writeln and sink_readline, which
* returned one all along. See libakstdlib #38.
*/
static akerr_ErrorContext *scroll(akbasic_AkglSink *state)
{
PREPARE_ERROR(errctx);
int row = 0;
for ( row = 0; row < SINK_MAX_ROWS - 1; row++ ) {
memcpy(state->text[row], state->text[row + 1], SINK_MAX_COLUMNS);
PASS(errctx, aksl_memcpy(state->text[row], state->text[row + 1], SINK_MAX_COLUMNS));
}
memset(state->text[SINK_MAX_ROWS - 1], 0, SINK_MAX_COLUMNS);
PASS(errctx, aksl_memset(state->text[SINK_MAX_ROWS - 1], 0, SINK_MAX_COLUMNS));
if ( state->cursorrow > 0 ) {
state->cursorrow -= 1;
}
@@ -59,13 +67,16 @@ static void scroll(akbasic_AkglSink *state)
}
/** @brief Move to the start of the next row, scrolling if that runs off the end. */
static void newline(akbasic_AkglSink *state)
static akerr_ErrorContext *newline(akbasic_AkglSink *state)
{
PREPARE_ERROR(errctx);
state->cursorcol = 0;
state->cursorrow += 1;
if ( state->cursorrow >= state->rows || state->cursorrow >= SINK_MAX_ROWS ) {
scroll(state);
PASS(errctx, scroll(state));
}
SUCCEED_RETURN(errctx);
}
/**
@@ -89,16 +100,17 @@ static void newline(akbasic_AkglSink *state)
* cleared between rows, so the gap holds the tail of some longer row that used
* to be here.
*/
static void putchar_at(akbasic_AkglSink *state, char c)
static akerr_ErrorContext *putchar_at(akbasic_AkglSink *state, char c)
{
PREPARE_ERROR(errctx);
int col = 0;
if ( c == '\n' ) {
newline(state);
return;
PASS(errctx, newline(state));
SUCCEED_RETURN(errctx);
}
if ( state->cursorcol >= state->columns || state->cursorcol >= SINK_MAX_COLUMNS - 1 ) {
newline(state);
PASS(errctx, newline(state));
}
for ( col = 0; col < state->cursorcol; col++ ) {
if ( state->text[state->cursorrow][col] == '\0' ) {
@@ -111,6 +123,7 @@ static void putchar_at(akbasic_AkglSink *state, char c)
state->text[state->cursorrow][state->cursorcol] = c;
state->cursorcol += 1;
state->text[state->cursorrow][state->cursorcol] = '\0';
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *sink_write(akbasic_TextSink *self, const char *text)
@@ -125,7 +138,7 @@ static akerr_ErrorContext *sink_write(akbasic_TextSink *self, const char *text)
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
"akgl sink has no state");
for ( i = 0; text[i] != '\0'; i++ ) {
putchar_at(state, text[i]);
PASS(errctx, putchar_at(state, text[i]));
}
SUCCEED_RETURN(errctx);
}
@@ -137,7 +150,7 @@ static akerr_ErrorContext *sink_writeln(akbasic_TextSink *self, const char *text
PASS(errctx, sink_write(self, text));
state = (akbasic_AkglSink *)self->self;
newline(state);
PASS(errctx, newline(state));
SUCCEED_RETURN(errctx);
}
@@ -151,8 +164,9 @@ static akerr_ErrorContext *sink_writeln(akbasic_TextSink *self, const char *text
* memory, and the host draws it when it draws its frame -- so the cost is a
* couple of memcpy-sized loops per keystroke.
*/
static void echo_line(akbasic_AkglSink *state)
static akerr_ErrorContext *echo_line(akbasic_AkglSink *state)
{
PREPARE_ERROR(errctx);
int i = 0;
int erasedto = 0;
int endedat = 0;
@@ -160,14 +174,14 @@ static void echo_line(akbasic_AkglSink *state)
state->cursorrow = state->editrow;
state->cursorcol = state->editcol;
for ( i = 0; i < state->echolen; i++ ) {
putchar_at(state, ' ');
PASS(errctx, putchar_at(state, ' '));
}
erasedto = state->cursorrow;
state->cursorrow = state->editrow;
state->cursorcol = state->editcol;
for ( i = 0; i < state->editlen; i++ ) {
putchar_at(state, state->editline[i]);
PASS(errctx, putchar_at(state, state->editline[i]));
}
endedat = state->cursorrow;
state->echolen = state->editlen;
@@ -186,6 +200,7 @@ static void echo_line(akbasic_AkglSink *state)
for ( i = endedat + 1; i <= erasedto && i < SINK_MAX_ROWS; i++ ) {
state->text[i][0] = '\0';
}
SUCCEED_RETURN(errctx);
}
/** @brief Append one byte to the line being typed, if there is room for it. */
@@ -215,14 +230,15 @@ static void edit_append(akbasic_AkglSink *state, char c)
* Backspace and Escape are keys rather than characters and several of them
* compose to text SDL would otherwise hand straight through.
*/
static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *submitted)
static akerr_ErrorContext *edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *submitted)
{
PREPARE_ERROR(errctx);
size_t i = 0;
if ( key->key == SDLK_RETURN || key->key == SDLK_KP_ENTER ||
key->key == '\r' || key->key == '\n' ) {
*submitted = true;
return;
SUCCEED_RETURN(errctx);
}
if ( key->key == SDLK_BACKSPACE || key->key == '\b' || key->key == 0x7f ) {
if ( state->editlen > 0 ) {
@@ -235,15 +251,15 @@ static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *s
*/
state->editlen -= 1;
state->editline[state->editlen] = '\0';
echo_line(state);
PASS(errctx, echo_line(state));
}
return;
SUCCEED_RETURN(errctx);
}
if ( key->key == SDLK_ESCAPE || key->key == 0x1b ) {
state->editlen = 0;
state->editline[0] = '\0';
echo_line(state);
return;
PASS(errctx, echo_line(state));
SUCCEED_RETURN(errctx);
}
if ( key->text[0] != '\0' ) {
@@ -257,8 +273,8 @@ static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *s
edit_append(state, key->text[i]);
}
}
echo_line(state);
return;
PASS(errctx, echo_line(state));
SUCCEED_RETURN(errctx);
}
/*
@@ -277,14 +293,15 @@ static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *s
*/
if ( key->key >= 0x20 && key->key < 0x7f ) {
edit_append(state, (char)toupper((unsigned char)key->key));
echo_line(state);
return;
PASS(errctx, echo_line(state));
SUCCEED_RETURN(errctx);
}
/*
* Neither: a cursor key, a function key or a bare modifier. Not an editing
* command here -- a script's own GET loop is what wants those.
*/
SUCCEED_RETURN(errctx);
}
/**
@@ -312,7 +329,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *edit_loop(akbasic_AkglSink *state, boo
*/
PASS(errctx, akgl_controller_poll_keystroke(&key, &available));
if ( available ) {
edit_key(state, &key, &submitted);
PASS(errctx, edit_key(state, &key, &submitted));
continue;
}
/*
@@ -375,9 +392,8 @@ static akerr_ErrorContext *sink_readline(akbasic_TextSink *self, char *dest, siz
if ( *eof ) {
SUCCEED_RETURN(errctx);
}
strncpy(dest, state->editline, len - 1);
dest[len - 1] = '\0';
newline(state);
PASS(errctx, aksl_strncpy(dest, len, state->editline, len - 1));
PASS(errctx, newline(state));
SUCCEED_RETURN(errctx);
}
@@ -391,7 +407,7 @@ static akerr_ErrorContext *sink_clear(akbasic_TextSink *self)
state = (akbasic_AkglSink *)self->self;
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
"akgl sink has no state");
memset(state->text, 0, sizeof(state->text));
PASS(errctx, aksl_memset(state->text, 0, sizeof(state->text)));
state->cursorcol = 0;
state->cursorrow = 0;
SUCCEED_RETURN(errctx);
@@ -600,7 +616,7 @@ akerr_ErrorContext *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSi
"A %dx%d text area has no room for a %dx%d character",
w, h, cellw, cellh);
memset(state, 0, sizeof(*state));
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer;
state->font = font;
state->color.r = 0xff;

View File

@@ -10,7 +10,6 @@
*/
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
@@ -59,8 +58,20 @@ static akerr_ErrorContext *stdio_readline(akbasic_TextSink *self, char *dest, si
*eof = false;
dest[0] = '\0';
if ( fgets(dest, (int)len, state->in) == NULL ) {
/*
* The end of the stream is AKERR_EOF rather than a NULL return, so it is
* handled here and turned back into the *eof flag this sink promises. A
* genuine read error is left to propagate, which is what a NULL from
* fgets(3) could never be told apart from.
*/
ATTEMPT {
CATCH(errctx, aksl_fgets(dest, len, state->in, &used));
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_EOF) {
*eof = true;
} FINISH(errctx, true);
if ( *eof ) {
SUCCEED_RETURN(errctx);
}
/*
@@ -68,7 +79,6 @@ static akerr_ErrorContext *stdio_readline(akbasic_TextSink *self, char *dest, si
* anyway, but leaving them on would make a stored source line differ from
* the same line typed at the REPL.
*/
used = strlen(dest);
while ( used > 0 && (dest[used - 1] == '\n' || dest[used - 1] == '\r') ) {
dest[used - 1] = '\0';
used -= 1;

View File

@@ -34,11 +34,11 @@
*/
#include <errno.h>
#include <string.h>
#include <SDL3_image/SDL_image.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akgl/error.h>
#include <akgl/game.h>
@@ -195,9 +195,9 @@ static akerr_ErrorContext *build_slot(akbasic_AkglSprites *state, int i, int wid
oldvisible = state->actors[i]->visible;
}
memset(spritename, 0, sizeof(spritename));
memset(charname, 0, sizeof(charname));
memset(actorname, 0, sizeof(actorname));
PASS(errctx, aksl_memset(spritename, 0, sizeof(spritename)));
PASS(errctx, aksl_memset(charname, 0, sizeof(charname)));
PASS(errctx, aksl_memset(actorname, 0, sizeof(actorname)));
SDL_snprintf(spritename, sizeof(spritename), "akbasic:sprite:%d", i + 1);
SDL_snprintf(charname, sizeof(charname), "akbasic:character:%d", i + 1);
SDL_snprintf(actorname, sizeof(actorname), "akbasic:actor:%d", i + 1);
@@ -263,8 +263,8 @@ static akerr_ErrorContext *install_surface(akbasic_AkglSprites *state, int i, SD
* which is what makes the texture's ownership one rule rather than two.
*/
PASS(errctx, akgl_heap_next_spritesheet(&state->sheets[i]));
memset(state->sheets[i], 0, sizeof(*state->sheets[i]));
memset(sheetname, 0, sizeof(sheetname));
PASS(errctx, aksl_memset(state->sheets[i], 0, sizeof(*state->sheets[i])));
PASS(errctx, aksl_memset(sheetname, 0, sizeof(sheetname)));
SDL_snprintf(sheetname, sizeof(sheetname), "akbasic:sheet:%d", i + 1);
SDL_strlcpy(state->sheets[i]->name, sheetname, sizeof(state->sheets[i]->name));
state->sheets[i]->texture = texture;
@@ -533,7 +533,7 @@ static akerr_ErrorContext *spr_shape(akbasic_SpriteBackend *self, int n, int kin
state->shapex2[i] = (float32_t)x2;
state->shapey2[i] = (float32_t)y2;
state->shapeexplicit[i] = (x2 > x1 && y2 > y1);
memset(&state->syncedbox[i], 0, sizeof(state->syncedbox[i]));
PASS(errctx, aksl_memset(&state->syncedbox[i], 0, sizeof(state->syncedbox[i])));
SUCCEED_RETURN(errctx);
}
@@ -703,7 +703,8 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_scan(akbasic_AkglSprites *state)
for ( i = 0; i < AKBASIC_MAX_SPRITES; i++ ) {
live[i] = slot_collidable(state, i);
synced[i] = false;
memset(&box[i], 0, sizeof(box[i]));
/* PASS rather than CATCH, as everywhere else in this loop-heavy function. */
PASS(errctx, aksl_memset(&box[i], 0, sizeof(box[i])));
if ( !live[i] ) {
continue;
}
@@ -745,7 +746,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_scan(akbasic_AkglSprites *state)
}
state->lastmask = 0;
state->lastsolidmask = 0;
memset(state->hascontact, 0, sizeof(state->hascontact));
PASS(errctx, aksl_memset(state->hascontact, 0, sizeof(state->hascontact)));
/*
* **Two phases, and the split is the whole performance story.**
@@ -970,7 +971,7 @@ akerr_ErrorContext *akbasic_sprite_init_akgl(akbasic_SpriteBackend *obj, akbasic
/* Before anything else in libakgl, so every AKGL_ERR_* has a name to print. */
PASS(errctx, akgl_error_init());
memset(state, 0, sizeof(*state));
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer;
state->graphics = graphics;
@@ -1012,7 +1013,7 @@ akerr_ErrorContext *akbasic_sprite_init_akgl(akbasic_SpriteBackend *obj, akbasic
&state->shapes[i], 0.0f, 0.0f, 0.0f));
}
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->self = state;
obj->define = spr_define;
obj->define_shape = spr_define_shape;

View File

@@ -9,9 +9,8 @@
* adaptor that has to turn a pattern into pixels.
*/
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/sprite.h>
@@ -40,7 +39,7 @@ akerr_ErrorContext *akbasic_sprite_state_init(akbasic_SpriteState *obj)
int i = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL sprite state in init");
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
for ( i = 0; i < AKBASIC_MAX_SPRITES; i++ ) {
obj->sprites[i].colorindex = SPRITE_COLOR_DEFAULT;
}

View File

@@ -23,6 +23,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/runtime.h>
@@ -88,10 +89,22 @@ static const char *next_word(const char *cursor, char *dest, size_t len)
return cursor;
}
/** @brief Compare a word against a keyword. Verbs are case-insensitive here as everywhere. */
static bool word_is(const char *word, const char *keyword)
/**
* @brief Compare a word against a keyword. Verbs are case-insensitive here as everywhere.
*
* The answer leaves through @p dest rather than the return value because the
* comparison itself can fail, and a `bool` has nowhere to put that. See
* libakstdlib #38.
*/
static akerr_ErrorContext *word_is(const char *word, const char *keyword, bool *dest)
{
return (strcmp(word, keyword) == 0);
PREPARE_ERROR(errctx);
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL dest in word_is");
PASS(errctx, aksl_strcmp(word, keyword, &cmp));
*dest = (cmp == 0);
SUCCEED_RETURN(errctx);
}
/**
@@ -103,8 +116,9 @@ static bool word_is(const char *word, const char *keyword)
static akerr_ErrorContext *type_from_suffix(const char *name, akbasic_Type *dest)
{
PREPARE_ERROR(errctx);
size_t len = strlen(name);
size_t len = 0;
PASS(errctx, aksl_strlen(name, &len));
FAIL_ZERO_RETURN(errctx, (len >= 2), AKBASIC_ERR_SYNTAX,
"Field name \"%s\" carries no type suffix", name);
switch ( name[len - 1] ) {
@@ -125,7 +139,7 @@ akerr_ErrorContext *akbasic_structtype_table_init(akbasic_StructTypeTable *obj)
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL table in structtype init");
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->count = 0;
SUCCEED_RETURN(errctx);
}
@@ -134,12 +148,17 @@ akerr_ErrorContext *akbasic_structtype_find(akbasic_StructTypeTable *obj, const
{
PREPARE_ERROR(errctx);
int i = 0;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in structtype find");
*dest = -1;
for ( i = 0; i < obj->count; i++ ) {
if ( obj->types[i].used && strcmp(obj->types[i].name, name) == 0 ) {
if ( !obj->types[i].used ) {
continue;
}
PASS(errctx, aksl_strcmp(obj->types[i].name, name, &cmp));
if ( cmp == 0 ) {
*dest = i;
SUCCEED_RETURN(errctx);
}
@@ -151,9 +170,14 @@ akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int t
{
PREPARE_ERROR(errctx);
char known[AKBASIC_MAX_STRING_LENGTH];
const char *separator = NULL;
akbasic_StructType *type = NULL;
size_t used = 0;
size_t namelen = 0;
size_t seplen = 0;
int written = 0;
int i = 0;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in structtype field");
@@ -162,7 +186,8 @@ akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int t
type = &obj->types[typeindex];
for ( i = 0; i < type->fieldcount; i++ ) {
if ( strcmp(type->fields[i].name, name) == 0 ) {
PASS(errctx, aksl_strcmp(type->fields[i].name, name, &cmp));
if ( cmp == 0 ) {
*dest = &type->fields[i];
SUCCEED_RETURN(errctx);
}
@@ -176,11 +201,20 @@ akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int t
*/
known[0] = '\0';
for ( i = 0; i < type->fieldcount; i++ ) {
int written = snprintf(known + used, sizeof(known) - used, "%s%s",
(i == 0 ? "" : ", "), type->fields[i].name);
if ( written < 0 || (size_t)written >= sizeof(known) - used ) {
separator = (i == 0 ? "" : ", ");
PASS(errctx, aksl_strlen(separator, &seplen));
PASS(errctx, aksl_strlen(type->fields[i].name, &namelen));
/*
* The list is advisory, so a full buffer ends it rather than failing the
* lookup. aksl_snprintf treats truncation as an error, which is right
* everywhere the output matters -- so the fit is decided here, before the
* call, instead of being read back out of a return value afterwards.
*/
if ( used + seplen + namelen >= sizeof(known) ) {
break;
}
PASS(errctx, aksl_snprintf(&written, known + used, sizeof(known) - used, "%s%s",
separator, type->fields[i].name));
used += (size_t)written;
}
FAIL_RETURN(errctx, AKBASIC_ERR_UNDEFINED, "%s has no field %s (%s)",
@@ -223,6 +257,8 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
int64_t i = 0;
int open = -1;
int existing = 0;
size_t namelen = 0;
bool matched = false;
const akbasic_Verb *verb = NULL;
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
@@ -231,9 +267,11 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
}
cursor = next_word(skip_lineno(obj->source[i].code), word, sizeof(word));
if ( word_is(word, "END") ) {
PASS(errctx, word_is(word, "END", &matched));
if ( matched ) {
(void)next_word(cursor, name, sizeof(name));
if ( !word_is(name, "TYPE") ) {
PASS(errctx, word_is(name, "TYPE", &matched));
if ( !matched ) {
continue; /* an ordinary END, not our terminator */
}
FAIL_ZERO_RETURN(errctx, (open >= 0), AKBASIC_ERR_SYNTAX,
@@ -242,7 +280,8 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
open = -1;
continue;
}
if ( !word_is(word, "TYPE") ) {
PASS(errctx, word_is(word, "TYPE", &matched));
if ( !matched ) {
continue;
}
@@ -268,16 +307,17 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
FAIL_ZERO_RETURN(errctx, (table->count < AKBASIC_MAX_STRUCT_TYPES), AKBASIC_ERR_BOUNDS,
"More than %d TYPE declarations", AKBASIC_MAX_STRUCT_TYPES);
FAIL_ZERO_RETURN(errctx, (strlen(name) < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(name, &namelen));
FAIL_ZERO_RETURN(errctx, (namelen < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
"TYPE name \"%s\" exceeds the %d character limit",
name, AKBASIC_MAX_STRUCT_NAME - 1);
open = table->count;
memset(&table->types[open], 0, sizeof(table->types[open]));
/* The memset is what terminates this: bounded at size - 1, the last byte
is the zero it already wrote. Refused above rather than truncated,
because two long names truncating to the same prefix would collide
silently -- and truncation is an error everywhere else here. */
strncpy(table->types[open].name, name, sizeof(table->types[open].name) - 1);
PASS(errctx, aksl_memset(&table->types[open], 0, sizeof(table->types[open])));
/* aksl_strcpy always terminates and refuses rather than truncates, which
is the behaviour this site already wanted -- two long names truncating
to the same prefix would collide silently. The length check above stays
because it names the limit in the message. */
PASS(errctx, aksl_strcpy(table->types[open].name, sizeof(table->types[open].name), name));
table->types[open].used = true;
table->types[open].firstline = i;
table->types[open].lastline = -1;
@@ -302,34 +342,38 @@ static akerr_ErrorContext *parse_field(akbasic_StructTypeTable *table, akbasic_S
const char *cursor = NULL;
akbasic_StructField *field = NULL;
akbasic_Type valuetype = AKBASIC_TYPE_UNDEFINED;
size_t namelen = 0;
bool matched = false;
int referenced = 0;
int cmp = 0;
int i = 0;
cursor = next_word(skip_lineno(line), fieldname, sizeof(fieldname));
if ( fieldname[0] == '\0' || fieldname[0] == '\'' ) {
SUCCEED_RETURN(errctx); /* a blank line inside the block */
}
if ( word_is(fieldname, "REM") ) {
PASS(errctx, word_is(fieldname, "REM", &matched));
if ( matched ) {
SUCCEED_RETURN(errctx);
}
FAIL_ZERO_RETURN(errctx, (type->fieldcount < AKBASIC_MAX_STRUCT_FIELDS), AKBASIC_ERR_BOUNDS,
"TYPE %s declares more than %d fields", type->name, AKBASIC_MAX_STRUCT_FIELDS);
for ( i = 0; i < type->fieldcount; i++ ) {
FAIL_NONZERO_RETURN(errctx, (strcmp(type->fields[i].name, fieldname) == 0),
AKBASIC_ERR_VALUE,
PASS(errctx, aksl_strcmp(type->fields[i].name, fieldname, &cmp));
FAIL_NONZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_VALUE,
"TYPE %s declares %s twice", type->name, fieldname);
}
PASS(errctx, type_from_suffix(fieldname, &valuetype));
FAIL_ZERO_RETURN(errctx, (strlen(fieldname) < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
PASS(errctx, aksl_strlen(fieldname, &namelen));
FAIL_ZERO_RETURN(errctx, (namelen < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
"Field name \"%s\" exceeds the %d character limit",
fieldname, AKBASIC_MAX_STRUCT_NAME - 1);
field = &type->fields[type->fieldcount];
memset(field, 0, sizeof(*field));
/* Terminated by the memset above; refused rather than truncated, for the
same reason a type name is. */
strncpy(field->name, fieldname, sizeof(field->name) - 1);
PASS(errctx, aksl_memset(field, 0, sizeof(*field)));
/* Refused rather than truncated, for the same reason a type name is. */
PASS(errctx, aksl_strcpy(field->name, sizeof(field->name), fieldname));
field->valuetype = valuetype;
field->typeindex = -1;
field->offset = -1;
@@ -354,13 +398,16 @@ static akerr_ErrorContext *parse_field(akbasic_StructTypeTable *table, akbasic_S
* not fit in one. So the declaration has to name it.
*/
cursor = next_word(cursor, word, sizeof(word));
FAIL_ZERO_RETURN(errctx, word_is(word, "AS"), AKBASIC_ERR_SYNTAX,
PASS(errctx, word_is(word, "AS", &matched));
FAIL_ZERO_RETURN(errctx, matched, AKBASIC_ERR_SYNTAX,
"Line %" PRId64 ": %s must name its type -- %s AS TYPENAME, or %s AS PTR TO TYPENAME",
lineno, fieldname, fieldname, fieldname);
cursor = next_word(cursor, word, sizeof(word));
if ( word_is(word, "PTR") ) {
PASS(errctx, word_is(word, "PTR", &matched));
if ( matched ) {
cursor = next_word(cursor, word, sizeof(word));
FAIL_ZERO_RETURN(errctx, word_is(word, "TO"), AKBASIC_ERR_SYNTAX,
PASS(errctx, word_is(word, "TO", &matched));
FAIL_ZERO_RETURN(errctx, matched, AKBASIC_ERR_SYNTAX,
"Line %" PRId64 ": expected PTR TO TYPENAME", lineno);
cursor = next_word(cursor, word, sizeof(word));
field->kind = AKBASIC_FIELD_POINTER;
@@ -466,7 +513,7 @@ akerr_ErrorContext *akbasic_structtype_scan(akbasic_Runtime *obj)
}
}
for ( i = t; i < table->count; i++ ) {
memset(&table->types[i], 0, sizeof(table->types[i]));
PASS(errctx, aksl_memset(&table->types[i], 0, sizeof(table->types[i])));
}
table->count = t;

View File

@@ -3,8 +3,6 @@
* @brief Implements the fixed-capacity open-addressed symbol table.
*/
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
@@ -23,10 +21,13 @@ static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int
{
PREPARE_ERROR(errctx);
uint32_t hashval = 0;
size_t keylen = 0;
int index = 0;
int cmp = 0;
int i = 0;
PASS(errctx, aksl_strhash_djb2((char *)key, strlen(key), &hashval));
PASS(errctx, aksl_strlen(key, &keylen));
PASS(errctx, aksl_strhash_djb2((char *)key, keylen, &hashval));
*found = false;
index = (int)(hashval % (uint32_t)obj->capacity);
@@ -36,7 +37,8 @@ static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int
*slot = probeidx;
SUCCEED_RETURN(errctx);
}
if ( strcmp(obj->slots[probeidx].key, key) == 0 ) {
PASS(errctx, aksl_strcmp(obj->slots[probeidx].key, key, &cmp));
if ( cmp == 0 ) {
*slot = probeidx;
*found = true;
SUCCEED_RETURN(errctx);
@@ -58,7 +60,7 @@ akerr_ErrorContext *akbasic_symtab_init(akbasic_SymbolTable *obj, int capacity)
"Symbol table capacity %d out of range 1..%d",
capacity, AKBASIC_SYMTAB_MAX_SLOTS);
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->capacity = capacity;
obj->count = 0;
SUCCEED_RETURN(errctx);
@@ -67,6 +69,7 @@ akerr_ErrorContext *akbasic_symtab_init(akbasic_SymbolTable *obj, int capacity)
akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key, void *value, int64_t ivalue)
{
PREPARE_ERROR(errctx);
size_t keylen = 0;
int slot = 0;
bool found = false;
@@ -74,7 +77,8 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
"NULL symbol table in set");
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
"NULL key in symbol table set");
FAIL_ZERO_RETURN(errctx, (strlen(key) < AKBASIC_SYMTAB_MAX_KEY),
PASS(errctx, aksl_strlen(key, &keylen));
FAIL_ZERO_RETURN(errctx, (keylen < AKBASIC_SYMTAB_MAX_KEY),
AKBASIC_ERR_BOUNDS,
"Symbol name '%s' exceeds %d characters",
key, AKBASIC_SYMTAB_MAX_KEY - 1);
@@ -82,8 +86,10 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
PASS(errctx, probe(obj, key, &slot, &found));
if ( !found ) {
strncpy(obj->slots[slot].key, key, AKBASIC_SYMTAB_MAX_KEY - 1);
obj->slots[slot].key[AKBASIC_SYMTAB_MAX_KEY - 1] = '\0';
/* aksl_strcpy always terminates, so the explicit terminator this
replaced is no longer needed. The length check above has already
refused anything that would not fit, so it cannot fail here. */
PASS(errctx, aksl_strcpy(obj->slots[slot].key, sizeof(obj->slots[slot].key), key));
obj->slots[slot].used = true;
obj->count += 1;
}
@@ -95,6 +101,7 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key, void **value, int64_t *ivalue)
{
PREPARE_ERROR(errctx);
size_t keylen = 0;
int slot = 0;
bool found = false;
@@ -102,7 +109,8 @@ akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key
"NULL symbol table in get");
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
"NULL key in symbol table get");
FAIL_ZERO_RETURN(errctx, (strlen(key) < AKBASIC_SYMTAB_MAX_KEY),
PASS(errctx, aksl_strlen(key, &keylen));
FAIL_ZERO_RETURN(errctx, (keylen < AKBASIC_SYMTAB_MAX_KEY),
AKERR_KEY,
"Symbol '%s' is not present", key);
@@ -127,7 +135,7 @@ akerr_ErrorContext *akbasic_symtab_clear(akbasic_SymbolTable *obj)
"NULL symbol table in clear");
capacity = obj->capacity;
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->capacity = capacity;
SUCCEED_RETURN(errctx);
}

View File

@@ -16,10 +16,8 @@
* string.
*/
#include <stdio.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akgl/error.h>
#include <akgl/registry.h>
@@ -94,8 +92,7 @@ static akerr_ErrorContext *ui_dialog(akbasic_UiBackend *self, const char *text)
state->dialogtext[0] = '\0';
SUCCEED_RETURN(errctx);
}
strncpy(state->dialogtext, text, sizeof(state->dialogtext) - 1);
state->dialogtext[sizeof(state->dialogtext) - 1] = '\0';
PASS(errctx, aksl_strcpy(state->dialogtext, sizeof(state->dialogtext), text));
state->dialogopen = true;
SUCCEED_RETURN(errctx);
}
@@ -113,8 +110,7 @@ static akerr_ErrorContext *ui_label(akbasic_UiBackend *self, int slot, int ancho
state->labeltext[slot][0] = '\0';
SUCCEED_RETURN(errctx);
}
strncpy(state->labeltext[slot], text, sizeof(state->labeltext[slot]) - 1);
state->labeltext[slot][sizeof(state->labeltext[slot]) - 1] = '\0';
PASS(errctx, aksl_strcpy(state->labeltext[slot], sizeof(state->labeltext[slot]), text));
state->labelanchor[slot] = anchor;
state->labelset[slot] = true;
SUCCEED_RETURN(errctx);
@@ -143,8 +139,8 @@ static akerr_ErrorContext *ui_menu(akbasic_UiBackend *self, int slot, const char
for ( i = 0; i < count; i++ ) {
FAIL_ZERO_RETURN(errctx, (items[i] != NULL), AKERR_NULLPOINTER,
"MENU entry %d is NULL", i + 1);
strncpy(state->menuitems[slot][i], items[i], sizeof(state->menuitems[slot][i]) - 1);
state->menuitems[slot][i][sizeof(state->menuitems[slot][i]) - 1] = '\0';
PASS(errctx, aksl_strcpy(state->menuitems[slot][i], sizeof(state->menuitems[slot][i]),
items[i]));
state->menus[slot].items[i] = state->menuitems[slot][i];
}
for ( i = count; i < AKBASIC_UI_MAX_MENU_ITEMS; i++ ) {
@@ -348,9 +344,9 @@ akerr_ErrorContext *akbasic_ui_init_akgl(akbasic_UiBackend *obj, akbasic_AkglUi
"A %dx%d UI at %d points is not a UI", width, height, fontsize);
PASS(errctx, akgl_error_init());
memset(state, 0, sizeof(*state));
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer;
strncpy(state->fontname, UI_FONT_NAME, sizeof(state->fontname) - 1);
PASS(errctx, aksl_strcpy(state->fontname, sizeof(state->fontname), UI_FONT_NAME));
/*
* The element ids, once. clay identifies an element by its string and keeps
@@ -359,13 +355,14 @@ akerr_ErrorContext *akbasic_ui_init_akgl(akbasic_UiBackend *obj, akbasic_AkglUi
* function that declares them.
*/
for ( i = 0; i < AKBASIC_UI_MAX_LABELS; i++ ) {
snprintf(state->labelid[i], sizeof(state->labelid[i]), "hud%d", i + 1);
PASS(errctx, aksl_snprintf(&count, state->labelid[i], sizeof(state->labelid[i]),
"hud%d", i + 1));
}
for ( i = 0; i < AKBASIC_UI_MAX_MENUS; i++ ) {
snprintf(state->menuid[i], sizeof(state->menuid[i]), "menu%d", i + 1);
PASS(errctx, aksl_snprintf(&count, state->menuid[i], sizeof(state->menuid[i]),
"menu%d", i + 1));
state->menus[i].id = state->menuid[i];
}
(void)count;
/*
* **akgl_registry_init() does not initialize the font registry** -- its own

View File

@@ -13,6 +13,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/value.h>
@@ -119,13 +120,17 @@ static akerr_ErrorContext *require_numeric(akbasic_Value *self, akbasic_Value *r
static akerr_ErrorContext *set_string(akbasic_Value *dest, const char *src)
{
PREPARE_ERROR(errctx);
size_t srclen = 0;
FAIL_ZERO_RETURN(errctx, (strlen(src) < AKBASIC_MAX_STRING_LENGTH),
PASS(errctx, aksl_strlen(src, &srclen));
FAIL_ZERO_RETURN(errctx, (srclen < AKBASIC_MAX_STRING_LENGTH),
AKBASIC_ERR_VALUE,
"String result of %zu characters exceeds the %d character limit",
strlen(src), AKBASIC_MAX_STRING_LENGTH - 1);
strncpy(dest->stringval, src, AKBASIC_MAX_STRING_LENGTH - 1);
dest->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
srclen, AKBASIC_MAX_STRING_LENGTH - 1);
/* aksl_strcpy always terminates and refuses rather than truncates, which is
the behaviour this site already wanted. The length check above stays
because it names the limit in the message. */
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), src));
SUCCEED_RETURN(errctx);
}
@@ -134,7 +139,7 @@ akerr_ErrorContext *akbasic_valuepool_init(akbasic_ValuePool *obj)
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL pool in init");
memset(obj, 0, sizeof(*obj));
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->next = 0;
SUCCEED_RETURN(errctx);
}
@@ -201,7 +206,7 @@ akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest
SUCCEED_RETURN(errctx);
}
dest->valuetype = self->valuetype;
memcpy(dest->stringval, self->stringval, sizeof(dest->stringval));
PASS(errctx, aksl_memcpy(dest->stringval, self->stringval, sizeof(dest->stringval)));
dest->intval = self->intval;
dest->floatval = self->floatval;
dest->boolvalue = self->boolvalue;
@@ -223,6 +228,7 @@ akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest
akerr_ErrorContext *akbasic_value_to_string(akbasic_Value *self, char *dest, size_t len)
{
PREPARE_ERROR(errctx);
int written = 0;
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in to_string");
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in to_string");
@@ -230,20 +236,22 @@ akerr_ErrorContext *akbasic_value_to_string(akbasic_Value *self, char *dest, siz
switch ( self->valuetype ) {
case AKBASIC_TYPE_STRING:
snprintf(dest, len, "%s", self->stringval);
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->stringval));
break;
case AKBASIC_TYPE_INTEGER:
snprintf(dest, len, "%" PRId64, self->intval);
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, self->intval));
break;
case AKBASIC_TYPE_FLOAT:
snprintf(dest, len, "%f", self->floatval);
PASS(errctx, aksl_snprintf(&written, dest, len, "%f", self->floatval));
break;
case AKBASIC_TYPE_BOOLEAN:
/* Go's %t, which is "true"/"false" and not the numeric -1/0. */
snprintf(dest, len, "%s", (self->boolvalue == AKBASIC_TRUE ? "true" : "false"));
PASS(errctx, aksl_snprintf(&written, dest, len, "%s",
(self->boolvalue == AKBASIC_TRUE ? "true" : "false")));
break;
default:
snprintf(dest, len, "(UNDEFINED STRING REPRESENTATION FOR %d)", (int)self->valuetype);
PASS(errctx, aksl_snprintf(&written, dest, len, "(UNDEFINED STRING REPRESENTATION FOR %d)",
(int)self->valuetype));
break;
}
SUCCEED_RETURN(errctx);
@@ -486,6 +494,7 @@ akerr_ErrorContext *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *
PREPARE_ERROR(errctx);
akbasic_Value *out = NULL;
char buf[AKBASIC_MAX_STRING_LENGTH * 2];
int written = 0;
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in math plus");
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
@@ -511,13 +520,16 @@ akerr_ErrorContext *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
out->floatval = self->floatval + rval_as_float(rval);
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_STRING ) {
snprintf(buf, sizeof(buf), "%s%s", self->stringval, rval->stringval);
PASS(errctx, aksl_snprintf(&written, buf, sizeof(buf), "%s%s",
self->stringval, rval->stringval));
PASS(errctx, set_string(out, buf));
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_INTEGER ) {
snprintf(buf, sizeof(buf), "%s%" PRId64, self->stringval, rval->intval);
PASS(errctx, aksl_snprintf(&written, buf, sizeof(buf), "%s%" PRId64,
self->stringval, rval->intval));
PASS(errctx, set_string(out, buf));
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_FLOAT ) {
snprintf(buf, sizeof(buf), "%s%f", self->stringval, rval->floatval);
PASS(errctx, aksl_snprintf(&written, buf, sizeof(buf), "%s%f",
self->stringval, rval->floatval));
PASS(errctx, set_string(out, buf));
} else {
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE, "Invalid arithmetic operation");
@@ -586,14 +598,14 @@ akerr_ErrorContext *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Val
*/
FAIL_NONZERO_RETURN(errctx, (rval->intval < 0), AKBASIC_ERR_VALUE,
"String multiplier %" PRId64 " must not be negative", rval->intval);
srclen = strlen((*dest)->stringval);
PASS(errctx, aksl_strlen((*dest)->stringval, &srclen));
FAIL_NONZERO_RETURN(errctx,
(srclen != 0 && (uint64_t)rval->intval > (AKBASIC_MAX_STRING_LENGTH - 1) / srclen),
AKBASIC_ERR_VALUE,
"Repeated string of %zu x %" PRId64 " characters exceeds the %d character limit",
srclen, rval->intval, AKBASIC_MAX_STRING_LENGTH - 1);
for ( i = 0; i < rval->intval; i++ ) {
memcpy(buf + offset, (*dest)->stringval, srclen);
PASS(errctx, aksl_memcpy(buf + offset, (*dest)->stringval, srclen));
offset += srclen;
}
buf[offset] = '\0';
@@ -619,6 +631,7 @@ akerr_ErrorContext *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Val
akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
@@ -626,7 +639,8 @@ akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval < rval_as_float(rval)));
} else {
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) < 0));
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
PASS(errctx, akbasic_value_set_bool(*dest, cmp < 0));
}
SUCCEED_RETURN(errctx);
}
@@ -634,6 +648,7 @@ akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *
akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
@@ -641,7 +656,8 @@ akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_V
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval <= rval_as_float(rval)));
} else {
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) <= 0));
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
PASS(errctx, akbasic_value_set_bool(*dest, cmp <= 0));
}
SUCCEED_RETURN(errctx);
}
@@ -649,6 +665,7 @@ akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_V
akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
@@ -656,7 +673,8 @@ akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Valu
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval > rval_as_float(rval)));
} else {
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) > 0));
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
PASS(errctx, akbasic_value_set_bool(*dest, cmp > 0));
}
SUCCEED_RETURN(errctx);
}
@@ -664,6 +682,7 @@ akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Valu
akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
@@ -671,7 +690,8 @@ akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasi
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval >= rval_as_float(rval)));
} else {
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) >= 0));
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
PASS(errctx, akbasic_value_set_bool(*dest, cmp >= 0));
}
SUCCEED_RETURN(errctx);
}
@@ -679,6 +699,7 @@ akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasi
akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
@@ -686,7 +707,8 @@ akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *r
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval == rval_as_float(rval)));
} else {
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) == 0));
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
PASS(errctx, akbasic_value_set_bool(*dest, cmp == 0));
}
SUCCEED_RETURN(errctx);
}
@@ -694,6 +716,7 @@ akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *r
akerr_ErrorContext *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{
PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
@@ -701,7 +724,8 @@ akerr_ErrorContext *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Valu
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval != rval_as_float(rval)));
} else {
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) != 0));
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
PASS(errctx, akbasic_value_set_bool(*dest, cmp != 0));
}
SUCCEED_RETURN(errctx);
}

View File

@@ -4,9 +4,9 @@
*/
#include <inttypes.h>
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/variable.h>
@@ -53,7 +53,7 @@ akerr_ErrorContext *akbasic_variable_init(akbasic_Variable *obj, akbasic_ValuePo
"Array dimension count %d out of range 1..%d",
sizecount, AKBASIC_MAX_ARRAY_DEPTH);
namelen = strlen(obj->name);
PASS(errctx, aksl_strlen(obj->name, &namelen));
FAIL_ZERO_RETURN(errctx, (namelen > 0), AKBASIC_ERR_VALUE, "Invalid variable name");
/* Type comes from the suffix. A bare name keeps whatever type it had. */
@@ -202,16 +202,17 @@ akerr_ErrorContext *akbasic_variable_set_string(akbasic_Variable *obj, const cha
{
PREPARE_ERROR(errctx);
akbasic_Value tmp;
size_t valuelen = 0;
FAIL_ZERO_RETURN(errctx, (value != NULL), AKERR_NULLPOINTER, "NULL string in set_string");
FAIL_ZERO_RETURN(errctx, (strlen(value) < AKBASIC_MAX_STRING_LENGTH),
PASS(errctx, aksl_strlen(value, &valuelen));
FAIL_ZERO_RETURN(errctx, (valuelen < AKBASIC_MAX_STRING_LENGTH),
AKBASIC_ERR_VALUE,
"String of %zu characters exceeds the %d character limit",
strlen(value), AKBASIC_MAX_STRING_LENGTH - 1);
valuelen, AKBASIC_MAX_STRING_LENGTH - 1);
PASS(errctx, akbasic_value_zero(&tmp));
tmp.valuetype = AKBASIC_TYPE_STRING;
strncpy(tmp.stringval, value, AKBASIC_MAX_STRING_LENGTH - 1);
tmp.stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
PASS(errctx, aksl_strcpy(tmp.stringval, sizeof(tmp.stringval), value));
PASS(errctx, akbasic_variable_set_subscript(obj, &tmp, subscripts, subscriptcount));
SUCCEED_RETURN(errctx);
}

View File

@@ -8,6 +8,7 @@
#include <string.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h>
#include <akbasic/verbs.h>
@@ -197,6 +198,28 @@ static const akbasic_Verb VERBS[] = {
#define VERB_COUNT ((int)(sizeof(VERBS) / sizeof(VERBS[0])))
/**
* @brief bsearch(3) comparator for the verb table.
*
* **This is the one call in `src/` that stays on raw `strcmp` on purpose.**
* Everything else that reaches past libakstdlib to a function that library wraps
* has been converted to the wrapper and propagates the error context; the ruling
* that required it is libakstdlib #38, and the rule is that a function which
* cannot report an error changes its own signature rather than swallowing one.
*
* That remedy is unavailable here, and only here. `bsearch(3)` fixes the
* comparator's signature at `int (*)(const void *, const void *)`, so there is
* no out-parameter to hand an `akerr_ErrorContext *` back through and no return
* value that is not already the ordering. Converting this one site means
* dropping `bsearch` for an in-house binary search that can propagate -- a
* separate change to a lookup that runs once per scanned identifier, and a
* decision that belongs in its own issue rather than riding along with a port.
*
* The bypass is safe as written: `key` is the caller's NUL-terminated `upper`
* buffer and `element->name` is a string literal in the static table above, so
* neither argument can be NULL and neither can be unterminated -- which is the
* whole of what `aksl_strcmp` would have checked.
*/
static int verb_compare(const void *key, const void *element)
{
const akbasic_Verb *verb = (const akbasic_Verb *)element;
@@ -223,7 +246,7 @@ akerr_ErrorContext *akbasic_verb_lookup(const char *name, const akbasic_Verb **d
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in verb lookup");
*dest = NULL;
len = strlen(name);
PASS(errctx, aksl_strlen(name, &len));
if ( len == 0 || len >= sizeof(upper) ) {
/* Too long to be any verb we know. Not an error, just a miss. */
SUCCEED_RETURN(errctx);

View File

@@ -13,6 +13,7 @@ int main(void)
akbasic_Variable *again = NULL;
akbasic_Value *value = NULL;
int64_t lineno = 0;
bool waiting = false;
int i = 0;
TEST_REQUIRE_OK(harness_start(NULL));
@@ -57,24 +58,34 @@ int main(void)
TEST_REQUIRE_STATUS(akbasic_runtime_prev_environment(&HARNESS_RUNTIME),
AKBASIC_ERR_ENVIRONMENT);
/* The wait mechanism, including the parent-chain search. */
TEST_REQUIRE(!akbasic_environment_is_waiting_for_any(root), "a fresh environment waits for nothing");
/*
* The wait mechanism, including the parent-chain search. Both predicates
* report through an out-parameter and return an error context, so each
* assertion is a TEST_REQUIRE_OK on the call and a TEST_REQUIRE on the
* answer. See libakstdlib #38.
*/
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
TEST_REQUIRE(!waiting, "a fresh environment waits for nothing");
TEST_REQUIRE_OK(akbasic_environment_wait_for_command(root, "NEXT"));
TEST_REQUIRE(akbasic_environment_is_waiting_for_any(root), "the wait must register");
TEST_REQUIRE(akbasic_environment_is_waiting_for(root, "NEXT"), "the wait must match by name");
TEST_REQUIRE(!akbasic_environment_is_waiting_for(root, "DATA"), "a different verb must not match");
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
TEST_REQUIRE(waiting, "the wait must register");
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(root, "NEXT", &waiting));
TEST_REQUIRE(waiting, "the wait must match by name");
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(root, "DATA", &waiting));
TEST_REQUIRE(!waiting, "a different verb must not match");
/* Two pending waits in one environment is a hard error, not a panic. */
TEST_REQUIRE_STATUS(akbasic_environment_wait_for_command(root, "DATA"), AKBASIC_ERR_STATE);
TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME));
child = HARNESS_RUNTIME.environment;
TEST_REQUIRE(akbasic_environment_is_waiting_for(child, "NEXT"),
"a child must see its parent's wait");
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(child, "NEXT", &waiting));
TEST_REQUIRE(waiting, "a child must see its parent's wait");
TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME));
TEST_REQUIRE_OK(akbasic_environment_stop_waiting(root, "NEXT"));
TEST_REQUIRE(!akbasic_environment_is_waiting_for_any(root), "stop_waiting must clear it");
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
TEST_REQUIRE(!waiting, "stop_waiting must clear it");
/* The per-line value pool is bounded and says so. */
for ( i = 0; i < AKBASIC_MAX_VALUES; i++ ) {