Compare commits
4 Commits
main
...
libakstdli
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f1d1cb2e0 | |||
| 9e5496f6b2 | |||
| 88ac2fc3f2 | |||
| d219f80777 |
@@ -26,14 +26,42 @@ jobs:
|
||||
# skipped for the same reason: our CMakeLists declares akerror::akerror
|
||||
# first and libakstdlib guards on if(NOT TARGET ...).
|
||||
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
|
||||
run: |
|
||||
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
|
||||
run: |
|
||||
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
|
||||
# reference's own corpus (checked in at tests/reference/, see its README),
|
||||
# 9 local golden cases for verbs the reference never implemented, 25 unit
|
||||
@@ -74,7 +102,7 @@ jobs:
|
||||
- name: dependencies
|
||||
run: |
|
||||
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
|
||||
# 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
|
||||
@@ -84,7 +112,7 @@ jobs:
|
||||
cmake -S . -B build-asan \
|
||||
-DAKBASIC_SANITIZE=ON \
|
||||
-DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build-asan --parallel
|
||||
cmake --build build-asan --parallel 2
|
||||
ctest --test-dir build-asan --output-on-failure
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
|
||||
@@ -98,7 +126,7 @@ jobs:
|
||||
- name: dependencies
|
||||
run: |
|
||||
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
|
||||
# 98.6% of functions, so 90 fails on a real regression (a test deleted, or
|
||||
# new untested code added) without tripping over rounding.
|
||||
@@ -124,7 +152,7 @@ jobs:
|
||||
cmake -S . -B build-coverage \
|
||||
-DAKBASIC_COVERAGE=ON \
|
||||
-DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build-coverage --parallel
|
||||
cmake --build build-coverage --parallel 2
|
||||
ctest --test-dir build-coverage --output-on-failure
|
||||
mkdir -p build-coverage/coverage
|
||||
gcovr --root . --filter 'src/.*' \
|
||||
@@ -134,9 +162,21 @@ jobs:
|
||||
--fail-under-line 90
|
||||
# Publish even when the threshold gate fails, so the uncovered lines are
|
||||
# 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
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: code-coverage
|
||||
path: build-coverage/coverage/
|
||||
@@ -161,22 +201,56 @@ jobs:
|
||||
# embedded it takes a find_package path instead, so our CMakeLists declares
|
||||
# those targets first and needs the submodules present. Six of them, none
|
||||
# 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
|
||||
run: |
|
||||
git -C deps/libakgl submodule update --init \
|
||||
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.
|
||||
# SDL_ttf prefers the system copies -- it reports "Using system freetype
|
||||
# library" and links libfreetype.so.6 -- and without them it would reach
|
||||
# for deps/SDL_ttf/external/freetype, which the checkout above
|
||||
# deliberately does not clone. Installing two dev packages is much cheaper
|
||||
# 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
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y cmake gcc g++ pkg-config \
|
||||
libfreetype-dev libharfbuzz-dev
|
||||
sudo apt-get install -y cmake gcc g++ pkg-config moreutils \
|
||||
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
|
||||
# backends, the standalone SDL frontend, and the two suites that drive
|
||||
# them against a real software renderer and read the pixels back.
|
||||
@@ -194,7 +268,7 @@ jobs:
|
||||
- name: build with libakgl
|
||||
run: |
|
||||
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
|
||||
# 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
|
||||
@@ -256,7 +330,7 @@ jobs:
|
||||
- name: dependencies
|
||||
run: |
|
||||
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
|
||||
# confirm the suite fails. This matters more here than in an ordinary C
|
||||
# library, because the akerror control-flow macros expand at their call
|
||||
|
||||
@@ -70,10 +70,14 @@ jobs:
|
||||
# corpus is part of what kills mutants and it is checked in now, so
|
||||
# that is libakerror and libakstdlib and nothing else.
|
||||
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
|
||||
run: |
|
||||
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
|
||||
# push; this is the one that actually covers the interpreter.
|
||||
#
|
||||
|
||||
2
deps/libakstdlib
vendored
2
deps/libakstdlib
vendored
Submodule deps/libakstdlib updated: 669b2b395f...2b79aca103
@@ -156,18 +156,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_leaf(akbasic_Environm
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command);
|
||||
/**
|
||||
* @brief True when this scope or any enclosing one is skipping forward.
|
||||
* @brief Whether this scope or any enclosing one is skipping forward.
|
||||
*
|
||||
* The answer leaves through @p dest rather than the return value. Deciding it
|
||||
* reads a recorded verb name, that read can fail, and a `bool` return has
|
||||
* nowhere to report the failure -- so the signature changes rather than the
|
||||
* error being swallowed. libakstdlib #38 is the ruling; `probe` in `symtab.c`
|
||||
* and `loop_continues` in `runtime_structure.c` are the same shape.
|
||||
*
|
||||
* @param obj Scope to inspect; NULL is not waiting.
|
||||
* @return `true` when execution is currently suppressed.
|
||||
* @param[out] dest `true` when execution is currently suppressed. Required.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When dest is NULL.
|
||||
*/
|
||||
bool akbasic_environment_is_waiting_for_any(akbasic_Environment *obj);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for_any(akbasic_Environment *obj, bool *dest);
|
||||
/**
|
||||
* @brief True when this scope or an enclosing one is waiting for a given verb.
|
||||
* @brief Whether this scope or an enclosing one is waiting for a given verb.
|
||||
*
|
||||
* Reports through @p dest for the same reason its sibling above does.
|
||||
*
|
||||
* @param obj Scope to inspect; NULL is not waiting.
|
||||
* @param command Verb to test for.
|
||||
* @return `true` when that verb is what execution is waiting on.
|
||||
* @param command Verb to test for; NULL is not waiting.
|
||||
* @param[out] dest `true` when that verb is what execution is waiting on. Required.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When dest is NULL.
|
||||
*/
|
||||
bool akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command, bool *dest);
|
||||
/**
|
||||
* @brief Clear a pending wait, searching the parent chain for it.
|
||||
*
|
||||
|
||||
18
src/data.c
18
src/data.c
@@ -8,7 +8,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
@@ -112,6 +111,7 @@ static akerr_ErrorContext *scan_line(akbasic_DataState *obj, const char *code, i
|
||||
const char *cursor = code;
|
||||
bool statementstart = true;
|
||||
bool instring = false;
|
||||
int cmp = 0;
|
||||
|
||||
while ( *cursor != '\0' ) {
|
||||
if ( instring ) {
|
||||
@@ -141,12 +141,14 @@ static akerr_ErrorContext *scan_line(akbasic_DataState *obj, const char *code, i
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ( statementstart && strncasecmp(cursor, "DATA", 4) == 0
|
||||
&& !isalnum((unsigned char)cursor[4]) ) {
|
||||
cursor += 4;
|
||||
PASS(errctx, collect_items(obj, &cursor, lineno));
|
||||
statementstart = false;
|
||||
continue;
|
||||
if ( statementstart ) {
|
||||
PASS(errctx, aksl_strncasecmp(cursor, "DATA", 4, &cmp));
|
||||
if ( cmp == 0 && !isalnum((unsigned char)cursor[4]) ) {
|
||||
cursor += 4;
|
||||
PASS(errctx, collect_items(obj, &cursor, lineno));
|
||||
statementstart = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
statementstart = false;
|
||||
cursor += 1;
|
||||
@@ -184,7 +186,7 @@ akerr_ErrorContext *akbasic_data_next(akbasic_Runtime *obj, akbasic_Type type, a
|
||||
PASS(errctx, akbasic_value_zero(dest));
|
||||
if ( type == AKBASIC_TYPE_STRING ) {
|
||||
dest->valuetype = AKBASIC_TYPE_STRING;
|
||||
snprintf(dest->stringval, sizeof(dest->stringval), "%s", item->text);
|
||||
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), item->text));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/environment.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -39,7 +40,7 @@ akerr_ErrorContext *akbasic_environment_init(akbasic_Environment *obj, akbasic_R
|
||||
obj->readIdentifierIdx = 0;
|
||||
obj->waitingForCommand[0] = '\0';
|
||||
obj->errorToken = NULL;
|
||||
memset(obj->readIdentifierLeaves, 0, sizeof(obj->readIdentifierLeaves));
|
||||
PASS(errctx, aksl_memset(obj->readIdentifierLeaves, 0, sizeof(obj->readIdentifierLeaves)));
|
||||
obj->doLeafPool.next = 0;
|
||||
obj->doLeafPool.capacity = AKBASIC_MAX_CONDITION_LEAVES;
|
||||
obj->doLeafPool.leaves = obj->doLeafStorage;
|
||||
@@ -123,6 +124,7 @@ akerr_ErrorContext *akbasic_environment_new_leaf(akbasic_Environment *obj, akbas
|
||||
akerr_ErrorContext *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t length = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in wait_for_command");
|
||||
@@ -135,38 +137,56 @@ akerr_ErrorContext *akbasic_environment_wait_for_command(akbasic_Environment *ob
|
||||
FAIL_NONZERO_RETURN(errctx, (obj->waitingForCommand[0] != '\0'), AKBASIC_ERR_STATE,
|
||||
"Can't wait on multiple commands in the same environment : %s",
|
||||
obj->waitingForCommand);
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(command) < sizeof(obj->waitingForCommand)),
|
||||
PASS(errctx, aksl_strlen(command, &length));
|
||||
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->waitingForCommand)),
|
||||
AKBASIC_ERR_BOUNDS, "Command name '%s' is too long to wait on", command);
|
||||
strncpy(obj->waitingForCommand, command, sizeof(obj->waitingForCommand) - 1);
|
||||
obj->waitingForCommand[sizeof(obj->waitingForCommand) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(obj->waitingForCommand, sizeof(obj->waitingForCommand), command));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
bool akbasic_environment_is_waiting_for_any(akbasic_Environment *obj)
|
||||
akerr_ErrorContext *akbasic_environment_is_waiting_for_any(akbasic_Environment *obj, bool *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL destination in is_waiting_for_any");
|
||||
*dest = false;
|
||||
if ( obj == NULL ) {
|
||||
return false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( obj->waitingForCommand[0] != '\0' ) {
|
||||
return true;
|
||||
*dest = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
return akbasic_environment_is_waiting_for_any(obj->parent);
|
||||
/* The recursive tail becomes a PASS; dest carries the answer back up. */
|
||||
PASS(errctx, akbasic_environment_is_waiting_for_any(obj->parent, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
bool akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command)
|
||||
akerr_ErrorContext *akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command, bool *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL destination in is_waiting_for");
|
||||
*dest = false;
|
||||
if ( obj == NULL || command == NULL ) {
|
||||
return false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( strcmp(obj->waitingForCommand, command) == 0 ) {
|
||||
return true;
|
||||
PASS(errctx, aksl_strcmp(obj->waitingForCommand, command, &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
*dest = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
return akbasic_environment_is_waiting_for(obj->parent, command);
|
||||
PASS(errctx, akbasic_environment_is_waiting_for(obj->parent, command, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_environment_stop_waiting(akbasic_Environment *obj, const char *command)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && command != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in stop_waiting");
|
||||
@@ -179,7 +199,8 @@ akerr_ErrorContext *akbasic_environment_stop_waiting(akbasic_Environment *obj, c
|
||||
* a miss is silently tolerated, exactly as today.
|
||||
*/
|
||||
while ( obj != NULL ) {
|
||||
if ( strcmp(obj->waitingForCommand, command) == 0 ) {
|
||||
PASS(errctx, aksl_strcmp(obj->waitingForCommand, command, &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
obj->waitingForCommand[0] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -197,7 +218,7 @@ akerr_ErrorContext *akbasic_environment_get_function(akbasic_Environment *obj, c
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && fname != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in get_function");
|
||||
len = strlen(fname);
|
||||
PASS(errctx, aksl_strlen(fname, &len));
|
||||
FAIL_ZERO_RETURN(errctx, (len < sizeof(upper)), AKERR_KEY, "Function '%s' is not defined", fname);
|
||||
for ( i = 0; i < len; i++ ) {
|
||||
char c = fname[i];
|
||||
@@ -291,6 +312,7 @@ akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const c
|
||||
akbasic_Variable *variable = NULL;
|
||||
int64_t sizes[1] = { 1 };
|
||||
void *slot = NULL;
|
||||
size_t namelen = 0;
|
||||
akerr_ErrorContext *found = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && varname != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
@@ -312,10 +334,10 @@ akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const c
|
||||
IGNORE(akerr_release_error(found));
|
||||
|
||||
PASS(errctx, akbasic_runtime_new_variable(obj->runtime, &variable));
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(varname) < sizeof(variable->name)), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(varname, &namelen));
|
||||
FAIL_ZERO_RETURN(errctx, (namelen < sizeof(variable->name)), AKBASIC_ERR_BOUNDS,
|
||||
"Variable name '%s' is too long", varname);
|
||||
strncpy(variable->name, varname, sizeof(variable->name) - 1);
|
||||
variable->name[sizeof(variable->name) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(variable->name, sizeof(variable->name), varname));
|
||||
variable->valuetype = AKBASIC_TYPE_UNDEFINED;
|
||||
variable->mutable_ = true;
|
||||
PASS(errctx, akbasic_variable_init(variable, &obj->runtime->valuepool, sizes, 1));
|
||||
|
||||
65
src/format.c
65
src/format.c
@@ -12,6 +12,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/format.h>
|
||||
@@ -36,7 +37,7 @@ akerr_ErrorContext *akbasic_format_state_init(akbasic_FormatState *obj)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL format state in init");
|
||||
memcpy(obj->chars, PUDEF_DEFAULTS, sizeof(obj->chars));
|
||||
PASS(errctx, aksl_memcpy(obj->chars, PUDEF_DEFAULTS, sizeof(obj->chars)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -161,11 +162,20 @@ static void measure_numeric(const char *field, size_t length, int *before, int *
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Fill @p dest with @p width copies of `*`, the overflow marker. */
|
||||
static void overflow(char *dest, size_t width)
|
||||
/**
|
||||
* @brief Fill @p dest with @p width copies of `*`, the overflow marker.
|
||||
*
|
||||
* Returns a context rather than `void` because the fill can fail and there was
|
||||
* nowhere to say so. Both call sites are already inside `render_numeric`, which
|
||||
* returns one. See libakstdlib #38.
|
||||
*/
|
||||
static akerr_ErrorContext *overflow(char *dest, size_t width)
|
||||
{
|
||||
memset(dest, '*', width);
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
PASS(errctx, aksl_memset(dest, '*', width));
|
||||
dest[width] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,15 +201,21 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
|
||||
bool leadsign = false;
|
||||
bool trailsign = false;
|
||||
bool negative = (number < 0.0);
|
||||
int written = 0;
|
||||
|
||||
measure_numeric(field, length, &before, &after, &commas, &dollar, &leadsign, &trailsign);
|
||||
FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS,
|
||||
"PRINT USING field of %zu characters does not fit", length);
|
||||
|
||||
snprintf(digits, sizeof(digits), "%.*f", after, (negative ? -number : number));
|
||||
PASS(errctx, aksl_snprintf(&written, digits, sizeof(digits), "%.*f",
|
||||
after, (negative ? -number : number)));
|
||||
|
||||
point = strchr(digits, '.');
|
||||
intlen = (point != NULL ? (size_t)(point - digits) : strlen(digits));
|
||||
PASS(errctx, aksl_strchr(digits, '.', &point));
|
||||
if ( point != NULL ) {
|
||||
intlen = (size_t)(point - digits);
|
||||
} else {
|
||||
PASS(errctx, aksl_strlen(digits, &intlen));
|
||||
}
|
||||
|
||||
/* Group the integer part, if the field asked for separators. */
|
||||
used = 0;
|
||||
@@ -213,7 +229,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
|
||||
used += 1;
|
||||
}
|
||||
} else {
|
||||
memcpy(grouped, digits, intlen);
|
||||
PASS(errctx, aksl_memcpy(grouped, digits, intlen));
|
||||
used = intlen;
|
||||
}
|
||||
grouped[used] = '\0';
|
||||
@@ -226,7 +242,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
|
||||
* printing wider than asked, which would misalign every later column.
|
||||
*/
|
||||
if ( (int)intlen > before ) {
|
||||
overflow(dest, length);
|
||||
PASS(errctx, overflow(dest, length));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -252,13 +268,13 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
|
||||
used += 1;
|
||||
}
|
||||
}
|
||||
memcpy(dest + used, grouped, (size_t)group);
|
||||
PASS(errctx, aksl_memcpy(dest + used, grouped, (size_t)group));
|
||||
used += (size_t)group;
|
||||
|
||||
if ( after > 0 ) {
|
||||
dest[used] = obj->chars[AKBASIC_PUDEF_POINT];
|
||||
used += 1;
|
||||
memcpy(dest + used, (point != NULL ? point + 1 : ""), (size_t)after);
|
||||
PASS(errctx, aksl_memcpy(dest + used, (point != NULL ? point + 1 : ""), (size_t)after));
|
||||
used += (size_t)after;
|
||||
}
|
||||
if ( trailsign ) {
|
||||
@@ -271,7 +287,7 @@ static akerr_ErrorContext *render_numeric(akbasic_FormatState *obj, const char *
|
||||
* because a printed -5 that reads as 5 is worse than a row of stars.
|
||||
*/
|
||||
if ( negative && !leadsign && !trailsign ) {
|
||||
overflow(dest, length);
|
||||
PASS(errctx, overflow(dest, length));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
dest[used] = '\0';
|
||||
@@ -283,26 +299,27 @@ static akerr_ErrorContext *render_string(const char *field, size_t length, const
|
||||
char *dest, size_t len)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t textlen = strlen(text);
|
||||
size_t textlen = 0;
|
||||
size_t pad = 0;
|
||||
|
||||
PASS(errctx, aksl_strlen(text, &textlen));
|
||||
FAIL_ZERO_RETURN(errctx, (length + 1 <= len), AKBASIC_ERR_BOUNDS,
|
||||
"PRINT USING field of %zu characters does not fit", length);
|
||||
if ( textlen > length ) {
|
||||
/* Truncated, not starred: BASIC 7.0 cuts a string to its field. */
|
||||
memcpy(dest, text, length);
|
||||
PASS(errctx, aksl_memcpy(dest, text, length));
|
||||
dest[length] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
memset(dest, ' ', length);
|
||||
PASS(errctx, aksl_memset(dest, ' ', length));
|
||||
dest[length] = '\0';
|
||||
if ( field[0] == '=' ) {
|
||||
pad = (length - textlen) / 2;
|
||||
} else {
|
||||
pad = length - textlen;
|
||||
}
|
||||
memcpy(dest + pad, text, textlen);
|
||||
PASS(errctx, aksl_memcpy(dest + pad, text, textlen));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -313,6 +330,9 @@ akerr_ErrorContext *akbasic_format_using(akbasic_FormatState *obj, const char *f
|
||||
size_t start = 0;
|
||||
size_t length = 0;
|
||||
size_t used = 0;
|
||||
size_t renderedlen = 0;
|
||||
size_t formatlen = 0;
|
||||
int written = 0;
|
||||
bool numeric = false;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && format != NULL && value != NULL && dest != NULL),
|
||||
@@ -343,12 +363,15 @@ akerr_ErrorContext *akbasic_format_using(akbasic_FormatState *obj, const char *f
|
||||
}
|
||||
|
||||
/* Literal text before the field, the field, then literal text after it. */
|
||||
used = strlen(rendered) + strlen(format) - length;
|
||||
PASS(errctx, aksl_strlen(rendered, &renderedlen));
|
||||
PASS(errctx, aksl_strlen(format, &formatlen));
|
||||
used = renderedlen + formatlen - length;
|
||||
FAIL_ZERO_RETURN(errctx, (used + 1 <= len), AKBASIC_ERR_BOUNDS,
|
||||
"PRINT USING result of %zu characters does not fit", used);
|
||||
memcpy(dest, format, start);
|
||||
memcpy(dest + start, rendered, strlen(rendered));
|
||||
snprintf(dest + start + strlen(rendered), len - start - strlen(rendered),
|
||||
"%s", format + start + length);
|
||||
PASS(errctx, aksl_memcpy(dest, format, start));
|
||||
PASS(errctx, aksl_memcpy(dest + start, rendered, renderedlen));
|
||||
/* The check above already proved the tail fits, so this cannot truncate. */
|
||||
PASS(errctx, aksl_snprintf(&written, dest + start + renderedlen,
|
||||
len - start - renderedlen, "%s", format + start + length));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akgl/controller.h>
|
||||
#include <akgl/error.h>
|
||||
@@ -55,7 +55,7 @@ akerr_ErrorContext *akbasic_frontend_akgl_init(akbasic_AkglFrontend *obj, const
|
||||
FAIL_ZERO_RETURN(errctx, (w > 0 && h > 0 && fontsize > 0), AKBASIC_ERR_VALUE,
|
||||
"A %dx%d window at %d points is not a window", w, h, fontsize);
|
||||
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->width = w;
|
||||
obj->height = h;
|
||||
|
||||
|
||||
@@ -17,14 +17,19 @@
|
||||
static akerr_ErrorContext *copy_bounded(char *dest, const char *src, const char *what)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t srclen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (src != NULL), AKERR_NULLPOINTER, "NULL %s", what);
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(src) < AKBASIC_MAX_STRING_LENGTH),
|
||||
PASS(errctx, aksl_strlen(src, &srclen));
|
||||
FAIL_ZERO_RETURN(errctx, (srclen < AKBASIC_MAX_STRING_LENGTH),
|
||||
AKBASIC_ERR_VALUE,
|
||||
"%s of %zu characters exceeds the %d character limit",
|
||||
what, strlen(src), AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
strncpy(dest, src, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
dest[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
|
||||
what, srclen, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
/* aksl_strcpy always terminates and refuses rather than truncates, which is
|
||||
the behaviour this site already wanted. The length check above stays
|
||||
because it names the limit in the message. Every destination is one of the
|
||||
leaf's AKBASIC_MAX_STRING_LENGTH buffers, which is what sizes the copy. */
|
||||
PASS(errctx, aksl_strcpy(dest, AKBASIC_MAX_STRING_LENGTH, src));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -90,8 +95,8 @@ static akerr_ErrorContext *clone_into(akbasic_ASTLeaf *self, akbasic_LeafPool *p
|
||||
copy->parent = self->parent;
|
||||
copy->literal_int = self->literal_int;
|
||||
copy->literal_float = self->literal_float;
|
||||
memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string));
|
||||
memcpy(copy->identifier, self->identifier, sizeof(copy->identifier));
|
||||
PASS(errctx, aksl_memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string)));
|
||||
PASS(errctx, aksl_memcpy(copy->identifier, self->identifier, sizeof(copy->identifier)));
|
||||
copy->operator_ = self->operator_;
|
||||
|
||||
PASS(errctx, clone_into(self->left, pool, ©->left));
|
||||
@@ -289,7 +294,9 @@ akerr_ErrorContext *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const cha
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
long long value = 0;
|
||||
size_t lexlen = 0;
|
||||
int base = 10;
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_literal_int");
|
||||
FAIL_ZERO_RETURN(errctx, (lexeme != NULL), AKERR_NULLPOINTER, "NULL lexeme in new_literal_int");
|
||||
@@ -302,7 +309,9 @@ akerr_ErrorContext *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const cha
|
||||
* Commodore BASIC has no octal literals, and a leading zero in a listing is
|
||||
* padding, not a radix.
|
||||
*/
|
||||
if ( strlen(lexeme) > 2 && strncmp(lexeme, "0x", 2) == 0 ) {
|
||||
PASS(errctx, aksl_strlen(lexeme, &lexlen));
|
||||
PASS(errctx, aksl_strncmp(lexeme, "0x", 2, &cmp));
|
||||
if ( lexlen > 2 && cmp == 0 ) {
|
||||
base = 16;
|
||||
}
|
||||
PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_LITERAL_INT));
|
||||
@@ -388,6 +397,7 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
|
||||
PREPARE_ERROR(errctx);
|
||||
char sub1[AKBASIC_MAX_STRING_LENGTH];
|
||||
char sub2[AKBASIC_MAX_STRING_LENGTH];
|
||||
int written = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL leaf in to_string");
|
||||
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in to_string");
|
||||
@@ -395,40 +405,42 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
|
||||
|
||||
switch ( self->leaftype ) {
|
||||
case AKBASIC_LEAF_LITERAL_INT:
|
||||
snprintf(dest, len, "%" PRId64, self->literal_int);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, self->literal_int));
|
||||
break;
|
||||
case AKBASIC_LEAF_LITERAL_FLOAT:
|
||||
snprintf(dest, len, "%f", self->literal_float);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%f", self->literal_float));
|
||||
break;
|
||||
case AKBASIC_LEAF_LITERAL_STRING:
|
||||
snprintf(dest, len, "%s", self->literal_string);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->literal_string));
|
||||
break;
|
||||
case AKBASIC_LEAF_IDENTIFIER_INT:
|
||||
case AKBASIC_LEAF_IDENTIFIER_FLOAT:
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRING:
|
||||
case AKBASIC_LEAF_IDENTIFIER:
|
||||
snprintf(dest, len, "%s", self->identifier);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->identifier));
|
||||
break;
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRUCT:
|
||||
snprintf(dest, len, "%s", self->identifier);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->identifier));
|
||||
break;
|
||||
case AKBASIC_LEAF_FIELD:
|
||||
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
|
||||
snprintf(dest, len, "%s%s%s", sub1,
|
||||
(self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%s%s%s", sub1,
|
||||
(self->operator_ == AKBASIC_TOK_ARROW ? "->" : "."), self->identifier));
|
||||
break;
|
||||
case AKBASIC_LEAF_UNARY:
|
||||
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
|
||||
snprintf(dest, len, "(%s %s)", operator_to_str(self->operator_), sub1);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "(%s %s)",
|
||||
operator_to_str(self->operator_), sub1));
|
||||
break;
|
||||
case AKBASIC_LEAF_BINARY:
|
||||
PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1)));
|
||||
PASS(errctx, akbasic_leaf_to_string(self->right, sub2, sizeof(sub2)));
|
||||
snprintf(dest, len, "(%s %s %s)", operator_to_str(self->operator_), sub1, sub2);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "(%s %s %s)",
|
||||
operator_to_str(self->operator_), sub1, sub2));
|
||||
break;
|
||||
case AKBASIC_LEAF_GROUPING:
|
||||
PASS(errctx, akbasic_leaf_to_string(self->expr, sub1, sizeof(sub1)));
|
||||
snprintf(dest, len, "(group %s)", sub1);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "(group %s)", sub1));
|
||||
break;
|
||||
case AKBASIC_LEAF_COMMAND:
|
||||
case AKBASIC_LEAF_COMMAND_IMMEDIATE:
|
||||
@@ -437,10 +449,10 @@ akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, si
|
||||
* The reference falls through to Go's %+v struct dump here, which has no
|
||||
* useful C equivalent. Print something a test can assert on instead.
|
||||
*/
|
||||
snprintf(dest, len, "(%s)", self->identifier);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "(%s)", self->identifier));
|
||||
break;
|
||||
default:
|
||||
snprintf(dest, len, "(leaf %d)", (int)self->leaftype);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "(leaf %d)", (int)self->leaftype));
|
||||
break;
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
* reaching for a global, which is exactly what goal 3 needs.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akgl/draw.h>
|
||||
#include <akgl/error.h>
|
||||
@@ -246,7 +245,7 @@ akerr_ErrorContext *akbasic_graphics_init_akgl(akbasic_GraphicsBackend *obj, akb
|
||||
"NULL renderer in graphics_init_akgl: the host creates it, not this");
|
||||
PASS(errctx, akgl_error_init());
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
|
||||
state->renderer = renderer;
|
||||
|
||||
obj->self = state;
|
||||
|
||||
36
src/host.c
36
src/host.c
@@ -19,9 +19,9 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/host.h>
|
||||
@@ -89,8 +89,14 @@ akerr_ErrorContext *akbasic_host_read_field(akbasic_Runtime *obj, akbasic_Struct
|
||||
* left it full: a BASIC string is inline and fixed, so there is nothing
|
||||
* to alias and nothing that can outlive the read.
|
||||
*/
|
||||
/*
|
||||
* Raw snprintf: the host's bytes need not be terminated, and `%.*s`
|
||||
* bounded by hostwidth is what reads at most that many of them. A field
|
||||
* wider than the destination truncates, which is the documented contract
|
||||
* for reading a host string into a fixed BASIC one. libakstdlib #34.
|
||||
*/
|
||||
snprintf(text, sizeof(text), "%.*s", (int)field->hostwidth, at);
|
||||
snprintf(dest->stringval, sizeof(dest->stringval), "%s", text);
|
||||
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), text));
|
||||
break;
|
||||
default:
|
||||
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE,
|
||||
@@ -115,6 +121,7 @@ akerr_ErrorContext *akbasic_host_write_field(akbasic_Runtime *obj, akbasic_Struc
|
||||
char *at = (char *)hostbase + field->hostoffset;
|
||||
int64_t n = 0;
|
||||
double d = 0.0;
|
||||
size_t len = 0;
|
||||
|
||||
(void)obj;
|
||||
FAIL_ZERO_RETURN(errctx, (hostbase != NULL && src != NULL), AKERR_NULLPOINTER,
|
||||
@@ -166,12 +173,13 @@ akerr_ErrorContext *akbasic_host_write_field(akbasic_Runtime *obj, akbasic_Struc
|
||||
* eight of them. BASIC strings have no width, so this is the one place
|
||||
* the two models genuinely disagree and the disagreement is reported.
|
||||
*/
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(src->stringval) < field->hostwidth),
|
||||
PASS(errctx, aksl_strlen(src->stringval, &len));
|
||||
FAIL_ZERO_RETURN(errctx, (len < field->hostwidth),
|
||||
AKBASIC_ERR_VALUE,
|
||||
"A string of %zu characters does not fit in %s, which holds %zu",
|
||||
strlen(src->stringval), field->name, field->hostwidth - 1);
|
||||
memset(at, 0, field->hostwidth);
|
||||
memcpy(at, src->stringval, strlen(src->stringval));
|
||||
len, field->name, field->hostwidth - 1);
|
||||
PASS(errctx, aksl_memset(at, 0, field->hostwidth));
|
||||
PASS(errctx, aksl_memcpy(at, src->stringval, len));
|
||||
break;
|
||||
default:
|
||||
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE,
|
||||
@@ -226,7 +234,16 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas
|
||||
type->name, type->fieldcount, AKBASIC_MAX_STRUCT_FIELDS);
|
||||
|
||||
dest = &obj->structtypes.types[obj->structtypes.count];
|
||||
memset(dest, 0, sizeof(*dest));
|
||||
PASS(errctx, aksl_memset(dest, 0, sizeof(*dest)));
|
||||
/*
|
||||
* Raw snprintf, and a latent defect rather than a settled decision: a host
|
||||
* type name over 31 characters truncates silently here, and two that share a
|
||||
* 31-character prefix then collide in akbasic_structtype_find. scan_names()
|
||||
* in structtype.c already refuses the same case for a script-declared type
|
||||
* with an explicit limit message, so the two paths disagree. Converting this
|
||||
* to aksl_strcpy is the fix and it is a behaviour change on a public
|
||||
* registration call, so it wants its own issue rather than this port.
|
||||
*/
|
||||
snprintf(dest->name, sizeof(dest->name), "%s", type->name);
|
||||
dest->used = true;
|
||||
dest->ishost = true;
|
||||
@@ -241,7 +258,7 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (src->name != NULL), AKERR_NULLPOINTER,
|
||||
"%s field %d has no name", type->name, i);
|
||||
len = strlen(src->name);
|
||||
PASS(errctx, aksl_strlen(src->name, &len));
|
||||
/*
|
||||
* The suffix has to agree with the C type. A host that writes "HP%" over
|
||||
* an int32_t has said two different things about one field, and the
|
||||
@@ -253,6 +270,7 @@ akerr_ErrorContext *akbasic_host_register_type(akbasic_Runtime *obj, const akbas
|
||||
"%s.%s must end in '%c' for the C type it describes",
|
||||
type->name, src->name, suffix_for(src->kind));
|
||||
|
||||
/* Same silent truncation as the type name above, and the same fix. */
|
||||
snprintf(field->name, sizeof(field->name), "%s", src->name);
|
||||
field->hostkind = src->kind;
|
||||
field->hostoffset = src->offset;
|
||||
@@ -296,7 +314,7 @@ akerr_ErrorContext *akbasic_host_bind(akbasic_Runtime *obj, const char *name,
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && typename_ != NULL && instance != NULL),
|
||||
AKERR_NULLPOINTER, "NULL argument in host bind");
|
||||
len = strlen(name);
|
||||
PASS(errctx, aksl_strlen(name, &len));
|
||||
FAIL_ZERO_RETURN(errctx, (len >= 2 && name[len - 1] == '@'), AKBASIC_ERR_VALUE,
|
||||
"A structure variable's name ends in '@', so \"%s\" cannot be bound", name);
|
||||
PASS(errctx, akbasic_structtype_find(&obj->structtypes, typename_, &typeindex));
|
||||
|
||||
@@ -157,6 +157,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_akgl(FILE *program, const char *pa
|
||||
const char *fontpath = getenv("AKBASIC_FONT");
|
||||
FILE *input = program;
|
||||
int mode = AKBASIC_MODE_RUNSTREAM;
|
||||
int fd = 0;
|
||||
|
||||
if ( fontpath == NULL ) {
|
||||
fontpath = AKBASIC_FONT_PATH;
|
||||
@@ -169,7 +170,8 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_akgl(FILE *program, const char *pa
|
||||
* in an AKGL build, and the golden corpus can be driven through this
|
||||
* binary as well as through the stdio one.
|
||||
*/
|
||||
input = (isatty(fileno(stdin)) ? NULL : stdin);
|
||||
PASS(errctx, aksl_fileno(stdin, &fd));
|
||||
input = (isatty(fd) ? NULL : stdin);
|
||||
mode = AKBASIC_MODE_REPL;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/parser.h>
|
||||
@@ -206,8 +206,7 @@ akerr_ErrorContext *akbasic_parser_command(akbasic_Parser *obj, akbasic_ASTLeaf
|
||||
|
||||
PASS(errctx, akbasic_parser_previous(obj, &operator_));
|
||||
optype = operator_->tokentype;
|
||||
strncpy(opname, operator_->lexeme, sizeof(opname) - 1);
|
||||
opname[sizeof(opname) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(opname, sizeof(opname), operator_->lexeme));
|
||||
|
||||
/* Does this verb need its own parse path? */
|
||||
PASS(errctx, akbasic_verb_lookup(opname, &verb));
|
||||
@@ -596,8 +595,7 @@ static akerr_ErrorContext *function_call(akbasic_Parser *obj, akbasic_ASTLeaf **
|
||||
}
|
||||
|
||||
PASS(errctx, akbasic_parser_previous(obj, &operator_));
|
||||
strncpy(fname, operator_->lexeme, sizeof(fname) - 1);
|
||||
fname[sizeof(fname) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(fname, sizeof(fname), operator_->lexeme));
|
||||
|
||||
PASS(errctx, akbasic_verb_lookup(fname, &verb));
|
||||
if ( verb != NULL && verb->tokentype == AKBASIC_TOK_FUNCTION ) {
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/parser.h>
|
||||
@@ -78,20 +77,23 @@ akerr_ErrorContext *akbasic_parse_graphic(akbasic_Parser *parser, akbasic_ASTLea
|
||||
akbasic_ASTLeaf *arglist = NULL;
|
||||
akbasic_ASTLeaf *expr = NULL;
|
||||
akbasic_Token *peeked = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
peeked = akbasic_parser_peek(parser);
|
||||
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND &&
|
||||
strcmp(peeked->lexeme, "CLR") == 0 ) {
|
||||
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
|
||||
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
|
||||
arglist->operator_ = AKBASIC_TOK_FUNCTION_ARGUMENT;
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist->right));
|
||||
PASS(errctx, akbasic_leaf_new_literal_int(arglist->right, "5"));
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
||||
PASS(errctx, akbasic_leaf_new_command(expr, "GRAPHIC", arglist));
|
||||
*dest = expr;
|
||||
SUCCEED_RETURN(errctx);
|
||||
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
|
||||
PASS(errctx, aksl_strcmp(peeked->lexeme, "CLR", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
|
||||
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
|
||||
arglist->operator_ = AKBASIC_TOK_FUNCTION_ARGUMENT;
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist->right));
|
||||
PASS(errctx, akbasic_leaf_new_literal_int(arglist->right, "5"));
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
||||
PASS(errctx, akbasic_leaf_new_command(expr, "GRAPHIC", arglist));
|
||||
*dest = expr;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
}
|
||||
PASS(errctx, akbasic_parse_arglist(parser, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -105,6 +107,7 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
|
||||
akbasic_ASTLeaf *tail = NULL;
|
||||
akbasic_Token *peeked = NULL;
|
||||
akbasic_Token *operator_ = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
/*
|
||||
* DRAW source, x1,y1 TO x2,y2 TO x3,y3 -- a polyline, with TO between pairs
|
||||
@@ -125,9 +128,11 @@ akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf *
|
||||
|
||||
for ( ;; ) {
|
||||
peeked = akbasic_parser_peek(parser);
|
||||
if ( peeked == NULL ||
|
||||
peeked->tokentype != AKBASIC_TOK_COMMAND ||
|
||||
strcmp(peeked->lexeme, "TO") != 0 ) {
|
||||
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ) {
|
||||
break;
|
||||
}
|
||||
PASS(errctx, aksl_strcmp(peeked->lexeme, "TO", &cmp));
|
||||
if ( cmp != 0 ) {
|
||||
break;
|
||||
}
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
@@ -259,6 +264,7 @@ static akerr_ErrorContext *parse_loop_condition(akbasic_Parser *parser, akbasic_
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Token *peeked = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
*condition = NULL;
|
||||
*kind = AKBASIC_LOOPCOND_NONE;
|
||||
@@ -267,12 +273,15 @@ static akerr_ErrorContext *parse_loop_condition(akbasic_Parser *parser, akbasic_
|
||||
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( strcmp(peeked->lexeme, "WHILE") == 0 ) {
|
||||
PASS(errctx, aksl_strcmp(peeked->lexeme, "WHILE", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
*kind = AKBASIC_LOOPCOND_WHILE;
|
||||
} else if ( strcmp(peeked->lexeme, "UNTIL") == 0 ) {
|
||||
*kind = AKBASIC_LOOPCOND_UNTIL;
|
||||
} else {
|
||||
SUCCEED_RETURN(errctx);
|
||||
PASS(errctx, aksl_strcmp(peeked->lexeme, "UNTIL", &cmp));
|
||||
if ( cmp != 0 ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
*kind = AKBASIC_LOOPCOND_UNTIL;
|
||||
}
|
||||
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
|
||||
|
||||
@@ -372,6 +381,7 @@ akerr_ErrorContext *akbasic_parse_on(akbasic_Parser *parser, akbasic_ASTLeaf **d
|
||||
akbasic_ASTLeaf *expr = NULL;
|
||||
akbasic_ASTLeaf *tail = NULL;
|
||||
akbasic_Token *operator_ = NULL;
|
||||
int cmp = 0;
|
||||
bool gosub = false;
|
||||
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
|
||||
@@ -389,10 +399,12 @@ akerr_ErrorContext *akbasic_parse_on(akbasic_Parser *parser, akbasic_ASTLeaf **d
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
AKBASIC_ERR_SYNTAX, "Expected GOTO or GOSUB after ON (expression)");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
||||
if ( strcmp(operator_->lexeme, "GOSUB") == 0 ) {
|
||||
PASS(errctx, aksl_strcmp(operator_->lexeme, "GOSUB", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
gosub = true;
|
||||
} else {
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "GOTO"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcmp(operator_->lexeme, "GOTO", &cmp));
|
||||
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
|
||||
"Expected GOTO or GOSUB after ON (expression)");
|
||||
}
|
||||
marker->literal_int = (gosub ? 1 : 0);
|
||||
@@ -429,14 +441,20 @@ akerr_ErrorContext *akbasic_parse_resume(akbasic_Parser *parser, akbasic_ASTLeaf
|
||||
akbasic_ASTLeaf *expr = NULL;
|
||||
akbasic_ASTLeaf *target = NULL;
|
||||
akbasic_Token *peeked = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
peeked = akbasic_parser_peek(parser);
|
||||
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND &&
|
||||
strcmp(peeked->lexeme, "NEXT") == 0 ) {
|
||||
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &target));
|
||||
PASS(errctx, akbasic_leaf_new_command(target, "NEXT", NULL));
|
||||
} else if ( peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
|
||||
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
|
||||
PASS(errctx, aksl_strcmp(peeked->lexeme, "NEXT", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
|
||||
PASS(errctx, akbasic_parser_new_leaf(parser, &target));
|
||||
PASS(errctx, akbasic_leaf_new_command(target, "NEXT", NULL));
|
||||
}
|
||||
}
|
||||
/* The other two forms, taken only when the word was not NEXT -- which is
|
||||
what a target already built says. */
|
||||
if ( target == NULL && peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
|
||||
PASS(errctx, akbasic_parser_expression(parser, &target));
|
||||
}
|
||||
|
||||
@@ -464,6 +482,8 @@ akerr_ErrorContext *akbasic_parse_print(akbasic_Parser *parser, akbasic_ASTLeaf
|
||||
akbasic_ASTLeaf *arglist = NULL;
|
||||
akbasic_ASTLeaf *right = NULL;
|
||||
akbasic_Token *peeked = NULL;
|
||||
int cmp = 0;
|
||||
bool matched = false;
|
||||
|
||||
peeked = akbasic_parser_peek(parser);
|
||||
|
||||
@@ -499,8 +519,11 @@ akerr_ErrorContext *akbasic_parse_print(akbasic_Parser *parser, akbasic_ASTLeaf
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
if ( peeked == NULL || peeked->tokentype != AKBASIC_TOK_COMMAND ||
|
||||
strcmp(peeked->lexeme, "USING") != 0 ) {
|
||||
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND ) {
|
||||
PASS(errctx, aksl_strcmp(peeked->lexeme, "USING", &cmp));
|
||||
matched = (cmp == 0);
|
||||
}
|
||||
if ( !matched ) {
|
||||
/* The ordinary PRINT: one expression, or none at all. */
|
||||
if ( peeked != NULL && peeked->tokentype != AKBASIC_TOK_COLON ) {
|
||||
PASS(errctx, akbasic_parser_expression(parser, &right));
|
||||
@@ -627,12 +650,14 @@ akerr_ErrorContext *akbasic_parse_point(akbasic_Parser *parser, akbasic_ASTLeaf
|
||||
akbasic_ASTLeaf *arglist = NULL;
|
||||
akbasic_ASTLeaf *command = NULL;
|
||||
akbasic_Token *word = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, akbasic_parser_expression(parser, &pointer));
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected POINT <pointer> AT <structure>");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AT") == 0), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "AT", &cmp));
|
||||
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected AT after POINT <pointer>, not %s", word->lexeme);
|
||||
PASS(errctx, akbasic_parser_expression(parser, &target));
|
||||
|
||||
@@ -668,6 +693,7 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_ASTLeaf *command = NULL;
|
||||
akbasic_Token *word = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, parse_verb_with_identifier(parser, "DIM", dest));
|
||||
command = *dest;
|
||||
@@ -676,13 +702,15 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AS") == 0), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "AS", &cmp));
|
||||
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected AS after DIM %s, not %s", command->right->identifier, word->lexeme);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
if ( strcasecmp(word->lexeme, "PTR") == 0 ) {
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "PTR", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
command->literal_int = 1;
|
||||
/*
|
||||
* `TO` is already a verb -- FOR ... TO owns it -- so it arrives as a
|
||||
@@ -694,13 +722,14 @@ akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "TO") == 0), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "TO", &cmp));
|
||||
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
}
|
||||
snprintf(command->literal_string, sizeof(command->literal_string), "%s", word->lexeme);
|
||||
PASS(errctx, aksl_strcpy(command->literal_string, sizeof(command->literal_string), word->lexeme));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -738,6 +767,7 @@ static akerr_ErrorContext *parse_def_parameters(akbasic_Parser *parser, akbasic_
|
||||
akbasic_ASTLeaf *param = NULL;
|
||||
akbasic_ASTLeaf *tail = NULL;
|
||||
akbasic_Token *word = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_LEFT_PAREN),
|
||||
AKBASIC_ERR_SYNTAX, "Expected an argument list after DEF <name>");
|
||||
@@ -762,25 +792,28 @@ static akerr_ErrorContext *parse_def_parameters(akbasic_Parser *parser, akbasic_
|
||||
"%s must name its type: %s AS TYPENAME, or %s AS PTR TO TYPENAME",
|
||||
param->identifier, param->identifier, param->identifier);
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "AS") == 0), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "AS", &cmp));
|
||||
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected AS after %s, not %s", param->identifier, word->lexeme);
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected a type name after AS");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
if ( strcasecmp(word->lexeme, "PTR") == 0 ) {
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "PTR", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
param->literal_int = 1;
|
||||
/* `TO` is already a verb -- FOR ... TO owns it -- so it arrives
|
||||
as a command token rather than an identifier. */
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
AKBASIC_ERR_SYNTAX, "Expected TO after PTR");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
FAIL_ZERO_RETURN(errctx, (strcasecmp(word->lexeme, "TO") == 0), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcasecmp(word->lexeme, "TO", &cmp));
|
||||
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_SYNTAX,
|
||||
"Expected PTR TO TYPENAME, not PTR %s", word->lexeme);
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_IDENTIFIER),
|
||||
AKBASIC_ERR_SYNTAX, "Expected a type name after PTR TO");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &word));
|
||||
}
|
||||
snprintf(param->literal_string, sizeof(param->literal_string), "%s", word->lexeme);
|
||||
PASS(errctx, aksl_strcpy(param->literal_string, sizeof(param->literal_string), word->lexeme));
|
||||
}
|
||||
|
||||
if ( tail == NULL ) {
|
||||
@@ -806,6 +839,7 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
akbasic_ASTLeaf *expression = NULL;
|
||||
akbasic_ASTLeaf *command = NULL;
|
||||
akbasic_FunctionDef *fndef = NULL;
|
||||
size_t namelen = 0;
|
||||
size_t i = 0;
|
||||
|
||||
PASS(errctx, akbasic_parser_primary(parser, &identifier));
|
||||
@@ -817,13 +851,14 @@ akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
PASS(errctx, akbasic_runtime_new_function(runtime, &fndef));
|
||||
|
||||
/* Uppercase the name: verbs and function names are case-insensitive. */
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(identifier->identifier) < sizeof(fndef->name)),
|
||||
PASS(errctx, aksl_strlen(identifier->identifier, &namelen));
|
||||
FAIL_ZERO_RETURN(errctx, (namelen < sizeof(fndef->name)),
|
||||
AKBASIC_ERR_BOUNDS, "Function name '%s' is too long", identifier->identifier);
|
||||
for ( i = 0; i < strlen(identifier->identifier); i++ ) {
|
||||
for ( i = 0; i < namelen; i++ ) {
|
||||
char c = identifier->identifier[i];
|
||||
fndef->name[i] = (char)((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c);
|
||||
}
|
||||
fndef->name[strlen(identifier->identifier)] = '\0';
|
||||
fndef->name[namelen] = '\0';
|
||||
|
||||
if ( akbasic_parser_match1(parser, AKBASIC_TOK_ASSIGNMENT) ) {
|
||||
PASS(errctx, akbasic_parser_expression(parser, &expression));
|
||||
@@ -866,13 +901,15 @@ akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
akbasic_Environment *parent = runtime->environment;
|
||||
akbasic_Environment *newenv = NULL;
|
||||
int64_t firstline = 0;
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, akbasic_parser_assignment(parser, &assignment));
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
AKBASIC_ERR_SYNTAX,
|
||||
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "TO"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcmp(operator_->lexeme, "TO", &cmp));
|
||||
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
|
||||
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
||||
FAIL_ZERO_RETURN(errctx,
|
||||
(assignment != NULL && akbasic_leaf_is_identifier(assignment->left)),
|
||||
@@ -894,7 +931,8 @@ akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
|
||||
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
|
||||
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "STEP"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcmp(operator_->lexeme, "STEP", &cmp));
|
||||
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
|
||||
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
||||
PASS(errctx, akbasic_parser_expression(parser, &newenv->forStepLeaf));
|
||||
} else {
|
||||
@@ -907,7 +945,8 @@ akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **
|
||||
}
|
||||
|
||||
/* A NEXT already being awaited means this is an inner loop over the same variable. */
|
||||
if ( strcmp(parent->waitingForCommand, "NEXT") == 0 ) {
|
||||
PASS(errctx, aksl_strcmp(parent->waitingForCommand, "NEXT", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
newenv->forNextVariable = parent->forNextVariable;
|
||||
}
|
||||
newenv->loopFirstLine = firstline;
|
||||
@@ -1038,6 +1077,7 @@ akerr_ErrorContext *akbasic_parse_if(akbasic_Parser *parser, akbasic_ASTLeaf **d
|
||||
akbasic_ASTLeaf *else_command = NULL;
|
||||
akbasic_ASTLeaf *branch = NULL;
|
||||
akbasic_Token *operator_ = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
/*
|
||||
* Everything from here to the THEN is a condition, so a lone `=` in it is an
|
||||
@@ -1057,14 +1097,16 @@ akerr_ErrorContext *akbasic_parse_if(akbasic_Parser *parser, akbasic_ASTLeaf **d
|
||||
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
||||
AKBASIC_ERR_SYNTAX, "Incomplete IF statement");
|
||||
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "THEN"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcmp(operator_->lexeme, "THEN", &cmp));
|
||||
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
|
||||
"Expected IF ... THEN");
|
||||
|
||||
PASS(errctx, akbasic_parser_command(parser, &then_command));
|
||||
|
||||
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
|
||||
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "ELSE"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, aksl_strcmp(operator_->lexeme, "ELSE", &cmp));
|
||||
FAIL_NONZERO_RETURN(errctx, cmp, AKBASIC_ERR_SYNTAX,
|
||||
"Expected IF ... THEN ... ELSE ...");
|
||||
PASS(errctx, akbasic_parser_command(parser, &else_command));
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/audio.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -96,7 +96,7 @@ akerr_ErrorContext *akbasic_play_parse(akbasic_Runtime *obj, const char *notes)
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && notes != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in play_parse");
|
||||
audio = &obj->audio_state;
|
||||
len = strlen(notes);
|
||||
PASS(errctx, aksl_strlen(notes, &len));
|
||||
|
||||
/*
|
||||
* A loop, so no CATCH and no _BREAK macros in here -- they expand to a C
|
||||
|
||||
@@ -27,10 +27,9 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/runtime.h>
|
||||
@@ -103,7 +102,7 @@ static akerr_ErrorContext *rewrite_targets(akbasic_TargetWalk *walk, const char
|
||||
for ( ;; ) {
|
||||
char replacement[32];
|
||||
int64_t line = 0;
|
||||
int written = 0;
|
||||
size_t written = 0;
|
||||
|
||||
while ( *p == ' ' || *p == '\t' ) {
|
||||
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS,
|
||||
@@ -126,9 +125,16 @@ static akerr_ErrorContext *rewrite_targets(akbasic_TargetWalk *walk, const char
|
||||
p += 1;
|
||||
}
|
||||
PASS(errctx, walk->visit(walk, line, replacement, sizeof(replacement)));
|
||||
written = snprintf(out, (size_t)(limit - out), "%s", replacement);
|
||||
/*
|
||||
* The fit is decided before the copy rather than read back out of a
|
||||
* return value afterwards: aksl_strcpy refuses rather than truncates,
|
||||
* so the bounds check has to come first to keep the diagnosis this
|
||||
* function already gives.
|
||||
*/
|
||||
PASS(errctx, aksl_strlen(replacement, &written));
|
||||
FAIL_ZERO_RETURN(errctx, (written > 0 && out + written < limit), AKBASIC_ERR_BOUNDS,
|
||||
"RENUMBER: a rewritten line does not fit");
|
||||
PASS(errctx, aksl_strcpy(out, (size_t)(limit - out), replacement));
|
||||
out += written;
|
||||
|
||||
/*
|
||||
@@ -176,6 +182,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
|
||||
const char *limit = dest + len - 1;
|
||||
bool statementstart = true;
|
||||
bool instring = false;
|
||||
int cmp = 0;
|
||||
int i = 0;
|
||||
|
||||
while ( *p != '\0' ) {
|
||||
@@ -227,12 +234,12 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
|
||||
* name in the language to reach it.
|
||||
*/
|
||||
for ( i = 0; i < BRANCH_VERB_COUNT; i++ ) {
|
||||
verblen = strlen(BRANCH_VERBS[i]);
|
||||
PASS(errctx, aksl_strlen(BRANCH_VERBS[i], &verblen));
|
||||
if ( p > code && isalnum((unsigned char)p[-1]) ) {
|
||||
break;
|
||||
}
|
||||
if ( strncasecmp(p, BRANCH_VERBS[i], verblen) == 0 &&
|
||||
!isalnum((unsigned char)p[verblen]) ) {
|
||||
PASS(errctx, aksl_strncasecmp(p, BRANCH_VERBS[i], verblen, &cmp));
|
||||
if ( cmp == 0 && !isalnum((unsigned char)p[verblen]) ) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
@@ -247,7 +254,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
|
||||
if ( matched ) {
|
||||
FAIL_ZERO_RETURN(errctx, (out + verblen < (size_t)(limit - dest) + dest),
|
||||
AKBASIC_ERR_BOUNDS, "RENUMBER: a rewritten line does not fit");
|
||||
memcpy(out, p, verblen);
|
||||
PASS(errctx, aksl_memcpy(out, p, verblen));
|
||||
out += verblen;
|
||||
p += verblen;
|
||||
PASS(errctx, rewrite_targets(walk, &p, &out, limit));
|
||||
@@ -260,9 +267,10 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co
|
||||
* cannot be pointed at it directly. Copy the type and the comma, then
|
||||
* rewrite what follows.
|
||||
*/
|
||||
if ( strncasecmp(p, "COLLISION", 9) == 0 && !isalnum((unsigned char)p[9])
|
||||
PASS(errctx, aksl_strncasecmp(p, "COLLISION", 9, &cmp));
|
||||
if ( cmp == 0 && !isalnum((unsigned char)p[9])
|
||||
&& !(p > code && isalnum((unsigned char)p[-1])) ) {
|
||||
memcpy(out, p, 9);
|
||||
PASS(errctx, aksl_memcpy(out, p, 9));
|
||||
out += 9;
|
||||
p += 9;
|
||||
while ( *p != '\0' && *p != ',' && *p != ':' ) {
|
||||
@@ -299,8 +307,9 @@ static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t targ
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
const int64_t *map = (const int64_t *)walk->self;
|
||||
int written = 0;
|
||||
|
||||
snprintf(dest, len, "%" PRId64, mapped(map, target));
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, mapped(map, target)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -347,7 +356,7 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
|
||||
next += increment;
|
||||
}
|
||||
|
||||
memset(rewritten, 0, sizeof(rewritten));
|
||||
PASS(errctx, aksl_memset(rewritten, 0, sizeof(rewritten)));
|
||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||
int64_t target = 0;
|
||||
|
||||
@@ -371,7 +380,7 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int
|
||||
rewritten[target].numbered = true;
|
||||
}
|
||||
|
||||
memcpy(obj->source, rewritten, sizeof(obj->source));
|
||||
PASS(errctx, aksl_memcpy(obj->source, rewritten, sizeof(obj->source)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -402,8 +411,9 @@ static akerr_ErrorContext *visit_check(akbasic_TargetWalk *walk, int64_t target,
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
CheckState *state = (CheckState *)walk->self;
|
||||
int written = 0;
|
||||
|
||||
snprintf(dest, len, "%" PRId64, target);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, target));
|
||||
if ( target >= 0 && target < AKBASIC_MAX_SOURCE_LINES ) {
|
||||
FAIL_NONZERO_RETURN(errctx,
|
||||
(state->runtime->source[target].code[0] != '\0'
|
||||
|
||||
197
src/runtime.c
197
src/runtime.c
@@ -6,10 +6,9 @@
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/parser.h>
|
||||
@@ -28,7 +27,7 @@ akerr_ErrorContext *akbasic_runtime_new_variable(akbasic_Runtime *obj, akbasic_V
|
||||
"NULL argument in new_variable");
|
||||
for ( i = 0; i < AKBASIC_MAX_VARIABLES; i++ ) {
|
||||
if ( !obj->variables[i].used ) {
|
||||
memset(&obj->variables[i], 0, sizeof(obj->variables[i]));
|
||||
PASS(errctx, aksl_memset(&obj->variables[i], 0, sizeof(obj->variables[i])));
|
||||
obj->variables[i].used = true;
|
||||
/*
|
||||
* Not zero: zero is a valid structure type index, so a memset alone
|
||||
@@ -102,7 +101,7 @@ akerr_ErrorContext *akbasic_runtime_new_function(akbasic_Runtime *obj, akbasic_F
|
||||
"NULL argument in new_function");
|
||||
for ( i = 0; i < AKBASIC_MAX_FUNCTIONS; i++ ) {
|
||||
if ( !obj->functions[i].used ) {
|
||||
memset(&obj->functions[i], 0, sizeof(obj->functions[i]));
|
||||
PASS(errctx, aksl_memset(&obj->functions[i], 0, sizeof(obj->functions[i])));
|
||||
obj->functions[i].used = true;
|
||||
obj->functions[i].leafpool.next = 0;
|
||||
obj->functions[i].leafpool.capacity = AKBASIC_MAX_LEAVES * 2;
|
||||
@@ -215,7 +214,7 @@ akerr_ErrorContext *akbasic_runtime_init(akbasic_Runtime *obj, akbasic_TextSink
|
||||
*/
|
||||
PASS(errctx, akbasic_error_register());
|
||||
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->sink = sink;
|
||||
obj->environment = NULL;
|
||||
obj->autoLineNumber = 0;
|
||||
@@ -279,7 +278,7 @@ akerr_ErrorContext *akbasic_runtime_set_ui(akbasic_Runtime *obj, akbasic_UiBacke
|
||||
akerr_ErrorContext *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const char *path)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
const char *slash = NULL;
|
||||
char *slash = NULL;
|
||||
size_t length = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in set_source_path");
|
||||
@@ -292,18 +291,18 @@ akerr_ErrorContext *akbasic_runtime_set_source_path(akbasic_Runtime *obj, const
|
||||
* would do it but it is allowed to modify its argument and two of the three
|
||||
* libcs this has to build on disagree about which one they implement.
|
||||
*/
|
||||
slash = strrchr(path, '/');
|
||||
PASS(errctx, aksl_strrchr(path, '/', &slash));
|
||||
length = (slash == NULL ? 0 : (size_t)(slash - path));
|
||||
if ( length == 0 ) {
|
||||
/* Either no directory at all, or the root. */
|
||||
strncpy(obj->sourcepath, (slash == NULL ? "." : "/"), sizeof(obj->sourcepath) - 1);
|
||||
obj->sourcepath[sizeof(obj->sourcepath) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(obj->sourcepath, sizeof(obj->sourcepath),
|
||||
(slash == NULL ? "." : "/")));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
FAIL_ZERO_RETURN(errctx, (length < sizeof(obj->sourcepath)), AKBASIC_ERR_BOUNDS,
|
||||
"Program path of %zu characters exceeds the %d character limit",
|
||||
length, AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
memcpy(obj->sourcepath, path, length);
|
||||
PASS(errctx, aksl_memcpy(obj->sourcepath, path, length));
|
||||
obj->sourcepath[length] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -393,6 +392,16 @@ akerr_ErrorContext *akbasic_runtime_error(akbasic_Runtime *obj, akbasic_ErrorCla
|
||||
* The format, the trailing \n inside the string, and the second newline
|
||||
* writeln adds are all part of the acceptance contract --
|
||||
* tests/language/array_outofbounds.txt ends in 0a 0a. See TODO.md 1.8.
|
||||
*
|
||||
* **Raw snprintf on purpose, and this is the site where it matters most.**
|
||||
* `line` is 512 bytes; `message` arrives from an akerr_ErrorContext, whose
|
||||
* own buffer is AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH -- 12384. An ordinary
|
||||
* long diagnostic therefore truncates, and truncating a report is correct
|
||||
* here: this is the one function that tells the user *what went wrong*, and
|
||||
* aksl_snprintf would turn a long message into a second, different failure
|
||||
* that replaces the first. A report may be shortened; it may not be lost.
|
||||
* libakstdlib #34 is the issue tracking the contract that would let a caller
|
||||
* ask for the length instead.
|
||||
*/
|
||||
snprintf(line, sizeof(line), "? %" PRId64 " : %s %s\n",
|
||||
obj->environment->lineno, errclass_to_string(errclass), message);
|
||||
@@ -469,7 +478,15 @@ akerr_ErrorContext *akbasic_runtime_set_mode(akbasic_Runtime *obj, int mode)
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
snprintf(message, sizeof(message), "%s", errctx->message);
|
||||
/*
|
||||
* Ignored rather than propagated: `errctx` is the error being
|
||||
* handled, so a PASS here would overwrite it with the copy's own
|
||||
* failure and the next line would read a released context. Same
|
||||
* reason the two calls below are ignored. aksl_strcpy empties the
|
||||
* destination before it copies, so a refusal leaves an empty
|
||||
* message rather than an uninitialised one.
|
||||
*/
|
||||
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
|
||||
obj->lasterrorstatus = errctx->status;
|
||||
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
|
||||
IGNORE(akbasic_runtime_set_mode(obj, obj->run_finished_mode));
|
||||
@@ -492,7 +509,14 @@ static akerr_ErrorContext *report_and_reraise(akbasic_Runtime *obj, akerr_ErrorC
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
int status = cause->status;
|
||||
|
||||
snprintf(message, sizeof(message), "%s", cause->message);
|
||||
/*
|
||||
* Ignored rather than passed: this function's whole contract is that the
|
||||
* program's own error is the one that leaves, so a failure to copy the
|
||||
* message must not become the error that gets raised. aksl_strcpy empties
|
||||
* the destination first, so a refusal reports an empty message rather than
|
||||
* an uninitialised one.
|
||||
*/
|
||||
IGNORE(aksl_strcpy(message, sizeof(message), cause->message));
|
||||
/* What ER# reports, if a TRAP is armed. Recorded before the context goes. */
|
||||
obj->lasterrorstatus = status;
|
||||
cause->handled = true;
|
||||
@@ -675,6 +699,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
akbasic_Value *rval = NULL;
|
||||
akbasic_Value *scratch = NULL;
|
||||
const akbasic_Verb *verb = NULL;
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in evaluate");
|
||||
@@ -724,9 +749,11 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
* reaches the end of this line. Arming the wait is what makes a
|
||||
* multi-line IF possible at all; BEND clears it.
|
||||
*/
|
||||
if ( notaken != NULL && notaken->leaftype == AKBASIC_LEAF_COMMAND &&
|
||||
strcmp(notaken->identifier, "BEGIN") == 0 ) {
|
||||
PASS(errctx, akbasic_environment_wait_for_command(obj->environment, "BEND"));
|
||||
if ( notaken != NULL && notaken->leaftype == AKBASIC_LEAF_COMMAND ) {
|
||||
PASS(errctx, aksl_strcmp(notaken->identifier, "BEGIN", &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
PASS(errctx, akbasic_environment_wait_for_command(obj->environment, "BEND"));
|
||||
}
|
||||
}
|
||||
if ( taken ) {
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, expr->left, dest));
|
||||
@@ -765,7 +792,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
|
||||
case AKBASIC_LEAF_LITERAL_STRING:
|
||||
lval->valuetype = AKBASIC_TYPE_STRING;
|
||||
memcpy(lval->stringval, expr->literal_string, sizeof(lval->stringval));
|
||||
PASS(errctx, aksl_memcpy(lval->stringval, expr->literal_string, sizeof(lval->stringval)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
case AKBASIC_LEAF_UNARY:
|
||||
@@ -816,6 +843,10 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
bool waiting = false;
|
||||
bool matchesverb = false;
|
||||
bool waitingbend = false;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && expr != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in interpret");
|
||||
@@ -824,9 +855,21 @@ akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTL
|
||||
* verb. This is what keeps a zero-iteration FOR body from executing, given
|
||||
* that the loop condition is evaluated at the bottom of the structure.
|
||||
*/
|
||||
if ( akbasic_environment_is_waiting_for_any(obj->environment) ) {
|
||||
if ( expr->leaftype != AKBASIC_LEAF_COMMAND ||
|
||||
!akbasic_environment_is_waiting_for(obj->environment, expr->identifier) ) {
|
||||
PASS(errctx, akbasic_environment_is_waiting_for_any(obj->environment, &waiting));
|
||||
if ( waiting ) {
|
||||
/*
|
||||
* Hoisted out of the condition it used to be a term in: the test reads a
|
||||
* recorded verb name, that read can fail, and the old `bool` return had
|
||||
* nowhere to report it. `matchesverb` stays false unless the leaf really
|
||||
* is a command whose name is what the scope is waiting for, which is what
|
||||
* the `||` short-circuit used to say. See libakstdlib #38.
|
||||
*/
|
||||
matchesverb = false;
|
||||
if ( expr->leaftype == AKBASIC_LEAF_COMMAND ) {
|
||||
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment,
|
||||
expr->identifier, &matchesverb));
|
||||
}
|
||||
if ( !matchesverb ) {
|
||||
/*
|
||||
* **A skipped loop has already pushed its scope, and this is where it
|
||||
* comes back.**
|
||||
@@ -860,11 +903,18 @@ akerr_ErrorContext *akbasic_runtime_interpret(akbasic_Runtime *obj, akbasic_ASTL
|
||||
*/
|
||||
if ( expr->leaftype == AKBASIC_LEAF_COMMAND &&
|
||||
obj->environment->loopFirstLine != 0 &&
|
||||
obj->environment->waitingForCommand[0] == '\0' &&
|
||||
akbasic_environment_is_waiting_for(obj->environment, "BEND") &&
|
||||
(strcmp(expr->identifier, "FOR") == 0 ||
|
||||
strcmp(expr->identifier, "DO") == 0) ) {
|
||||
PASS(errctx, akbasic_runtime_prev_environment(obj));
|
||||
obj->environment->waitingForCommand[0] == '\0' ) {
|
||||
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "BEND",
|
||||
&waitingbend));
|
||||
if ( waitingbend ) {
|
||||
PASS(errctx, aksl_strcmp(expr->identifier, "FOR", &cmp));
|
||||
if ( cmp != 0 ) {
|
||||
PASS(errctx, aksl_strcmp(expr->identifier, "DO", &cmp));
|
||||
}
|
||||
if ( cmp == 0 ) {
|
||||
PASS(errctx, akbasic_runtime_prev_environment(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
*dest = &obj->staticTrueValue;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -1125,15 +1175,18 @@ int64_t akbasic_runtime_find_previous_lineno(akbasic_Runtime *obj)
|
||||
akerr_ErrorContext *akbasic_runtime_store_line(akbasic_Runtime *obj, int64_t lineno, const char *code, bool numbered)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t length = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (lineno >= 0 && lineno < AKBASIC_MAX_SOURCE_LINES),
|
||||
AKBASIC_ERR_BOUNDS,
|
||||
"Line number %" PRId64 " is outside 0..%d",
|
||||
lineno, AKBASIC_MAX_SOURCE_LINES - 1);
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(code) < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(code, &length));
|
||||
FAIL_ZERO_RETURN(errctx, (length < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
|
||||
"Source line exceeds the %d character limit", AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
strncpy(obj->source[lineno].code, code, AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
obj->source[lineno].code[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
|
||||
/* aksl_strcpy always terminates and refuses rather than truncates; the
|
||||
length check above is what makes the refusal unreachable. */
|
||||
PASS(errctx, aksl_strcpy(obj->source[lineno].code, sizeof(obj->source[lineno].code), code));
|
||||
obj->source[lineno].lineno = lineno;
|
||||
obj->source[lineno].numbered = numbered;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -1242,11 +1295,12 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
|
||||
akbasic_ASTLeaf *leaf = NULL;
|
||||
akbasic_Value *value = NULL;
|
||||
akbasic_Parser parser;
|
||||
int written = 0;
|
||||
bool eof = false;
|
||||
|
||||
if ( obj->autoLineNumber > 0 ) {
|
||||
snprintf(prompt, sizeof(prompt), "%" PRId64 " ",
|
||||
obj->environment->lineno + obj->autoLineNumber);
|
||||
PASS(errctx, aksl_snprintf(&written, prompt, sizeof(prompt), "%" PRId64 " ",
|
||||
obj->environment->lineno + obj->autoLineNumber));
|
||||
PASS(errctx, akbasic_runtime_write(obj, prompt));
|
||||
}
|
||||
|
||||
@@ -1272,7 +1326,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
snprintf(message, sizeof(message), "%s", errctx->message);
|
||||
/* Ignored, not passed: `errctx` is the error being handled here. */
|
||||
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
|
||||
obj->lasterrorstatus = errctx->status;
|
||||
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
|
||||
} FINISH(errctx, false);
|
||||
@@ -1337,14 +1392,14 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
|
||||
akbasic_ASTLeaf *leaf = NULL;
|
||||
akbasic_Value *value = NULL;
|
||||
akbasic_Parser parser;
|
||||
int written = 0;
|
||||
|
||||
if ( obj->environment->nextline >= AKBASIC_MAX_SOURCE_LINES ) {
|
||||
PASS(errctx, akbasic_runtime_set_mode(obj, obj->run_finished_mode));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
strncpy(line, obj->source[obj->environment->nextline].code, sizeof(line) - 1);
|
||||
line[sizeof(line) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(line, sizeof(line), obj->source[obj->environment->nextline].code));
|
||||
obj->environment->lineno = obj->environment->nextline;
|
||||
obj->environment->nextline += 1;
|
||||
if ( line[0] == '\0' ) {
|
||||
@@ -1358,7 +1413,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
|
||||
*/
|
||||
if ( obj->trace ) {
|
||||
char tracemark[32];
|
||||
snprintf(tracemark, sizeof(tracemark), "[%" PRId64 "]", obj->environment->lineno);
|
||||
PASS(errctx, aksl_snprintf(&written, tracemark, sizeof(tracemark), "[%" PRId64 "]",
|
||||
obj->environment->lineno));
|
||||
PASS(errctx, akbasic_runtime_write(obj, tracemark));
|
||||
}
|
||||
|
||||
@@ -1373,7 +1429,8 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
snprintf(message, sizeof(message), "%s", errctx->message);
|
||||
/* Ignored, not passed: `errctx` is the error being handled here. */
|
||||
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
|
||||
/*
|
||||
* What ER# reports, recorded before the context goes -- the same line
|
||||
* report_and_reraise() carries for a runtime error, and it was missing
|
||||
@@ -1442,6 +1499,7 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
const char *cursor = code;
|
||||
int cmp = 0;
|
||||
bool statementstart = true;
|
||||
bool instring = false;
|
||||
|
||||
@@ -1479,31 +1537,33 @@ static akerr_ErrorContext *scan_line_labels(akbasic_Environment *root, const cha
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ( statementstart && strncasecmp(cursor, "LABEL", 5) == 0
|
||||
&& !isalnum((unsigned char)cursor[5]) ) {
|
||||
char name[AKBASIC_SYMTAB_MAX_KEY];
|
||||
size_t used = 0;
|
||||
if ( statementstart ) {
|
||||
PASS(errctx, aksl_strncasecmp(cursor, "LABEL", 5, &cmp));
|
||||
if ( cmp == 0 && !isalnum((unsigned char)cursor[5]) ) {
|
||||
char name[AKBASIC_SYMTAB_MAX_KEY];
|
||||
size_t used = 0;
|
||||
|
||||
cursor += 5;
|
||||
while ( isspace((unsigned char)*cursor) ) {
|
||||
cursor += 1;
|
||||
cursor += 5;
|
||||
while ( isspace((unsigned char)*cursor) ) {
|
||||
cursor += 1;
|
||||
}
|
||||
/*
|
||||
* Copied as written. Verbs are case-insensitive in this dialect and
|
||||
* identifiers are not, so folding the name here would file a label
|
||||
* under a spelling `LABEL` itself never uses.
|
||||
*/
|
||||
while ( isalnum((unsigned char)*cursor) && used < sizeof(name) - 1 ) {
|
||||
name[used] = *cursor;
|
||||
used += 1;
|
||||
cursor += 1;
|
||||
}
|
||||
name[used] = '\0';
|
||||
if ( used > 0 ) {
|
||||
PASS(errctx, akbasic_symtab_set(&root->labels, name, NULL, lineno));
|
||||
}
|
||||
statementstart = false;
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* Copied as written. Verbs are case-insensitive in this dialect and
|
||||
* identifiers are not, so folding the name here would file a label
|
||||
* under a spelling `LABEL` itself never uses.
|
||||
*/
|
||||
while ( isalnum((unsigned char)*cursor) && used < sizeof(name) - 1 ) {
|
||||
name[used] = *cursor;
|
||||
used += 1;
|
||||
cursor += 1;
|
||||
}
|
||||
name[used] = '\0';
|
||||
if ( used > 0 ) {
|
||||
PASS(errctx, akbasic_symtab_set(&root->labels, name, NULL, lineno));
|
||||
}
|
||||
statementstart = false;
|
||||
continue;
|
||||
}
|
||||
statementstart = false;
|
||||
cursor += 1;
|
||||
@@ -1536,6 +1596,7 @@ akerr_ErrorContext *akbasic_runtime_arm_interrupt(akbasic_Runtime *obj, akbasic_
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Interrupt *slot = NULL;
|
||||
size_t length = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in arm_interrupt");
|
||||
FAIL_ZERO_RETURN(errctx, (source >= 0 && source < AKBASIC_MAX_INTERRUPTS),
|
||||
@@ -1550,11 +1611,13 @@ akerr_ErrorContext *akbasic_runtime_arm_interrupt(akbasic_Runtime *obj, akbasic_
|
||||
slot->line = line;
|
||||
slot->label[0] = '\0';
|
||||
if ( label != NULL && label[0] != '\0' ) {
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(label) < sizeof(slot->label)), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(label, &length));
|
||||
FAIL_ZERO_RETURN(errctx, (length < sizeof(slot->label)), AKBASIC_ERR_BOUNDS,
|
||||
"Handler label \"%s\" exceeds the %zu character limit",
|
||||
label, sizeof(slot->label) - 1);
|
||||
strncpy(slot->label, label, sizeof(slot->label) - 1);
|
||||
slot->label[sizeof(slot->label) - 1] = '\0';
|
||||
/* aksl_strcpy always terminates and refuses rather than truncates; the
|
||||
length check above is what makes the refusal unreachable. */
|
||||
PASS(errctx, aksl_strcpy(slot->label, sizeof(slot->label), label));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -1567,7 +1630,7 @@ akerr_ErrorContext *akbasic_runtime_disarm_interrupt(akbasic_Runtime *obj, akbas
|
||||
FAIL_ZERO_RETURN(errctx, (source >= 0 && source < AKBASIC_MAX_INTERRUPTS),
|
||||
AKBASIC_ERR_BOUNDS, "Interrupt source %d is outside 0..%d",
|
||||
(int)source, AKBASIC_MAX_INTERRUPTS - 1);
|
||||
memset(&obj->interrupts[source], 0, sizeof(obj->interrupts[source]));
|
||||
PASS(errctx, aksl_memset(&obj->interrupts[source], 0, sizeof(obj->interrupts[source])));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -1682,6 +1745,7 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
|
||||
char scanned[AKBASIC_MAX_LINE_LENGTH];
|
||||
const char *cursor = NULL;
|
||||
const char *eol = NULL;
|
||||
char *match = NULL;
|
||||
size_t length = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in load");
|
||||
@@ -1696,9 +1760,11 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
|
||||
obj->environment->lineno = 0;
|
||||
|
||||
for ( cursor = source; *cursor != '\0'; cursor = (*eol == '\0' ? eol : eol + 1) ) {
|
||||
eol = strchr(cursor, '\n');
|
||||
PASS(errctx, aksl_strchr(cursor, '\n', &match));
|
||||
eol = match;
|
||||
if ( eol == NULL ) {
|
||||
eol = cursor + strlen(cursor);
|
||||
PASS(errctx, aksl_strlen(cursor, &length));
|
||||
eol = cursor + length;
|
||||
}
|
||||
length = (size_t)(eol - cursor);
|
||||
if ( length > 0 && cursor[length - 1] == '\r' ) {
|
||||
@@ -1707,7 +1773,7 @@ akerr_ErrorContext *akbasic_runtime_load(akbasic_Runtime *obj, const char *sourc
|
||||
FAIL_ZERO_RETURN(errctx, (length < sizeof(line)), AKBASIC_ERR_BOUNDS,
|
||||
"Source line of %zu characters exceeds the %d character limit",
|
||||
length, AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
memcpy(line, cursor, length);
|
||||
PASS(errctx, aksl_memcpy(line, cursor, length));
|
||||
line[length] = '\0';
|
||||
if ( line[0] == '\0' ) {
|
||||
continue;
|
||||
@@ -1815,7 +1881,8 @@ akerr_ErrorContext *akbasic_runtime_step(akbasic_Runtime *obj)
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
snprintf(message, sizeof(message), "%s", errctx->message);
|
||||
/* Ignored, not passed: `errctx` is the error being handled here. */
|
||||
IGNORE(aksl_strcpy(message, sizeof(message), errctx->message));
|
||||
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_RUNTIME, message));
|
||||
} FINISH(errctx, false);
|
||||
if ( obj->errclass != AKBASIC_ERRCLASS_NONE ) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
@@ -96,7 +95,7 @@ akerr_ErrorContext *akbasic_cmd_print(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
*/
|
||||
if ( (*dest)->valuetype == AKBASIC_TYPE_STRUCT || (*dest)->valuetype == AKBASIC_TYPE_POINTER ) {
|
||||
if ( (*dest)->structbase == NULL ) {
|
||||
snprintf(rendered, sizeof(rendered), "NOTHING");
|
||||
PASS(errctx, aksl_strcpy(rendered, sizeof(rendered), "NOTHING"));
|
||||
} else {
|
||||
PASS(errctx, akbasic_struct_to_string(obj, (*dest)->structtype, (*dest)->structbase,
|
||||
0, rendered, sizeof(rendered)));
|
||||
@@ -149,13 +148,15 @@ akerr_ErrorContext *akbasic_cmd_return(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Value *result = NULL;
|
||||
bool waiting = false;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
/*
|
||||
* A RETURN reached while skipping forward to one is the end of a DEF body,
|
||||
* not a subroutine return. Stop waiting and carry on.
|
||||
*/
|
||||
if ( akbasic_environment_is_waiting_for(obj->environment, "RETURN") ) {
|
||||
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "RETURN", &waiting));
|
||||
if ( waiting ) {
|
||||
PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "RETURN"));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -445,6 +446,7 @@ akerr_ErrorContext *akbasic_cmd_input(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
char rendered[AKBASIC_MAX_STRING_LENGTH];
|
||||
char buffer[AKBASIC_MAX_LINE_LENGTH];
|
||||
long long converted = 0;
|
||||
size_t len = 0;
|
||||
bool eof = false;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
@@ -479,10 +481,11 @@ akerr_ErrorContext *akbasic_cmd_input(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
break;
|
||||
default:
|
||||
entered->valuetype = AKBASIC_TYPE_STRING;
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(buffer) < AKBASIC_MAX_STRING_LENGTH), AKBASIC_ERR_VALUE,
|
||||
PASS(errctx, aksl_strlen(buffer, &len));
|
||||
FAIL_ZERO_RETURN(errctx, (len < AKBASIC_MAX_STRING_LENGTH), AKBASIC_ERR_VALUE,
|
||||
"Input line exceeds the %d character limit", AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
strncpy(entered->stringval, buffer, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
entered->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
|
||||
/* aksl_strcpy always terminates, so the explicit terminator is gone. */
|
||||
PASS(errctx, aksl_strcpy(entered->stringval, sizeof(entered->stringval), buffer));
|
||||
break;
|
||||
}
|
||||
PASS(errctx, akbasic_environment_assign(obj->environment, identifier, entered, &unused));
|
||||
@@ -539,6 +542,7 @@ akerr_ErrorContext *akbasic_cmd_list(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
int64_t startidx = 0;
|
||||
int64_t endidx = 0;
|
||||
int64_t i = 0;
|
||||
int written = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, parse_line_range(obj, expr, &startidx, &endidx));
|
||||
@@ -552,7 +556,8 @@ akerr_ErrorContext *akbasic_cmd_list(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
if ( obj->source[i].code[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
snprintf(line, sizeof(line), "%" PRId64 " %s", i, obj->source[i].code);
|
||||
PASS(errctx, aksl_snprintf(&written, line, sizeof(line), "%" PRId64 " %s",
|
||||
i, obj->source[i].code));
|
||||
PASS(errctx, akbasic_runtime_println(obj, line));
|
||||
}
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
@@ -588,6 +593,7 @@ static akerr_ErrorContext *filename_argument(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Value *value = NULL;
|
||||
size_t namelen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX,
|
||||
"Expected a filename");
|
||||
@@ -596,10 +602,107 @@ static akerr_ErrorContext *filename_argument(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
"Expected a filename string");
|
||||
FAIL_ZERO_RETURN(errctx, (value->stringval[0] != '\0'), AKBASIC_ERR_VALUE,
|
||||
"Filename must not be empty");
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(value->stringval) < len), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(value->stringval, &namelen));
|
||||
FAIL_ZERO_RETURN(errctx, (namelen < len), AKBASIC_ERR_BOUNDS,
|
||||
"Filename exceeds the %zu character limit", len - 1);
|
||||
strncpy(dest, value->stringval, len - 1);
|
||||
dest[len - 1] = '\0';
|
||||
/* aksl_strcpy always terminates, so the explicit terminator is gone. */
|
||||
PASS(errctx, aksl_strcpy(dest, len, value->stringval));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one line, reporting end of stream rather than raising it.
|
||||
*
|
||||
* aksl_fgets raises AKERR_EOF where fgets(3) returned NULL, so the end of an
|
||||
* ordinary file arrives as an error and has to be handled. It is handled here,
|
||||
* in a function whose ATTEMPT block contains no loop at all, because a CATCH
|
||||
* inside a loop expands to a break that leaves the loop rather than the
|
||||
* ATTEMPT. The caller reads @p eof instead.
|
||||
*/
|
||||
static akerr_ErrorContext *dload_read_line(FILE *fp, char *buffer, size_t len, bool *eof)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t used = 0;
|
||||
|
||||
*eof = false;
|
||||
ATTEMPT {
|
||||
CATCH(errctx, aksl_fgets(buffer, len, fp, &used));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_EOF) {
|
||||
*eof = true;
|
||||
used = 0;
|
||||
} FINISH(errctx, true);
|
||||
|
||||
while ( used > 0 && (buffer[used - 1] == '\n' || buffer[used - 1] == '\r') ) {
|
||||
buffer[used - 1] = '\0';
|
||||
used -= 1;
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a whole program in, one line at a time.
|
||||
*
|
||||
* Hoisted out of akbasic_cmd_dload()'s ATTEMPT block because a loop inside one
|
||||
* can use neither CATCH -- which breaks the loop -- nor PASS, which returns
|
||||
* past CLEANUP and leaves the file open. Out here PASS is correct, and the
|
||||
* caller CATCHes this one call.
|
||||
*/
|
||||
static akerr_ErrorContext *dload_read_program(akbasic_Runtime *obj, FILE *fp,
|
||||
char *buffer, size_t buflen,
|
||||
char *scanned, size_t scanlen)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
bool eof = false;
|
||||
|
||||
for ( ;; ) {
|
||||
PASS(errctx, dload_read_line(fp, buffer, buflen, &eof));
|
||||
if ( eof ) {
|
||||
break;
|
||||
}
|
||||
if ( buffer[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
PASS(errctx, akbasic_scanner_scan(obj, buffer, scanned, scanlen));
|
||||
PASS(errctx, akbasic_runtime_file_line(obj, scanned));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write every stored line out, numbered.
|
||||
*
|
||||
* Hoisted out of akbasic_cmd_dsave()'s ATTEMPT block for the same reason
|
||||
* dload_read_program() is.
|
||||
*/
|
||||
static akerr_ErrorContext *dsave_write_program(akbasic_Runtime *obj, FILE *fp, char *line, size_t len)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t written = 0;
|
||||
int64_t i = 0;
|
||||
int count = 0;
|
||||
|
||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||
if ( obj->source[i].code[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* aksl_snprintf's count is the length it wrote, and it treats
|
||||
* truncation as an error rather than reporting a length it did not
|
||||
* write -- so it is what goes to fwrite, and the strlen that used to
|
||||
* recompute it is gone.
|
||||
*/
|
||||
PASS(errctx, aksl_snprintf(&count, line, len, "%" PRId64 " %s\n", i, obj->source[i].code));
|
||||
/*
|
||||
* The written count is required by libakstdlib 0.2.0 and discarded
|
||||
* here on purpose: a short write is no longer something a caller has
|
||||
* to notice for itself, because that release made it an AKERR_IO
|
||||
* rather than a silent success. Before it, a DSAVE onto a full disk
|
||||
* reported nothing.
|
||||
*/
|
||||
PASS(errctx, aksl_fwrite(line, 1, (size_t)count, fp, &written));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -610,7 +713,6 @@ akerr_ErrorContext *akbasic_cmd_dload(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
char buffer[AKBASIC_MAX_LINE_LENGTH];
|
||||
char scanned[AKBASIC_MAX_LINE_LENGTH];
|
||||
FILE *fp = NULL;
|
||||
size_t used = 0;
|
||||
int64_t i = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
@@ -632,23 +734,14 @@ akerr_ErrorContext *akbasic_cmd_dload(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, aksl_fopen(filename, "r", &fp));
|
||||
while ( fgets(buffer, sizeof(buffer), fp) != NULL ) {
|
||||
used = strlen(buffer);
|
||||
while ( used > 0 && (buffer[used - 1] == '\n' || buffer[used - 1] == '\r') ) {
|
||||
buffer[used - 1] = '\0';
|
||||
used -= 1;
|
||||
}
|
||||
if ( buffer[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* PASS inside the loop, never CATCH: CATCH expands to a break, which
|
||||
* would leave this loop rather than the ATTEMPT and let the rest of
|
||||
* the block run with an error pending.
|
||||
*/
|
||||
PASS(errctx, akbasic_scanner_scan(obj, buffer, scanned, sizeof(scanned)));
|
||||
PASS(errctx, akbasic_runtime_file_line(obj, scanned));
|
||||
}
|
||||
/*
|
||||
* The read is one CATCH of one call, because the loop it used to be
|
||||
* lives in dload_read_program() now: inside this block a loop can use
|
||||
* neither CATCH nor PASS without either escaping the loop or returning
|
||||
* past the fclose below.
|
||||
*/
|
||||
CATCH(errctx, dload_read_program(obj, fp, buffer, sizeof(buffer),
|
||||
scanned, sizeof(scanned)));
|
||||
} CLEANUP {
|
||||
if ( fp != NULL ) {
|
||||
IGNORE(aksl_fclose(fp));
|
||||
@@ -676,30 +769,13 @@ akerr_ErrorContext *akbasic_cmd_dsave(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
char filename[AKBASIC_MAX_STRING_LENGTH];
|
||||
char line[AKBASIC_MAX_LINE_LENGTH * 2];
|
||||
FILE *fp = NULL;
|
||||
int64_t i = 0;
|
||||
int count = 0;
|
||||
size_t written = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, filename_argument(obj, expr, filename, sizeof(filename)));
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, aksl_fopen(filename, "w", &fp));
|
||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||
if ( obj->source[i].code[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
snprintf(line, sizeof(line), "%" PRId64 " %s\n", i, obj->source[i].code);
|
||||
/*
|
||||
* The written count is required by libakstdlib 0.2.0 and discarded
|
||||
* here on purpose: a short write is no longer something a caller has
|
||||
* to notice for itself, because that release made it an AKERR_IO
|
||||
* rather than a silent success. Before it, a DSAVE onto a full disk
|
||||
* reported nothing.
|
||||
*/
|
||||
PASS(errctx, aksl_fwrite(line, 1, strlen(line), fp, &written));
|
||||
count += 1;
|
||||
}
|
||||
CATCH(errctx, dsave_write_program(obj, fp, line, sizeof(line)));
|
||||
} CLEANUP {
|
||||
if ( fp != NULL ) {
|
||||
IGNORE(aksl_fclose(fp));
|
||||
@@ -807,6 +883,7 @@ akerr_ErrorContext *akbasic_cmd_next(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
akbasic_Value *updated = NULL;
|
||||
akbasic_Value scratch;
|
||||
int64_t zerosubscript[1] = { 0 };
|
||||
int cmp = 0;
|
||||
bool met = false;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
@@ -842,7 +919,9 @@ akerr_ErrorContext *akbasic_cmd_next(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
* A NEXT for someone else's loop variable: this environment is done, hand
|
||||
* the line back to the parent and pop. That is how nested loops unwind.
|
||||
*/
|
||||
if ( strcmp(expr->right->identifier, obj->environment->forNextVariable->name) != 0 ) {
|
||||
PASS(errctx, aksl_strcmp(expr->right->identifier,
|
||||
obj->environment->forNextVariable->name, &cmp));
|
||||
if ( cmp != 0 ) {
|
||||
FAIL_ZERO_RETURN(errctx, (obj->environment->parent != NULL), AKBASIC_ERR_ENVIRONMENT,
|
||||
"NEXT in an orphaned environment");
|
||||
obj->environment->parent->nextline = obj->environment->nextline;
|
||||
@@ -951,7 +1030,8 @@ akerr_ErrorContext *akbasic_cmd_read(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
PASS(errctx, akbasic_leaf_init(&literal, AKBASIC_LEAF_LITERAL_INT));
|
||||
if ( value.valuetype == AKBASIC_TYPE_STRING ) {
|
||||
literal.leaftype = AKBASIC_LEAF_LITERAL_STRING;
|
||||
snprintf(literal.literal_string, sizeof(literal.literal_string), "%s", value.stringval);
|
||||
PASS(errctx, aksl_strcpy(literal.literal_string, sizeof(literal.literal_string),
|
||||
value.stringval));
|
||||
} else if ( value.valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
literal.leaftype = AKBASIC_LEAF_LITERAL_FLOAT;
|
||||
literal.literal_float = value.floatval;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/args.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -114,6 +115,7 @@ akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
/* Room for the longest macro plus `KEY n, ""` around it. */
|
||||
char line[AKBASIC_MAX_STRING_LENGTH + 32];
|
||||
int64_t number = 0;
|
||||
int written = 0;
|
||||
int i = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
@@ -124,8 +126,8 @@ akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
if ( arg == NULL ) {
|
||||
/* Bare KEY lists the definitions, which is what a C128 does. */
|
||||
for ( i = 0; i < AKBASIC_MAX_FUNCTION_KEYS; i++ ) {
|
||||
snprintf(line, sizeof(line), "KEY %d, \"%s\"",
|
||||
i + 1, obj->console_state.keys[i]);
|
||||
PASS(errctx, aksl_snprintf(&written, line, sizeof(line), "KEY %d, \"%s\"",
|
||||
i + 1, obj->console_state.keys[i]));
|
||||
PASS(errctx, akbasic_runtime_println(obj, line));
|
||||
}
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
@@ -146,8 +148,8 @@ akerr_ErrorContext *akbasic_cmd_key(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||||
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||||
"KEY expected a string");
|
||||
snprintf(obj->console_state.keys[number - 1],
|
||||
sizeof(obj->console_state.keys[0]), "%s", value->stringval);
|
||||
PASS(errctx, aksl_snprintf(&written, obj->console_state.keys[number - 1],
|
||||
sizeof(obj->console_state.keys[0]), "%s", value->stringval));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -253,7 +255,7 @@ akerr_ErrorContext *akbasic_console_state_init(akbasic_ConsoleState *obj)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL console state in init");
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -302,6 +304,7 @@ akerr_ErrorContext *akbasic_console_update_clock(akbasic_Runtime *obj)
|
||||
int64_t jiffies = 0;
|
||||
int64_t seconds = 0;
|
||||
char text[16];
|
||||
int written = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in update_clock");
|
||||
if ( obj->environment == NULL ) {
|
||||
@@ -324,8 +327,9 @@ akerr_ErrorContext *akbasic_console_update_clock(akbasic_Runtime *obj)
|
||||
PASS(errctx, akbasic_variable_set_integer(variable, jiffies, zerosubscript, 1));
|
||||
|
||||
seconds = obj->timems / 1000;
|
||||
snprintf(text, sizeof(text), "%02" PRId64 "%02" PRId64 "%02" PRId64,
|
||||
(seconds / 3600) % 24, (seconds / 60) % 60, seconds % 60);
|
||||
PASS(errctx, aksl_snprintf(&written, text, sizeof(text),
|
||||
"%02" PRId64 "%02" PRId64 "%02" PRId64,
|
||||
(seconds / 3600) % 24, (seconds / 60) % 60, seconds % 60));
|
||||
PASS(errctx, akbasic_runtime_global(obj, "TI$", &variable));
|
||||
PASS(errctx, akbasic_variable_set_string(variable, text, zerosubscript, 1));
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
@@ -44,7 +43,7 @@ akerr_ErrorContext *akbasic_disk_state_init(akbasic_DiskState *obj)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL disk state in init");
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -103,7 +102,12 @@ static akerr_ErrorContext *string_arg(akbasic_Runtime *obj, akbasic_ASTLeaf *arg
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||||
FAIL_NONZERO_RETURN(errctx, (value->valuetype != AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||||
"%s expected a file name", verb);
|
||||
snprintf(dest, len, "%s", value->stringval);
|
||||
/*
|
||||
* A plain copy, so aksl_strcpy rather than aksl_snprintf: it refuses rather
|
||||
* than truncates, and every caller here hands in a buffer the size of a
|
||||
* string value, so a name that did not fit could only ever be a bug.
|
||||
*/
|
||||
PASS(errctx, aksl_strcpy(dest, len, value->stringval));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -157,7 +161,7 @@ static akerr_ErrorContext *open_file(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
}
|
||||
|
||||
PASS(errctx, aksl_fopen(name, mode, &channel->fp));
|
||||
snprintf(channel->name, sizeof(channel->name), "%s", name);
|
||||
PASS(errctx, aksl_strcpy(channel->name, sizeof(channel->name), name));
|
||||
channel->writing = writing;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -265,13 +269,24 @@ akerr_ErrorContext *akbasic_cmd_record(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
akerr_ErrorContext *akbasic_cmd_scratch(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akerr_ErrorContext *failure = NULL;
|
||||
char name[AKBASIC_MAX_STRING_LENGTH];
|
||||
|
||||
(void)lval; (void)rval;
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in SCRATCH");
|
||||
PASS(errctx, string_arg(obj, akbasic_leaf_first_argument(expr), "SCRATCH", name, sizeof(name)));
|
||||
FAIL_ZERO_RETURN(errctx, (remove(name) == 0), AKERR_IO,
|
||||
"SCRATCH could not delete \"%s\"", name);
|
||||
/*
|
||||
* Caught and restated rather than passed. aksl_remove raises the bare errno,
|
||||
* and "No such file or directory" on its own does not say which file or
|
||||
* which verb wanted it -- so the wrapper's error is retired here and the
|
||||
* message the program sees keeps naming both.
|
||||
*/
|
||||
failure = aksl_remove(name);
|
||||
if ( failure != NULL ) {
|
||||
failure->handled = true;
|
||||
IGNORE(akerr_release_error(failure));
|
||||
FAIL_RETURN(errctx, AKERR_IO, "SCRATCH could not delete \"%s\"", name);
|
||||
}
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -279,6 +294,7 @@ akerr_ErrorContext *akbasic_cmd_scratch(akbasic_Runtime *obj, akbasic_ASTLeaf *e
|
||||
akerr_ErrorContext *akbasic_cmd_rename(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akerr_ErrorContext *failure = NULL;
|
||||
akbasic_ASTLeaf *arg = NULL;
|
||||
char from[AKBASIC_MAX_STRING_LENGTH];
|
||||
char to[AKBASIC_MAX_STRING_LENGTH];
|
||||
@@ -288,8 +304,13 @@ akerr_ErrorContext *akbasic_cmd_rename(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
arg = akbasic_leaf_first_argument(expr);
|
||||
PASS(errctx, string_arg(obj, arg, "RENAME", from, sizeof(from)));
|
||||
PASS(errctx, string_arg(obj, (arg != NULL ? arg->next : NULL), "RENAME", to, sizeof(to)));
|
||||
FAIL_ZERO_RETURN(errctx, (rename(from, to) == 0), AKERR_IO,
|
||||
"RENAME could not rename \"%s\" to \"%s\"", from, to);
|
||||
/* Caught and restated, for the reason SCRATCH gives: the message names both files. */
|
||||
failure = aksl_rename(from, to);
|
||||
if ( failure != NULL ) {
|
||||
failure->handled = true;
|
||||
IGNORE(akerr_release_error(failure));
|
||||
FAIL_RETURN(errctx, AKERR_IO, "RENAME could not rename \"%s\" to \"%s\"", from, to);
|
||||
}
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -486,6 +507,7 @@ akerr_ErrorContext *akbasic_disk_write(akbasic_Runtime *obj, int64_t number, con
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Channel *channel = NULL;
|
||||
size_t length = 0;
|
||||
size_t put = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && text != NULL), AKERR_NULLPOINTER,
|
||||
@@ -493,7 +515,8 @@ akerr_ErrorContext *akbasic_disk_write(akbasic_Runtime *obj, int64_t number, con
|
||||
PASS(errctx, open_channel(obj, number, &channel));
|
||||
FAIL_ZERO_RETURN(errctx, channel->writing, AKBASIC_ERR_STATE,
|
||||
"Channel %" PRId64 " was opened for reading", number);
|
||||
PASS(errctx, aksl_fwrite(text, 1, strlen(text), channel->fp, &put));
|
||||
PASS(errctx, aksl_strlen(text, &length));
|
||||
PASS(errctx, aksl_fwrite(text, 1, length, channel->fp, &put));
|
||||
PASS(errctx, aksl_fwrite("\n", 1, 1, channel->fp, &put));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -525,7 +548,7 @@ akerr_ErrorContext *akbasic_disk_readline(akbasic_Runtime *obj, int64_t number,
|
||||
*eof = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
newline = strchr(dest, '\n');
|
||||
PASS(errctx, aksl_strchr(dest, '\n', &newline));
|
||||
if ( newline != NULL ) {
|
||||
*newline = '\0';
|
||||
}
|
||||
@@ -582,7 +605,7 @@ akerr_ErrorContext *akbasic_cmd_input_channel(akbasic_Runtime *obj, akbasic_ASTL
|
||||
* variable reading an empty line gets zero, the same as a C128.
|
||||
*/
|
||||
PASS(errctx, akbasic_leaf_init(&literal, AKBASIC_LEAF_LITERAL_STRING));
|
||||
snprintf(literal.literal_string, sizeof(literal.literal_string), "%s", text);
|
||||
PASS(errctx, aksl_strcpy(literal.literal_string, sizeof(literal.literal_string), text));
|
||||
if ( akbasic_leaf_identifier_type(identifier) == AKBASIC_TYPE_INTEGER ) {
|
||||
long long converted = 0;
|
||||
|
||||
@@ -610,13 +633,69 @@ akerr_ErrorContext *akbasic_cmd_input_channel(akbasic_Runtime *obj, akbasic_ASTL
|
||||
|
||||
/* ---------------------------------------------------------------- VERIFY -- */
|
||||
|
||||
/**
|
||||
* @brief Count the lines of @p fp that differ from the program in memory.
|
||||
*
|
||||
* Its own function so that the comparison can be reached with a single CATCH.
|
||||
* Written inline it would be a loop inside VERIFY's ATTEMPT, where neither form
|
||||
* is available: CATCH expands to a break that would escape only the loop, and
|
||||
* PASS returns past the CLEANUP that closes the file. One call is neither, so
|
||||
* the wrappers below can PASS in the ordinary way.
|
||||
*/
|
||||
static akerr_ErrorContext *verify_lines(akbasic_Runtime *obj, FILE *fp, int64_t *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akerr_ErrorContext *got = NULL;
|
||||
char line[AKBASIC_MAX_LINE_LENGTH];
|
||||
const char *filetext = NULL;
|
||||
char *newline = NULL;
|
||||
int64_t lineno = 0;
|
||||
size_t length = 0;
|
||||
int cmp = 0;
|
||||
|
||||
*dest = 0;
|
||||
for ( lineno = 0; lineno < AKBASIC_MAX_SOURCE_LINES; lineno++ ) {
|
||||
if ( obj->source[lineno].code[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
got = aksl_fgets(line, sizeof(line), fp, &length);
|
||||
if ( got != NULL ) {
|
||||
got->handled = true;
|
||||
IGNORE(akerr_release_error(got));
|
||||
*dest += 1;
|
||||
break;
|
||||
}
|
||||
PASS(errctx, aksl_strchr(line, '\n', &newline));
|
||||
if ( newline != NULL ) {
|
||||
*newline = '\0';
|
||||
}
|
||||
/*
|
||||
* The stored line has had its number stripped, so the file's copy is
|
||||
* compared from past its own number.
|
||||
*/
|
||||
filetext = line;
|
||||
while ( *filetext == ' ' ) {
|
||||
filetext += 1;
|
||||
}
|
||||
while ( *filetext >= '0' && *filetext <= '9' ) {
|
||||
filetext += 1;
|
||||
}
|
||||
while ( *filetext == ' ' ) {
|
||||
filetext += 1;
|
||||
}
|
||||
PASS(errctx, aksl_strcmp(filetext, obj->source[lineno].code, &cmp));
|
||||
if ( cmp != 0 ) {
|
||||
*dest += 1;
|
||||
}
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_cmd_dverify(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FILE *fp = NULL;
|
||||
char name[AKBASIC_MAX_STRING_LENGTH];
|
||||
char line[AKBASIC_MAX_LINE_LENGTH];
|
||||
int64_t lineno = 0;
|
||||
int64_t mismatch = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
@@ -632,47 +711,7 @@ akerr_ErrorContext *akbasic_cmd_dverify(akbasic_Runtime *obj, akbasic_ASTLeaf *e
|
||||
*/
|
||||
PASS(errctx, aksl_fopen(name, "r", &fp));
|
||||
ATTEMPT {
|
||||
for ( lineno = 0; lineno < AKBASIC_MAX_SOURCE_LINES; lineno++ ) {
|
||||
size_t length = 0;
|
||||
akerr_ErrorContext *got = NULL;
|
||||
char *newline = NULL;
|
||||
|
||||
if ( obj->source[lineno].code[0] == '\0' ) {
|
||||
continue;
|
||||
}
|
||||
got = aksl_fgets(line, sizeof(line), fp, &length);
|
||||
if ( got != NULL ) {
|
||||
got->handled = true;
|
||||
IGNORE(akerr_release_error(got));
|
||||
mismatch += 1;
|
||||
break;
|
||||
}
|
||||
newline = strchr(line, '\n');
|
||||
if ( newline != NULL ) {
|
||||
*newline = '\0';
|
||||
}
|
||||
/*
|
||||
* The stored line has had its number stripped, so the file's copy is
|
||||
* compared from past its own number. PASS rather than CATCH: this is
|
||||
* a loop.
|
||||
*/
|
||||
{
|
||||
const char *filetext = line;
|
||||
|
||||
while ( *filetext == ' ' ) {
|
||||
filetext += 1;
|
||||
}
|
||||
while ( *filetext >= '0' && *filetext <= '9' ) {
|
||||
filetext += 1;
|
||||
}
|
||||
while ( *filetext == ' ' ) {
|
||||
filetext += 1;
|
||||
}
|
||||
if ( strcmp(filetext, obj->source[lineno].code) != 0 ) {
|
||||
mismatch += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH(errctx, verify_lines(obj, fp, &mismatch));
|
||||
} CLEANUP {
|
||||
IGNORE(aksl_fclose(fp));
|
||||
} PROCESS(errctx) {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
@@ -189,13 +188,19 @@ akerr_ErrorContext *akbasic_fn_hex(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Value *arg = NULL;
|
||||
akbasic_Value *out = NULL;
|
||||
int written = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, first_arg(obj, expr, "HEX", NULL, &arg, &out));
|
||||
FAIL_NONZERO_RETURN(errctx, (arg->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
||||
"HEX expected an integer");
|
||||
out->valuetype = AKBASIC_TYPE_STRING;
|
||||
snprintf(out->stringval, sizeof(out->stringval), "%" PRIx64, arg->intval);
|
||||
/*
|
||||
* aksl_snprintf refuses to truncate, which costs nothing here: sixteen hex
|
||||
* digits is the longest an int64_t can be and a string value holds far more.
|
||||
*/
|
||||
PASS(errctx, aksl_snprintf(&written, out->stringval, sizeof(out->stringval),
|
||||
"%" PRIx64, arg->intval));
|
||||
*dest = out;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -270,6 +275,7 @@ akerr_ErrorContext *akbasic_fn_len(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
akbasic_Value *strval = NULL;
|
||||
akbasic_Value *out = NULL;
|
||||
akbasic_Variable *variable = NULL;
|
||||
size_t length = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
/*
|
||||
@@ -291,7 +297,8 @@ akerr_ErrorContext *akbasic_fn_len(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
if ( argleaf->leaftype == AKBASIC_LEAF_LITERAL_STRING ||
|
||||
argleaf->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ) {
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, argleaf, &strval));
|
||||
out->intval = (int64_t)strlen(strval->stringval);
|
||||
PASS(errctx, aksl_strlen(strval->stringval, &length));
|
||||
out->intval = (int64_t)length;
|
||||
} else {
|
||||
PASS(errctx, akbasic_environment_get(obj->environment, argleaf->identifier, &variable));
|
||||
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED,
|
||||
@@ -338,7 +345,8 @@ akerr_ErrorContext *akbasic_fn_instr(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
|
||||
PASS(errctx, akbasic_value_zero(out));
|
||||
out->valuetype = AKBASIC_TYPE_INTEGER;
|
||||
hit = strstr(haystack->stringval, needle->stringval);
|
||||
/* Not finding it is a successful NULL, which is exactly the -1 answer below. */
|
||||
PASS(errctx, aksl_strstr(haystack->stringval, needle->stringval, &hit));
|
||||
out->intval = (hit == NULL ? -1 : (int64_t)(hit - haystack->stringval));
|
||||
*dest = out;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -360,7 +368,7 @@ akerr_ErrorContext *akbasic_fn_left(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
"LEFT expected a string");
|
||||
FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
||||
"LEFT expected an integer count");
|
||||
sourcelen = strlen(source->stringval);
|
||||
PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
|
||||
take = count->intval;
|
||||
if ( take < 0 ) {
|
||||
take = 0;
|
||||
@@ -371,7 +379,7 @@ akerr_ErrorContext *akbasic_fn_left(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
|
||||
PASS(errctx, akbasic_value_zero(out));
|
||||
out->valuetype = AKBASIC_TYPE_STRING;
|
||||
memcpy(out->stringval, source->stringval, (size_t)take);
|
||||
PASS(errctx, aksl_memcpy(out->stringval, source->stringval, (size_t)take));
|
||||
out->stringval[take] = '\0';
|
||||
*dest = out;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -393,7 +401,7 @@ akerr_ErrorContext *akbasic_fn_right(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
"RIGHT expected a string");
|
||||
FAIL_NONZERO_RETURN(errctx, (count->valuetype != AKBASIC_TYPE_INTEGER), AKBASIC_ERR_TYPE,
|
||||
"RIGHT expected an integer count");
|
||||
sourcelen = strlen(source->stringval);
|
||||
PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
|
||||
take = count->intval;
|
||||
if ( take < 0 ) {
|
||||
take = 0;
|
||||
@@ -404,7 +412,8 @@ akerr_ErrorContext *akbasic_fn_right(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
|
||||
PASS(errctx, akbasic_value_zero(out));
|
||||
out->valuetype = AKBASIC_TYPE_STRING;
|
||||
memcpy(out->stringval, source->stringval + (sourcelen - (size_t)take), (size_t)take);
|
||||
PASS(errctx, aksl_memcpy(out->stringval, source->stringval + (sourcelen - (size_t)take),
|
||||
(size_t)take));
|
||||
out->stringval[take] = '\0';
|
||||
*dest = out;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -432,7 +441,7 @@ akerr_ErrorContext *akbasic_fn_mid(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
lengthval->valuetype == AKBASIC_TYPE_INTEGER),
|
||||
AKBASIC_ERR_TYPE, "MID expected integer start and length");
|
||||
|
||||
sourcelen = strlen(source->stringval);
|
||||
PASS(errctx, aksl_strlen(source->stringval, &sourcelen));
|
||||
start = startval->intval;
|
||||
length = lengthval->intval;
|
||||
FAIL_ZERO_RETURN(errctx, (start >= 0 && (size_t)start <= sourcelen), AKBASIC_ERR_BOUNDS,
|
||||
@@ -446,7 +455,7 @@ akerr_ErrorContext *akbasic_fn_mid(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
PASS(errctx, akbasic_environment_new_value(obj->environment, &out));
|
||||
PASS(errctx, akbasic_value_zero(out));
|
||||
out->valuetype = AKBASIC_TYPE_STRING;
|
||||
memcpy(out->stringval, source->stringval + start, (size_t)length);
|
||||
PASS(errctx, aksl_memcpy(out->stringval, source->stringval + start, (size_t)length));
|
||||
out->stringval[length] = '\0';
|
||||
*dest = out;
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -709,6 +709,7 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
char encoded[AKBASIC_MAX_STRING_LENGTH];
|
||||
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
|
||||
int subscriptcount = 0;
|
||||
int written = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, require_graphics(obj, "SSHAPE"));
|
||||
@@ -742,7 +743,7 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
* BASIC ever does with it; what it can no longer do is save it to disk or
|
||||
* take its LEN and get a size. TODO.md section 5.
|
||||
*/
|
||||
snprintf(encoded, sizeof(encoded), "%s%d", SHAPE_PREFIX, handle);
|
||||
PASS(errctx, aksl_snprintf(&written, encoded, sizeof(encoded), "%s%d", SHAPE_PREFIX, handle));
|
||||
PASS(errctx, akbasic_variable_set_string(variable, encoded, subscripts, subscriptcount));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -762,7 +763,9 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
|
||||
size_t prefixlen = 0;
|
||||
int subscriptcount = 0;
|
||||
int cmp = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, require_graphics(obj, "GSHAPE"));
|
||||
@@ -782,11 +785,11 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
y = (count >= 2) ? coords[1] : obj->gfx.y;
|
||||
|
||||
PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &stored));
|
||||
FAIL_ZERO_RETURN(errctx,
|
||||
(strncmp(stored->stringval, SHAPE_PREFIX, strlen(SHAPE_PREFIX)) == 0),
|
||||
AKBASIC_ERR_VALUE,
|
||||
PASS(errctx, aksl_strlen(SHAPE_PREFIX, &prefixlen));
|
||||
PASS(errctx, aksl_strncmp(stored->stringval, SHAPE_PREFIX, prefixlen, &cmp));
|
||||
FAIL_ZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_VALUE,
|
||||
"GSHAPE was given a string that did not come from SSHAPE");
|
||||
PASS(errctx, aksl_atoll(stored->stringval + strlen(SHAPE_PREFIX), &converted));
|
||||
PASS(errctx, aksl_atoll(stored->stringval + prefixlen, &converted));
|
||||
handle = (int64_t)converted;
|
||||
|
||||
scale_point(&obj->gfx, &x, &y);
|
||||
|
||||
@@ -13,10 +13,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/args.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -322,13 +321,13 @@ akerr_ErrorContext *akbasic_cmd_swap(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
* exactly this reason. The names stay put: they are what the symbol table
|
||||
* points at.
|
||||
*/
|
||||
memcpy(namea, a->name, sizeof(namea));
|
||||
memcpy(nameb, b->name, sizeof(nameb));
|
||||
memcpy(&swap, a, sizeof(swap));
|
||||
memcpy(a, b, sizeof(*a));
|
||||
memcpy(b, &swap, sizeof(*b));
|
||||
memcpy(a->name, namea, sizeof(a->name));
|
||||
memcpy(b->name, nameb, sizeof(b->name));
|
||||
PASS(errctx, aksl_memcpy(namea, a->name, sizeof(namea)));
|
||||
PASS(errctx, aksl_memcpy(nameb, b->name, sizeof(nameb)));
|
||||
PASS(errctx, aksl_memcpy(&swap, a, sizeof(swap)));
|
||||
PASS(errctx, aksl_memcpy(a, b, sizeof(*a)));
|
||||
PASS(errctx, aksl_memcpy(b, &swap, sizeof(*b)));
|
||||
PASS(errctx, aksl_memcpy(a->name, namea, sizeof(a->name)));
|
||||
PASS(errctx, aksl_memcpy(b->name, nameb, sizeof(b->name)));
|
||||
/*
|
||||
* A scalar's storage is *inside* the record, so the pointer that came over
|
||||
* with it names the other variable's inline slot -- which now holds this
|
||||
@@ -352,6 +351,7 @@ akerr_ErrorContext *akbasic_cmd_help(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
char line[AKBASIC_MAX_LINE_LENGTH * 2];
|
||||
int written = 0;
|
||||
|
||||
(void)expr; (void)lval; (void)rval;
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in HELP");
|
||||
@@ -369,8 +369,14 @@ akerr_ErrorContext *akbasic_cmd_help(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
snprintf(line, sizeof(line), "%" PRId64 " %s",
|
||||
obj->errorline, obj->source[obj->errorline].code);
|
||||
/*
|
||||
* aksl_snprintf treats truncation as an error, and this destination is twice
|
||||
* AKBASIC_MAX_LINE_LENGTH against a stored line that is at most one of them
|
||||
* plus a line number, so the fit is a property of the buffer rather than
|
||||
* something to check for.
|
||||
*/
|
||||
PASS(errctx, aksl_snprintf(&written, line, sizeof(line), "%" PRId64 " %s",
|
||||
obj->errorline, obj->source[obj->errorline].code));
|
||||
PASS(errctx, akbasic_runtime_println(obj, line));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* as sitting on the keyboard. See TODO.md section 5.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/input.h>
|
||||
@@ -152,9 +151,8 @@ akerr_ErrorContext *akbasic_cmd_getkey(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
}
|
||||
obj->input_state.waiting = true;
|
||||
obj->input_state.numeric = numeric;
|
||||
strncpy(obj->input_state.variable, arg->identifier,
|
||||
sizeof(obj->input_state.variable) - 1);
|
||||
obj->input_state.variable[sizeof(obj->input_state.variable) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(obj->input_state.variable,
|
||||
sizeof(obj->input_state.variable), arg->identifier));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/args.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -69,7 +69,8 @@ static akerr_ErrorContext *copy_bytes(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
* POKE and PEEK. A BASIC integer here is a real address, so a wrong one is a
|
||||
* segmentation fault rather than an error message. See TODO.md section 5.
|
||||
*/
|
||||
memmove((void *)(uintptr_t)args[2], (const void *)(uintptr_t)args[1], (size_t)length);
|
||||
PASS(errctx, aksl_memmove((void *)(uintptr_t)args[2], (const void *)(uintptr_t)args[1],
|
||||
(size_t)length));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/args.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -403,6 +404,9 @@ static akerr_ErrorContext *define_from_leaf(akbasic_Runtime *obj, akbasic_ASTLea
|
||||
akbasic_Color fg;
|
||||
akbasic_Color bg;
|
||||
uint8_t pattern[AKBASIC_SPRITE_PATTERN_BYTES];
|
||||
size_t prefixlen = 0;
|
||||
int handle = 0;
|
||||
int cmp = 0;
|
||||
|
||||
if ( arg->leaftype == AKBASIC_LEAF_IDENTIFIER_INT &&
|
||||
akbasic_leaf_first_subscript(arg) == NULL ) {
|
||||
@@ -428,11 +432,17 @@ static akerr_ErrorContext *define_from_leaf(akbasic_Runtime *obj, akbasic_ASTLea
|
||||
FAIL_ZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||||
"SPRSAV expected a sprite number, an image path, a saved shape or an integer array");
|
||||
|
||||
if ( strncmp(value->stringval, SHAPE_PREFIX, strlen(SHAPE_PREFIX)) == 0 ) {
|
||||
int handle = atoi(value->stringval + strlen(SHAPE_PREFIX));
|
||||
|
||||
PASS(errctx, aksl_strlen(SHAPE_PREFIX, &prefixlen));
|
||||
PASS(errctx, aksl_strncmp(value->stringval, SHAPE_PREFIX, prefixlen, &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
FAIL_ZERO_RETURN(errctx, (obj->sprites->define_shape != NULL), AKBASIC_ERR_DEVICE,
|
||||
"This sprite device cannot take a saved shape");
|
||||
/*
|
||||
* aksl_atoi refuses a slot number that is not one, where atoi(3) answered
|
||||
* a silent 0 -- so `SPRSAV "SHAPE:", 1` is now told what is wrong with it
|
||||
* rather than being handed shape 0.
|
||||
*/
|
||||
PASS(errctx, aksl_atoi(value->stringval + prefixlen, &handle));
|
||||
PASS(errctx, obj->sprites->define_shape(obj->sprites, index + 1, handle));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -779,8 +789,9 @@ akerr_ErrorContext *akbasic_cmd_solid(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
if ( !obj->sprite_state.solids[i].active ) {
|
||||
continue;
|
||||
}
|
||||
memset(&obj->sprite_state.solids[i], 0, sizeof(obj->sprite_state.solids[i]));
|
||||
/* PASS rather than CATCH: this is a loop. */
|
||||
PASS(errctx, aksl_memset(&obj->sprite_state.solids[i], 0,
|
||||
sizeof(obj->sprite_state.solids[i])));
|
||||
PASS(errctx, obj->sprites->solid(obj->sprites, i + 1, false, 0.0, 0.0, 0.0, 0.0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -792,7 +803,7 @@ akerr_ErrorContext *akbasic_cmd_solid(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
solid = &obj->sprite_state.solids[id - 1];
|
||||
|
||||
if ( count == 1 ) {
|
||||
memset(solid, 0, sizeof(*solid));
|
||||
PASS(errctx, aksl_memset(solid, 0, sizeof(*solid)));
|
||||
PASS(errctx, obj->sprites->solid(obj->sprites, id, false, 0.0, 0.0, 0.0, 0.0));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/runtime.h>
|
||||
@@ -194,7 +195,11 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
|
||||
PREPARE_ERROR(errctx);
|
||||
char rendered[AKBASIC_MAX_STRING_LENGTH];
|
||||
akbasic_StructType *type = NULL;
|
||||
const char *separator = NULL;
|
||||
size_t used = 0;
|
||||
size_t seplen = 0;
|
||||
size_t namelen = 0;
|
||||
size_t renderedlen = 0;
|
||||
int written = 0;
|
||||
int i = 0;
|
||||
|
||||
@@ -204,6 +209,20 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
|
||||
AKBASIC_ERR_BOUNDS, "Structure type index %d is out of range", typeindex);
|
||||
type = &obj->structtypes.types[typeindex];
|
||||
|
||||
/*
|
||||
* These two stay on raw snprintf on purpose, and are two of the six such
|
||||
* sites left in src/. Both want truncation as an *answer*: `dest` and `len`
|
||||
* come from the caller, this renders a value for display, and a name too long
|
||||
* for the buffer should print short rather than fail the whole PRINT. The
|
||||
* second one reads the return value to detect it -- on overflow `used` lands
|
||||
* past `len`, which is exactly what makes the field loop below and the
|
||||
* closing ")" both skip.
|
||||
*
|
||||
* aksl_snprintf treats truncation as AKERR_OUTOFBOUNDS and its `count` is 0
|
||||
* on that path, so it can express neither the tolerance nor the detection.
|
||||
* libakstdlib #34 is the issue that would give `count` the required length
|
||||
* back; until it lands there is nothing to convert these to.
|
||||
*/
|
||||
if ( depth >= AKBASIC_MAX_STRUCT_DEPTH ) {
|
||||
snprintf(dest, len, "%s(...)", type->name);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -222,7 +241,7 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
|
||||
break;
|
||||
case AKBASIC_FIELD_POINTER:
|
||||
if ( slot->structbase == NULL ) {
|
||||
snprintf(rendered, sizeof(rendered), "NOTHING");
|
||||
PASS(errctx, aksl_snprintf(&written, rendered, sizeof(rendered), "NOTHING"));
|
||||
} else {
|
||||
PASS(errctx, akbasic_struct_to_string(obj, slot->structtype, slot->structbase,
|
||||
depth + 1, rendered, sizeof(rendered)));
|
||||
@@ -232,15 +251,24 @@ akerr_ErrorContext *akbasic_struct_to_string(akbasic_Runtime *obj, int typeindex
|
||||
PASS(errctx, akbasic_value_to_string(slot, rendered, sizeof(rendered)));
|
||||
break;
|
||||
}
|
||||
written = snprintf(dest + used, len - used, "%s%s=%s",
|
||||
(i == 0 ? "" : ", "), field->name, rendered);
|
||||
if ( written < 0 || (size_t)written >= len - used ) {
|
||||
separator = (i == 0 ? "" : ", ");
|
||||
PASS(errctx, aksl_strlen(separator, &seplen));
|
||||
PASS(errctx, aksl_strlen(field->name, &namelen));
|
||||
PASS(errctx, aksl_strlen(rendered, &renderedlen));
|
||||
/*
|
||||
* A render that does not fit ends the list rather than failing it, so the
|
||||
* fit is decided here -- aksl_snprintf treats truncation as an error, and
|
||||
* the one byte counted beyond the three lengths is the `=`.
|
||||
*/
|
||||
if ( used + seplen + namelen + 1 + renderedlen >= len ) {
|
||||
break;
|
||||
}
|
||||
PASS(errctx, aksl_snprintf(&written, dest + used, len - used, "%s%s=%s",
|
||||
separator, field->name, rendered));
|
||||
used += (size_t)written;
|
||||
}
|
||||
if ( used + 1 < len ) {
|
||||
snprintf(dest + used, len - used, ")");
|
||||
PASS(errctx, aksl_snprintf(&written, dest + used, len - used, ")"));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -173,6 +173,7 @@ akerr_ErrorContext *akbasic_cmd_begin(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
akerr_ErrorContext *akbasic_cmd_bend(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
bool waiting = false;
|
||||
|
||||
(void)expr; (void)lval; (void)rval;
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
@@ -182,7 +183,8 @@ akerr_ErrorContext *akbasic_cmd_bend(akbasic_Runtime *obj, akbasic_ASTLeaf *expr
|
||||
* to do, or it is the BEND a skipped block was skipping to, in which case
|
||||
* stopping the skip is the whole job.
|
||||
*/
|
||||
if ( akbasic_environment_is_waiting_for(obj->environment, "BEND") ) {
|
||||
PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "BEND", &waiting));
|
||||
if ( waiting ) {
|
||||
PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "BEND"));
|
||||
}
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/args.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -107,6 +106,8 @@ akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
akbasic_Variable *variable = NULL;
|
||||
int64_t zerosubscript[1] = { 0 };
|
||||
int64_t resumeline = 0;
|
||||
int cmp = 0;
|
||||
bool isnext = false;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
@@ -124,6 +125,10 @@ akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
resumeline = value->intval;
|
||||
|
||||
target = (expr != NULL ? expr->right : NULL);
|
||||
if ( target != NULL && target->leaftype == AKBASIC_LEAF_COMMAND ) {
|
||||
PASS(errctx, aksl_strcmp(target->identifier, "NEXT", &cmp));
|
||||
isnext = (cmp == 0);
|
||||
}
|
||||
if ( target == NULL ) {
|
||||
/*
|
||||
* Bare RESUME retries the line that failed. That is a loop unless the
|
||||
@@ -131,8 +136,7 @@ akerr_ErrorContext *akbasic_cmd_resume(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
* is exactly what the verb is for.
|
||||
*/
|
||||
obj->environment->parent->nextline = resumeline;
|
||||
} else if ( target->leaftype == AKBASIC_LEAF_COMMAND &&
|
||||
strcmp(target->identifier, "NEXT") == 0 ) {
|
||||
} else if ( isnext ) {
|
||||
/* RESUME NEXT carries on at the line after the one that failed. */
|
||||
obj->environment->parent->nextline = resumeline + 1;
|
||||
} else {
|
||||
@@ -182,8 +186,8 @@ akerr_ErrorContext *akbasic_fn_err(akbasic_Runtime *obj, akbasic_ASTLeaf *expr,
|
||||
* that ever decides otherwise.
|
||||
*/
|
||||
name = akerr_name_for_status((int)status, NULL);
|
||||
snprintf(out->stringval, sizeof(out->stringval), "%s",
|
||||
(name != NULL ? name : "Unknown Error"));
|
||||
PASS(errctx, aksl_strcpy(out->stringval, sizeof(out->stringval),
|
||||
(name != NULL ? name : "Unknown Error")));
|
||||
*dest = out;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/args.h>
|
||||
#include <akbasic/error.h>
|
||||
@@ -153,8 +153,7 @@ static akerr_ErrorContext *nth_string(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||||
FAIL_ZERO_RETURN(errctx, (value->valuetype == AKBASIC_TYPE_STRING), AKBASIC_ERR_TYPE,
|
||||
"%s expected a string in argument %d", verb, n + 1);
|
||||
strncpy(dest, value->stringval, len - 1);
|
||||
dest[len - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(dest, len, value->stringval));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -259,8 +258,7 @@ static akerr_ErrorContext *target_variable(akbasic_Runtime *obj, akbasic_ASTLeaf
|
||||
PASS(errctx, akbasic_environment_get(obj->environment, arg->identifier, dest));
|
||||
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKBASIC_ERR_UNDEFINED,
|
||||
"GETMENU could not reach the variable %s", arg->identifier);
|
||||
strncpy(name, arg->identifier, len - 1);
|
||||
name[len - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(name, len, arg->identifier));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -309,8 +307,7 @@ akerr_ErrorContext *akbasic_cmd_getmenu(akbasic_Runtime *obj, akbasic_ASTLeaf *e
|
||||
*/
|
||||
obj->ui_state.waiting = true;
|
||||
obj->ui_state.waitmenu = slot;
|
||||
strncpy(obj->ui_state.variable, name, sizeof(obj->ui_state.variable) - 1);
|
||||
obj->ui_state.variable[sizeof(obj->ui_state.variable) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(obj->ui_state.variable, sizeof(obj->ui_state.variable), name));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -487,7 +484,7 @@ akerr_ErrorContext *akbasic_ui_state_init(akbasic_UiState *obj)
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in ui_state_init");
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
201
src/scanner.c
201
src/scanner.c
@@ -26,27 +26,67 @@ akerr_ErrorContext *akbasic_scanner_zero(akbasic_Runtime *obj)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
static bool is_at_end(akbasic_Runtime *obj)
|
||||
/**
|
||||
* @brief Is the cursor past the end of the line?
|
||||
*
|
||||
* The answer leaves through @p dest rather than the return value because
|
||||
* measuring the line can fail, and a `bool` has nowhere to put that. Same shape
|
||||
* as symtab.c's `probe`, and for the same reason. See libakstdlib #38.
|
||||
*/
|
||||
static akerr_ErrorContext *is_at_end(akbasic_Runtime *obj, bool *dest)
|
||||
{
|
||||
return (obj->current >= (int)strlen(obj->line));
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t linelen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in is_at_end");
|
||||
PASS(errctx, aksl_strlen(obj->line, &linelen));
|
||||
*dest = (obj->current >= (int)linelen);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
static bool peek(akbasic_Runtime *obj, char *dest)
|
||||
/**
|
||||
* @brief The character under the cursor.
|
||||
* @param[out] dest The character. Untouched when there is none.
|
||||
* @param[out] got Whether there was one. The old `bool` return.
|
||||
*/
|
||||
static akerr_ErrorContext *peek(akbasic_Runtime *obj, char *dest, bool *got)
|
||||
{
|
||||
if ( is_at_end(obj) ) {
|
||||
return false;
|
||||
PREPARE_ERROR(errctx);
|
||||
bool atend = false;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL && got != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in peek");
|
||||
PASS(errctx, is_at_end(obj, &atend));
|
||||
if ( atend ) {
|
||||
*got = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
*dest = obj->line[obj->current];
|
||||
return true;
|
||||
*got = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
static bool peek_next(akbasic_Runtime *obj, char *dest)
|
||||
/**
|
||||
* @brief The character one past the cursor.
|
||||
* @param[out] dest The character. Untouched when there is none.
|
||||
* @param[out] got Whether there was one. The old `bool` return.
|
||||
*/
|
||||
static akerr_ErrorContext *peek_next(akbasic_Runtime *obj, char *dest, bool *got)
|
||||
{
|
||||
if ( (obj->current + 1) >= (int)strlen(obj->line) ) {
|
||||
return false;
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t linelen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL && got != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in peek_next");
|
||||
PASS(errctx, aksl_strlen(obj->line, &linelen));
|
||||
if ( (obj->current + 1) >= (int)linelen ) {
|
||||
*got = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
*dest = obj->line[obj->current + 1];
|
||||
return true;
|
||||
*got = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -58,9 +98,12 @@ static bool peek_next(akbasic_Runtime *obj, char *dest)
|
||||
static akerr_ErrorContext *get_lexeme(akbasic_Runtime *obj, char *dest, size_t len)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int linelen = (int)strlen(obj->line);
|
||||
size_t measured = 0;
|
||||
int linelen = 0;
|
||||
int span = 0;
|
||||
|
||||
PASS(errctx, aksl_strlen(obj->line, &measured));
|
||||
linelen = (int)measured;
|
||||
if ( obj->current == linelen ) {
|
||||
span = linelen - obj->start;
|
||||
} else if ( obj->start == obj->current ) {
|
||||
@@ -74,7 +117,7 @@ static akerr_ErrorContext *get_lexeme(akbasic_Runtime *obj, char *dest, size_t l
|
||||
}
|
||||
FAIL_ZERO_RETURN(errctx, (span >= 0 && (size_t)span < len), AKBASIC_ERR_BOUNDS,
|
||||
"Lexeme of %d characters exceeds the %zu character limit", span, len - 1);
|
||||
memcpy(dest, obj->line + obj->start, (size_t)span);
|
||||
PASS(errctx, aksl_memcpy(dest, obj->line + obj->start, (size_t)span));
|
||||
dest[span] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -83,26 +126,39 @@ static akerr_ErrorContext *add_token(akbasic_Runtime *obj, akbasic_TokenType tok
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Environment *env = obj->environment;
|
||||
size_t lexemelen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (env->nexttoken < AKBASIC_MAX_TOKENS), AKBASIC_ERR_BOUNDS,
|
||||
"Line %" PRId64 " has more than %d tokens",
|
||||
env->lineno, AKBASIC_MAX_TOKENS);
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(lexeme) < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(lexeme, &lexemelen));
|
||||
FAIL_ZERO_RETURN(errctx, (lexemelen < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
|
||||
"Token lexeme exceeds the %d character limit", AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
env->tokens[env->nexttoken].tokentype = token;
|
||||
env->tokens[env->nexttoken].lineno = env->lineno;
|
||||
strncpy(env->tokens[env->nexttoken].lexeme, lexeme, AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
env->tokens[env->nexttoken].lexeme[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(env->tokens[env->nexttoken].lexeme,
|
||||
sizeof(env->tokens[env->nexttoken].lexeme), lexeme));
|
||||
env->nexttoken += 1;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/* Consume one more character when it matches, choosing between two token types. */
|
||||
static bool match_next_char(akbasic_Runtime *obj, char cm, akbasic_TokenType truetype, akbasic_TokenType falsetype)
|
||||
/**
|
||||
* @brief Consume one more character when it matches, choosing between two token types.
|
||||
* @param[out] matched Whether the character was consumed. The old `bool` return.
|
||||
*
|
||||
* On the chain below `peek`, so it reports the same way. See libakstdlib #38.
|
||||
*/
|
||||
static akerr_ErrorContext *match_next_char(akbasic_Runtime *obj, char cm, akbasic_TokenType truetype,
|
||||
akbasic_TokenType falsetype, bool *matched)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
char nc = '\0';
|
||||
bool got = false;
|
||||
|
||||
if ( !peek(obj, &nc) ) {
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && matched != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in match_next_char");
|
||||
PASS(errctx, peek(obj, &nc, &got));
|
||||
if ( !got ) {
|
||||
/*
|
||||
* Nothing left to peek at, so the operator is whatever it is on its
|
||||
* own. The reference returns here *without* setting a type
|
||||
@@ -112,24 +168,34 @@ static bool match_next_char(akbasic_Runtime *obj, char cm, akbasic_TokenType tru
|
||||
* TODO.md section 6 item 14.
|
||||
*/
|
||||
obj->tokentype = falsetype;
|
||||
return false;
|
||||
*matched = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( nc == cm ) {
|
||||
obj->current += 1;
|
||||
obj->tokentype = truetype;
|
||||
return true;
|
||||
*matched = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
obj->tokentype = falsetype;
|
||||
return false;
|
||||
*matched = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
static akerr_ErrorContext *match_string(akbasic_Runtime *obj)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
char c = '\0';
|
||||
bool atend = false;
|
||||
bool got = false;
|
||||
|
||||
while ( !is_at_end(obj) ) {
|
||||
if ( !peek(obj, &c) ) {
|
||||
for ( ;; ) {
|
||||
PASS(errctx, is_at_end(obj, &atend));
|
||||
if ( atend ) {
|
||||
break;
|
||||
}
|
||||
PASS(errctx, peek(obj, &c, &got));
|
||||
if ( !got ) {
|
||||
PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE,
|
||||
"UNTERMINATED STRING LITERAL\n"));
|
||||
obj->hasError = true;
|
||||
@@ -154,12 +220,19 @@ static akerr_ErrorContext *match_number(akbasic_Runtime *obj)
|
||||
int64_t lineno = 0;
|
||||
long long converted = 0;
|
||||
bool hex = false;
|
||||
bool atend = false;
|
||||
bool got = false;
|
||||
|
||||
obj->tokentype = AKBASIC_TOK_LITERAL_INT;
|
||||
while ( !is_at_end(obj) ) {
|
||||
(void)peek(obj, &c);
|
||||
for ( ;; ) {
|
||||
PASS(errctx, is_at_end(obj, &atend));
|
||||
if ( atend ) {
|
||||
break;
|
||||
}
|
||||
PASS(errctx, peek(obj, &c, &got));
|
||||
if ( c == '.' ) {
|
||||
if ( !peek_next(obj, &nc) || !isdigit((unsigned char)nc) ) {
|
||||
PASS(errctx, peek_next(obj, &nc, &got));
|
||||
if ( !got || !isdigit((unsigned char)nc) ) {
|
||||
PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE,
|
||||
"INVALID FLOATING POINT LITERAL\n"));
|
||||
obj->hasError = true;
|
||||
@@ -200,13 +273,17 @@ static akerr_ErrorContext *match_number(akbasic_Runtime *obj)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
char message[AKBASIC_MAX_LINE_LENGTH + 32];
|
||||
snprintf(message, sizeof(message), "INTEGER CONVERSION ON '%s'", lexeme);
|
||||
char message[AKBASIC_MAX_LINE_LENGTH + 32] = "INTEGER CONVERSION";
|
||||
int written = 0;
|
||||
/*
|
||||
* Reporting can itself fail if the sink is broken. Nothing useful
|
||||
* remains to be done about that here, so record the flag and let the
|
||||
* next operation surface it.
|
||||
* Reporting can itself fail if the sink is broken, and so can
|
||||
* formatting the message. Neither leaves anything useful to do here --
|
||||
* this block is already handling an error -- so both are ignored, the
|
||||
* message keeps the initialiser above if the format fails, and the flag
|
||||
* lets the next operation surface it.
|
||||
*/
|
||||
IGNORE(aksl_snprintf(&written, message, sizeof(message),
|
||||
"INTEGER CONVERSION ON '%s'", lexeme));
|
||||
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
|
||||
obj->hasError = true;
|
||||
} FINISH(errctx, false);
|
||||
@@ -230,10 +307,16 @@ static akerr_ErrorContext *match_identifier(akbasic_Runtime *obj)
|
||||
bool userfunction = false;
|
||||
char c = '\0';
|
||||
size_t used = 0;
|
||||
bool atend = false;
|
||||
bool got = false;
|
||||
|
||||
obj->tokentype = AKBASIC_TOK_IDENTIFIER;
|
||||
while ( !is_at_end(obj) ) {
|
||||
(void)peek(obj, &c);
|
||||
for ( ;; ) {
|
||||
PASS(errctx, is_at_end(obj, &atend));
|
||||
if ( atend ) {
|
||||
break;
|
||||
}
|
||||
PASS(errctx, peek(obj, &c, &got));
|
||||
if ( isdigit((unsigned char)c) || isalpha((unsigned char)c) ) {
|
||||
obj->current += 1;
|
||||
continue;
|
||||
@@ -270,9 +353,8 @@ static akerr_ErrorContext *match_identifier(akbasic_Runtime *obj)
|
||||
* in variable name" branch below is dead code, and `PRINT$ = 1` is quietly
|
||||
* accepted as an ordinary string variable. TODO.md section 6 item 16.
|
||||
*/
|
||||
strncpy(basename, lexeme, sizeof(basename) - 1);
|
||||
basename[sizeof(basename) - 1] = '\0';
|
||||
used = strlen(basename);
|
||||
PASS(errctx, aksl_strcpy(basename, sizeof(basename), lexeme));
|
||||
PASS(errctx, aksl_strlen(basename, &used));
|
||||
if ( obj->tokentype != AKBASIC_TOK_IDENTIFIER && used > 0 ) {
|
||||
basename[used - 1] = '\0';
|
||||
}
|
||||
@@ -311,15 +393,18 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
char lexeme[AKBASIC_MAX_LINE_LENGTH];
|
||||
char c = '\0';
|
||||
bool done = false;
|
||||
bool atend = false;
|
||||
bool matched = false;
|
||||
size_t linelen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in scan");
|
||||
FAIL_ZERO_RETURN(errctx, (line != NULL), AKERR_NULLPOINTER, "NULL line in scan");
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(line) < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(line, &linelen));
|
||||
FAIL_ZERO_RETURN(errctx, (linelen < AKBASIC_MAX_LINE_LENGTH), AKBASIC_ERR_BOUNDS,
|
||||
"Source line of %zu characters exceeds the %d character limit",
|
||||
strlen(line), AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
linelen, AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
|
||||
strncpy(obj->line, line, AKBASIC_MAX_LINE_LENGTH - 1);
|
||||
obj->line[AKBASIC_MAX_LINE_LENGTH - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(obj->line, sizeof(obj->line), line));
|
||||
PASS(errctx, akbasic_environment_zero_parser(obj->environment));
|
||||
obj->current = 0;
|
||||
obj->start = 0;
|
||||
@@ -333,7 +418,11 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
*/
|
||||
obj->hadlinenumber = false;
|
||||
|
||||
while ( !is_at_end(obj) && !done ) {
|
||||
for ( ;; ) {
|
||||
PASS(errctx, is_at_end(obj, &atend));
|
||||
if ( atend || done ) {
|
||||
break;
|
||||
}
|
||||
c = obj->line[obj->current];
|
||||
obj->current += 1;
|
||||
|
||||
@@ -350,7 +439,7 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
* no spelling this makes ambiguous.
|
||||
*/
|
||||
case '-':
|
||||
(void)match_next_char(obj, '>', AKBASIC_TOK_ARROW, AKBASIC_TOK_MINUS);
|
||||
PASS(errctx, match_next_char(obj, '>', AKBASIC_TOK_ARROW, AKBASIC_TOK_MINUS, &matched));
|
||||
break;
|
||||
case '/': obj->tokentype = AKBASIC_TOK_LEFT_SLASH; break;
|
||||
case '*': obj->tokentype = AKBASIC_TOK_STAR; break;
|
||||
@@ -373,15 +462,19 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
case '[': obj->tokentype = AKBASIC_TOK_LEFT_SQUAREBRACKET; break;
|
||||
case ']': obj->tokentype = AKBASIC_TOK_RIGHT_SQUAREBRACKET; break;
|
||||
case '=':
|
||||
(void)match_next_char(obj, '=', AKBASIC_TOK_EQUAL, AKBASIC_TOK_ASSIGNMENT);
|
||||
PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_EQUAL, AKBASIC_TOK_ASSIGNMENT, &matched));
|
||||
break;
|
||||
case '<':
|
||||
if ( !match_next_char(obj, '=', AKBASIC_TOK_LESS_THAN_EQUAL, AKBASIC_TOK_LESS_THAN) ) {
|
||||
(void)match_next_char(obj, '>', AKBASIC_TOK_NOT_EQUAL, AKBASIC_TOK_LESS_THAN);
|
||||
PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_LESS_THAN_EQUAL,
|
||||
AKBASIC_TOK_LESS_THAN, &matched));
|
||||
if ( !matched ) {
|
||||
PASS(errctx, match_next_char(obj, '>', AKBASIC_TOK_NOT_EQUAL,
|
||||
AKBASIC_TOK_LESS_THAN, &matched));
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
(void)match_next_char(obj, '=', AKBASIC_TOK_GREATER_THAN_EQUAL, AKBASIC_TOK_GREATER_THAN);
|
||||
PASS(errctx, match_next_char(obj, '=', AKBASIC_TOK_GREATER_THAN_EQUAL,
|
||||
AKBASIC_TOK_GREATER_THAN, &matched));
|
||||
break;
|
||||
case '"':
|
||||
obj->start = obj->current;
|
||||
@@ -402,7 +495,10 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
PASS(errctx, match_identifier(obj));
|
||||
} else {
|
||||
char message[AKBASIC_MAX_LINE_LENGTH];
|
||||
snprintf(message, sizeof(message), "UNKNOWN TOKEN %c\n", c);
|
||||
int written = 0;
|
||||
|
||||
PASS(errctx, aksl_snprintf(&written, message, sizeof(message),
|
||||
"UNKNOWN TOKEN %c\n", c));
|
||||
PASS(errctx, akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
|
||||
obj->hasError = true;
|
||||
obj->start = obj->current;
|
||||
@@ -426,10 +522,13 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
* stores *that* as the program text.
|
||||
*/
|
||||
int skip = obj->current;
|
||||
size_t tail = 0;
|
||||
|
||||
while ( obj->line[skip] == ' ' ) {
|
||||
skip += 1;
|
||||
}
|
||||
memmove(obj->line, obj->line + skip, strlen(obj->line + skip) + 1);
|
||||
PASS(errctx, aksl_strlen(obj->line + skip, &tail));
|
||||
PASS(errctx, aksl_memmove(obj->line, obj->line + skip, tail + 1));
|
||||
obj->current = 0;
|
||||
} else {
|
||||
PASS(errctx, get_lexeme(obj, lexeme, sizeof(lexeme)));
|
||||
@@ -445,10 +544,10 @@ akerr_ErrorContext *akbasic_scanner_scan(akbasic_Runtime *obj, const char *line,
|
||||
}
|
||||
|
||||
if ( dest != NULL ) {
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(obj->line) < len), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(obj->line, &linelen));
|
||||
FAIL_ZERO_RETURN(errctx, (linelen < len), AKBASIC_ERR_BOUNDS,
|
||||
"Scanned line does not fit the caller's buffer");
|
||||
strncpy(dest, obj->line, len - 1);
|
||||
dest[len - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(dest, len, obj->line));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akgl/controller.h>
|
||||
#include <akgl/draw.h>
|
||||
@@ -32,15 +31,24 @@
|
||||
/** @brief Bytes per row, including the terminator. */
|
||||
#define SINK_MAX_COLUMNS 256
|
||||
|
||||
/** @brief Scroll the grid up by one row, dropping the top one. */
|
||||
static void scroll(akbasic_AkglSink *state)
|
||||
/**
|
||||
* @brief Scroll the grid up by one row, dropping the top one.
|
||||
*
|
||||
* Returns a context rather than `void` because the row moves can fail and there
|
||||
* was nowhere to say so. Everything on this chain -- scroll, newline,
|
||||
* putchar_at, echo_line, edit_key -- returns one for the same reason, and the
|
||||
* chain already ends at sink_write, sink_writeln and sink_readline, which
|
||||
* returned one all along. See libakstdlib #38.
|
||||
*/
|
||||
static akerr_ErrorContext *scroll(akbasic_AkglSink *state)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int row = 0;
|
||||
|
||||
for ( row = 0; row < SINK_MAX_ROWS - 1; row++ ) {
|
||||
memcpy(state->text[row], state->text[row + 1], SINK_MAX_COLUMNS);
|
||||
PASS(errctx, aksl_memcpy(state->text[row], state->text[row + 1], SINK_MAX_COLUMNS));
|
||||
}
|
||||
memset(state->text[SINK_MAX_ROWS - 1], 0, SINK_MAX_COLUMNS);
|
||||
PASS(errctx, aksl_memset(state->text[SINK_MAX_ROWS - 1], 0, SINK_MAX_COLUMNS));
|
||||
if ( state->cursorrow > 0 ) {
|
||||
state->cursorrow -= 1;
|
||||
}
|
||||
@@ -59,13 +67,16 @@ static void scroll(akbasic_AkglSink *state)
|
||||
}
|
||||
|
||||
/** @brief Move to the start of the next row, scrolling if that runs off the end. */
|
||||
static void newline(akbasic_AkglSink *state)
|
||||
static akerr_ErrorContext *newline(akbasic_AkglSink *state)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
state->cursorcol = 0;
|
||||
state->cursorrow += 1;
|
||||
if ( state->cursorrow >= state->rows || state->cursorrow >= SINK_MAX_ROWS ) {
|
||||
scroll(state);
|
||||
PASS(errctx, scroll(state));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,16 +100,17 @@ static void newline(akbasic_AkglSink *state)
|
||||
* cleared between rows, so the gap holds the tail of some longer row that used
|
||||
* to be here.
|
||||
*/
|
||||
static void putchar_at(akbasic_AkglSink *state, char c)
|
||||
static akerr_ErrorContext *putchar_at(akbasic_AkglSink *state, char c)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int col = 0;
|
||||
|
||||
if ( c == '\n' ) {
|
||||
newline(state);
|
||||
return;
|
||||
PASS(errctx, newline(state));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( state->cursorcol >= state->columns || state->cursorcol >= SINK_MAX_COLUMNS - 1 ) {
|
||||
newline(state);
|
||||
PASS(errctx, newline(state));
|
||||
}
|
||||
for ( col = 0; col < state->cursorcol; col++ ) {
|
||||
if ( state->text[state->cursorrow][col] == '\0' ) {
|
||||
@@ -111,6 +123,7 @@ static void putchar_at(akbasic_AkglSink *state, char c)
|
||||
state->text[state->cursorrow][state->cursorcol] = c;
|
||||
state->cursorcol += 1;
|
||||
state->text[state->cursorrow][state->cursorcol] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
static akerr_ErrorContext *sink_write(akbasic_TextSink *self, const char *text)
|
||||
@@ -125,7 +138,7 @@ static akerr_ErrorContext *sink_write(akbasic_TextSink *self, const char *text)
|
||||
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
|
||||
"akgl sink has no state");
|
||||
for ( i = 0; text[i] != '\0'; i++ ) {
|
||||
putchar_at(state, text[i]);
|
||||
PASS(errctx, putchar_at(state, text[i]));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -137,7 +150,7 @@ static akerr_ErrorContext *sink_writeln(akbasic_TextSink *self, const char *text
|
||||
|
||||
PASS(errctx, sink_write(self, text));
|
||||
state = (akbasic_AkglSink *)self->self;
|
||||
newline(state);
|
||||
PASS(errctx, newline(state));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -151,8 +164,9 @@ static akerr_ErrorContext *sink_writeln(akbasic_TextSink *self, const char *text
|
||||
* memory, and the host draws it when it draws its frame -- so the cost is a
|
||||
* couple of memcpy-sized loops per keystroke.
|
||||
*/
|
||||
static void echo_line(akbasic_AkglSink *state)
|
||||
static akerr_ErrorContext *echo_line(akbasic_AkglSink *state)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int i = 0;
|
||||
int erasedto = 0;
|
||||
int endedat = 0;
|
||||
@@ -160,14 +174,14 @@ static void echo_line(akbasic_AkglSink *state)
|
||||
state->cursorrow = state->editrow;
|
||||
state->cursorcol = state->editcol;
|
||||
for ( i = 0; i < state->echolen; i++ ) {
|
||||
putchar_at(state, ' ');
|
||||
PASS(errctx, putchar_at(state, ' '));
|
||||
}
|
||||
erasedto = state->cursorrow;
|
||||
|
||||
state->cursorrow = state->editrow;
|
||||
state->cursorcol = state->editcol;
|
||||
for ( i = 0; i < state->editlen; i++ ) {
|
||||
putchar_at(state, state->editline[i]);
|
||||
PASS(errctx, putchar_at(state, state->editline[i]));
|
||||
}
|
||||
endedat = state->cursorrow;
|
||||
state->echolen = state->editlen;
|
||||
@@ -186,6 +200,7 @@ static void echo_line(akbasic_AkglSink *state)
|
||||
for ( i = endedat + 1; i <= erasedto && i < SINK_MAX_ROWS; i++ ) {
|
||||
state->text[i][0] = '\0';
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/** @brief Append one byte to the line being typed, if there is room for it. */
|
||||
@@ -215,14 +230,15 @@ static void edit_append(akbasic_AkglSink *state, char c)
|
||||
* Backspace and Escape are keys rather than characters and several of them
|
||||
* compose to text SDL would otherwise hand straight through.
|
||||
*/
|
||||
static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *submitted)
|
||||
static akerr_ErrorContext *edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *submitted)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t i = 0;
|
||||
|
||||
if ( key->key == SDLK_RETURN || key->key == SDLK_KP_ENTER ||
|
||||
key->key == '\r' || key->key == '\n' ) {
|
||||
*submitted = true;
|
||||
return;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( key->key == SDLK_BACKSPACE || key->key == '\b' || key->key == 0x7f ) {
|
||||
if ( state->editlen > 0 ) {
|
||||
@@ -235,15 +251,15 @@ static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *s
|
||||
*/
|
||||
state->editlen -= 1;
|
||||
state->editline[state->editlen] = '\0';
|
||||
echo_line(state);
|
||||
PASS(errctx, echo_line(state));
|
||||
}
|
||||
return;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( key->key == SDLK_ESCAPE || key->key == 0x1b ) {
|
||||
state->editlen = 0;
|
||||
state->editline[0] = '\0';
|
||||
echo_line(state);
|
||||
return;
|
||||
PASS(errctx, echo_line(state));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
if ( key->text[0] != '\0' ) {
|
||||
@@ -257,8 +273,8 @@ static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *s
|
||||
edit_append(state, key->text[i]);
|
||||
}
|
||||
}
|
||||
echo_line(state);
|
||||
return;
|
||||
PASS(errctx, echo_line(state));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -277,14 +293,15 @@ static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *s
|
||||
*/
|
||||
if ( key->key >= 0x20 && key->key < 0x7f ) {
|
||||
edit_append(state, (char)toupper((unsigned char)key->key));
|
||||
echo_line(state);
|
||||
return;
|
||||
PASS(errctx, echo_line(state));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Neither: a cursor key, a function key or a bare modifier. Not an editing
|
||||
* command here -- a script's own GET loop is what wants those.
|
||||
*/
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,7 +329,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *edit_loop(akbasic_AkglSink *state, boo
|
||||
*/
|
||||
PASS(errctx, akgl_controller_poll_keystroke(&key, &available));
|
||||
if ( available ) {
|
||||
edit_key(state, &key, &submitted);
|
||||
PASS(errctx, edit_key(state, &key, &submitted));
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
@@ -375,9 +392,8 @@ static akerr_ErrorContext *sink_readline(akbasic_TextSink *self, char *dest, siz
|
||||
if ( *eof ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
strncpy(dest, state->editline, len - 1);
|
||||
dest[len - 1] = '\0';
|
||||
newline(state);
|
||||
PASS(errctx, aksl_strncpy(dest, len, state->editline, len - 1));
|
||||
PASS(errctx, newline(state));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -391,7 +407,7 @@ static akerr_ErrorContext *sink_clear(akbasic_TextSink *self)
|
||||
state = (akbasic_AkglSink *)self->self;
|
||||
FAIL_ZERO_RETURN(errctx, (state != NULL), AKERR_NULLPOINTER,
|
||||
"akgl sink has no state");
|
||||
memset(state->text, 0, sizeof(state->text));
|
||||
PASS(errctx, aksl_memset(state->text, 0, sizeof(state->text)));
|
||||
state->cursorcol = 0;
|
||||
state->cursorrow = 0;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -600,7 +616,7 @@ akerr_ErrorContext *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSi
|
||||
"A %dx%d text area has no room for a %dx%d character",
|
||||
w, h, cellw, cellh);
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
|
||||
state->renderer = renderer;
|
||||
state->font = font;
|
||||
state->color.r = 0xff;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
@@ -59,8 +58,20 @@ static akerr_ErrorContext *stdio_readline(akbasic_TextSink *self, char *dest, si
|
||||
*eof = false;
|
||||
dest[0] = '\0';
|
||||
|
||||
if ( fgets(dest, (int)len, state->in) == NULL ) {
|
||||
/*
|
||||
* The end of the stream is AKERR_EOF rather than a NULL return, so it is
|
||||
* handled here and turned back into the *eof flag this sink promises. A
|
||||
* genuine read error is left to propagate, which is what a NULL from
|
||||
* fgets(3) could never be told apart from.
|
||||
*/
|
||||
ATTEMPT {
|
||||
CATCH(errctx, aksl_fgets(dest, len, state->in, &used));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_EOF) {
|
||||
*eof = true;
|
||||
} FINISH(errctx, true);
|
||||
if ( *eof ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
/*
|
||||
@@ -68,7 +79,6 @@ static akerr_ErrorContext *stdio_readline(akbasic_TextSink *self, char *dest, si
|
||||
* anyway, but leaving them on would make a stored source line differ from
|
||||
* the same line typed at the REPL.
|
||||
*/
|
||||
used = strlen(dest);
|
||||
while ( used > 0 && (dest[used - 1] == '\n' || dest[used - 1] == '\r') ) {
|
||||
dest[used - 1] = '\0';
|
||||
used -= 1;
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/game.h>
|
||||
@@ -195,9 +195,9 @@ static akerr_ErrorContext *build_slot(akbasic_AkglSprites *state, int i, int wid
|
||||
oldvisible = state->actors[i]->visible;
|
||||
}
|
||||
|
||||
memset(spritename, 0, sizeof(spritename));
|
||||
memset(charname, 0, sizeof(charname));
|
||||
memset(actorname, 0, sizeof(actorname));
|
||||
PASS(errctx, aksl_memset(spritename, 0, sizeof(spritename)));
|
||||
PASS(errctx, aksl_memset(charname, 0, sizeof(charname)));
|
||||
PASS(errctx, aksl_memset(actorname, 0, sizeof(actorname)));
|
||||
SDL_snprintf(spritename, sizeof(spritename), "akbasic:sprite:%d", i + 1);
|
||||
SDL_snprintf(charname, sizeof(charname), "akbasic:character:%d", i + 1);
|
||||
SDL_snprintf(actorname, sizeof(actorname), "akbasic:actor:%d", i + 1);
|
||||
@@ -263,8 +263,8 @@ static akerr_ErrorContext *install_surface(akbasic_AkglSprites *state, int i, SD
|
||||
* which is what makes the texture's ownership one rule rather than two.
|
||||
*/
|
||||
PASS(errctx, akgl_heap_next_spritesheet(&state->sheets[i]));
|
||||
memset(state->sheets[i], 0, sizeof(*state->sheets[i]));
|
||||
memset(sheetname, 0, sizeof(sheetname));
|
||||
PASS(errctx, aksl_memset(state->sheets[i], 0, sizeof(*state->sheets[i])));
|
||||
PASS(errctx, aksl_memset(sheetname, 0, sizeof(sheetname)));
|
||||
SDL_snprintf(sheetname, sizeof(sheetname), "akbasic:sheet:%d", i + 1);
|
||||
SDL_strlcpy(state->sheets[i]->name, sheetname, sizeof(state->sheets[i]->name));
|
||||
state->sheets[i]->texture = texture;
|
||||
@@ -533,7 +533,7 @@ static akerr_ErrorContext *spr_shape(akbasic_SpriteBackend *self, int n, int kin
|
||||
state->shapex2[i] = (float32_t)x2;
|
||||
state->shapey2[i] = (float32_t)y2;
|
||||
state->shapeexplicit[i] = (x2 > x1 && y2 > y1);
|
||||
memset(&state->syncedbox[i], 0, sizeof(state->syncedbox[i]));
|
||||
PASS(errctx, aksl_memset(&state->syncedbox[i], 0, sizeof(state->syncedbox[i])));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -703,7 +703,8 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_scan(akbasic_AkglSprites *state)
|
||||
for ( i = 0; i < AKBASIC_MAX_SPRITES; i++ ) {
|
||||
live[i] = slot_collidable(state, i);
|
||||
synced[i] = false;
|
||||
memset(&box[i], 0, sizeof(box[i]));
|
||||
/* PASS rather than CATCH, as everywhere else in this loop-heavy function. */
|
||||
PASS(errctx, aksl_memset(&box[i], 0, sizeof(box[i])));
|
||||
if ( !live[i] ) {
|
||||
continue;
|
||||
}
|
||||
@@ -745,7 +746,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_scan(akbasic_AkglSprites *state)
|
||||
}
|
||||
state->lastmask = 0;
|
||||
state->lastsolidmask = 0;
|
||||
memset(state->hascontact, 0, sizeof(state->hascontact));
|
||||
PASS(errctx, aksl_memset(state->hascontact, 0, sizeof(state->hascontact)));
|
||||
|
||||
/*
|
||||
* **Two phases, and the split is the whole performance story.**
|
||||
@@ -970,7 +971,7 @@ akerr_ErrorContext *akbasic_sprite_init_akgl(akbasic_SpriteBackend *obj, akbasic
|
||||
/* Before anything else in libakgl, so every AKGL_ERR_* has a name to print. */
|
||||
PASS(errctx, akgl_error_init());
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
|
||||
state->renderer = renderer;
|
||||
state->graphics = graphics;
|
||||
|
||||
@@ -1012,7 +1013,7 @@ akerr_ErrorContext *akbasic_sprite_init_akgl(akbasic_SpriteBackend *obj, akbasic
|
||||
&state->shapes[i], 0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->self = state;
|
||||
obj->define = spr_define;
|
||||
obj->define_shape = spr_define_shape;
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
* adaptor that has to turn a pattern into pixels.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/sprite.h>
|
||||
@@ -40,7 +39,7 @@ akerr_ErrorContext *akbasic_sprite_state_init(akbasic_SpriteState *obj)
|
||||
int i = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL sprite state in init");
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
for ( i = 0; i < AKBASIC_MAX_SPRITES; i++ ) {
|
||||
obj->sprites[i].colorindex = SPRITE_COLOR_DEFAULT;
|
||||
}
|
||||
|
||||
111
src/structtype.c
111
src/structtype.c
@@ -23,6 +23,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/runtime.h>
|
||||
@@ -88,10 +89,22 @@ static const char *next_word(const char *cursor, char *dest, size_t len)
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/** @brief Compare a word against a keyword. Verbs are case-insensitive here as everywhere. */
|
||||
static bool word_is(const char *word, const char *keyword)
|
||||
/**
|
||||
* @brief Compare a word against a keyword. Verbs are case-insensitive here as everywhere.
|
||||
*
|
||||
* The answer leaves through @p dest rather than the return value because the
|
||||
* comparison itself can fail, and a `bool` has nowhere to put that. See
|
||||
* libakstdlib #38.
|
||||
*/
|
||||
static akerr_ErrorContext *word_is(const char *word, const char *keyword, bool *dest)
|
||||
{
|
||||
return (strcmp(word, keyword) == 0);
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL dest in word_is");
|
||||
PASS(errctx, aksl_strcmp(word, keyword, &cmp));
|
||||
*dest = (cmp == 0);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,8 +116,9 @@ static bool word_is(const char *word, const char *keyword)
|
||||
static akerr_ErrorContext *type_from_suffix(const char *name, akbasic_Type *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t len = strlen(name);
|
||||
size_t len = 0;
|
||||
|
||||
PASS(errctx, aksl_strlen(name, &len));
|
||||
FAIL_ZERO_RETURN(errctx, (len >= 2), AKBASIC_ERR_SYNTAX,
|
||||
"Field name \"%s\" carries no type suffix", name);
|
||||
switch ( name[len - 1] ) {
|
||||
@@ -125,7 +139,7 @@ akerr_ErrorContext *akbasic_structtype_table_init(akbasic_StructTypeTable *obj)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL table in structtype init");
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->count = 0;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -134,12 +148,17 @@ akerr_ErrorContext *akbasic_structtype_find(akbasic_StructTypeTable *obj, const
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int i = 0;
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in structtype find");
|
||||
*dest = -1;
|
||||
for ( i = 0; i < obj->count; i++ ) {
|
||||
if ( obj->types[i].used && strcmp(obj->types[i].name, name) == 0 ) {
|
||||
if ( !obj->types[i].used ) {
|
||||
continue;
|
||||
}
|
||||
PASS(errctx, aksl_strcmp(obj->types[i].name, name, &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
*dest = i;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -151,9 +170,14 @@ akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int t
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
char known[AKBASIC_MAX_STRING_LENGTH];
|
||||
const char *separator = NULL;
|
||||
akbasic_StructType *type = NULL;
|
||||
size_t used = 0;
|
||||
size_t namelen = 0;
|
||||
size_t seplen = 0;
|
||||
int written = 0;
|
||||
int i = 0;
|
||||
int cmp = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in structtype field");
|
||||
@@ -162,7 +186,8 @@ akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int t
|
||||
type = &obj->types[typeindex];
|
||||
|
||||
for ( i = 0; i < type->fieldcount; i++ ) {
|
||||
if ( strcmp(type->fields[i].name, name) == 0 ) {
|
||||
PASS(errctx, aksl_strcmp(type->fields[i].name, name, &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
*dest = &type->fields[i];
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -176,11 +201,20 @@ akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int t
|
||||
*/
|
||||
known[0] = '\0';
|
||||
for ( i = 0; i < type->fieldcount; i++ ) {
|
||||
int written = snprintf(known + used, sizeof(known) - used, "%s%s",
|
||||
(i == 0 ? "" : ", "), type->fields[i].name);
|
||||
if ( written < 0 || (size_t)written >= sizeof(known) - used ) {
|
||||
separator = (i == 0 ? "" : ", ");
|
||||
PASS(errctx, aksl_strlen(separator, &seplen));
|
||||
PASS(errctx, aksl_strlen(type->fields[i].name, &namelen));
|
||||
/*
|
||||
* The list is advisory, so a full buffer ends it rather than failing the
|
||||
* lookup. aksl_snprintf treats truncation as an error, which is right
|
||||
* everywhere the output matters -- so the fit is decided here, before the
|
||||
* call, instead of being read back out of a return value afterwards.
|
||||
*/
|
||||
if ( used + seplen + namelen >= sizeof(known) ) {
|
||||
break;
|
||||
}
|
||||
PASS(errctx, aksl_snprintf(&written, known + used, sizeof(known) - used, "%s%s",
|
||||
separator, type->fields[i].name));
|
||||
used += (size_t)written;
|
||||
}
|
||||
FAIL_RETURN(errctx, AKBASIC_ERR_UNDEFINED, "%s has no field %s (%s)",
|
||||
@@ -223,6 +257,8 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
||||
int64_t i = 0;
|
||||
int open = -1;
|
||||
int existing = 0;
|
||||
size_t namelen = 0;
|
||||
bool matched = false;
|
||||
const akbasic_Verb *verb = NULL;
|
||||
|
||||
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
||||
@@ -231,9 +267,11 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
||||
}
|
||||
cursor = next_word(skip_lineno(obj->source[i].code), word, sizeof(word));
|
||||
|
||||
if ( word_is(word, "END") ) {
|
||||
PASS(errctx, word_is(word, "END", &matched));
|
||||
if ( matched ) {
|
||||
(void)next_word(cursor, name, sizeof(name));
|
||||
if ( !word_is(name, "TYPE") ) {
|
||||
PASS(errctx, word_is(name, "TYPE", &matched));
|
||||
if ( !matched ) {
|
||||
continue; /* an ordinary END, not our terminator */
|
||||
}
|
||||
FAIL_ZERO_RETURN(errctx, (open >= 0), AKBASIC_ERR_SYNTAX,
|
||||
@@ -242,7 +280,8 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
||||
open = -1;
|
||||
continue;
|
||||
}
|
||||
if ( !word_is(word, "TYPE") ) {
|
||||
PASS(errctx, word_is(word, "TYPE", &matched));
|
||||
if ( !matched ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -268,16 +307,17 @@ static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
||||
FAIL_ZERO_RETURN(errctx, (table->count < AKBASIC_MAX_STRUCT_TYPES), AKBASIC_ERR_BOUNDS,
|
||||
"More than %d TYPE declarations", AKBASIC_MAX_STRUCT_TYPES);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(name) < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(name, &namelen));
|
||||
FAIL_ZERO_RETURN(errctx, (namelen < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
|
||||
"TYPE name \"%s\" exceeds the %d character limit",
|
||||
name, AKBASIC_MAX_STRUCT_NAME - 1);
|
||||
open = table->count;
|
||||
memset(&table->types[open], 0, sizeof(table->types[open]));
|
||||
/* The memset is what terminates this: bounded at size - 1, the last byte
|
||||
is the zero it already wrote. Refused above rather than truncated,
|
||||
because two long names truncating to the same prefix would collide
|
||||
silently -- and truncation is an error everywhere else here. */
|
||||
strncpy(table->types[open].name, name, sizeof(table->types[open].name) - 1);
|
||||
PASS(errctx, aksl_memset(&table->types[open], 0, sizeof(table->types[open])));
|
||||
/* aksl_strcpy always terminates and refuses rather than truncates, which
|
||||
is the behaviour this site already wanted -- two long names truncating
|
||||
to the same prefix would collide silently. The length check above stays
|
||||
because it names the limit in the message. */
|
||||
PASS(errctx, aksl_strcpy(table->types[open].name, sizeof(table->types[open].name), name));
|
||||
table->types[open].used = true;
|
||||
table->types[open].firstline = i;
|
||||
table->types[open].lastline = -1;
|
||||
@@ -302,34 +342,38 @@ static akerr_ErrorContext *parse_field(akbasic_StructTypeTable *table, akbasic_S
|
||||
const char *cursor = NULL;
|
||||
akbasic_StructField *field = NULL;
|
||||
akbasic_Type valuetype = AKBASIC_TYPE_UNDEFINED;
|
||||
size_t namelen = 0;
|
||||
bool matched = false;
|
||||
int referenced = 0;
|
||||
int cmp = 0;
|
||||
int i = 0;
|
||||
|
||||
cursor = next_word(skip_lineno(line), fieldname, sizeof(fieldname));
|
||||
if ( fieldname[0] == '\0' || fieldname[0] == '\'' ) {
|
||||
SUCCEED_RETURN(errctx); /* a blank line inside the block */
|
||||
}
|
||||
if ( word_is(fieldname, "REM") ) {
|
||||
PASS(errctx, word_is(fieldname, "REM", &matched));
|
||||
if ( matched ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (type->fieldcount < AKBASIC_MAX_STRUCT_FIELDS), AKBASIC_ERR_BOUNDS,
|
||||
"TYPE %s declares more than %d fields", type->name, AKBASIC_MAX_STRUCT_FIELDS);
|
||||
for ( i = 0; i < type->fieldcount; i++ ) {
|
||||
FAIL_NONZERO_RETURN(errctx, (strcmp(type->fields[i].name, fieldname) == 0),
|
||||
AKBASIC_ERR_VALUE,
|
||||
PASS(errctx, aksl_strcmp(type->fields[i].name, fieldname, &cmp));
|
||||
FAIL_NONZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_VALUE,
|
||||
"TYPE %s declares %s twice", type->name, fieldname);
|
||||
}
|
||||
|
||||
PASS(errctx, type_from_suffix(fieldname, &valuetype));
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(fieldname) < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
|
||||
PASS(errctx, aksl_strlen(fieldname, &namelen));
|
||||
FAIL_ZERO_RETURN(errctx, (namelen < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
|
||||
"Field name \"%s\" exceeds the %d character limit",
|
||||
fieldname, AKBASIC_MAX_STRUCT_NAME - 1);
|
||||
field = &type->fields[type->fieldcount];
|
||||
memset(field, 0, sizeof(*field));
|
||||
/* Terminated by the memset above; refused rather than truncated, for the
|
||||
same reason a type name is. */
|
||||
strncpy(field->name, fieldname, sizeof(field->name) - 1);
|
||||
PASS(errctx, aksl_memset(field, 0, sizeof(*field)));
|
||||
/* Refused rather than truncated, for the same reason a type name is. */
|
||||
PASS(errctx, aksl_strcpy(field->name, sizeof(field->name), fieldname));
|
||||
field->valuetype = valuetype;
|
||||
field->typeindex = -1;
|
||||
field->offset = -1;
|
||||
@@ -354,13 +398,16 @@ static akerr_ErrorContext *parse_field(akbasic_StructTypeTable *table, akbasic_S
|
||||
* not fit in one. So the declaration has to name it.
|
||||
*/
|
||||
cursor = next_word(cursor, word, sizeof(word));
|
||||
FAIL_ZERO_RETURN(errctx, word_is(word, "AS"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, word_is(word, "AS", &matched));
|
||||
FAIL_ZERO_RETURN(errctx, matched, AKBASIC_ERR_SYNTAX,
|
||||
"Line %" PRId64 ": %s must name its type -- %s AS TYPENAME, or %s AS PTR TO TYPENAME",
|
||||
lineno, fieldname, fieldname, fieldname);
|
||||
cursor = next_word(cursor, word, sizeof(word));
|
||||
if ( word_is(word, "PTR") ) {
|
||||
PASS(errctx, word_is(word, "PTR", &matched));
|
||||
if ( matched ) {
|
||||
cursor = next_word(cursor, word, sizeof(word));
|
||||
FAIL_ZERO_RETURN(errctx, word_is(word, "TO"), AKBASIC_ERR_SYNTAX,
|
||||
PASS(errctx, word_is(word, "TO", &matched));
|
||||
FAIL_ZERO_RETURN(errctx, matched, AKBASIC_ERR_SYNTAX,
|
||||
"Line %" PRId64 ": expected PTR TO TYPENAME", lineno);
|
||||
cursor = next_word(cursor, word, sizeof(word));
|
||||
field->kind = AKBASIC_FIELD_POINTER;
|
||||
@@ -466,7 +513,7 @@ akerr_ErrorContext *akbasic_structtype_scan(akbasic_Runtime *obj)
|
||||
}
|
||||
}
|
||||
for ( i = t; i < table->count; i++ ) {
|
||||
memset(&table->types[i], 0, sizeof(table->types[i]));
|
||||
PASS(errctx, aksl_memset(&table->types[i], 0, sizeof(table->types[i])));
|
||||
}
|
||||
table->count = t;
|
||||
|
||||
|
||||
28
src/symtab.c
28
src/symtab.c
@@ -3,8 +3,6 @@
|
||||
* @brief Implements the fixed-capacity open-addressed symbol table.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
@@ -23,10 +21,13 @@ static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
uint32_t hashval = 0;
|
||||
size_t keylen = 0;
|
||||
int index = 0;
|
||||
int cmp = 0;
|
||||
int i = 0;
|
||||
|
||||
PASS(errctx, aksl_strhash_djb2((char *)key, strlen(key), &hashval));
|
||||
PASS(errctx, aksl_strlen(key, &keylen));
|
||||
PASS(errctx, aksl_strhash_djb2((char *)key, keylen, &hashval));
|
||||
|
||||
*found = false;
|
||||
index = (int)(hashval % (uint32_t)obj->capacity);
|
||||
@@ -36,7 +37,8 @@ static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int
|
||||
*slot = probeidx;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( strcmp(obj->slots[probeidx].key, key) == 0 ) {
|
||||
PASS(errctx, aksl_strcmp(obj->slots[probeidx].key, key, &cmp));
|
||||
if ( cmp == 0 ) {
|
||||
*slot = probeidx;
|
||||
*found = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -58,7 +60,7 @@ akerr_ErrorContext *akbasic_symtab_init(akbasic_SymbolTable *obj, int capacity)
|
||||
"Symbol table capacity %d out of range 1..%d",
|
||||
capacity, AKBASIC_SYMTAB_MAX_SLOTS);
|
||||
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->capacity = capacity;
|
||||
obj->count = 0;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -67,6 +69,7 @@ akerr_ErrorContext *akbasic_symtab_init(akbasic_SymbolTable *obj, int capacity)
|
||||
akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key, void *value, int64_t ivalue)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t keylen = 0;
|
||||
int slot = 0;
|
||||
bool found = false;
|
||||
|
||||
@@ -74,7 +77,8 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
|
||||
"NULL symbol table in set");
|
||||
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
|
||||
"NULL key in symbol table set");
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(key) < AKBASIC_SYMTAB_MAX_KEY),
|
||||
PASS(errctx, aksl_strlen(key, &keylen));
|
||||
FAIL_ZERO_RETURN(errctx, (keylen < AKBASIC_SYMTAB_MAX_KEY),
|
||||
AKBASIC_ERR_BOUNDS,
|
||||
"Symbol name '%s' exceeds %d characters",
|
||||
key, AKBASIC_SYMTAB_MAX_KEY - 1);
|
||||
@@ -82,8 +86,10 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
|
||||
PASS(errctx, probe(obj, key, &slot, &found));
|
||||
|
||||
if ( !found ) {
|
||||
strncpy(obj->slots[slot].key, key, AKBASIC_SYMTAB_MAX_KEY - 1);
|
||||
obj->slots[slot].key[AKBASIC_SYMTAB_MAX_KEY - 1] = '\0';
|
||||
/* aksl_strcpy always terminates, so the explicit terminator this
|
||||
replaced is no longer needed. The length check above has already
|
||||
refused anything that would not fit, so it cannot fail here. */
|
||||
PASS(errctx, aksl_strcpy(obj->slots[slot].key, sizeof(obj->slots[slot].key), key));
|
||||
obj->slots[slot].used = true;
|
||||
obj->count += 1;
|
||||
}
|
||||
@@ -95,6 +101,7 @@ akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key
|
||||
akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key, void **value, int64_t *ivalue)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t keylen = 0;
|
||||
int slot = 0;
|
||||
bool found = false;
|
||||
|
||||
@@ -102,7 +109,8 @@ akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key
|
||||
"NULL symbol table in get");
|
||||
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
|
||||
"NULL key in symbol table get");
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(key) < AKBASIC_SYMTAB_MAX_KEY),
|
||||
PASS(errctx, aksl_strlen(key, &keylen));
|
||||
FAIL_ZERO_RETURN(errctx, (keylen < AKBASIC_SYMTAB_MAX_KEY),
|
||||
AKERR_KEY,
|
||||
"Symbol '%s' is not present", key);
|
||||
|
||||
@@ -127,7 +135,7 @@ akerr_ErrorContext *akbasic_symtab_clear(akbasic_SymbolTable *obj)
|
||||
"NULL symbol table in clear");
|
||||
|
||||
capacity = obj->capacity;
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->capacity = capacity;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
* string.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/registry.h>
|
||||
@@ -94,8 +92,7 @@ static akerr_ErrorContext *ui_dialog(akbasic_UiBackend *self, const char *text)
|
||||
state->dialogtext[0] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
strncpy(state->dialogtext, text, sizeof(state->dialogtext) - 1);
|
||||
state->dialogtext[sizeof(state->dialogtext) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(state->dialogtext, sizeof(state->dialogtext), text));
|
||||
state->dialogopen = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -113,8 +110,7 @@ static akerr_ErrorContext *ui_label(akbasic_UiBackend *self, int slot, int ancho
|
||||
state->labeltext[slot][0] = '\0';
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
strncpy(state->labeltext[slot], text, sizeof(state->labeltext[slot]) - 1);
|
||||
state->labeltext[slot][sizeof(state->labeltext[slot]) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(state->labeltext[slot], sizeof(state->labeltext[slot]), text));
|
||||
state->labelanchor[slot] = anchor;
|
||||
state->labelset[slot] = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -143,8 +139,8 @@ static akerr_ErrorContext *ui_menu(akbasic_UiBackend *self, int slot, const char
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
FAIL_ZERO_RETURN(errctx, (items[i] != NULL), AKERR_NULLPOINTER,
|
||||
"MENU entry %d is NULL", i + 1);
|
||||
strncpy(state->menuitems[slot][i], items[i], sizeof(state->menuitems[slot][i]) - 1);
|
||||
state->menuitems[slot][i][sizeof(state->menuitems[slot][i]) - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(state->menuitems[slot][i], sizeof(state->menuitems[slot][i]),
|
||||
items[i]));
|
||||
state->menus[slot].items[i] = state->menuitems[slot][i];
|
||||
}
|
||||
for ( i = count; i < AKBASIC_UI_MAX_MENU_ITEMS; i++ ) {
|
||||
@@ -348,9 +344,9 @@ akerr_ErrorContext *akbasic_ui_init_akgl(akbasic_UiBackend *obj, akbasic_AkglUi
|
||||
"A %dx%d UI at %d points is not a UI", width, height, fontsize);
|
||||
PASS(errctx, akgl_error_init());
|
||||
|
||||
memset(state, 0, sizeof(*state));
|
||||
PASS(errctx, aksl_memset(state, 0, sizeof(*state)));
|
||||
state->renderer = renderer;
|
||||
strncpy(state->fontname, UI_FONT_NAME, sizeof(state->fontname) - 1);
|
||||
PASS(errctx, aksl_strcpy(state->fontname, sizeof(state->fontname), UI_FONT_NAME));
|
||||
|
||||
/*
|
||||
* The element ids, once. clay identifies an element by its string and keeps
|
||||
@@ -359,13 +355,14 @@ akerr_ErrorContext *akbasic_ui_init_akgl(akbasic_UiBackend *obj, akbasic_AkglUi
|
||||
* function that declares them.
|
||||
*/
|
||||
for ( i = 0; i < AKBASIC_UI_MAX_LABELS; i++ ) {
|
||||
snprintf(state->labelid[i], sizeof(state->labelid[i]), "hud%d", i + 1);
|
||||
PASS(errctx, aksl_snprintf(&count, state->labelid[i], sizeof(state->labelid[i]),
|
||||
"hud%d", i + 1));
|
||||
}
|
||||
for ( i = 0; i < AKBASIC_UI_MAX_MENUS; i++ ) {
|
||||
snprintf(state->menuid[i], sizeof(state->menuid[i]), "menu%d", i + 1);
|
||||
PASS(errctx, aksl_snprintf(&count, state->menuid[i], sizeof(state->menuid[i]),
|
||||
"menu%d", i + 1));
|
||||
state->menus[i].id = state->menuid[i];
|
||||
}
|
||||
(void)count;
|
||||
|
||||
/*
|
||||
* **akgl_registry_init() does not initialize the font registry** -- its own
|
||||
|
||||
68
src/value.c
68
src/value.c
@@ -13,6 +13,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/value.h>
|
||||
@@ -119,13 +120,17 @@ static akerr_ErrorContext *require_numeric(akbasic_Value *self, akbasic_Value *r
|
||||
static akerr_ErrorContext *set_string(akbasic_Value *dest, const char *src)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
size_t srclen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(src) < AKBASIC_MAX_STRING_LENGTH),
|
||||
PASS(errctx, aksl_strlen(src, &srclen));
|
||||
FAIL_ZERO_RETURN(errctx, (srclen < AKBASIC_MAX_STRING_LENGTH),
|
||||
AKBASIC_ERR_VALUE,
|
||||
"String result of %zu characters exceeds the %d character limit",
|
||||
strlen(src), AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
strncpy(dest->stringval, src, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
dest->stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
|
||||
srclen, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
/* aksl_strcpy always terminates and refuses rather than truncates, which is
|
||||
the behaviour this site already wanted. The length check above stays
|
||||
because it names the limit in the message. */
|
||||
PASS(errctx, aksl_strcpy(dest->stringval, sizeof(dest->stringval), src));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -134,7 +139,7 @@ akerr_ErrorContext *akbasic_valuepool_init(akbasic_ValuePool *obj)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL pool in init");
|
||||
memset(obj, 0, sizeof(*obj));
|
||||
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
||||
obj->next = 0;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -201,7 +206,7 @@ akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
dest->valuetype = self->valuetype;
|
||||
memcpy(dest->stringval, self->stringval, sizeof(dest->stringval));
|
||||
PASS(errctx, aksl_memcpy(dest->stringval, self->stringval, sizeof(dest->stringval)));
|
||||
dest->intval = self->intval;
|
||||
dest->floatval = self->floatval;
|
||||
dest->boolvalue = self->boolvalue;
|
||||
@@ -223,6 +228,7 @@ akerr_ErrorContext *akbasic_value_clone(akbasic_Value *self, akbasic_Value *dest
|
||||
akerr_ErrorContext *akbasic_value_to_string(akbasic_Value *self, char *dest, size_t len)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int written = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in to_string");
|
||||
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in to_string");
|
||||
@@ -230,20 +236,22 @@ akerr_ErrorContext *akbasic_value_to_string(akbasic_Value *self, char *dest, siz
|
||||
|
||||
switch ( self->valuetype ) {
|
||||
case AKBASIC_TYPE_STRING:
|
||||
snprintf(dest, len, "%s", self->stringval);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%s", self->stringval));
|
||||
break;
|
||||
case AKBASIC_TYPE_INTEGER:
|
||||
snprintf(dest, len, "%" PRId64, self->intval);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, self->intval));
|
||||
break;
|
||||
case AKBASIC_TYPE_FLOAT:
|
||||
snprintf(dest, len, "%f", self->floatval);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%f", self->floatval));
|
||||
break;
|
||||
case AKBASIC_TYPE_BOOLEAN:
|
||||
/* Go's %t, which is "true"/"false" and not the numeric -1/0. */
|
||||
snprintf(dest, len, "%s", (self->boolvalue == AKBASIC_TRUE ? "true" : "false"));
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "%s",
|
||||
(self->boolvalue == AKBASIC_TRUE ? "true" : "false")));
|
||||
break;
|
||||
default:
|
||||
snprintf(dest, len, "(UNDEFINED STRING REPRESENTATION FOR %d)", (int)self->valuetype);
|
||||
PASS(errctx, aksl_snprintf(&written, dest, len, "(UNDEFINED STRING REPRESENTATION FOR %d)",
|
||||
(int)self->valuetype));
|
||||
break;
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -486,6 +494,7 @@ akerr_ErrorContext *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Value *out = NULL;
|
||||
char buf[AKBASIC_MAX_STRING_LENGTH * 2];
|
||||
int written = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL value in math plus");
|
||||
FAIL_ZERO_RETURN(errctx, (rval != NULL), AKERR_NULLPOINTER, "nil rval");
|
||||
@@ -511,13 +520,16 @@ akerr_ErrorContext *akbasic_value_math_plus(akbasic_Value *self, akbasic_Value *
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
out->floatval = self->floatval + rval_as_float(rval);
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_STRING ) {
|
||||
snprintf(buf, sizeof(buf), "%s%s", self->stringval, rval->stringval);
|
||||
PASS(errctx, aksl_snprintf(&written, buf, sizeof(buf), "%s%s",
|
||||
self->stringval, rval->stringval));
|
||||
PASS(errctx, set_string(out, buf));
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
snprintf(buf, sizeof(buf), "%s%" PRId64, self->stringval, rval->intval);
|
||||
PASS(errctx, aksl_snprintf(&written, buf, sizeof(buf), "%s%" PRId64,
|
||||
self->stringval, rval->intval));
|
||||
PASS(errctx, set_string(out, buf));
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_STRING && rval->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
snprintf(buf, sizeof(buf), "%s%f", self->stringval, rval->floatval);
|
||||
PASS(errctx, aksl_snprintf(&written, buf, sizeof(buf), "%s%f",
|
||||
self->stringval, rval->floatval));
|
||||
PASS(errctx, set_string(out, buf));
|
||||
} else {
|
||||
FAIL_RETURN(errctx, AKBASIC_ERR_TYPE, "Invalid arithmetic operation");
|
||||
@@ -586,14 +598,14 @@ akerr_ErrorContext *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Val
|
||||
*/
|
||||
FAIL_NONZERO_RETURN(errctx, (rval->intval < 0), AKBASIC_ERR_VALUE,
|
||||
"String multiplier %" PRId64 " must not be negative", rval->intval);
|
||||
srclen = strlen((*dest)->stringval);
|
||||
PASS(errctx, aksl_strlen((*dest)->stringval, &srclen));
|
||||
FAIL_NONZERO_RETURN(errctx,
|
||||
(srclen != 0 && (uint64_t)rval->intval > (AKBASIC_MAX_STRING_LENGTH - 1) / srclen),
|
||||
AKBASIC_ERR_VALUE,
|
||||
"Repeated string of %zu x %" PRId64 " characters exceeds the %d character limit",
|
||||
srclen, rval->intval, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
for ( i = 0; i < rval->intval; i++ ) {
|
||||
memcpy(buf + offset, (*dest)->stringval, srclen);
|
||||
PASS(errctx, aksl_memcpy(buf + offset, (*dest)->stringval, srclen));
|
||||
offset += srclen;
|
||||
}
|
||||
buf[offset] = '\0';
|
||||
@@ -619,6 +631,7 @@ akerr_ErrorContext *akbasic_value_math_multiply(akbasic_Value *self, akbasic_Val
|
||||
akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||||
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
@@ -626,7 +639,8 @@ akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval < rval_as_float(rval)));
|
||||
} else {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) < 0));
|
||||
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, cmp < 0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -634,6 +648,7 @@ akerr_ErrorContext *akbasic_value_less_than(akbasic_Value *self, akbasic_Value *
|
||||
akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||||
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
@@ -641,7 +656,8 @@ akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_V
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval <= rval_as_float(rval)));
|
||||
} else {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) <= 0));
|
||||
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, cmp <= 0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -649,6 +665,7 @@ akerr_ErrorContext *akbasic_value_less_than_equal(akbasic_Value *self, akbasic_V
|
||||
akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||||
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
@@ -656,7 +673,8 @@ akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Valu
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval > rval_as_float(rval)));
|
||||
} else {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) > 0));
|
||||
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, cmp > 0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -664,6 +682,7 @@ akerr_ErrorContext *akbasic_value_greater_than(akbasic_Value *self, akbasic_Valu
|
||||
akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||||
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
@@ -671,7 +690,8 @@ akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasi
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval >= rval_as_float(rval)));
|
||||
} else {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) >= 0));
|
||||
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, cmp >= 0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -679,6 +699,7 @@ akerr_ErrorContext *akbasic_value_greater_than_equal(akbasic_Value *self, akbasi
|
||||
akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||||
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
@@ -686,7 +707,8 @@ akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *r
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval == rval_as_float(rval)));
|
||||
} else {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) == 0));
|
||||
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, cmp == 0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -694,6 +716,7 @@ akerr_ErrorContext *akbasic_value_is_equal(akbasic_Value *self, akbasic_Value *r
|
||||
akerr_ErrorContext *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Value *rval, akbasic_Value *scratch, akbasic_Value **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int cmp = 0;
|
||||
|
||||
PASS(errctx, binary_prologue(self, rval, scratch, dest));
|
||||
if ( self->valuetype == AKBASIC_TYPE_INTEGER ) {
|
||||
@@ -701,7 +724,8 @@ akerr_ErrorContext *akbasic_value_is_not_equal(akbasic_Value *self, akbasic_Valu
|
||||
} else if ( self->valuetype == AKBASIC_TYPE_FLOAT ) {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, self->floatval != rval_as_float(rval)));
|
||||
} else {
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, strcmp(self->stringval, rval->stringval) != 0));
|
||||
PASS(errctx, aksl_strcmp(self->stringval, rval->stringval, &cmp));
|
||||
PASS(errctx, akbasic_value_set_bool(*dest, cmp != 0));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/variable.h>
|
||||
@@ -53,7 +53,7 @@ akerr_ErrorContext *akbasic_variable_init(akbasic_Variable *obj, akbasic_ValuePo
|
||||
"Array dimension count %d out of range 1..%d",
|
||||
sizecount, AKBASIC_MAX_ARRAY_DEPTH);
|
||||
|
||||
namelen = strlen(obj->name);
|
||||
PASS(errctx, aksl_strlen(obj->name, &namelen));
|
||||
FAIL_ZERO_RETURN(errctx, (namelen > 0), AKBASIC_ERR_VALUE, "Invalid variable name");
|
||||
|
||||
/* Type comes from the suffix. A bare name keeps whatever type it had. */
|
||||
@@ -202,16 +202,17 @@ akerr_ErrorContext *akbasic_variable_set_string(akbasic_Variable *obj, const cha
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Value tmp;
|
||||
size_t valuelen = 0;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (value != NULL), AKERR_NULLPOINTER, "NULL string in set_string");
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(value) < AKBASIC_MAX_STRING_LENGTH),
|
||||
PASS(errctx, aksl_strlen(value, &valuelen));
|
||||
FAIL_ZERO_RETURN(errctx, (valuelen < AKBASIC_MAX_STRING_LENGTH),
|
||||
AKBASIC_ERR_VALUE,
|
||||
"String of %zu characters exceeds the %d character limit",
|
||||
strlen(value), AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
valuelen, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
PASS(errctx, akbasic_value_zero(&tmp));
|
||||
tmp.valuetype = AKBASIC_TYPE_STRING;
|
||||
strncpy(tmp.stringval, value, AKBASIC_MAX_STRING_LENGTH - 1);
|
||||
tmp.stringval[AKBASIC_MAX_STRING_LENGTH - 1] = '\0';
|
||||
PASS(errctx, aksl_strcpy(tmp.stringval, sizeof(tmp.stringval), value));
|
||||
PASS(errctx, akbasic_variable_set_subscript(obj, &tmp, subscripts, subscriptcount));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
25
src/verbs.c
25
src/verbs.c
@@ -8,6 +8,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <akerror.h>
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <akbasic/error.h>
|
||||
#include <akbasic/verbs.h>
|
||||
@@ -197,6 +198,28 @@ static const akbasic_Verb VERBS[] = {
|
||||
|
||||
#define VERB_COUNT ((int)(sizeof(VERBS) / sizeof(VERBS[0])))
|
||||
|
||||
/**
|
||||
* @brief bsearch(3) comparator for the verb table.
|
||||
*
|
||||
* **This is the one call in `src/` that stays on raw `strcmp` on purpose.**
|
||||
* Everything else that reaches past libakstdlib to a function that library wraps
|
||||
* has been converted to the wrapper and propagates the error context; the ruling
|
||||
* that required it is libakstdlib #38, and the rule is that a function which
|
||||
* cannot report an error changes its own signature rather than swallowing one.
|
||||
*
|
||||
* That remedy is unavailable here, and only here. `bsearch(3)` fixes the
|
||||
* comparator's signature at `int (*)(const void *, const void *)`, so there is
|
||||
* no out-parameter to hand an `akerr_ErrorContext *` back through and no return
|
||||
* value that is not already the ordering. Converting this one site means
|
||||
* dropping `bsearch` for an in-house binary search that can propagate -- a
|
||||
* separate change to a lookup that runs once per scanned identifier, and a
|
||||
* decision that belongs in its own issue rather than riding along with a port.
|
||||
*
|
||||
* The bypass is safe as written: `key` is the caller's NUL-terminated `upper`
|
||||
* buffer and `element->name` is a string literal in the static table above, so
|
||||
* neither argument can be NULL and neither can be unterminated -- which is the
|
||||
* whole of what `aksl_strcmp` would have checked.
|
||||
*/
|
||||
static int verb_compare(const void *key, const void *element)
|
||||
{
|
||||
const akbasic_Verb *verb = (const akbasic_Verb *)element;
|
||||
@@ -223,7 +246,7 @@ akerr_ErrorContext *akbasic_verb_lookup(const char *name, const akbasic_Verb **d
|
||||
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in verb lookup");
|
||||
|
||||
*dest = NULL;
|
||||
len = strlen(name);
|
||||
PASS(errctx, aksl_strlen(name, &len));
|
||||
if ( len == 0 || len >= sizeof(upper) ) {
|
||||
/* Too long to be any verb we know. Not an error, just a miss. */
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -13,6 +13,7 @@ int main(void)
|
||||
akbasic_Variable *again = NULL;
|
||||
akbasic_Value *value = NULL;
|
||||
int64_t lineno = 0;
|
||||
bool waiting = false;
|
||||
int i = 0;
|
||||
|
||||
TEST_REQUIRE_OK(harness_start(NULL));
|
||||
@@ -57,24 +58,34 @@ int main(void)
|
||||
TEST_REQUIRE_STATUS(akbasic_runtime_prev_environment(&HARNESS_RUNTIME),
|
||||
AKBASIC_ERR_ENVIRONMENT);
|
||||
|
||||
/* The wait mechanism, including the parent-chain search. */
|
||||
TEST_REQUIRE(!akbasic_environment_is_waiting_for_any(root), "a fresh environment waits for nothing");
|
||||
/*
|
||||
* The wait mechanism, including the parent-chain search. Both predicates
|
||||
* report through an out-parameter and return an error context, so each
|
||||
* assertion is a TEST_REQUIRE_OK on the call and a TEST_REQUIRE on the
|
||||
* answer. See libakstdlib #38.
|
||||
*/
|
||||
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
|
||||
TEST_REQUIRE(!waiting, "a fresh environment waits for nothing");
|
||||
TEST_REQUIRE_OK(akbasic_environment_wait_for_command(root, "NEXT"));
|
||||
TEST_REQUIRE(akbasic_environment_is_waiting_for_any(root), "the wait must register");
|
||||
TEST_REQUIRE(akbasic_environment_is_waiting_for(root, "NEXT"), "the wait must match by name");
|
||||
TEST_REQUIRE(!akbasic_environment_is_waiting_for(root, "DATA"), "a different verb must not match");
|
||||
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
|
||||
TEST_REQUIRE(waiting, "the wait must register");
|
||||
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(root, "NEXT", &waiting));
|
||||
TEST_REQUIRE(waiting, "the wait must match by name");
|
||||
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(root, "DATA", &waiting));
|
||||
TEST_REQUIRE(!waiting, "a different verb must not match");
|
||||
|
||||
/* Two pending waits in one environment is a hard error, not a panic. */
|
||||
TEST_REQUIRE_STATUS(akbasic_environment_wait_for_command(root, "DATA"), AKBASIC_ERR_STATE);
|
||||
|
||||
TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME));
|
||||
child = HARNESS_RUNTIME.environment;
|
||||
TEST_REQUIRE(akbasic_environment_is_waiting_for(child, "NEXT"),
|
||||
"a child must see its parent's wait");
|
||||
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(child, "NEXT", &waiting));
|
||||
TEST_REQUIRE(waiting, "a child must see its parent's wait");
|
||||
TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME));
|
||||
|
||||
TEST_REQUIRE_OK(akbasic_environment_stop_waiting(root, "NEXT"));
|
||||
TEST_REQUIRE(!akbasic_environment_is_waiting_for_any(root), "stop_waiting must clear it");
|
||||
TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
|
||||
TEST_REQUIRE(!waiting, "stop_waiting must clear it");
|
||||
|
||||
/* The per-line value pool is bounded and says so. */
|
||||
for ( i = 0; i < AKBASIC_MAX_VALUES; i++ ) {
|
||||
|
||||
Reference in New Issue
Block a user