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