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
42 changed files with 1302 additions and 597 deletions

View File

@@ -26,14 +26,42 @@ jobs:
# skipped for the same reason: our CMakeLists declares akerror::akerror # skipped for the same reason: our CMakeLists declares akerror::akerror
# first and libakstdlib guards on if(NOT TARGET ...). # first and libakstdlib guards on if(NOT TARGET ...).
submodules: true submodules: true
# moreutils is load-bearing, not incidental: it supplies errno(1), which
# deps/libakerror/scripts/generrno.sh shells out to at build time to
# generate its errno name table and to stamp AKERR_LAST_ERRNO_VALUE into
# the generated akerror.h.
#
# Its absence does not fail the build, which is what made this expensive to
# find. The script prints "errno: command not found" twice, emits an empty
# akerr_init_errno() so no errno ever gets a name, and substitutes an empty
# AKERR_LAST_ERRNO_VALUE -- so every code in libakerror's reserved band
# collapses from (134 + n) to ( + n), i.e. to n. AKERR_OUTOFBOUNDS is
# (AKERR_LAST_ERRNO_VALUE + 2), so it becomes literally 2, and ERR(2)
# answers "Out Of Bounds Error" where ENOENT's "No such file or directory"
# belongs. That is the docs_examples failure at docs/15-error-codes.md:105
# that this job carried on every run since #2.
- name: dependencies - name: dependencies
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y cmake gcc sudo apt-get install -y cmake gcc moreutils
# --parallel 2 rather than a bare --parallel, and that bound is measured
# rather than cautious. All five jobs in this file say runs-on:
# ubuntu-latest and land on the same runner, so a bare --parallel asks for
# nproc compilers *per job* and the runner is handed five times that at
# once. Run #30 never showed it because akgl_build and coverage were still
# dying early; run #31 was the first push on which all five did real work
# simultaneously, and the sanitizers job went from a 55-second link phase
# to a 14-minute one -- individual `Linking C executable` steps taking two
# minutes each, starting at the exact second akgl_build began its SDL tree
# -- before the runner's Docker daemon fell over outright and took the job
# with it. That is memory exhaustion, and the only lever on it is the
# number of concurrent compiler and linker processes. Applied at all four
# build sites for the same reason; mutation_test drives its own builds
# through the harness and is not bounded here.
- name: build - name: build
run: | run: |
cmake -S . -B build cmake -S . -B build
cmake --build build --parallel cmake --build build --parallel 2
# The suite is 78 cases: 41 golden files byte-compared against the Go # The suite is 78 cases: 41 golden files byte-compared against the Go
# reference's own corpus (checked in at tests/reference/, see its README), # reference's own corpus (checked in at tests/reference/, see its README),
# 9 local golden cases for verbs the reference never implemented, 25 unit # 9 local golden cases for verbs the reference never implemented, 25 unit
@@ -74,7 +102,7 @@ jobs:
- name: dependencies - name: dependencies
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y cmake gcc sudo apt-get install -y cmake gcc moreutils
# The whole suite under ASan and UBSan, golden files included. This is the # The whole suite under ASan and UBSan, golden files included. This is the
# gate libakstdlib's TODO.md section 1 calls its highest-value missing item # gate libakstdlib's TODO.md section 1 calls its highest-value missing item
# -- worth having here from the start, because this library is all fixed # -- worth having here from the start, because this library is all fixed
@@ -84,7 +112,7 @@ jobs:
cmake -S . -B build-asan \ cmake -S . -B build-asan \
-DAKBASIC_SANITIZE=ON \ -DAKBASIC_SANITIZE=ON \
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-asan --parallel cmake --build build-asan --parallel 2
ctest --test-dir build-asan --output-on-failure ctest --test-dir build-asan --output-on-failure
- run: echo "🍏 This job's status is ${{ job.status }}." - run: echo "🍏 This job's status is ${{ job.status }}."
@@ -98,7 +126,7 @@ jobs:
- name: dependencies - name: dependencies
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y cmake gcc gcovr sudo apt-get install -y cmake gcc gcovr moreutils
# The gate is a ratchet, not a target: src/ sits at 94.6% of lines and # The gate is a ratchet, not a target: src/ sits at 94.6% of lines and
# 98.6% of functions, so 90 fails on a real regression (a test deleted, or # 98.6% of functions, so 90 fails on a real regression (a test deleted, or
# new untested code added) without tripping over rounding. # new untested code added) without tripping over rounding.
@@ -124,7 +152,7 @@ jobs:
cmake -S . -B build-coverage \ cmake -S . -B build-coverage \
-DAKBASIC_COVERAGE=ON \ -DAKBASIC_COVERAGE=ON \
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-coverage --parallel cmake --build build-coverage --parallel 2
ctest --test-dir build-coverage --output-on-failure ctest --test-dir build-coverage --output-on-failure
mkdir -p build-coverage/coverage mkdir -p build-coverage/coverage
gcovr --root . --filter 'src/.*' \ gcovr --root . --filter 'src/.*' \
@@ -134,9 +162,21 @@ jobs:
--fail-under-line 90 --fail-under-line 90
# Publish even when the threshold gate fails, so the uncovered lines are # Publish even when the threshold gate fails, so the uncovered lines are
# visible -- each one is a missing test. # visible -- each one is a missing test.
#
# @v3, not @v4, and that is Gitea rather than preference. @v4 bundles
# @actions/artifact v2, whose isGhes() treats any GITHUB_SERVER_URL that is
# not github.com as GitHub Enterprise Server and refuses outright:
# "GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+
# and download-artifact@v4+ are not currently supported on GHES". @v3 uses
# the older artifact API, which this forge does implement.
#
# This never showed before because the step had nothing to upload: gcovr
# was never reached, so the step warned "No files were found" and passed.
# Fixing the suite is what first gave it a real file to refuse. Same class
# of accommodation as the annotate_only flag on the junit reporter above.
- name: upload coverage reports - name: upload coverage reports
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: code-coverage name: code-coverage
path: build-coverage/coverage/ path: build-coverage/coverage/
@@ -161,22 +201,56 @@ jobs:
# embedded it takes a find_package path instead, so our CMakeLists declares # embedded it takes a find_package path instead, so our CMakeLists declares
# those targets first and needs the submodules present. Six of them, none # those targets first and needs the submodules present. Six of them, none
# recursive. Filed upstream as libakgl API-gap item 5. # recursive. Filed upstream as libakgl API-gap item 5.
#
# Eight, not six: deps/libccd and deps/clay were missing and are not
# optional. libakgl does not add_subdirectory either of them -- their own
# CMakeLists are unusable as subprojects -- it compiles them into itself,
# so nothing declares them and configuration dies late with "File
# deps/libccd/src/ccd/config.h.cmake.in does not exist" at
# deps/libakgl/CMakeLists.txt:282. clay is the same shape one step later:
# deps/clay/clay.h is on the include path and installed.
#
# deps/tg is a real submodule of libakgl and is deliberately not here --
# nothing in its CMakeLists references it. libakerror and libakstdlib are
# skipped for the reason the checkout note above gives.
- name: libakgl dependencies - name: libakgl dependencies
run: | run: |
git -C deps/libakgl submodule update --init \ git -C deps/libakgl submodule update --init \
deps/SDL deps/SDL_image deps/SDL_mixer deps/SDL_ttf \ deps/SDL deps/SDL_image deps/SDL_mixer deps/SDL_ttf \
deps/jansson deps/semver deps/jansson deps/semver deps/libccd deps/clay
# libfreetype-dev and libharfbuzz-dev are load-bearing, not incidental. # libfreetype-dev and libharfbuzz-dev are load-bearing, not incidental.
# SDL_ttf prefers the system copies -- it reports "Using system freetype # SDL_ttf prefers the system copies -- it reports "Using system freetype
# library" and links libfreetype.so.6 -- and without them it would reach # library" and links libfreetype.so.6 -- and without them it would reach
# for deps/SDL_ttf/external/freetype, which the checkout above # for deps/SDL_ttf/external/freetype, which the checkout above
# deliberately does not clone. Installing two dev packages is much cheaper # deliberately does not clone. Installing two dev packages is much cheaper
# than cloning freetype and harfbuzz. # than cloning freetype and harfbuzz.
#
# The X11 dev packages are the other half, and they are what this job has
# been dying on since run #2 -- it has never once been green. SDL_X11 and
# its nine sub-options default ON on Linux, and CheckX11() in
# deps/SDL/cmake/sdlchecks.cmake calls SDL_missing_dependency() -- a hard
# CMake error, not a downgrade -- for any one of them whose header is
# absent. The runner image carries libx11-dev (X11/XKBlib.h resolved) but
# none of the extension packages, so configuration stopped at the first of
# the nine: "Couldn't find dependency package for XCURSOR".
#
# Turning the missing ones OFF would also configure, and is the wrong
# answer: the point of this job is that an AKGL build is a real SDL
# program, and a real SDL program on Linux builds the X11 backend. The
# tests still run headless under SDL_VIDEODRIVER=dummy -- see the env block
# below -- so this buys a faithful build, not a display.
#
# The list is SDL's own, from deps/SDL/docs/README-linux.md, reduced to the
# X11 entries: the audio, Wayland, KMSDRM and Vulkan backends all degrade
# to "not found" and skip themselves rather than erroring, so they cost
# nothing to leave out. Nine packages, all in noble.
- name: dependencies - name: dependencies
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y cmake gcc g++ pkg-config \ sudo apt-get install -y cmake gcc g++ pkg-config moreutils \
libfreetype-dev libharfbuzz-dev libfreetype-dev libharfbuzz-dev \
libx11-dev libxext-dev libxcursor-dev libxi-dev libxfixes-dev \
libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev
# The akgl-backed half: the text sink and the graphics, audio and input # The akgl-backed half: the text sink and the graphics, audio and input
# backends, the standalone SDL frontend, and the two suites that drive # backends, the standalone SDL frontend, and the two suites that drive
# them against a real software renderer and read the pixels back. # them against a real software renderer and read the pixels back.
@@ -194,7 +268,7 @@ jobs:
- name: build with libakgl - name: build with libakgl
run: | run: |
cmake -S . -B build-akgl -DAKBASIC_WITH_AKGL=ON cmake -S . -B build-akgl -DAKBASIC_WITH_AKGL=ON
cmake --build build-akgl --parallel cmake --build build-akgl --parallel 2
# Dummy video and audio drivers, set per test by CMakeLists rather than in # Dummy video and audio drivers, set per test by CMakeLists rather than in
# this job's environment, because an AKGL build of `basic` is now an SDL # this job's environment, because an AKGL build of `basic` is now an SDL
# program and the golden cases run *it*: forty-one real windows is not what # program and the golden cases run *it*: forty-one real windows is not what
@@ -256,7 +330,7 @@ jobs:
- name: dependencies - name: dependencies
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y cmake gcc python3 sudo apt-get install -y cmake gcc python3 moreutils
# Verify the tests actually catch bugs: break the library many ways and # Verify the tests actually catch bugs: break the library many ways and
# confirm the suite fails. This matters more here than in an ordinary C # confirm the suite fails. This matters more here than in an ordinary C
# library, because the akerror control-flow macros expand at their call # library, because the akerror control-flow macros expand at their call

View File

@@ -70,10 +70,14 @@ jobs:
# corpus is part of what kills mutants and it is checked in now, so # corpus is part of what kills mutants and it is checked in now, so
# that is libakerror and libakstdlib and nothing else. # that is libakerror and libakstdlib and nothing else.
submodules: true submodules: true
# moreutils for errno(1), which deps/libakerror's scripts/generrno.sh needs
# to generate its errno table. Without it the generated table is empty and
# every AKERR_* code collapses onto a low integer -- see the long note on
# ci.yaml's cmake_build job for what that breaks and how it presents.
- name: dependencies - name: dependencies
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y cmake gcc python3 sudo apt-get install -y cmake gcc python3 moreutils
# The whole akbasic-owned src/ tree. ci.yaml runs a two-file subset on every # The whole akbasic-owned src/ tree. ci.yaml runs a two-file subset on every
# push; this is the one that actually covers the interpreter. # push; this is the one that actually covers the interpreter.
# #

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); 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. * @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 obj Scope to inspect; NULL is not waiting.
* @param command Verb to test for. * @param command Verb to test for; NULL is not waiting.
* @return `true` when that verb is what execution is waiting on. * @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. * @brief Clear a pending wait, searching the parent chain for it.
* *

View File

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

View File

@@ -7,6 +7,7 @@
#include <string.h> #include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/environment.h> #include <akbasic/environment.h>
#include <akbasic/error.h> #include <akbasic/error.h>
@@ -39,7 +40,7 @@ akerr_ErrorContext *akbasic_environment_init(akbasic_Environment *obj, akbasic_R
obj->readIdentifierIdx = 0; obj->readIdentifierIdx = 0;
obj->waitingForCommand[0] = '\0'; obj->waitingForCommand[0] = '\0';
obj->errorToken = NULL; 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.next = 0;
obj->doLeafPool.capacity = AKBASIC_MAX_CONDITION_LEAVES; obj->doLeafPool.capacity = AKBASIC_MAX_CONDITION_LEAVES;
obj->doLeafPool.leaves = obj->doLeafStorage; 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) akerr_ErrorContext *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER,
"NULL argument in wait_for_command"); "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, FAIL_NONZERO_RETURN(errctx, (obj->waitingForCommand[0] != '\0'), AKBASIC_ERR_STATE,
"Can't wait on multiple commands in the same environment : %s", "Can't wait on multiple commands in the same environment : %s",
obj->waitingForCommand); 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); AKBASIC_ERR_BOUNDS, "Command name '%s' is too long to wait on", command);
strncpy(obj->waitingForCommand, command, sizeof(obj->waitingForCommand) - 1); PASS(errctx, aksl_strcpy(obj->waitingForCommand, sizeof(obj->waitingForCommand), command));
obj->waitingForCommand[sizeof(obj->waitingForCommand) - 1] = '\0';
SUCCEED_RETURN(errctx); 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 ) { if ( obj == NULL ) {
return false; SUCCEED_RETURN(errctx);
} }
if ( obj->waitingForCommand[0] != '\0' ) { 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 ) { if ( obj == NULL || command == NULL ) {
return false; SUCCEED_RETURN(errctx);
} }
if ( strcmp(obj->waitingForCommand, command) == 0 ) { PASS(errctx, aksl_strcmp(obj->waitingForCommand, command, &cmp));
return true; 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) akerr_ErrorContext *akbasic_environment_stop_waiting(akbasic_Environment *obj, const char *command)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER,
"NULL argument in stop_waiting"); "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. * a miss is silently tolerated, exactly as today.
*/ */
while ( obj != NULL ) { while ( obj != NULL ) {
if ( strcmp(obj->waitingForCommand, command) == 0 ) { PASS(errctx, aksl_strcmp(obj->waitingForCommand, command, &cmp));
if ( cmp == 0 ) {
obj->waitingForCommand[0] = '\0'; obj->waitingForCommand[0] = '\0';
SUCCEED_RETURN(errctx); 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, FAIL_ZERO_RETURN(errctx, (obj != NULL && fname != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in get_function"); "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); FAIL_ZERO_RETURN(errctx, (len < sizeof(upper)), AKERR_KEY, "Function '%s' is not defined", fname);
for ( i = 0; i < len; i++ ) { for ( i = 0; i < len; i++ ) {
char c = fname[i]; char c = fname[i];
@@ -291,6 +312,7 @@ akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const c
akbasic_Variable *variable = NULL; akbasic_Variable *variable = NULL;
int64_t sizes[1] = { 1 }; int64_t sizes[1] = { 1 };
void *slot = NULL; void *slot = NULL;
size_t namelen = 0;
akerr_ErrorContext *found = NULL; akerr_ErrorContext *found = NULL;
FAIL_ZERO_RETURN(errctx, (obj != NULL && varname != NULL && dest != NULL), AKERR_NULLPOINTER, 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)); IGNORE(akerr_release_error(found));
PASS(errctx, akbasic_runtime_new_variable(obj->runtime, &variable)); 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); "Variable name '%s' is too long", varname);
strncpy(variable->name, varname, sizeof(variable->name) - 1); PASS(errctx, aksl_strcpy(variable->name, sizeof(variable->name), varname));
variable->name[sizeof(variable->name) - 1] = '\0';
variable->valuetype = AKBASIC_TYPE_UNDEFINED; variable->valuetype = AKBASIC_TYPE_UNDEFINED;
variable->mutable_ = true; variable->mutable_ = true;
PASS(errctx, akbasic_variable_init(variable, &obj->runtime->valuepool, sizes, 1)); PASS(errctx, akbasic_variable_init(variable, &obj->runtime->valuepool, sizes, 1));

View File

@@ -12,6 +12,7 @@
#include <string.h> #include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/format.h> #include <akbasic/format.h>
@@ -36,7 +37,7 @@ akerr_ErrorContext *akbasic_format_state_init(akbasic_FormatState *obj)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL format state in init"); 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); 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'; dest[width] = '\0';
SUCCEED_RETURN(errctx);
} }
/** /**
@@ -191,15 +201,21 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
bool leadsign = false; bool leadsign = false;
bool trailsign = false; bool trailsign = false;
bool negative = (number < 0.0); bool negative = (number < 0.0);
int written = 0;
measure_numeric(field, length, &before, &after, &commas, &dollar, &leadsign, &trailsign); measure_numeric(field, length, &before, &after, &commas, &dollar, &leadsign, &trailsign);
FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS, FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS,
"PRINT USING field of %zu characters does not fit", length); "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, '.'); PASS(errctx, aksl_strchr(digits, '.', &point));
intlen = (point != NULL ? (size_t)(point - digits) : strlen(digits)); 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. */ /* Group the integer part, if the field asked for separators. */
used = 0; used = 0;
@@ -213,7 +229,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
used += 1; used += 1;
} }
} else { } else {
memcpy(grouped, digits, intlen); PASS(errctx, aksl_memcpy(grouped, digits, intlen));
used = intlen; used = intlen;
} }
grouped[used] = '\0'; 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. * printing wider than asked, which would misalign every later column.
*/ */
if ( (int)intlen > before ) { if ( (int)intlen > before ) {
overflow(dest, length); PASS(errctx, overflow(dest, length));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -252,13 +268,13 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
used += 1; used += 1;
} }
} }
memcpy(dest + used, grouped, (size_t)group); PASS(errctx, aksl_memcpy(dest + used, grouped, (size_t)group));
used += (size_t)group; used += (size_t)group;
if ( after > 0 ) { if ( after > 0 ) {
dest[used] = obj->chars[AKBASIC_PUDEF_POINT]; dest[used] = obj->chars[AKBASIC_PUDEF_POINT];
used += 1; 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; used += (size_t)after;
} }
if ( trailsign ) { 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. * because a printed -5 that reads as 5 is worse than a row of stars.
*/ */
if ( negative && !leadsign && !trailsign ) { if ( negative && !leadsign && !trailsign ) {
overflow(dest, length); PASS(errctx, overflow(dest, length));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
dest[used] = '\0'; dest[used] = '\0';
@@ -283,26 +299,27 @@ static akerr_ErrorContext *render_string(const char *field, size_t length, const
char *dest, size_t len) char *dest, size_t len)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
size_t textlen = strlen(text); size_t textlen = 0;
size_t pad = 0; size_t pad = 0;
PASS(errctx, aksl_strlen(text, &textlen));
FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS, FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS,
"PRINT USING field of %zu characters does not fit", length); "PRINT USING field of %zu characters does not fit", length);
if ( textlen > length ) { if ( textlen > length ) {
/* Truncated, not starred: BASIC 7.0 cuts a string to its field. */ /* 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'; dest[length] = '\0';
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
memset(dest, ' ', length); PASS(errctx, aksl_memset(dest, ' ', length));
dest[length] = '\0'; dest[length] = '\0';
if ( field[0] == '=' ) { if ( field[0] == '=' ) {
pad = (length - textlen) / 2; pad = (length - textlen) / 2;
} else { } else {
pad = length - textlen; pad = length - textlen;
} }
memcpy(dest + pad, text, textlen); PASS(errctx, aksl_memcpy(dest + pad, text, textlen));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -313,6 +330,9 @@ akerr_ErrorContext *akbasic_format_using(akbasic_FormatState *obj, const char *f
size_t start = 0; size_t start = 0;
size_t length = 0; size_t length = 0;
size_t used = 0; size_t used = 0;
size_t renderedlen = 0;
size_t formatlen = 0;
int written = 0;
bool numeric = false; bool numeric = false;
FAIL_ZERO_RETURN(errctx, (obj != NULL && format != NULL && value != NULL && dest != NULL), 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. */ /* 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, FAIL_ZERO_RETURN(errctx, (used + 1 <= len), AKBASIC_ERR_BOUNDS,
"PRINT USING result of %zu characters does not fit", used); "PRINT USING result of %zu characters does not fit", used);
memcpy(dest, format, start); PASS(errctx, aksl_memcpy(dest, format, start));
memcpy(dest + start, rendered, strlen(rendered)); PASS(errctx, aksl_memcpy(dest + start, rendered, renderedlen));
snprintf(dest + start + strlen(rendered), len - start - strlen(rendered), /* The check above already proved the tail fits, so this cannot truncate. */
"%s", format + start + length); PASS(errctx, aksl_snprintf(&written, dest + start + renderedlen,
len - start - renderedlen, "%s", format + start + length));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -23,9 +23,9 @@
*/ */
#include <stddef.h> #include <stddef.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akgl/controller.h> #include <akgl/controller.h>
#include <akgl/error.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, 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); "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->width = w;
obj->height = h; obj->height = h;

View File

@@ -17,14 +17,19 @@
static akerr_ErrorContext *copy_bounded(char *dest, const char *src, const char *what) static akerr_ErrorContext *copy_bounded(char *dest, const char *src, const char *what)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
size_t srclen = 0;
FAIL_ZERO_RETURN(errctx, (src != NULL), AKERR_NULLPOINTER, "NULL %s", what); 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, AKBASIC_ERR_VALUE,
"%s of %zu characters exceeds the %d character limit", "%s of %zu characters exceeds the %d character limit",
what, strlen(src), AKBASIC_MAX_STRING_LENGTH - 1); what, srclen, AKBASIC_MAX_STRING_LENGTH - 1);
strncpy(dest, src, AKBASIC_MAX_STRING_LENGTH - 1); /* aksl_strcpy always terminates and refuses rather than truncates, which is
dest[AKBASIC_MAX_STRING_LENGTH - 1] = '\0'; 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); SUCCEED_RETURN(errctx);
} }
@@ -90,8 +95,8 @@ static akerr_ErrorContext *clone_into(akbasic_ASTLeaf *self, akbasic_LeafPool *p
copy->parent = self->parent; copy->parent = self->parent;
copy->literal_int = self->literal_int; copy->literal_int = self->literal_int;
copy->literal_float = self->literal_float; copy->literal_float = self->literal_float;
memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string)); PASS(errctx, aksl_memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string)));
memcpy(copy->identifier, self->identifier, sizeof(copy->identifier)); PASS(errctx, aksl_memcpy(copy->identifier, self->identifier, sizeof(copy->identifier)));
copy->operator_ = self->operator_; copy->operator_ = self->operator_;
PASS(errctx, clone_into(self->left, pool, &copy->left)); 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); PREPARE_ERROR(errctx);
long long value = 0; long long value = 0;
size_t lexlen = 0;
int base = 10; int base = 10;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_literal_int"); 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"); 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 * Commodore BASIC has no octal literals, and a leading zero in a listing is
* padding, not a radix. * 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; base = 16;
} }
PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_LITERAL_INT)); 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); PREPARE_ERROR(errctx);
char sub1[AKBASIC_MAX_STRING_LENGTH]; char sub1[AKBASIC_MAX_STRING_LENGTH];
char sub2[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, (self != NULL), AKERR_NULLPOINTER, "NULL leaf in to_string");
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination 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 ) { switch ( self->leaftype ) {
case AKBASIC_LEAF_LITERAL_INT: case AKBASIC_LEAF_LITERAL_INT:
snprintf(dest, len, "%" PRId64, self->literal_int); PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, self->literal_int));
break; break;
case AKBASIC_LEAF_LITERAL_FLOAT: case AKBASIC_LEAF_LITERAL_FLOAT:
snprintf(dest, len, "%f", self->literal_float); PASS(errctx, aksl_snprintf(&written, dest, len, "%f", self->literal_float));
break; break;
case AKBASIC_LEAF_LITERAL_STRING: case AKBASIC_LEAF_LITERAL_STRING:
snprintf(dest, len, "%s", self->literal_string); PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->literal_string));
break; break;
case AKBASIC_LEAF_IDENTIFIER_INT: case AKBASIC_LEAF_IDENTIFIER_INT:
case AKBASIC_LEAF_IDENTIFIER_FLOAT: case AKBASIC_LEAF_IDENTIFIER_FLOAT:
case AKBASIC_LEAF_IDENTIFIER_STRING: case AKBASIC_LEAF_IDENTIFIER_STRING:
case AKBASIC_LEAF_IDENTIFIER: case AKBASIC_LEAF_IDENTIFIER:
snprintf(dest, len, "%s", self->identifier); PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->identifier));
break; break;
case AKBASIC_LEAF_IDENTIFIER_STRUCT: case AKBASIC_LEAF_IDENTIFIER_STRUCT:
snprintf(dest, len, "%s", self->identifier); PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->identifier));
break; break;
case AKBASIC_LEAF_FIELD: case AKBASIC_LEAF_FIELD:
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1))); PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
snprintf(dest, len, "%s%s%s", sub1, PASS(errctx, aksl_snprintf(&written, dest, len, "%s%s%s", sub1,
(self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier); (self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier));
break; break;
case AKBASIC_LEAF_UNARY: case AKBASIC_LEAF_UNARY:
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1))); 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; break;
case AKBASIC_LEAF_BINARY: case AKBASIC_LEAF_BINARY:
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1))); PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
PASS(errctx, akbasic_leaf_to_string(self->right, sub2, sizeof(sub2))); 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; break;
case AKBASIC_LEAF_GROUPING: case AKBASIC_LEAF_GROUPING:
PASS(errctx, akbasic_leaf_to_string(self->expr, sub1, sizeof(sub1))); 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; break;
case AKBASIC_LEAF_COMMAND: case AKBASIC_LEAF_COMMAND:
case AKBASIC_LEAF_COMMAND_IMMEDIATE: 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 * 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. * 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; break;
default: default:
snprintf(dest, len, "(leaf %d)", (int)self->leaftype); PASS(errctx, aksl_snprintf(&written, dest, len, "(leaf %d)", (int)self->leaftype));
break; break;
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);

View File

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

View File

@@ -19,9 +19,9 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/host.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 * left it full: a BASIC string is inline and fixed, so there is nothing
* to alias and nothing that can outlive the read. * 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(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; break;
default: default:
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE, 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; char *at = (char *)hostbase + field->hostoffset;
int64_t n = 0; int64_t n = 0;
double d = 0.0; double d = 0.0;
size_t len = 0;
(void)obj; (void)obj;
FAIL_ZERO_RETURN(errctx, (hostbase != NULL && src != NULL), AKERR_NULLPOINTER, 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 * eight of them. BASIC strings have no width, so this is the one place
* the two models genuinely disagree and the disagreement is reported. * 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, AKBASIC_ERR_VALUE,
"A string of %zu characters does not fit in %s, which holds %zu", "A string of %zu characters does not fit in %s, which holds %zu",
strlen(src->stringval), field->name, field->hostwidth - 1); len, field->name, field->hostwidth - 1);
memset(at, 0, field->hostwidth); PASS(errctx, aksl_memset(at, 0, field->hostwidth));
memcpy(at, src->stringval, strlen(src->stringval)); PASS(errctx, aksl_memcpy(at, src->stringval, len));
break; break;
default: default:
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE, 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); type->name, type->fieldcount, AKBASIC_MAX_STRUCT_FIELDS);
dest = &obj->structtypes.types[obj->structtypes.count]; 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); snprintf(dest->name, sizeof(dest->name), "%s", type->name);
dest->used = true; dest->used = true;
dest->ishost = 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, FAIL_ZERO_RETURN(errctx, (src->name != NULL), AKERR_NULLPOINTER,
"%s field %d has no name", type->name, i); "%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 * 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 * 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", "%s.%s must end in '%c' for the C type it describes",
type->name, src->name, suffix_for(src->kind)); 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); snprintf(field->name, sizeof(field->name), "%s", src->name);
field->hostkind = src->kind; field->hostkind = src->kind;
field->hostoffset = src->offset; 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), FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && typename_ != NULL && instance != NULL),
AKERR_NULLPOINTER, "NULL argument in host bind"); 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, 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); "A structure variable's name ends in '@', so \"%s\" cannot be bound", name);
PASS(errctx, akbasic_structtype_find(&obj->structtypes, typename_, &typeindex)); 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"); const char *fontpath = getenv("AKBASIC_FONT");
FILE *input = program; FILE *input = program;
int mode = AKBASIC_MODE_RUNSTREAM; int mode = AKBASIC_MODE_RUNSTREAM;
int fd = 0;
if ( fontpath == NULL ) { if ( fontpath == NULL ) {
fontpath = AKBASIC_FONT_PATH; 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 * in an AKGL build, and the golden corpus can be driven through this
* binary as well as through the stdio one. * 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; mode = AKBASIC_MODE_REPL;
} }

View File

@@ -10,9 +10,9 @@
*/ */
#include <inttypes.h> #include <inttypes.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/parser.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_)); PASS(errctx, akbasic_parser_previous(obj, &operator_));
optype = operator_->tokentype; optype = operator_->tokentype;
strncpy(opname, operator_->lexeme, sizeof(opname) - 1); PASS(errctx, aksl_strcpy(opname, sizeof(opname), operator_->lexeme));
opname[sizeof(opname) - 1] = '\0';
/* Does this verb need its own parse path? */ /* Does this verb need its own parse path? */
PASS(errctx, akbasic_verb_lookup(opname, &verb)); 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_)); PASS(errctx, akbasic_parser_previous(obj, &operator_));
strncpy(fname, operator_->lexeme, sizeof(fname) - 1); PASS(errctx, aksl_strcpy(fname, sizeof(fname), operator_->lexeme));
fname[sizeof(fname) - 1] = '\0';
PASS(errctx, akbasic_verb_lookup(fname, &verb)); PASS(errctx, akbasic_verb_lookup(fname, &verb));
if ( verb != NULL && verb->tokentype == AKBASIC_TOK_FUNCTION ) { if ( verb != NULL && verb->tokentype == AKBASIC_TOK_FUNCTION ) {

View File

@@ -10,10 +10,9 @@
*/ */
#include <inttypes.h> #include <inttypes.h>
#include <string.h>
#include <strings.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/parser.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 *arglist = NULL;
akbasic_ASTLeaf *expr = NULL; akbasic_ASTLeaf *expr = NULL;
akbasic_Token *peeked = NULL; akbasic_Token *peeked = NULL;
int cmp = 0;
peeked = akbasic_parser_peek(parser); peeked = akbasic_parser_peek(parser);
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND && if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
strcmp(peeked->lexeme, "CLR") == 0 ) { PASS(errctx, aksl_strcmp(peeked->lexeme, "CLR", &cmp));
if ( cmp == 0 ) {
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND); (void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist)); PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST; arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
@@ -93,6 +94,7 @@ akerr_ErrorContext *akbasic_parse_graphic(akbasic_Parser *parser, akbasic_ASTLea
*dest = expr; *dest = expr;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
}
PASS(errctx, akbasic_parse_arglist(parser, dest)); PASS(errctx, akbasic_parse_arglist(parser, dest));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -105,6 +107,7 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
akbasic_ASTLeaf *tail = NULL; akbasic_ASTLeaf *tail = NULL;
akbasic_Token *peeked = NULL; akbasic_Token *peeked = NULL;
akbasic_Token *operator_ = NULL; akbasic_Token *operator_ = NULL;
int cmp = 0;
/* /*
* DRAW source, x1,y1 TO x2,y2 TO x3,y3 -- a polyline, with TO between pairs * 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 ( ;; ) { for ( ;; ) {
peeked = akbasic_parser_peek(parser); peeked = akbasic_parser_peek(parser);
if ( peeked == NULL || if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ) {
peeked->tokentype != AKBASIC_TOK_COMMAND || break;
strcmp(peeked->lexeme, "TO") != 0 ) { }
PASS(errctx, aksl_strcmp(peeked->lexeme, "TO", &cmp));
if ( cmp != 0 ) {
break; break;
} }
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND), 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); PREPARE_ERROR(errctx);
akbasic_Token *peeked = NULL; akbasic_Token *peeked = NULL;
int cmp = 0;
*condition = NULL; *condition = NULL;
*kind = AKBASIC_LOOPCOND_NONE; *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 ) { if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ) {
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
if ( strcmp(peeked->lexeme, "WHILE") == 0 ) { PASS(errctx, aksl_strcmp(peeked->lexeme, "WHILE", &cmp));
if ( cmp == 0 ) {
*kind = AKBASIC_LOOPCOND_WHILE; *kind = AKBASIC_LOOPCOND_WHILE;
} else if ( strcmp(peeked->lexeme, "UNTIL") == 0 ) {
*kind = AKBASIC_LOOPCOND_UNTIL;
} else { } else {
PASS(errctx, aksl_strcmp(peeked->lexeme, "UNTIL", &cmp));
if ( cmp != 0 ) {
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
*kind = AKBASIC_LOOPCOND_UNTIL;
}
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND); (void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
/* A loop condition is a condition, so a lone `=` in it is an equality test. */ /* 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 *expr = NULL;
akbasic_ASTLeaf *tail = NULL; akbasic_ASTLeaf *tail = NULL;
akbasic_Token *operator_ = NULL; akbasic_Token *operator_ = NULL;
int cmp = 0;
bool gosub = false; bool gosub = false;
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist)); 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), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Expected GOTO or GOSUB after ON (expression)"); AKBASIC_ERR_SYNTAX, "Expected GOTO or GOSUB after ON (expression)");
PASS(errctx, akbasic_parser_previous(parser, &operator_)); 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; gosub = true;
} else { } 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)"); "Expected GOTO or GOSUB after ON (expression)");
} }
marker->literal_int = (gosub ? 1 : 0); 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 *expr = NULL;
akbasic_ASTLeaf *target = NULL; akbasic_ASTLeaf *target = NULL;
akbasic_Token *peeked = NULL; akbasic_Token *peeked = NULL;
int cmp = 0;
peeked = akbasic_parser_peek(parser); peeked = akbasic_parser_peek(parser);
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND && if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
strcmp(peeked->lexeme, "NEXT") == 0 ) { PASS(errctx, aksl_strcmp(peeked->lexeme, "NEXT", &cmp));
if ( cmp == 0 ) {
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND); (void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
PASS(errctx, akbasic_parser_new_leaf(parser, &target)); PASS(errctx, akbasic_parser_new_leaf(parser, &target));
PASS(errctx, akbasic_leaf_new_command(target, "NEXT", NULL)); 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)); 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 *arglist = NULL;
akbasic_ASTLeaf *right = NULL; akbasic_ASTLeaf *right = NULL;
akbasic_Token *peeked = NULL; akbasic_Token *peeked = NULL;
int cmp = 0;
bool matched = false;
peeked = akbasic_parser_peek(parser); peeked = akbasic_parser_peek(parser);
@@ -499,8 +519,11 @@ akerr_ErrorContext *akbasic_parse_print(akbasic_Parser *parser, akbasic_ASTLeaf
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND || if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
strcmp(peeked->lexeme, "USING") != 0 ) { PASS(errctx, aksl_strcmp(peeked->lexeme, "USING", &cmp));
matched = (cmp == 0);
}
if ( !matched ) {
/* The ordinary PRINT: one expression, or none at all. */ /* The ordinary PRINT: one expression, or none at all. */
if ( peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) { if ( peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
PASS(errctx, akbasic_parser_expression(parser, &right)); 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 *arglist = NULL;
akbasic_ASTLeaf *command = NULL; akbasic_ASTLeaf *command = NULL;
akbasic_Token *word = NULL; akbasic_Token *word = NULL;
int cmp = 0;
PASS(errctx, akbasic_parser_expression(parser, &pointer)); PASS(errctx, akbasic_parser_expression(parser, &pointer));
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected POINT <pointer> AT <structure>"); AKBASIC_ERR_SYNTAX, "Expected POINT <pointer> AT <structure>");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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); "Expected AT after POINT <pointer>, not %s", word->lexeme);
PASS(errctx, akbasic_parser_expression(parser, &target)); PASS(errctx, akbasic_parser_expression(parser, &target));
@@ -668,6 +693,7 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_ASTLeaf *command = NULL; akbasic_ASTLeaf *command = NULL;
akbasic_Token *word = NULL; akbasic_Token *word = NULL;
int cmp = 0;
PASS(errctx, parse_verb_with_identifier(parser, "DIM", dest)); PASS(errctx, parse_verb_with_identifier(parser, "DIM", dest));
command = *dest; command = *dest;
@@ -676,13 +702,15 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
PASS(errctx, akbasic_parser_previous(parser, &word)); 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); "Expected AS after DIM %s, not %s", command->right->identifier, word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after AS"); AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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; command->literal_int = 1;
/* /*
* `TO` is already a verb -- FOR ... TO owns it -- so it arrives as a * `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), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Expected TO after PTR"); AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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); "Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO"); AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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); SUCCEED_RETURN(errctx);
} }
@@ -738,6 +767,7 @@ static akerr_ErrorContext *parse_def_parameters(akbasic_Parser *parser, akbasic_
akbasic_ASTLeaf *param = NULL; akbasic_ASTLeaf *param = NULL;
akbasic_ASTLeaf *tail = NULL; akbasic_ASTLeaf *tail = NULL;
akbasic_Token *word = NULL; akbasic_Token *word = NULL;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_LEFT_PAREN), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_LEFT_PAREN),
AKBASIC_ERR_SYNTAX, "Expected an argument list after DEF <name>"); 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", "%s must name its type: %s AS TYPENAME, or %s AS PTR TO TYPENAME",
param->identifier, param->identifier, param->identifier); param->identifier, param->identifier, param->identifier);
PASS(errctx, akbasic_parser_previous(parser, &word)); 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); "Expected AS after %s, not %s", param->identifier, word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after AS"); AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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; param->literal_int = 1;
/* `TO` is already a verb -- FOR ... TO owns it -- so it arrives /* `TO` is already a verb -- FOR ... TO owns it -- so it arrives
as a command token rather than an identifier. */ as a command token rather than an identifier. */
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Expected TO after PTR"); AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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); "Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO"); AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
PASS(errctx, akbasic_parser_previous(parser, &word)); 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 ) { if ( tail == NULL ) {
@@ -806,6 +839,7 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
akbasic_ASTLeaf *expression = NULL; akbasic_ASTLeaf *expression = NULL;
akbasic_ASTLeaf *command = NULL; akbasic_ASTLeaf *command = NULL;
akbasic_FunctionDef *fndef = NULL; akbasic_FunctionDef *fndef = NULL;
size_t namelen = 0;
size_t i = 0; size_t i = 0;
PASS(errctx, akbasic_parser_primary(parser, &identifier)); 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)); PASS(errctx, akbasic_runtime_new_function(runtime, &fndef));
/* Uppercase the name: verbs and function names are case-insensitive. */ /* 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); 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]; char c = identifier->identifier[i];
fndef->name[i] = (char)((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c); 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) ) { if ( akbasic_parser_match1(parser, AKBASIC_TOK_ASSIGNMENT) ) {
PASS(errctx, akbasic_parser_expression(parser, &expression)); 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 *parent = runtime->environment;
akbasic_Environment *newenv = NULL; akbasic_Environment *newenv = NULL;
int64_t firstline = 0; int64_t firstline = 0;
int cmp = 0;
PASS(errctx, akbasic_parser_assignment(parser, &assignment)); PASS(errctx, akbasic_parser_assignment(parser, &assignment));
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, AKBASIC_ERR_SYNTAX,
"Expected FOR (assignment) TO (expression) [STEP (expression)]"); "Expected FOR (assignment) TO (expression) [STEP (expression)]");
PASS(errctx, akbasic_parser_previous(parser, &operator_)); 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)]"); "Expected FOR (assignment) TO (expression) [STEP (expression)]");
FAIL_ZERO_RETURN(errctx, FAIL_ZERO_RETURN(errctx,
(assignment != NULL && akbasic_leaf_is_identifier(assignment->left)), (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) ) { if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
PASS(errctx, akbasic_parser_previous(parser, &operator_)); 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)]"); "Expected FOR (assignment) TO (expression) [STEP (expression)]");
PASS(errctx, akbasic_parser_expression(parser, &newenv->forStepLeaf)); PASS(errctx, akbasic_parser_expression(parser, &newenv->forStepLeaf));
} else { } 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. */ /* 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->forNextVariable = parent->forNextVariable;
} }
newenv->loopFirstLine = firstline; 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 *else_command = NULL;
akbasic_ASTLeaf *branch = NULL; akbasic_ASTLeaf *branch = NULL;
akbasic_Token *operator_ = NULL; akbasic_Token *operator_ = NULL;
int cmp = 0;
/* /*
* Everything from here to the THEN is a condition, so a lone `=` in it is an * 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), FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
AKBASIC_ERR_SYNTAX, "Incomplete IF statement"); AKBASIC_ERR_SYNTAX, "Incomplete IF statement");
PASS(errctx, akbasic_parser_previous(parser, &operator_)); 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"); "Expected IF ... THEN");
PASS(errctx, akbasic_parser_command(parser, &then_command)); PASS(errctx, akbasic_parser_command(parser, &then_command));
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) { if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
PASS(errctx, akbasic_parser_previous(parser, &operator_)); 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 ..."); "Expected IF ... THEN ... ELSE ...");
PASS(errctx, akbasic_parser_command(parser, &else_command)); PASS(errctx, akbasic_parser_command(parser, &else_command));
} }

View File

@@ -15,9 +15,9 @@
*/ */
#include <ctype.h> #include <ctype.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/audio.h> #include <akbasic/audio.h>
#include <akbasic/error.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, FAIL_ZERO_RETURN(errctx, (obj != NULL && notes != NULL), AKERR_NULLPOINTER,
"NULL argument in play_parse"); "NULL argument in play_parse");
audio = &obj->audio_state; 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 * 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 <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/runtime.h> #include <akbasic/runtime.h>
@@ -103,7 +102,7 @@ static akerr_ErrorContext *rewrite_targets(akbasic_TargetWalk *walk, const char
for ( ;; ) { for ( ;; ) {
char replacement[32]; char replacement[32];
int64_t line = 0; int64_t line = 0;
int written = 0; size_t written = 0;
while ( *p == ' ' || *p == '\t' ) { while ( *p == ' ' || *p == '\t' ) {
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS, 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; p += 1;
} }
PASS(errctx, walk->visit(walk, line, replacement, sizeof(replacement))); 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, FAIL_ZERO_RETURN(errctx, (written > 0 && out + written < limit), AKBASIC_ERR_BOUNDS,
"RENUMBER: a rewritten line does not fit"); "RENUMBER: a rewritten line does not fit");
PASS(errctx, aksl_strcpy(out, (size_t)(limit - out), replacement));
out += written; out += written;
/* /*
@@ -176,6 +182,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
const char *limit = dest + len - 1; const char *limit = dest + len - 1;
bool statementstart = true; bool statementstart = true;
bool instring = false; bool instring = false;
int cmp = 0;
int i = 0; int i = 0;
while ( *p != '\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. * name in the language to reach it.
*/ */
for ( i = 0; i < BRANCH_VERB_COUNT; i++ ) { 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]) ) { if ( p > code && isalnum((unsigned char)p[-1]) ) {
break; break;
} }
if ( strncasecmp(p, BRANCH_VERBS[i], verblen) == 0 && PASS(errctx, aksl_strncasecmp(p, BRANCH_VERBS[i], verblen, &cmp));
!isalnum((unsigned char)p[verblen]) ) { if ( cmp == 0 && !isalnum((unsigned char)p[verblen]) ) {
matched = true; matched = true;
break; break;
} }
@@ -247,7 +254,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
if ( matched ) { if ( matched ) {
FAIL_ZERO_RETURN(errctx, (out + verblen < (size_t)(limit - dest) + dest), FAIL_ZERO_RETURN(errctx, (out + verblen < (size_t)(limit - dest) + dest),
AKBASIC_ERR_BOUNDS, "RENUMBER: a rewritten line does not fit"); AKBASIC_ERR_BOUNDS, "RENUMBER: a rewritten line does not fit");
memcpy(out, p, verblen); PASS(errctx, aksl_memcpy(out, p, verblen));
out += verblen; out += verblen;
p += verblen; p += verblen;
PASS(errctx, rewrite_targets(walk, &p, &out, limit)); 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 * cannot be pointed at it directly. Copy the type and the comma, then
* rewrite what follows. * 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])) ) { && !(p > code && isalnum((unsigned char)p[-1])) ) {
memcpy(out, p, 9); PASS(errctx, aksl_memcpy(out, p, 9));
out += 9; out += 9;
p += 9; p += 9;
while ( *p != '\0' && *p != ',' && *p != ':' ) { while ( *p != '\0' && *p != ',' && *p != ':' ) {
@@ -299,8 +307,9 @@ static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t targ
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
const int64_t *map = (const int64_t *)walk->self; 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); SUCCEED_RETURN(errctx);
} }
@@ -347,7 +356,7 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
next += increment; next += increment;
} }
memset(rewritten, 0, sizeof(rewritten)); PASS(errctx, aksl_memset(rewritten, 0, sizeof(rewritten)));
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) { for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
int64_t target = 0; int64_t target = 0;
@@ -371,7 +380,7 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
rewritten[target].numbered = true; rewritten[target].numbered = true;
} }
memcpy(obj->source, rewritten, sizeof(obj->source)); PASS(errctx, aksl_memcpy(obj->source, rewritten, sizeof(obj->source)));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -402,8 +411,9 @@ static akerr_ErrorContext *visit_check(akbasic_TargetWalk *walk, int64_t target,
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
CheckState *state = (CheckState *)walk->self; 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 ) { if ( target >= 0 && target < AKBASIC_MAX_SOURCE_LINES ) {
FAIL_NONZERO_RETURN(errctx, FAIL_NONZERO_RETURN(errctx,
(state->runtime->source[target].code[0] != '\0' (state->runtime->source[target].code[0] != '\0'

View File

@@ -6,10 +6,9 @@
#include <ctype.h> #include <ctype.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <strings.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/parser.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"); "NULL argument in new_variable");
for ( i = 0; i < AKBASIC_MAX_VARIABLES; i++ ) { for ( i = 0; i < AKBASIC_MAX_VARIABLES; i++ ) {
if ( !obj->variables[i].used ) { 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; obj->variables[i].used = true;
/* /*
* Not zero: zero is a valid structure type index, so a memset alone * 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"); "NULL argument in new_function");
for ( i = 0; i < AKBASIC_MAX_FUNCTIONS; i++ ) { for ( i = 0; i < AKBASIC_MAX_FUNCTIONS; i++ ) {
if ( !obj->functions[i].used ) { 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].used = true;
obj->functions[i].leafpool.next = 0; obj->functions[i].leafpool.next = 0;
obj->functions[i].leafpool.capacity = AKBASIC_MAX_LEAVES * 2; 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()); PASS(errctx, akbasic_error_register());
memset(obj, 0, sizeof(*obj)); PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->sink = sink; obj->sink = sink;
obj->environment = NULL; obj->environment = NULL;
obj->autoLineNumber = 0; 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) akerr_ErrorContext *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const char *path)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
const char *slash = NULL; char *slash = NULL;
size_t length = 0; size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in set_source_path"); 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 * 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. * 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)); length = (slash == NULL ? 0 : (size_t)(slash - path));
if ( length == 0 ) { if ( length == 0 ) {
/* Either no directory at all, or the root. */ /* Either no directory at all, or the root. */
strncpy(obj->sourcepath, (slash == NULL ? "." : "/"), sizeof(obj->sourcepath) - 1); PASS(errctx, aksl_strcpy(obj->sourcepath, sizeof(obj->sourcepath),
obj->sourcepath[sizeof(obj->sourcepath) - 1] = '\0'; (slash == NULL ? "." : "/")));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->sourcepath)), AKBASIC_ERR_BOUNDS, FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->sourcepath)), AKBASIC_ERR_BOUNDS,
"Program path of %zu characters exceeds the %d character limit", "Program path of %zu characters exceeds the %d character limit",
length, AKBASIC_MAX_LINE_LENGTH - 1); length, AKBASIC_MAX_LINE_LENGTH - 1);
memcpy(obj->sourcepath, path, length); PASS(errctx, aksl_memcpy(obj->sourcepath, path, length));
obj->sourcepath[length] = '\0'; obj->sourcepath[length] = '\0';
SUCCEED_RETURN(errctx); 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 * The format, the trailing \n inside the string, and the second newline
* writeln adds are all part of the acceptance contract -- * writeln adds are all part of the acceptance contract --
* tests/language/array_outofbounds.txt ends in 0a 0a. See TODO.md 1.8. * 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", snprintf(line, sizeof(line), "? %" PRId64 " : %s %s\n",
obj->environment->lineno, errclass_to_string(errclass), message); 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) { } PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) { } HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH]; 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; obj->lasterrorstatus = errctx->status;
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message)); IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
IGNORE(akbasic_runtime_set_mode(obj, obj->run_finished_mode)); 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]; char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
int status = cause->status; 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. */ /* What ER# reports, if a TRAP is armed. Recorded before the context goes. */
obj->lasterrorstatus = status; obj->lasterrorstatus = status;
cause->handled = true; cause->handled = true;
@@ -675,6 +699,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
akbasic_Value *rval = NULL; akbasic_Value *rval = NULL;
akbasic_Value *scratch = NULL; akbasic_Value *scratch = NULL;
const akbasic_Verb *verb = NULL; const akbasic_Verb *verb = NULL;
int cmp = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in evaluate"); "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 * reaches the end of this line. Arming the wait is what makes a
* multi-line IF possible at all; BEND clears it. * multi-line IF possible at all; BEND clears it.
*/ */
if ( notaken != NULL && notaken->leaftype == AKBASIC_LEAF_COMMAND && if ( notaken != NULL && notaken->leaftype == AKBASIC_LEAF_COMMAND ) {
strcmp(notaken->identifier, "BEGIN") == 0 ) { PASS(errctx, aksl_strcmp(notaken->identifier, "BEGIN", &cmp));
if ( cmp == 0 ) {
PASS(errctx, akbasic_environment_wait_for_command(obj->environment, "BEND")); PASS(errctx, akbasic_environment_wait_for_command(obj->environment, "BEND"));
} }
}
if ( taken ) { if ( taken ) {
PASS(errctx, akbasic_runtime_evaluate(obj, expr->left, dest)); PASS(errctx, akbasic_runtime_evaluate(obj, expr->left, dest));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -765,7 +792,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
case AKBASIC_LEAF_LITERAL_STRING: case AKBASIC_LEAF_LITERAL_STRING:
lval->valuetype = AKBASIC_TYPE_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); SUCCEED_RETURN(errctx);
case AKBASIC_LEAF_UNARY: 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) akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); 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, FAIL_ZERO_RETURN(errctx, (obj != NULL && expr != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in interpret"); "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 * 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. * that the loop condition is evaluated at the bottom of the structure.
*/ */
if ( akbasic_environment_is_waiting_for_any(obj->environment) ) { PASS(errctx, akbasic_environment_is_waiting_for_any(obj->environment, &waiting));
if ( expr->leaftype != AKBASIC_LEAF_COMMAND || if ( waiting ) {
!akbasic_environment_is_waiting_for(obj->environment, expr->identifier) ) { /*
* 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 * **A skipped loop has already pushed its scope, and this is where it
* comes back.** * comes back.**
@@ -860,12 +903,19 @@ akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTL
*/ */
if ( expr->leaftype == AKBASIC_LEAF_COMMAND && if ( expr->leaftype == AKBASIC_LEAF_COMMAND &&
obj->environment->loopFirstLine != 0 && obj->environment->loopFirstLine != 0 &&
obj->environment->waitingForCommand[0] == '\0' && obj->environment->waitingForCommand[0] == '\0' ) {
akbasic_environment_is_waiting_for(obj->environment, "BEND") && PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "BEND",
(strcmp(expr->identifier, "FOR") == 0 || &waitingbend));
strcmp(expr->identifier, "DO") == 0) ) { 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)); PASS(errctx, akbasic_runtime_prev_environment(obj));
} }
}
}
*dest = &obj->staticTrueValue; *dest = &obj->staticTrueValue;
SUCCEED_RETURN(errctx); 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) akerr_ErrorContext *akbasic_runtime_store_line(akbasic_Runtime *obj, int64_t lineno, const char *code, bool numbered)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
size_t length = 0;
FAIL_ZERO_RETURN(errctx, (lineno >= 0 && lineno < AKBASIC_MAX_SOURCE_LINES), FAIL_ZERO_RETURN(errctx, (lineno >= 0 && lineno < AKBASIC_MAX_SOURCE_LINES),
AKBASIC_ERR_BOUNDS, AKBASIC_ERR_BOUNDS,
"Line number %" PRId64 " is outside 0..%d", "Line number %" PRId64 " is outside 0..%d",
lineno, AKBASIC_MAX_SOURCE_LINES - 1); 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); "Source line exceeds the %d character limit", AKBASIC_MAX_LINE_LENGTH - 1);
strncpy(obj->source[lineno].code, code, AKBASIC_MAX_LINE_LENGTH - 1); /* aksl_strcpy always terminates and refuses rather than truncates; the
obj->source[lineno].code[AKBASIC_MAX_LINE_LENGTH - 1] = '\0'; 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].lineno = lineno;
obj->source[lineno].numbered = numbered; obj->source[lineno].numbered = numbered;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -1242,11 +1295,12 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
akbasic_ASTLeaf *leaf = NULL; akbasic_ASTLeaf *leaf = NULL;
akbasic_Value *value = NULL; akbasic_Value *value = NULL;
akbasic_Parser parser; akbasic_Parser parser;
int written = 0;
bool eof = false; bool eof = false;
if ( obj->autoLineNumber > 0 ) { if ( obj->autoLineNumber > 0 ) {
snprintf(prompt, sizeof(prompt), "%" PRId64 " ", PASS(errctx, aksl_snprintf(&written, prompt, sizeof(prompt), "%" PRId64 " ",
obj->environment->lineno + obj->autoLineNumber); obj->environment->lineno + obj->autoLineNumber));
PASS(errctx, akbasic_runtime_write(obj, prompt)); PASS(errctx, akbasic_runtime_write(obj, prompt));
} }
@@ -1272,7 +1326,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
} PROCESS(errctx) { } PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) { } HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH]; 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; obj->lasterrorstatus = errctx->status;
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message)); IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
} FINISH(errctx, false); } FINISH(errctx, false);
@@ -1337,14 +1392,14 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
akbasic_ASTLeaf *leaf = NULL; akbasic_ASTLeaf *leaf = NULL;
akbasic_Value *value = NULL; akbasic_Value *value = NULL;
akbasic_Parser parser; akbasic_Parser parser;
int written = 0;
if ( obj->environment->nextline >= AKBASIC_MAX_SOURCE_LINES ) { if ( obj->environment->nextline >= AKBASIC_MAX_SOURCE_LINES ) {
PASS(errctx, akbasic_runtime_set_mode(obj, obj->run_finished_mode)); PASS(errctx, akbasic_runtime_set_mode(obj, obj->run_finished_mode));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
strncpy(line, obj->source[obj->environment->nextline].code, sizeof(line) - 1); PASS(errctx, aksl_strcpy(line, sizeof(line), obj->source[obj->environment->nextline].code));
line[sizeof(line) - 1] = '\0';
obj->environment->lineno = obj->environment->nextline; obj->environment->lineno = obj->environment->nextline;
obj->environment->nextline += 1; obj->environment->nextline += 1;
if ( line[0] == '\0' ) { if ( line[0] == '\0' ) {
@@ -1358,7 +1413,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
*/ */
if ( obj->trace ) { if ( obj->trace ) {
char tracemark[32]; 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)); PASS(errctx, akbasic_runtime_write(obj, tracemark));
} }
@@ -1373,7 +1429,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
} PROCESS(errctx) { } PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) { } HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH]; 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 * What ER# reports, recorded before the context goes -- the same line
* report_and_reraise() carries for a runtime error, and it was missing * 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); PREPARE_ERROR(errctx);
const char *cursor = code; const char *cursor = code;
int cmp = 0;
bool statementstart = true; bool statementstart = true;
bool instring = false; bool instring = false;
@@ -1479,8 +1537,9 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
} }
continue; continue;
} }
if ( statementstart && strncasecmp(cursor, "LABEL", 5) == 0 if ( statementstart ) {
&& !isalnum((unsigned char)cursor[5]) ) { PASS(errctx, aksl_strncasecmp(cursor, "LABEL", 5, &cmp));
if ( cmp == 0 && !isalnum((unsigned char)cursor[5]) ) {
char name[AKBASIC_SYMTAB_MAX_KEY]; char name[AKBASIC_SYMTAB_MAX_KEY];
size_t used = 0; size_t used = 0;
@@ -1505,6 +1564,7 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
statementstart = false; statementstart = false;
continue; continue;
} }
}
statementstart = false; statementstart = false;
cursor += 1; cursor += 1;
} }
@@ -1536,6 +1596,7 @@ akerr_ErrorContext *akbasic_runtime_arm_interrupt(akbasic_Runtime *obj, akbasic_
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Interrupt *slot = NULL; 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, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in arm_interrupt");
FAIL_ZERO_RETURN(errctx, (source >= 0 && source < AKBASIC_MAX_INTERRUPTS), 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->line = line;
slot->label[0] = '\0'; slot->label[0] = '\0';
if ( label != NULL && 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", "Handler label \"%s\" exceeds the %zu character limit",
label, sizeof(slot->label) - 1); label, sizeof(slot->label) - 1);
strncpy(slot->label, label, sizeof(slot->label) - 1); /* aksl_strcpy always terminates and refuses rather than truncates; the
slot->label[sizeof(slot->label) - 1] = '\0'; length check above is what makes the refusal unreachable. */
PASS(errctx, aksl_strcpy(slot->label, sizeof(slot->label), label));
} }
SUCCEED_RETURN(errctx); 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), FAIL_ZERO_RETURN(errctx, (source >= 0 && source < AKBASIC_MAX_INTERRUPTS),
AKBASIC_ERR_BOUNDS, "Interrupt source %d is outside 0..%d", AKBASIC_ERR_BOUNDS, "Interrupt source %d is outside 0..%d",
(int)source, AKBASIC_MAX_INTERRUPTS - 1); (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); SUCCEED_RETURN(errctx);
} }
@@ -1682,6 +1745,7 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
char scanned[AKBASIC_MAX_LINE_LENGTH]; char scanned[AKBASIC_MAX_LINE_LENGTH];
const char *cursor = NULL; const char *cursor = NULL;
const char *eol = NULL; const char *eol = NULL;
char *match = NULL;
size_t length = 0; size_t length = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in load"); 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; obj->environment->lineno = 0;
for ( cursor = source; *cursor != '\0'; cursor = (*eol == '\0' ? eol : eol + 1) ) { 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 ) { if ( eol == NULL ) {
eol = cursor + strlen(cursor); PASS(errctx, aksl_strlen(cursor, &length));
eol = cursor + length;
} }
length = (size_t)(eol - cursor); length = (size_t)(eol - cursor);
if ( length > 0 && cursor[length - 1] == '\r' ) { 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, FAIL_ZERO_RETURN(errctx, (length < sizeof(line)), AKBASIC_ERR_BOUNDS,
"Source line of %zu characters exceeds the %d character limit", "Source line of %zu characters exceeds the %d character limit",
length, AKBASIC_MAX_LINE_LENGTH - 1); length, AKBASIC_MAX_LINE_LENGTH - 1);
memcpy(line, cursor, length); PASS(errctx, aksl_memcpy(line, cursor, length));
line[length] = '\0'; line[length] = '\0';
if ( line[0] == '\0' ) { if ( line[0] == '\0' ) {
continue; continue;
@@ -1815,7 +1881,8 @@ akerr_ErrorContext *akbasic_runtime_step(akbasic_Runtime *obj)
} PROCESS(errctx) { } PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) { } HANDLE_DEFAULT(errctx) {
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH]; 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)); IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_RUNTIME, message));
} FINISH(errctx, false); } FINISH(errctx, false);
if ( obj->errclass != AKBASIC_ERRCLASS_NONE ) { if ( obj->errclass != AKBASIC_ERRCLASS_NONE ) {

View File

@@ -9,7 +9,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.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)->valuetype == AKBASIC_TYPE_STRUCT || (*dest)->valuetype == AKBASIC_TYPE_POINTER ) {
if ( (*dest)->structbase == NULL ) { if ( (*dest)->structbase == NULL ) {
snprintf(rendered, sizeof(rendered), "NOTHING"); PASS(errctx, aksl_strcpy(rendered, sizeof(rendered), "NOTHING"));
} else { } else {
PASS(errctx, akbasic_struct_to_string(obj, (*dest)->structtype, (*dest)->structbase, PASS(errctx, akbasic_struct_to_string(obj, (*dest)->structtype, (*dest)->structbase,
0, rendered, sizeof(rendered))); 0, rendered, sizeof(rendered)));
@@ -149,13 +148,15 @@ akerr_ErrorContext *akbasic_cmd_return(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Value *result = NULL; akbasic_Value *result = NULL;
bool waiting = false;
(void)lval; (void)rval; (void)lval; (void)rval;
/* /*
* A RETURN reached while skipping forward to one is the end of a DEF body, * A RETURN reached while skipping forward to one is the end of a DEF body,
* not a subroutine return. Stop waiting and carry on. * 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")); PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "RETURN"));
SUCCEED_TRUE(obj, dest); SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx); 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 rendered[AKBASIC_MAX_STRING_LENGTH];
char buffer[AKBASIC_MAX_LINE_LENGTH]; char buffer[AKBASIC_MAX_LINE_LENGTH];
long long converted = 0; long long converted = 0;
size_t len = 0;
bool eof = false; bool eof = false;
(void)lval; (void)rval; (void)lval; (void)rval;
@@ -479,10 +481,11 @@ akerr_ErrorContext *akbasic_cmd_input(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
break; break;
default: default:
entered->valuetype = AKBASIC_TYPE_STRING; 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); "Input line exceeds the %d character limit", AKBASIC_MAX_STRING_LENGTH - 1);
strncpy(entered->stringval, buffer, AKBASIC_MAX_STRING_LENGTH - 1); /* aksl_strcpy always terminates, so the explicit terminator is gone. */
entered->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0'; PASS(errctx, aksl_strcpy(entered->stringval, sizeof(entered->stringval), buffer));
break; break;
} }
PASS(errctx, akbasic_environment_assign(obj->environment, identifier, entered, &unused)); 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 startidx = 0;
int64_t endidx = 0; int64_t endidx = 0;
int64_t i = 0; int64_t i = 0;
int written = 0;
(void)lval; (void)rval; (void)lval; (void)rval;
PASS(errctx, parse_line_range(obj, expr, &startidx, &endidx)); 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' ) { if ( obj->source[i].code[0] == '\0' ) {
continue; 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)); PASS(errctx, akbasic_runtime_println(obj, line));
} }
SUCCEED_TRUE(obj, dest); SUCCEED_TRUE(obj, dest);
@@ -588,6 +593,7 @@ static akerr_ErrorContext *filename_argument(akbasic_Runtime *obj, akbasic_ASTLe
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Value *value = NULL; akbasic_Value *value = NULL;
size_t namelen = 0;
FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX, FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX,
"Expected a filename"); "Expected a filename");
@@ -596,10 +602,107 @@ static akerr_ErrorContext *filename_argument(akbasic_Runtime *obj, akbasic_ASTLe
"Expected a filename string"); "Expected a filename string");
FAIL_ZERO_RETURN(errctx, (value->stringval[0] != '\0'), AKBASIC_ERR_VALUE, FAIL_ZERO_RETURN(errctx, (value->stringval[0] != '\0'), AKBASIC_ERR_VALUE,
"Filename must not be empty"); "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); "Filename exceeds the %zu character limit", len - 1);
strncpy(dest, value->stringval, len - 1); /* aksl_strcpy always terminates, so the explicit terminator is gone. */
dest[len - 1] = '\0'; 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); 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 buffer[AKBASIC_MAX_LINE_LENGTH];
char scanned[AKBASIC_MAX_LINE_LENGTH]; char scanned[AKBASIC_MAX_LINE_LENGTH];
FILE *fp = NULL; FILE *fp = NULL;
size_t used = 0;
int64_t i = 0; int64_t i = 0;
(void)lval; (void)rval; (void)lval; (void)rval;
@@ -632,23 +734,14 @@ akerr_ErrorContext *akbasic_cmd_dload(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
ATTEMPT { ATTEMPT {
CATCH(errctx, aksl_fopen(filename, "r", &fp)); 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 * The read is one CATCH of one call, because the loop it used to be
* would leave this loop rather than the ATTEMPT and let the rest of * lives in dload_read_program() now: inside this block a loop can use
* the block run with an error pending. * 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))); CATCH(errctx, dload_read_program(obj, fp, buffer, sizeof(buffer),
PASS(errctx, akbasic_runtime_file_line(obj, scanned)); scanned, sizeof(scanned)));
}
} CLEANUP { } CLEANUP {
if ( fp != NULL ) { if ( fp != NULL ) {
IGNORE(aksl_fclose(fp)); 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 filename[AKBASIC_MAX_STRING_LENGTH];
char line[AKBASIC_MAX_LINE_LENGTH * 2]; char line[AKBASIC_MAX_LINE_LENGTH * 2];
FILE *fp = NULL; FILE *fp = NULL;
int64_t i = 0;
int count = 0;
size_t written = 0;
(void)lval; (void)rval; (void)lval; (void)rval;
PASS(errctx, filename_argument(obj, expr, filename, sizeof(filename))); PASS(errctx, filename_argument(obj, expr, filename, sizeof(filename)));
ATTEMPT { ATTEMPT {
CATCH(errctx, aksl_fopen(filename, "w", &fp)); CATCH(errctx, aksl_fopen(filename, "w", &fp));
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) { CATCH(errctx, dsave_write_program(obj, fp, line, sizeof(line)));
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;
}
} CLEANUP { } CLEANUP {
if ( fp != NULL ) { if ( fp != NULL ) {
IGNORE(aksl_fclose(fp)); 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 *updated = NULL;
akbasic_Value scratch; akbasic_Value scratch;
int64_t zerosubscript[1] = { 0 }; int64_t zerosubscript[1] = { 0 };
int cmp = 0;
bool met = false; bool met = false;
(void)lval; (void)rval; (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 * 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. * 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, FAIL_ZERO_RETURN(errctx, (obj->environment->parent != NULL), AKBASIC_ERR_ENVIRONMENT,
"NEXT in an orphaned environment"); "NEXT in an orphaned environment");
obj->environment->parent->nextline = obj->environment->nextline; 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)); PASS(errctx, akbasic_leaf_init(&literal, AKBASIC_LEAF_LITERAL_INT));
if ( value.valuetype == AKBASIC_TYPE_STRING ) { if ( value.valuetype == AKBASIC_TYPE_STRING ) {
literal.leaftype = AKBASIC_LEAF_LITERAL_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 ) { } else if ( value.valuetype == AKBASIC_TYPE_FLOAT ) {
literal.leaftype = AKBASIC_LEAF_LITERAL_FLOAT; literal.leaftype = AKBASIC_LEAF_LITERAL_FLOAT;
literal.literal_float = value.floatval; literal.literal_float = value.floatval;

View File

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

View File

@@ -22,7 +22,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h> #include <akstdlib.h>
@@ -44,7 +43,7 @@ akerr_ErrorContext *akbasic_disk_state_init(akbasic_DiskState *obj)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL disk state in init"); 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); 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)); PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE, FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
"%s expected a file name", verb); "%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); 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)); 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; channel->writing = writing;
SUCCEED_RETURN(errctx); 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) akerr_ErrorContext *akbasic_cmd_scratch(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akerr_ErrorContext *failure = NULL;
char name[AKBASIC_MAX_STRING_LENGTH]; char name[AKBASIC_MAX_STRING_LENGTH];
(void)lval; (void)rval; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in SCRATCH"); 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))); 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_TRUE(obj, dest);
SUCCEED_RETURN(errctx); 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) akerr_ErrorContext *akbasic_cmd_rename(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akerr_ErrorContext *failure = NULL;
akbasic_ASTLeaf *arg = NULL; akbasic_ASTLeaf *arg = NULL;
char from[AKBASIC_MAX_STRING_LENGTH]; char from[AKBASIC_MAX_STRING_LENGTH];
char to[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); arg = akbasic_leaf_first_argument(expr);
PASS(errctx, string_arg(obj, arg, "RENAME", from, sizeof(from))); PASS(errctx, string_arg(obj, arg, "RENAME", from, sizeof(from)));
PASS(errctx, string_arg(obj, (arg != NULL ? arg->next : NULL), "RENAME", to, sizeof(to))); PASS(errctx, string_arg(obj, (arg != NULL ? arg->next : NULL), "RENAME", to, sizeof(to)));
FAIL_ZERO_RETURN(errctx, (rename(from, to) == 0), AKERR_IO, /* Caught and restated, for the reason SCRATCH gives: the message names both files. */
"RENAME could not rename \"%s\" to \"%s\"", from, to); 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_TRUE(obj, dest);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -486,6 +507,7 @@ akerr_ErrorContext *akbasic_disk_write(akbasic_Runtime *obj, int64_t number, con
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Channel *channel = NULL; akbasic_Channel *channel = NULL;
size_t length = 0;
size_t put = 0; size_t put = 0;
FAIL_ZERO_RETURN(errctx, (obj != NULL && text != NULL), AKERR_NULLPOINTER, 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)); PASS(errctx, open_channel(obj, number, &channel));
FAIL_ZERO_RETURN(errctx, channel->writing, AKBASIC_ERR_STATE, FAIL_ZERO_RETURN(errctx, channel->writing, AKBASIC_ERR_STATE,
"Channel %" PRId64 " was opened for reading", number); "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)); PASS(errctx, aksl_fwrite("\n", 1, 1, channel->fp, &put));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -525,7 +548,7 @@ akerr_ErrorContext *akbasic_disk_readline(akbasic_Runtime *obj, int64_t number,
*eof = true; *eof = true;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
newline = strchr(dest, '\n'); PASS(errctx, aksl_strchr(dest, '\n', &newline));
if ( newline != NULL ) { if ( newline != NULL ) {
*newline = '\0'; *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. * variable reading an empty line gets zero, the same as a C128.
*/ */
PASS(errctx, akbasic_leaf_init(&literal, AKBASIC_LEAF_LITERAL_STRING)); 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 ) { if ( akbasic_leaf_identifier_type(identifier) == AKBASIC_TYPE_INTEGER ) {
long long converted = 0; long long converted = 0;
@@ -610,13 +633,69 @@ akerr_ErrorContext *akbasic_cmd_input_channel(akbasic_Runtime *obj, akbasic_ASTL
/* ---------------------------------------------------------------- VERIFY -- */ /* ---------------------------------------------------------------- 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) akerr_ErrorContext *akbasic_cmd_dverify(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FILE *fp = NULL; FILE *fp = NULL;
char name[AKBASIC_MAX_STRING_LENGTH]; char name[AKBASIC_MAX_STRING_LENGTH];
char line[AKBASIC_MAX_LINE_LENGTH];
int64_t lineno = 0;
int64_t mismatch = 0; int64_t mismatch = 0;
(void)lval; (void)rval; (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)); PASS(errctx, aksl_fopen(name, "r", &fp));
ATTEMPT { ATTEMPT {
for ( lineno = 0; lineno < AKBASIC_MAX_SOURCE_LINES; lineno++ ) { CATCH(errctx, verify_lines(obj, fp, &mismatch));
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;
}
}
}
} CLEANUP { } CLEANUP {
IGNORE(aksl_fclose(fp)); IGNORE(aksl_fclose(fp));
} PROCESS(errctx) { } PROCESS(errctx) {

View File

@@ -14,7 +14,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h> #include <akstdlib.h>
@@ -189,13 +188,19 @@ akerr_ErrorContext *akbasic_fn_hex(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Value *arg = NULL; akbasic_Value *arg = NULL;
akbasic_Value *out = NULL; akbasic_Value *out = NULL;
int written = 0;
(void)lval; (void)rval; (void)lval; (void)rval;
PASS(errctx, first_arg(obj, expr, "HEX", NULL, &arg, &out)); PASS(errctx, first_arg(obj, expr, "HEX", NULL, &arg, &out));
FAIL_NONZERO_RETURN(errctx, (arg->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE, FAIL_NONZERO_RETURN(errctx, (arg->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
"HEX expected an integer"); "HEX expected an integer");
out->valuetype = AKBASIC_TYPE_STRING; 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; *dest = out;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -270,6 +275,7 @@ akerr_ErrorContext *akbasic_fn_len(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
akbasic_Value *strval = NULL; akbasic_Value *strval = NULL;
akbasic_Value *out = NULL; akbasic_Value *out = NULL;
akbasic_Variable *variable = NULL; akbasic_Variable *variable = NULL;
size_t length = 0;
(void)lval; (void)rval; (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 || if ( argleaf->leaftype == AKBASIC_LEAF_LITERAL_STRING ||
argleaf->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ) { argleaf->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ) {
PASS(errctx, akbasic_runtime_evaluate(obj, argleaf, &strval)); 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 { } else {
PASS(errctx, akbasic_environment_get(obj->environment, argleaf->identifier, &variable)); PASS(errctx, akbasic_environment_get(obj->environment, argleaf->identifier, &variable));
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED, 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_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out)); PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_INTEGER; 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)); out->intval = (hit == NULL ? -1 : (int64_t)(hit - haystack->stringval));
*dest = out; *dest = out;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -360,7 +368,7 @@ akerr_ErrorContext *akbasic_fn_left(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
"LEFT expected a string"); "LEFT expected a string");
FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE, FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
"LEFT expected an integer count"); "LEFT expected an integer count");
sourcelen = strlen(source->stringval); PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
take = count->intval; take = count->intval;
if ( take < 0 ) { if ( take < 0 ) {
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_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out)); PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_STRING; 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'; out->stringval[take] = '\0';
*dest = out; *dest = out;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -393,7 +401,7 @@ akerr_ErrorContext *akbasic_fn_right(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
"RIGHT expected a string"); "RIGHT expected a string");
FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE, FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
"RIGHT expected an integer count"); "RIGHT expected an integer count");
sourcelen = strlen(source->stringval); PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
take = count->intval; take = count->intval;
if ( take < 0 ) { if ( take < 0 ) {
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_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out)); PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_STRING; 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'; out->stringval[take] = '\0';
*dest = out; *dest = out;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -432,7 +441,7 @@ akerr_ErrorContext *akbasic_fn_mid(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
lengthval->valuetype == AKBASIC_TYPE_INTEGER), lengthval->valuetype == AKBASIC_TYPE_INTEGER),
AKBASIC_ERR_TYPE, "MID expected integer start and length"); AKBASIC_ERR_TYPE, "MID expected integer start and length");
sourcelen = strlen(source->stringval); PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
start = startval->intval; start = startval->intval;
length = lengthval->intval; length = lengthval->intval;
FAIL_ZERO_RETURN(errctx, (start >= 0 && (size_t)start <= sourcelen), AKBASIC_ERR_BOUNDS, 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_environment_new_value(obj->environment, &out));
PASS(errctx, akbasic_value_zero(out)); PASS(errctx, akbasic_value_zero(out));
out->valuetype = AKBASIC_TYPE_STRING; 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'; out->stringval[length] = '\0';
*dest = out; *dest = out;
SUCCEED_RETURN(errctx); 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]; char encoded[AKBASIC_MAX_STRING_LENGTH];
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH]; int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
int subscriptcount = 0; int subscriptcount = 0;
int written = 0;
(void)lval; (void)rval; (void)lval; (void)rval;
PASS(errctx, require_graphics(obj, "SSHAPE")); 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 * 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. * 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)); PASS(errctx, akbasic_variable_set_string(variable, encoded, subscripts, subscriptcount));
SUCCEED_TRUE(obj, dest); SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -762,7 +763,9 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
double x = 0.0; double x = 0.0;
double y = 0.0; double y = 0.0;
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH]; int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
size_t prefixlen = 0;
int subscriptcount = 0; int subscriptcount = 0;
int cmp = 0;
(void)lval; (void)rval; (void)lval; (void)rval;
PASS(errctx, require_graphics(obj, "GSHAPE")); 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; y = (count >= 2) ? coords[1] : obj->gfx.y;
PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &stored)); PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &stored));
FAIL_ZERO_RETURN(errctx, PASS(errctx, aksl_strlen(SHAPE_PREFIX, &prefixlen));
(strncmp(stored->stringval, SHAPE_PREFIX, strlen(SHAPE_PREFIX)) == 0), PASS(errctx, aksl_strncmp(stored->stringval, SHAPE_PREFIX, prefixlen, &cmp));
AKBASIC_ERR_VALUE, FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_VALUE,
"GSHAPE was given a string that did not come from SSHAPE"); "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; handle = (int64_t)converted;
scale_point(&obj->gfx, &x, &y); scale_point(&obj->gfx, &x, &y);

View File

@@ -13,10 +13,9 @@
*/ */
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h> #include <akbasic/args.h>
#include <akbasic/error.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 * exactly this reason. The names stay put: they are what the symbol table
* points at. * points at.
*/ */
memcpy(namea, a->name, sizeof(namea)); PASS(errctx, aksl_memcpy(namea, a->name, sizeof(namea)));
memcpy(nameb, b->name, sizeof(nameb)); PASS(errctx, aksl_memcpy(nameb, b->name, sizeof(nameb)));
memcpy(&swap, a, sizeof(swap)); PASS(errctx, aksl_memcpy(&swap, a, sizeof(swap)));
memcpy(a, b, sizeof(*a)); PASS(errctx, aksl_memcpy(a, b, sizeof(*a)));
memcpy(b, &swap, sizeof(*b)); PASS(errctx, aksl_memcpy(b, &swap, sizeof(*b)));
memcpy(a->name, namea, sizeof(a->name)); PASS(errctx, aksl_memcpy(a->name, namea, sizeof(a->name)));
memcpy(b->name, nameb, sizeof(b->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 * 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 * 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); PREPARE_ERROR(errctx);
char line[AKBASIC_MAX_LINE_LENGTH * 2]; char line[AKBASIC_MAX_LINE_LENGTH * 2];
int written = 0;
(void)expr; (void)lval; (void)rval; (void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in HELP"); 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_TRUE(obj, dest);
SUCCEED_RETURN(errctx); 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)); PASS(errctx, akbasic_runtime_println(obj, line));
SUCCEED_TRUE(obj, dest); SUCCEED_TRUE(obj, dest);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);

View File

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

View File

@@ -14,9 +14,9 @@
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h> #include <akbasic/args.h>
#include <akbasic/error.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 * 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. * 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); SUCCEED_RETURN(errctx);
} }

View File

@@ -31,6 +31,7 @@
#include <string.h> #include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/args.h> #include <akbasic/args.h>
#include <akbasic/error.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 fg;
akbasic_Color bg; akbasic_Color bg;
uint8_t pattern[AKBASIC_SPRITE_PATTERN_BYTES]; uint8_t pattern[AKBASIC_SPRITE_PATTERN_BYTES];
size_t prefixlen = 0;
int handle = 0;
int cmp = 0;
if ( arg->leaftype == AKBASIC_LEAF_IDENTIFIER_INT && if ( arg->leaftype == AKBASIC_LEAF_IDENTIFIER_INT &&
akbasic_leaf_first_subscript(arg) == NULL ) { 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, 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"); "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 ) { PASS(errctx, aksl_strlen(SHAPE_PREFIX, &prefixlen));
int handle = atoi(value->stringval + strlen(SHAPE_PREFIX)); 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, FAIL_ZERO_RETURN(errctx, (obj->sprites->define_shape != NULL), AKBASIC_ERR_DEVICE,
"This sprite device cannot take a saved shape"); "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)); PASS(errctx, obj->sprites->define_shape(obj->sprites, index + 1, handle));
SUCCEED_RETURN(errctx); 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 ) { if ( !obj->sprite_state.solids[i].active ) {
continue; continue;
} }
memset(&obj->sprite_state.solids[i], 0, sizeof(obj->sprite_state.solids[i]));
/* PASS rather than CATCH: this is a loop. */ /* 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)); PASS(errctx, obj->sprites->solid(obj->sprites, i + 1, false, 0.0, 0.0, 0.0, 0.0));
} }
SUCCEED_RETURN(errctx); 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]; solid = &obj->sprite_state.solids[id - 1];
if ( count == 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)); PASS(errctx, obj->sprites->solid(obj->sprites, id, false, 0.0, 0.0, 0.0, 0.0));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -28,6 +28,7 @@
#include <string.h> #include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/runtime.h> #include <akbasic/runtime.h>
@@ -194,7 +195,11 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
char rendered[AKBASIC_MAX_STRING_LENGTH]; char rendered[AKBASIC_MAX_STRING_LENGTH];
akbasic_StructType *type = NULL; akbasic_StructType *type = NULL;
const char *separator = NULL;
size_t used = 0; size_t used = 0;
size_t seplen = 0;
size_t namelen = 0;
size_t renderedlen = 0;
int written = 0; int written = 0;
int i = 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); AKBASIC_ERR_BOUNDS, "Structure type index %d is out of range", typeindex);
type = &obj->structtypes.types[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 ) { if ( depth >= AKBASIC_MAX_STRUCT_DEPTH ) {
snprintf(dest, len, "%s(...)", type->name); snprintf(dest, len, "%s(...)", type->name);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -222,7 +241,7 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
break; break;
case AKBASIC_FIELD_POINTER: case AKBASIC_FIELD_POINTER:
if ( slot->structbase == NULL ) { if ( slot->structbase == NULL ) {
snprintf(rendered, sizeof(rendered), "NOTHING"); PASS(errctx, aksl_snprintf(&written, rendered, sizeof(rendered), "NOTHING"));
} else { } else {
PASS(errctx, akbasic_struct_to_string(obj, slot->structtype, slot->structbase, PASS(errctx, akbasic_struct_to_string(obj, slot->structtype, slot->structbase,
depth + 1, rendered, sizeof(rendered))); 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))); PASS(errctx, akbasic_value_to_string(slot, rendered, sizeof(rendered)));
break; break;
} }
written = snprintf(dest + used, len - used, "%s%s=%s", separator = (i == 0 ? "" : ", ");
(i == 0 ? "" : ", "), field->name, rendered); PASS(errctx, aksl_strlen(separator, &seplen));
if ( written < 0 || (size_t)written >= len - used ) { 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; break;
} }
PASS(errctx, aksl_snprintf(&written, dest + used, len - used, "%s%s=%s",
separator, field->name, rendered));
used += (size_t)written; used += (size_t)written;
} }
if ( used + 1 < len ) { if ( used + 1 < len ) {
snprintf(dest + used, len - used, ")"); PASS(errctx, aksl_snprintf(&written, dest + used, len - used, ")"));
} }
SUCCEED_RETURN(errctx); 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) akerr_ErrorContext *akbasic_cmd_bend(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
bool waiting = false;
(void)expr; (void)lval; (void)rval; (void)expr; (void)lval; (void)rval;
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, 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 * to do, or it is the BEND a skipped block was skipping to, in which case
* stopping the skip is the whole job. * 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")); PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "BEND"));
} }
SUCCEED_TRUE(obj, dest); SUCCEED_TRUE(obj, dest);

View File

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

View File

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

View File

@@ -26,27 +26,67 @@ akerr_ErrorContext *akbasic_scanner_zero(akbasic_Runtime *obj)
SUCCEED_RETURN(errctx); 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) ) { PREPARE_ERROR(errctx);
return false; 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]; *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) ) { PREPARE_ERROR(errctx);
return false; 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]; *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) static akerr_ErrorContext *get_lexeme(akbasic_Runtime *obj, char *dest, size_t len)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int linelen = (int)strlen(obj->line); size_t measured = 0;
int linelen = 0;
int span = 0; int span = 0;
PASS(errctx, aksl_strlen(obj->line, &measured));
linelen = (int)measured;
if ( obj->current == linelen ) { if ( obj->current == linelen ) {
span = linelen - obj->start; span = linelen - obj->start;
} else if ( obj->start == obj->current ) { } 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, 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); "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'; dest[span] = '\0';
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -83,26 +126,39 @@ static akerr_ErrorContext *add_token(akbasic_Runtime *obj, akbasic_TokenType tok
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Environment *env = obj->environment; akbasic_Environment *env = obj->environment;
size_t lexemelen = 0;
FAIL_ZERO_RETURN(errctx, (env->nexttoken < AKBASIC_MAX_TOKENS), AKBASIC_ERR_BOUNDS, FAIL_ZERO_RETURN(errctx, (env->nexttoken < AKBASIC_MAX_TOKENS), AKBASIC_ERR_BOUNDS,
"Line %" PRId64 " has more than %d tokens", "Line %" PRId64 " has more than %d tokens",
env->lineno, AKBASIC_MAX_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); "Token lexeme exceeds the %d character limit", AKBASIC_MAX_LINE_LENGTH - 1);
env->tokens[env->nexttoken].tokentype = token; env->tokens[env->nexttoken].tokentype = token;
env->tokens[env->nexttoken].lineno = env->lineno; env->tokens[env->nexttoken].lineno = env->lineno;
strncpy(env->tokens[env->nexttoken].lexeme, lexeme, AKBASIC_MAX_LINE_LENGTH - 1); PASS(errctx, aksl_strcpy(env->tokens[env->nexttoken].lexeme,
env->tokens[env->nexttoken].lexeme[AKBASIC_MAX_LINE_LENGTH - 1] = '\0'; sizeof(env->tokens[env->nexttoken].lexeme), lexeme));
env->nexttoken += 1; env->nexttoken += 1;
SUCCEED_RETURN(errctx); 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'; 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 * Nothing left to peek at, so the operator is whatever it is on its
* own. The reference returns here *without* setting a type * 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. * TODO.md section 6 item 14.
*/ */
obj->tokentype = falsetype; obj->tokentype = falsetype;
return false; *matched = false;
SUCCEED_RETURN(errctx);
} }
if ( nc == cm ) { if ( nc == cm ) {
obj->current += 1; obj->current += 1;
obj->tokentype = truetype; obj->tokentype = truetype;
return true; *matched = true;
SUCCEED_RETURN(errctx);
} }
obj->tokentype = falsetype; obj->tokentype = falsetype;
return false; *matched = false;
SUCCEED_RETURN(errctx);
} }
static akerr_ErrorContext *match_string(akbasic_Runtime *obj) static akerr_ErrorContext *match_string(akbasic_Runtime *obj)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
char c = '\0'; char c = '\0';
bool atend = false;
bool got = false;
while ( !is_at_end(obj) ) { for ( ;; ) {
if ( !peek(obj, &c) ) { 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, PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE,
"UNTERMINATED STRING LITERAL\n")); "UNTERMINATED STRING LITERAL\n"));
obj->hasError = true; obj->hasError = true;
@@ -154,12 +220,19 @@ static akerr_ErrorContext *match_number(akbasic_Runtime *obj)
int64_t lineno = 0; int64_t lineno = 0;
long long converted = 0; long long converted = 0;
bool hex = false; bool hex = false;
bool atend = false;
bool got = false;
obj->tokentype = AKBASIC_TOK_LITERAL_INT; obj->tokentype = AKBASIC_TOK_LITERAL_INT;
while ( !is_at_end(obj) ) { for ( ;; ) {
(void)peek(obj, &c); PASS(errctx, is_at_end(obj, &atend));
if ( atend ) {
break;
}
PASS(errctx, peek(obj, &c, &got));
if ( c == '.' ) { 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, PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE,
"INVALID FLOATING POINT LITERAL\n")); "INVALID FLOATING POINT LITERAL\n"));
obj->hasError = true; obj->hasError = true;
@@ -200,13 +273,17 @@ static akerr_ErrorContext *match_number(akbasic_Runtime *obj)
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} HANDLE_DEFAULT(errctx) { } HANDLE_DEFAULT(errctx) {
char message[AKBASIC_MAX_LINE_LENGTH + 32]; char message[AKBASIC_MAX_LINE_LENGTH + 32] = "INTEGER CONVERSION";
snprintf(message, sizeof(message), "INTEGER CONVERSION ON '%s'", lexeme); int written = 0;
/* /*
* Reporting can itself fail if the sink is broken. Nothing useful * Reporting can itself fail if the sink is broken, and so can
* remains to be done about that here, so record the flag and let the * formatting the message. Neither leaves anything useful to do here --
* next operation surface it. * 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)); IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
obj->hasError = true; obj->hasError = true;
} FINISH(errctx, false); } FINISH(errctx, false);
@@ -230,10 +307,16 @@ static akerr_ErrorContext *match_identifier(akbasic_Runtime *obj)
bool userfunction = false; bool userfunction = false;
char c = '\0'; char c = '\0';
size_t used = 0; size_t used = 0;
bool atend = false;
bool got = false;
obj->tokentype = AKBASIC_TOK_IDENTIFIER; obj->tokentype = AKBASIC_TOK_IDENTIFIER;
while ( !is_at_end(obj) ) { for ( ;; ) {
(void)peek(obj, &c); 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) ) { if ( isdigit((unsigned char)c) || isalpha((unsigned char)c) ) {
obj->current += 1; obj->current += 1;
continue; 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 * 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. * accepted as an ordinary string variable. TODO.md section 6 item 16.
*/ */
strncpy(basename, lexeme, sizeof(basename) - 1); PASS(errctx, aksl_strcpy(basename, sizeof(basename), lexeme));
basename[sizeof(basename) - 1] = '\0'; PASS(errctx, aksl_strlen(basename, &used));
used = strlen(basename);
if ( obj->tokentype != AKBASIC_TOK_IDENTIFIER && used > 0 ) { if ( obj->tokentype != AKBASIC_TOK_IDENTIFIER && used > 0 ) {
basename[used - 1] = '\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 lexeme[AKBASIC_MAX_LINE_LENGTH];
char c = '\0'; char c = '\0';
bool done = false; 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, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in scan");
FAIL_ZERO_RETURN(errctx, (line != NULL), AKERR_NULLPOINTER, "NULL line 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", "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); PASS(errctx, aksl_strcpy(obj->line, sizeof(obj->line), line));
obj->line[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
PASS(errctx, akbasic_environment_zero_parser(obj->environment)); PASS(errctx, akbasic_environment_zero_parser(obj->environment));
obj->current = 0; obj->current = 0;
obj->start = 0; obj->start = 0;
@@ -333,7 +418,11 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
*/ */
obj->hadlinenumber = false; 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]; c = obj->line[obj->current];
obj->current += 1; obj->current += 1;
@@ -350,7 +439,7 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
* no spelling this makes ambiguous. * no spelling this makes ambiguous.
*/ */
case '-': 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; break;
case '/': obj->tokentype = AKBASIC_TOK_LEFT_SLASH; break; case '/': obj->tokentype = AKBASIC_TOK_LEFT_SLASH; break;
case '*': obj->tokentype = AKBASIC_TOK_STAR; 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_LEFT_SQUAREBRACKET; break;
case ']': obj->tokentype = AKBASIC_TOK_RIGHT_SQUAREBRACKET; break; case ']': obj->tokentype = AKBASIC_TOK_RIGHT_SQUAREBRACKET; break;
case '=': 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; break;
case '<': case '<':
if ( !match_next_char(obj, '=', AKBASIC_TOK_LESS_THAN_EQUAL, AKBASIC_TOK_LESS_THAN) ) { PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_LESS_THAN_EQUAL,
(void)match_next_char(obj, '>', AKBASIC_TOK_NOT_EQUAL, AKBASIC_TOK_LESS_THAN); AKBASIC_TOK_LESS_THAN, &matched));
if ( !matched ) {
PASS(errctx, match_next_char(obj, '>', AKBASIC_TOK_NOT_EQUAL,
AKBASIC_TOK_LESS_THAN, &matched));
} }
break; break;
case '>': 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; break;
case '"': case '"':
obj->start = obj->current; obj->start = obj->current;
@@ -402,7 +495,10 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
PASS(errctx, match_identifier(obj)); PASS(errctx, match_identifier(obj));
} else { } else {
char message[AKBASIC_MAX_LINE_LENGTH]; 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)); PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
obj->hasError = true; obj->hasError = true;
obj->start = obj->current; 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. * stores *that* as the program text.
*/ */
int skip = obj->current; int skip = obj->current;
size_t tail = 0;
while ( obj->line[skip] == ' ' ) { while ( obj->line[skip] == ' ' ) {
skip += 1; 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; obj->current = 0;
} else { } else {
PASS(errctx, get_lexeme(obj, lexeme, sizeof(lexeme))); 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 ) { 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"); "Scanned line does not fit the caller's buffer");
strncpy(dest, obj->line, len - 1); PASS(errctx, aksl_strcpy(dest, len, obj->line));
dest[len - 1] = '\0';
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -15,9 +15,8 @@
#include <ctype.h> #include <ctype.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akgl/controller.h> #include <akgl/controller.h>
#include <akgl/draw.h> #include <akgl/draw.h>
@@ -32,15 +31,24 @@
/** @brief Bytes per row, including the terminator. */ /** @brief Bytes per row, including the terminator. */
#define SINK_MAX_COLUMNS 256 #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; int row = 0;
for ( row = 0; row < SINK_MAX_ROWS - 1; row++ ) { 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 ) { if ( state->cursorrow > 0 ) {
state->cursorrow -= 1; 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. */ /** @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->cursorcol = 0;
state->cursorrow += 1; state->cursorrow += 1;
if ( state->cursorrow >= state->rows || state->cursorrow >= SINK_MAX_ROWS ) { 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 * cleared between rows, so the gap holds the tail of some longer row that used
* to be here. * 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; int col = 0;
if ( c == '\n' ) { if ( c == '\n' ) {
newline(state); PASS(errctx, newline(state));
return; SUCCEED_RETURN(errctx);
} }
if ( state->cursorcol >= state->columns || state->cursorcol >= SINK_MAX_COLUMNS - 1 ) { if ( state->cursorcol >= state->columns || state->cursorcol >= SINK_MAX_COLUMNS - 1 ) {
newline(state); PASS(errctx, newline(state));
} }
for ( col = 0; col < state->cursorcol; col++ ) { for ( col = 0; col < state->cursorcol; col++ ) {
if ( state->text[state->cursorrow][col] == '\0' ) { 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->text[state->cursorrow][state->cursorcol] = c;
state->cursorcol += 1; state->cursorcol += 1;
state->text[state->cursorrow][state->cursorcol] = '\0'; state->text[state->cursorrow][state->cursorcol] = '\0';
SUCCEED_RETURN(errctx);
} }
static akerr_ErrorContext *sink_write(akbasic_TextSink *self, const char *text) 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, FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
"akgl sink has no state"); "akgl sink has no state");
for ( i = 0; text[i] != '\0'; i++ ) { for ( i = 0; text[i] != '\0'; i++ ) {
putchar_at(state, text[i]); PASS(errctx, putchar_at(state, text[i]));
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -137,7 +150,7 @@ static akerr_ErrorContext *sink_writeln(akbasic_TextSink *self, const char *text
PASS(errctx, sink_write(self, text)); PASS(errctx, sink_write(self, text));
state = (akbasic_AkglSink *)self->self; state = (akbasic_AkglSink *)self->self;
newline(state); PASS(errctx, newline(state));
SUCCEED_RETURN(errctx); 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 * memory, and the host draws it when it draws its frame -- so the cost is a
* couple of memcpy-sized loops per keystroke. * 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 i = 0;
int erasedto = 0; int erasedto = 0;
int endedat = 0; int endedat = 0;
@@ -160,14 +174,14 @@ static void echo_line(akbasic_AkglSink *state)
state->cursorrow = state->editrow; state->cursorrow = state->editrow;
state->cursorcol = state->editcol; state->cursorcol = state->editcol;
for ( i = 0; i < state->echolen; i++ ) { for ( i = 0; i < state->echolen; i++ ) {
putchar_at(state, ' '); PASS(errctx, putchar_at(state, ' '));
} }
erasedto = state->cursorrow; erasedto = state->cursorrow;
state->cursorrow = state->editrow; state->cursorrow = state->editrow;
state->cursorcol = state->editcol; state->cursorcol = state->editcol;
for ( i = 0; i < state->editlen; i++ ) { for ( i = 0; i < state->editlen; i++ ) {
putchar_at(state, state->editline[i]); PASS(errctx, putchar_at(state, state->editline[i]));
} }
endedat = state->cursorrow; endedat = state->cursorrow;
state->echolen = state->editlen; 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++ ) { for ( i = endedat + 1; i <= erasedto && i < SINK_MAX_ROWS; i++ ) {
state->text[i][0] = '\0'; state->text[i][0] = '\0';
} }
SUCCEED_RETURN(errctx);
} }
/** @brief Append one byte to the line being typed, if there is room for it. */ /** @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 * Backspace and Escape are keys rather than characters and several of them
* compose to text SDL would otherwise hand straight through. * 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; size_t i = 0;
if ( key->key == SDLK_RETURN || key->key == SDLK_KP_ENTER || if ( key->key == SDLK_RETURN || key->key == SDLK_KP_ENTER ||
key->key == '\r' || key->key == '\n' ) { key->key == '\r' || key->key == '\n' ) {
*submitted = true; *submitted = true;
return; SUCCEED_RETURN(errctx);
} }
if ( key->key == SDLK_BACKSPACE || key->key == '\b' || key->key == 0x7f ) { if ( key->key == SDLK_BACKSPACE || key->key == '\b' || key->key == 0x7f ) {
if ( state->editlen > 0 ) { 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->editlen -= 1;
state->editline[state->editlen] = '\0'; 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 ) { if ( key->key == SDLK_ESCAPE || key->key == 0x1b ) {
state->editlen = 0; state->editlen = 0;
state->editline[0] = '\0'; state->editline[0] = '\0';
echo_line(state); PASS(errctx, echo_line(state));
return; SUCCEED_RETURN(errctx);
} }
if ( key->text[0] != '\0' ) { 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]); edit_append(state, key->text[i]);
} }
} }
echo_line(state); PASS(errctx, echo_line(state));
return; 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 ) { if ( key->key >= 0x20 && key->key < 0x7f ) {
edit_append(state, (char)toupper((unsigned char)key->key)); edit_append(state, (char)toupper((unsigned char)key->key));
echo_line(state); PASS(errctx, echo_line(state));
return; SUCCEED_RETURN(errctx);
} }
/* /*
* Neither: a cursor key, a function key or a bare modifier. Not an editing * 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. * 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)); PASS(errctx, akgl_controller_poll_keystroke(&key, &available));
if ( available ) { if ( available ) {
edit_key(state, &key, &submitted); PASS(errctx, edit_key(state, &key, &submitted));
continue; continue;
} }
/* /*
@@ -375,9 +392,8 @@ static akerr_ErrorContext *sink_readline(akbasic_TextSink *self, char *dest, siz
if ( *eof ) { if ( *eof ) {
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
strncpy(dest, state->editline, len - 1); PASS(errctx, aksl_strncpy(dest, len, state->editline, len - 1));
dest[len - 1] = '\0'; PASS(errctx, newline(state));
newline(state);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -391,7 +407,7 @@ static akerr_ErrorContext *sink_clear(akbasic_TextSink *self)
state = (akbasic_AkglSink *)self->self; state = (akbasic_AkglSink *)self->self;
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
"akgl sink has no state"); "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->cursorcol = 0;
state->cursorrow = 0; state->cursorrow = 0;
SUCCEED_RETURN(errctx); 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", "A %dx%d text area has no room for a %dx%d character",
w, h, cellw, cellh); w, h, cellw, cellh);
memset(state, 0, sizeof(*state)); PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer; state->renderer = renderer;
state->font = font; state->font = font;
state->color.r = 0xff; state->color.r = 0xff;

View File

@@ -10,7 +10,6 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h> #include <akstdlib.h>
@@ -59,8 +58,20 @@ static akerr_ErrorContext *stdio_readline(akbasic_TextSink *self, char *dest, si
*eof = false; *eof = false;
dest[0] = '\0'; 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; *eof = true;
} FINISH(errctx, true);
if ( *eof ) {
SUCCEED_RETURN(errctx); 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 * anyway, but leaving them on would make a stored source line differ from
* the same line typed at the REPL. * the same line typed at the REPL.
*/ */
used = strlen(dest);
while ( used > 0 && (dest[used - 1] == '\n' || dest[used - 1] == '\r') ) { while ( used > 0 && (dest[used - 1] == '\n' || dest[used - 1] == '\r') ) {
dest[used - 1] = '\0'; dest[used - 1] = '\0';
used -= 1; used -= 1;

View File

@@ -34,11 +34,11 @@
*/ */
#include <errno.h> #include <errno.h>
#include <string.h>
#include <SDL3_image/SDL_image.h> #include <SDL3_image/SDL_image.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akgl/error.h> #include <akgl/error.h>
#include <akgl/game.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; oldvisible = state->actors[i]->visible;
} }
memset(spritename, 0, sizeof(spritename)); PASS(errctx, aksl_memset(spritename, 0, sizeof(spritename)));
memset(charname, 0, sizeof(charname)); PASS(errctx, aksl_memset(charname, 0, sizeof(charname)));
memset(actorname, 0, sizeof(actorname)); PASS(errctx, aksl_memset(actorname, 0, sizeof(actorname)));
SDL_snprintf(spritename, sizeof(spritename), "akbasic:sprite:%d", i + 1); SDL_snprintf(spritename, sizeof(spritename), "akbasic:sprite:%d", i + 1);
SDL_snprintf(charname, sizeof(charname), "akbasic:character:%d", i + 1); SDL_snprintf(charname, sizeof(charname), "akbasic:character:%d", i + 1);
SDL_snprintf(actorname, sizeof(actorname), "akbasic:actor:%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. * which is what makes the texture's ownership one rule rather than two.
*/ */
PASS(errctx, akgl_heap_next_spritesheet(&state->sheets[i])); PASS(errctx, akgl_heap_next_spritesheet(&state->sheets[i]));
memset(state->sheets[i], 0, sizeof(*state->sheets[i])); PASS(errctx, aksl_memset(state->sheets[i], 0, sizeof(*state->sheets[i])));
memset(sheetname, 0, sizeof(sheetname)); PASS(errctx, aksl_memset(sheetname, 0, sizeof(sheetname)));
SDL_snprintf(sheetname, sizeof(sheetname), "akbasic:sheet:%d", i + 1); SDL_snprintf(sheetname, sizeof(sheetname), "akbasic:sheet:%d", i + 1);
SDL_strlcpy(state->sheets[i]->name, sheetname, sizeof(state->sheets[i]->name)); SDL_strlcpy(state->sheets[i]->name, sheetname, sizeof(state->sheets[i]->name));
state->sheets[i]->texture = texture; 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->shapex2[i] = (float32_t)x2;
state->shapey2[i] = (float32_t)y2; state->shapey2[i] = (float32_t)y2;
state->shapeexplicit[i] = (x2 > x1 && y2 > y1); 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); 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++ ) { for ( i = 0; i < AKBASIC_MAX_SPRITES; i++ ) {
live[i] = slot_collidable(state, i); live[i] = slot_collidable(state, i);
synced[i] = false; 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] ) { if ( !live[i] ) {
continue; continue;
} }
@@ -745,7 +746,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_scan(akbasic_AkglSprites *state)
} }
state->lastmask = 0; state->lastmask = 0;
state->lastsolidmask = 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.** * **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. */ /* Before anything else in libakgl, so every AKGL_ERR_* has a name to print. */
PASS(errctx, akgl_error_init()); PASS(errctx, akgl_error_init());
memset(state, 0, sizeof(*state)); PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer; state->renderer = renderer;
state->graphics = graphics; 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)); &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->self = state;
obj->define = spr_define; obj->define = spr_define;
obj->define_shape = spr_define_shape; obj->define_shape = spr_define_shape;

View File

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

View File

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

View File

@@ -3,8 +3,6 @@
* @brief Implements the fixed-capacity open-addressed symbol table. * @brief Implements the fixed-capacity open-addressed symbol table.
*/ */
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h> #include <akstdlib.h>
@@ -23,10 +21,13 @@ static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
uint32_t hashval = 0; uint32_t hashval = 0;
size_t keylen = 0;
int index = 0; int index = 0;
int cmp = 0;
int i = 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; *found = false;
index = (int)(hashval % (uint32_t)obj->capacity); index = (int)(hashval % (uint32_t)obj->capacity);
@@ -36,7 +37,8 @@ static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int
*slot = probeidx; *slot = probeidx;
SUCCEED_RETURN(errctx); 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; *slot = probeidx;
*found = true; *found = true;
SUCCEED_RETURN(errctx); 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", "Symbol table capacity %d out of range 1..%d",
capacity, AKBASIC_SYMTAB_MAX_SLOTS); capacity, AKBASIC_SYMTAB_MAX_SLOTS);
memset(obj, 0, sizeof(*obj)); PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->capacity = capacity; obj->capacity = capacity;
obj->count = 0; obj->count = 0;
SUCCEED_RETURN(errctx); 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) akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key, void *value, int64_t ivalue)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
size_t keylen = 0;
int slot = 0; int slot = 0;
bool found = false; bool found = false;
@@ -74,7 +77,8 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
"NULL symbol table in set"); "NULL symbol table in set");
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
"NULL key in symbol table set"); "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, AKBASIC_ERR_BOUNDS,
"Symbol name '%s' exceeds %d characters", "Symbol name '%s' exceeds %d characters",
key, AKBASIC_SYMTAB_MAX_KEY - 1); 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)); PASS(errctx, probe(obj, key, &slot, &found));
if ( !found ) { if ( !found ) {
strncpy(obj->slots[slot].key, key, AKBASIC_SYMTAB_MAX_KEY - 1); /* aksl_strcpy always terminates, so the explicit terminator this
obj->slots[slot].key[AKBASIC_SYMTAB_MAX_KEY - 1] = '\0'; 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->slots[slot].used = true;
obj->count += 1; 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) akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key, void **value, int64_t *ivalue)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
size_t keylen = 0;
int slot = 0; int slot = 0;
bool found = false; bool found = false;
@@ -102,7 +109,8 @@ akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key
"NULL symbol table in get"); "NULL symbol table in get");
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
"NULL key in symbol table get"); "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, AKERR_KEY,
"Symbol '%s' is not present", key); "Symbol '%s' is not present", key);
@@ -127,7 +135,7 @@ akerr_ErrorContext *akbasic_symtab_clear(akbasic_SymbolTable *obj)
"NULL symbol table in clear"); "NULL symbol table in clear");
capacity = obj->capacity; capacity = obj->capacity;
memset(obj, 0, sizeof(*obj)); PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
obj->capacity = capacity; obj->capacity = capacity;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -16,10 +16,8 @@
* string. * string.
*/ */
#include <stdio.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akgl/error.h> #include <akgl/error.h>
#include <akgl/registry.h> #include <akgl/registry.h>
@@ -94,8 +92,7 @@ static akerr_ErrorContext *ui_dialog(akbasic_UiBackend *self, const char *text)
state->dialogtext[0] = '\0'; state->dialogtext[0] = '\0';
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
strncpy(state->dialogtext, text, sizeof(state->dialogtext) - 1); PASS(errctx, aksl_strcpy(state->dialogtext, sizeof(state->dialogtext), text));
state->dialogtext[sizeof(state->dialogtext) - 1] = '\0';
state->dialogopen = true; state->dialogopen = true;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -113,8 +110,7 @@ static akerr_ErrorContext *ui_label(akbasic_UiBackend *self, int slot, int ancho
state->labeltext[slot][0] = '\0'; state->labeltext[slot][0] = '\0';
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
strncpy(state->labeltext[slot], text, sizeof(state->labeltext[slot]) - 1); PASS(errctx, aksl_strcpy(state->labeltext[slot], sizeof(state->labeltext[slot]), text));
state->labeltext[slot][sizeof(state->labeltext[slot]) - 1] = '\0';
state->labelanchor[slot] = anchor; state->labelanchor[slot] = anchor;
state->labelset[slot] = true; state->labelset[slot] = true;
SUCCEED_RETURN(errctx); 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++ ) { for ( i = 0; i < count; i++ ) {
FAIL_ZERO_RETURN(errctx, (items[i] != NULL), AKERR_NULLPOINTER, FAIL_ZERO_RETURN(errctx, (items[i] != NULL), AKERR_NULLPOINTER,
"MENU entry %d is NULL", i + 1); "MENU entry %d is NULL", i + 1);
strncpy(state->menuitems[slot][i], items[i], sizeof(state->menuitems[slot][i]) - 1); PASS(errctx, aksl_strcpy(state->menuitems[slot][i], sizeof(state->menuitems[slot][i]),
state->menuitems[slot][i][sizeof(state->menuitems[slot][i]) - 1] = '\0'; items[i]));
state->menus[slot].items[i] = state->menuitems[slot][i]; state->menus[slot].items[i] = state->menuitems[slot][i];
} }
for ( i = count; i < AKBASIC_UI_MAX_MENU_ITEMS; 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); "A %dx%d UI at %d points is not a UI", width, height, fontsize);
PASS(errctx, akgl_error_init()); PASS(errctx, akgl_error_init());
memset(state, 0, sizeof(*state)); PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
state->renderer = renderer; 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 * 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. * function that declares them.
*/ */
for ( i = 0; i < AKBASIC_UI_MAX_LABELS; i++ ) { 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++ ) { 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]; state->menus[i].id = state->menuid[i];
} }
(void)count;
/* /*
* **akgl_registry_init() does not initialize the font registry** -- its own * **akgl_registry_init() does not initialize the font registry** -- its own

View File

@@ -13,6 +13,7 @@
#include <string.h> #include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/value.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) static akerr_ErrorContext *set_string(akbasic_Value *dest, const char *src)
{ {
PREPARE_ERROR(errctx); 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, AKBASIC_ERR_VALUE,
"String result of %zu characters exceeds the %d character limit", "String result of %zu characters exceeds the %d character limit",
strlen(src), AKBASIC_MAX_STRING_LENGTH - 1); srclen, AKBASIC_MAX_STRING_LENGTH - 1);
strncpy(dest->stringval, src, AKBASIC_MAX_STRING_LENGTH - 1); /* aksl_strcpy always terminates and refuses rather than truncates, which is
dest->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0'; 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); SUCCEED_RETURN(errctx);
} }
@@ -134,7 +139,7 @@ akerr_ErrorContext *akbasic_valuepool_init(akbasic_ValuePool *obj)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL pool in init"); 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; obj->next = 0;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -201,7 +206,7 @@ akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
dest->valuetype = self->valuetype; 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->intval = self->intval;
dest->floatval = self->floatval; dest->floatval = self->floatval;
dest->boolvalue = self->boolvalue; 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) akerr_ErrorContext *akbasic_value_to_string(akbasic_Value *self, char *dest, size_t len)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int written = 0;
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in to_string"); 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"); 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 ) { switch ( self->valuetype ) {
case AKBASIC_TYPE_STRING: case AKBASIC_TYPE_STRING:
snprintf(dest, len, "%s", self->stringval); PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->stringval));
break; break;
case AKBASIC_TYPE_INTEGER: case AKBASIC_TYPE_INTEGER:
snprintf(dest, len, "%" PRId64, self->intval); PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, self->intval));
break; break;
case AKBASIC_TYPE_FLOAT: case AKBASIC_TYPE_FLOAT:
snprintf(dest, len, "%f", self->floatval); PASS(errctx, aksl_snprintf(&written, dest, len, "%f", self->floatval));
break; break;
case AKBASIC_TYPE_BOOLEAN: case AKBASIC_TYPE_BOOLEAN:
/* Go's %t, which is "true"/"false" and not the numeric -1/0. */ /* 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; break;
default: 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; break;
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -486,6 +494,7 @@ akerr_ErrorContext *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akbasic_Value *out = NULL; akbasic_Value *out = NULL;
char buf[AKBASIC_MAX_STRING_LENGTH * 2]; 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, (self != NULL), AKERR_NULLPOINTER, "NULL value in math plus");
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval"); 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
out->floatval = self->floatval + rval_as_float(rval); out->floatval = self->floatval + rval_as_float(rval);
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_STRING ) { } 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)); PASS(errctx, set_string(out, buf));
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_INTEGER ) { } 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)); PASS(errctx, set_string(out, buf));
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_FLOAT ) { } 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)); PASS(errctx, set_string(out, buf));
} else { } else {
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE, "Invalid arithmetic operation"); 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, FAIL_NONZERO_RETURN(errctx, (rval->intval < 0), AKBASIC_ERR_VALUE,
"String multiplier %" PRId64 " must not be negative", rval->intval); "String multiplier %" PRId64 " must not be negative", rval->intval);
srclen = strlen((*dest)->stringval); PASS(errctx, aksl_strlen((*dest)->stringval, &srclen));
FAIL_NONZERO_RETURN(errctx, FAIL_NONZERO_RETURN(errctx,
(srclen != 0 && (uint64_t)rval->intval > (AKBASIC_MAX_STRING_LENGTH - 1) / srclen), (srclen != 0 && (uint64_t)rval->intval > (AKBASIC_MAX_STRING_LENGTH - 1) / srclen),
AKBASIC_ERR_VALUE, AKBASIC_ERR_VALUE,
"Repeated string of %zu x %" PRId64 " characters exceeds the %d character limit", "Repeated string of %zu x %" PRId64 " characters exceeds the %d character limit",
srclen, rval->intval, AKBASIC_MAX_STRING_LENGTH - 1); srclen, rval->intval, AKBASIC_MAX_STRING_LENGTH - 1);
for ( i = 0; i < rval->intval; i++ ) { for ( i = 0; i < rval->intval; i++ ) {
memcpy(buf + offset, (*dest)->stringval, srclen); PASS(errctx, aksl_memcpy(buf + offset, (*dest)->stringval, srclen));
offset += srclen; offset += srclen;
} }
buf[offset] = '\0'; 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) akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest)); PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) { 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval < rval_as_float(rval))); PASS(errctx, akbasic_value_set_bool(*dest, self->floatval < rval_as_float(rval)));
} else { } 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); 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) akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest)); PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) { 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval <= rval_as_float(rval))); PASS(errctx, akbasic_value_set_bool(*dest, self->floatval <= rval_as_float(rval)));
} else { } 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); 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) akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest)); PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) { 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval > rval_as_float(rval))); PASS(errctx, akbasic_value_set_bool(*dest, self->floatval > rval_as_float(rval)));
} else { } 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); 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) akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest)); PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) { 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval >= rval_as_float(rval))); PASS(errctx, akbasic_value_set_bool(*dest, self->floatval >= rval_as_float(rval)));
} else { } 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); 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) akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest)); PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) { 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval == rval_as_float(rval))); PASS(errctx, akbasic_value_set_bool(*dest, self->floatval == rval_as_float(rval)));
} else { } 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); 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) akerr_ErrorContext *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
int cmp = 0;
PASS(errctx, binary_prologue(self, rval, scratch, dest)); PASS(errctx, binary_prologue(self, rval, scratch, dest));
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) { 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 ) { } else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval != rval_as_float(rval))); PASS(errctx, akbasic_value_set_bool(*dest, self->floatval != rval_as_float(rval)));
} else { } 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); SUCCEED_RETURN(errctx);
} }

View File

@@ -4,9 +4,9 @@
*/ */
#include <inttypes.h> #include <inttypes.h>
#include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/variable.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", "Array dimension count %d out of range 1..%d",
sizecount, AKBASIC_MAX_ARRAY_DEPTH); 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"); 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. */ /* 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); PREPARE_ERROR(errctx);
akbasic_Value tmp; akbasic_Value tmp;
size_t valuelen = 0;
FAIL_ZERO_RETURN(errctx, (value != NULL), AKERR_NULLPOINTER, "NULL string in set_string"); 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, AKBASIC_ERR_VALUE,
"String of %zu characters exceeds the %d character limit", "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)); PASS(errctx, akbasic_value_zero(&tmp));
tmp.valuetype = AKBASIC_TYPE_STRING; tmp.valuetype = AKBASIC_TYPE_STRING;
strncpy(tmp.stringval, value, AKBASIC_MAX_STRING_LENGTH - 1); PASS(errctx, aksl_strcpy(tmp.stringval, sizeof(tmp.stringval), value));
tmp.stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
PASS(errctx, akbasic_variable_set_subscript(obj, &tmp, subscripts, subscriptcount)); PASS(errctx, akbasic_variable_set_subscript(obj, &tmp, subscripts, subscriptcount));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -8,6 +8,7 @@
#include <string.h> #include <string.h>
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h>
#include <akbasic/error.h> #include <akbasic/error.h>
#include <akbasic/verbs.h> #include <akbasic/verbs.h>
@@ -197,6 +198,28 @@ static const akbasic_Verb VERBS[] = {
#define VERB_COUNT ((int)(sizeof(VERBS) / sizeof(VERBS[0]))) #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) static int verb_compare(const void *key, const void *element)
{ {
const akbasic_Verb *verb = (const akbasic_Verb *)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"); FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in verb lookup");
*dest = NULL; *dest = NULL;
len = strlen(name); PASS(errctx, aksl_strlen(name, &len));
if ( len == 0 || len >= sizeof(upper) ) { if ( len == 0 || len >= sizeof(upper) ) {
/* Too long to be any verb we know. Not an error, just a miss. */ /* Too long to be any verb we know. Not an error, just a miss. */
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);

View File

@@ -13,6 +13,7 @@ int main(void)
akbasic_Variable *again = NULL; akbasic_Variable *again = NULL;
akbasic_Value *value = NULL; akbasic_Value *value = NULL;
int64_t lineno = 0; int64_t lineno = 0;
bool waiting = false;
int i = 0; int i = 0;
TEST_REQUIRE_OK(harness_start(NULL)); TEST_REQUIRE_OK(harness_start(NULL));
@@ -57,24 +58,34 @@ int main(void)
TEST_REQUIRE_STATUS(akbasic_runtime_prev_environment(&HARNESS_RUNTIME), TEST_REQUIRE_STATUS(akbasic_runtime_prev_environment(&HARNESS_RUNTIME),
AKBASIC_ERR_ENVIRONMENT); 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_OK(akbasic_environment_wait_for_command(root, "NEXT"));
TEST_REQUIRE(akbasic_environment_is_waiting_for_any(root), "the wait must register"); TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
TEST_REQUIRE(akbasic_environment_is_waiting_for(root, "NEXT"), "the wait must match by name"); TEST_REQUIRE(waiting, "the wait must register");
TEST_REQUIRE(!akbasic_environment_is_waiting_for(root, "DATA"), "a different verb must not match"); 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. */ /* 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_STATUS(akbasic_environment_wait_for_command(root, "DATA"), AKBASIC_ERR_STATE);
TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME)); TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME));
child = HARNESS_RUNTIME.environment; child = HARNESS_RUNTIME.environment;
TEST_REQUIRE(akbasic_environment_is_waiting_for(child, "NEXT"), TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(child, "NEXT", &waiting));
"a child must see its parent's wait"); TEST_REQUIRE(waiting, "a child must see its parent's wait");
TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME)); TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME));
TEST_REQUIRE_OK(akbasic_environment_stop_waiting(root, "NEXT")); 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. */ /* The per-line value pool is bounded and says so. */
for ( i = 0; i < AKBASIC_MAX_VALUES; i++ ) { for ( i = 0; i < AKBASIC_MAX_VALUES; i++ ) {