Port the BASIC interpreter from Go to C
Reproduces deps/basicinterpret in C, in the idiom of the ak* libraries. All 41
.bas files in the reference's corpus produce byte-identical stdout, including
the trailing double newline on an error line -- that comes from basicError
building a string ending in \n and handing it to Println, and
array_outofbounds.txt encodes it.
The corpus is driven in place from the submodule as 41 individual CTest cases
rather than copied, so it cannot drift from upstream. Eighteen unit tests cover
what the corpus cannot reach.
Three structural changes carry most of the work. Go's three reflection lookups
(Command*, Function*, ParseCommand*) become one sorted dispatch table in
src/verbs.c searched with bsearch; adding a verb is a row and two functions. The
five Go maps become one fixed open-addressed table over aksl_strhash_djb2. And
run(), which owned the process until MODE_QUIT, splits into step() plus a
bounded run() -- goal 3 requires a host game to be able to bound execution, and
nothing in the library now terminates the process or touches SDL.
Output goes through an akbasic_TextSink vtable. src/sink_stdio.c is what makes
the corpus runnable with no SDL present; the akgl-backed sink is still to come
and is blocked on libakgl having no text-measurement call.
src/convert.c exists because libakstdlib's aksl_ato* family cannot report a
conversion failure (its TODO.md 2.1.5). The reference checks strconv's error at
four sites and turns it into a BASIC error; routing those through aksl_atoi
would have turned four diagnosable errors into wrong answers, with VAL("garbage")
quietly returning 0. TODO.md 1.9 records which libakstdlib calls are cleared for
use here and which are not.
Reference defects are reproduced, not fixed: the golden files encode the observed
behaviour and a silent correction is a behaviour change. TODO.md section 6 lists
sixteen, and tests/known_reference_defects.c asserts the *correct* contract for
six of them under AKBASIC_KNOWN_FAILING_TESTS, so a fix shows up as
"unexpectedly passed". Five of the sixteen were found by this port and are new:
subtraction stops after one operator so 1-2-3 computes 1-2 and abandons the rest
of the line (a wrong answer, not a refused one); a unary-minus argument inflates
a function's arity so ABS(-9) is rejected; a comparison operator in a line's
final column is dropped; hex literals never survive the scanner; and the
"Reserved word in variable name" check is dead code.
Where the reference reaches undefined behaviour by a route that is defined in Go
-- an out-of-range shift, a negative string multiplier, integer division by zero
-- this raises instead of inheriting the UB. No golden case exercises any of
them.
The top-level CMakeLists shadows add_test, set_tests_properties and
add_custom_target around all three add_subdirectory calls. Without it libakerror's
tests land in our suite as Not Run, and its un-namespaced `coverage` target stops
a coverage build from configuring at all. Test targets are akbasic_test_<name>:
bare test_<name> collides with libakstdlib's, which is what broke libakgl's
configure in c2b16d3.
ctest 59/59; ASan+UBSan 59/59; 92.3% line and 96.9% function coverage; no
warnings under -Wall -Wextra. Branch coverage is not a target, for the reason
libakstdlib and libakgl both record: the akerror macros expand into large branch
trees at every call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:53:56 -04:00
|
|
|
cmake_minimum_required(VERSION 3.10)
|
|
|
|
|
# The single source of truth for the version, in the shape libakstdlib and
|
|
|
|
|
# libakgl both use. It flows into the library SOVERSION and nothing else spells
|
|
|
|
|
# a version number.
|
|
|
|
|
#
|
|
|
|
|
# 0.x on purpose: TODO.md section 12 records eleven defects carried over from the
|
|
|
|
|
# Go reference that are deliberately reproduced and not yet fixed, so the
|
|
|
|
|
# language surface is not being promised yet.
|
|
|
|
|
project(akbasic VERSION 0.1.0 LANGUAGES C)
|
|
|
|
|
|
|
|
|
|
# Pre-1.0 the ABI may break on a minor bump, so the soname carries MAJOR.MINOR.
|
|
|
|
|
# At 1.0 this becomes ${PROJECT_VERSION_MAJOR} alone.
|
|
|
|
|
if(PROJECT_VERSION_MAJOR EQUAL 0)
|
|
|
|
|
set(AKBASIC_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
|
|
|
|
else()
|
|
|
|
|
set(AKBASIC_SOVERSION "${PROJECT_VERSION_MAJOR}")
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
include(CTest)
|
|
|
|
|
include(GNUInstallDirs)
|
|
|
|
|
|
|
|
|
|
option(AKBASIC_WITH_AKGL "Build the libakgl-backed text sink and link SDL3" OFF)
|
Port the README from the Go version and document embedding
Carries the reference README across, adjusted where C changes the answer: cmake
instead of make, the ak* libraries instead of the go-sdl2 bindings, and the
limits table now includes the three ceilings the Go version did not need because
it called make().
The new "Embedding the interpreter" section is the point of the rewrite, so it
states the four rules the library holds to -- nothing terminates the process,
nothing calls malloc, no file-scope mutable state, the host owns the loop -- and
each was checked against the tree rather than asserted.
Adds akbasic_runtime_load(). Writing the section turned up a real gap: a host
usually already holds its script as a string and wants the sink reserved for
output, and the only path that existed was AKBASIC_MODE_RUNSTREAM reading the
program through the sink's readline, which forces a game to point its output
device at its source text. The alternative was reaching into the header's
"internal API" block for store_line. Neither is something to put in a README.
Adds examples/embed.c, which is the code the README quotes -- a custom sink, a
bounded per-frame run, and the PASS-not-CATCH rule for a loop inside an ATTEMPT.
It is built by every build and registered as a CTest case, so a signature change
breaks the build instead of rotting the document. The README's own snippet is
compiled separately as a check; both were run before committing.
The "What Isn't Implemented / Isn't Working" section leads with the eleven
inherited defects rather than burying them, because five of them were found by
this port and a reader deserves to know that 1 - 2 - 3 computes 1 - 2 before
they hit it. Corrected two claims while verifying: the runtime is 10.1MB rather
than the ~8MB first written, and its largest single cost is the environment pool
at 4.1MB, not the source table.
ctest 60/60; ASan+UBSan 60/60; no warnings under -Wall -Wextra.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 06:42:08 -04:00
|
|
|
option(AKBASIC_BUILD_EXAMPLES "Build the embedding example in examples/" ON)
|
Port the BASIC interpreter from Go to C
Reproduces deps/basicinterpret in C, in the idiom of the ak* libraries. All 41
.bas files in the reference's corpus produce byte-identical stdout, including
the trailing double newline on an error line -- that comes from basicError
building a string ending in \n and handing it to Println, and
array_outofbounds.txt encodes it.
The corpus is driven in place from the submodule as 41 individual CTest cases
rather than copied, so it cannot drift from upstream. Eighteen unit tests cover
what the corpus cannot reach.
Three structural changes carry most of the work. Go's three reflection lookups
(Command*, Function*, ParseCommand*) become one sorted dispatch table in
src/verbs.c searched with bsearch; adding a verb is a row and two functions. The
five Go maps become one fixed open-addressed table over aksl_strhash_djb2. And
run(), which owned the process until MODE_QUIT, splits into step() plus a
bounded run() -- goal 3 requires a host game to be able to bound execution, and
nothing in the library now terminates the process or touches SDL.
Output goes through an akbasic_TextSink vtable. src/sink_stdio.c is what makes
the corpus runnable with no SDL present; the akgl-backed sink is still to come
and is blocked on libakgl having no text-measurement call.
src/convert.c exists because libakstdlib's aksl_ato* family cannot report a
conversion failure (its TODO.md 2.1.5). The reference checks strconv's error at
four sites and turns it into a BASIC error; routing those through aksl_atoi
would have turned four diagnosable errors into wrong answers, with VAL("garbage")
quietly returning 0. TODO.md 1.9 records which libakstdlib calls are cleared for
use here and which are not.
Reference defects are reproduced, not fixed: the golden files encode the observed
behaviour and a silent correction is a behaviour change. TODO.md section 6 lists
sixteen, and tests/known_reference_defects.c asserts the *correct* contract for
six of them under AKBASIC_KNOWN_FAILING_TESTS, so a fix shows up as
"unexpectedly passed". Five of the sixteen were found by this port and are new:
subtraction stops after one operator so 1-2-3 computes 1-2 and abandons the rest
of the line (a wrong answer, not a refused one); a unary-minus argument inflates
a function's arity so ABS(-9) is rejected; a comparison operator in a line's
final column is dropped; hex literals never survive the scanner; and the
"Reserved word in variable name" check is dead code.
Where the reference reaches undefined behaviour by a route that is defined in Go
-- an out-of-range shift, a negative string multiplier, integer division by zero
-- this raises instead of inheriting the UB. No golden case exercises any of
them.
The top-level CMakeLists shadows add_test, set_tests_properties and
add_custom_target around all three add_subdirectory calls. Without it libakerror's
tests land in our suite as Not Run, and its un-namespaced `coverage` target stops
a coverage build from configuring at all. Test targets are akbasic_test_<name>:
bare test_<name> collides with libakstdlib's, which is what broke libakgl's
configure in c2b16d3.
ctest 59/59; ASan+UBSan 59/59; 92.3% line and 96.9% function coverage; no
warnings under -Wall -Wextra. Branch coverage is not a target, for the reason
libakstdlib and libakgl both record: the akerror macros expand into large branch
trees at every call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:53:56 -04:00
|
|
|
option(AKBASIC_COVERAGE "Instrument the build with gcov coverage counters" OFF)
|
|
|
|
|
option(AKBASIC_SANITIZE "Build with ASan + UBSan" OFF)
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Dependencies
|
|
|
|
|
#
|
|
|
|
|
# libakgl vendors its own copies of libakerror and libakstdlib and guards every
|
|
|
|
|
# dependency with if(NOT TARGET ...), so akerror::akerror and akstdlib::akstdlib
|
|
|
|
|
# must exist *before* add_subdirectory(deps/libakgl) or the targets are declared
|
|
|
|
|
# twice.
|
|
|
|
|
#
|
|
|
|
|
# All three dependencies register their CTest tests unconditionally. Pulled in
|
|
|
|
|
# EXCLUDE_FROM_ALL their test binaries are never built, so each one would land in
|
|
|
|
|
# our suite as "Not Run" and fail. CMake has no way to un-register a test and
|
|
|
|
|
# set_tests_properties cannot reach across directory scopes, so add_test() and
|
|
|
|
|
# set_tests_properties() are shadowed for the duration of the add_subdirectory()
|
|
|
|
|
# calls. Note libakstdlib carries the same shadow but only arms it when *it* is
|
|
|
|
|
# top-level, so it does nothing for us -- this one has to wrap all three.
|
|
|
|
|
#
|
|
|
|
|
# libakerror additionally namespaces its `mutation` target when embedded but not
|
|
|
|
|
# its `coverage` target (deps/libakerror/CMakeLists.txt:194 vs :172), so a
|
|
|
|
|
# coverage build collides on the `coverage` target and fails to configure at all.
|
|
|
|
|
# Rename the dependency's on the way past. Remove this once libakerror applies
|
|
|
|
|
# the same CMAKE_SOURCE_DIR test to `coverage` that it already applies to
|
|
|
|
|
# `mutation` -- filed in deps/libakstdlib/TODO.md section 2.3.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
set(AKBASIC_SUPPRESS_ADD_TEST TRUE)
|
|
|
|
|
|
|
|
|
|
function(add_test)
|
|
|
|
|
if(NOT AKBASIC_SUPPRESS_ADD_TEST)
|
|
|
|
|
_add_test(${ARGV})
|
|
|
|
|
endif()
|
|
|
|
|
endfunction()
|
|
|
|
|
|
|
|
|
|
function(set_tests_properties)
|
|
|
|
|
if(NOT AKBASIC_SUPPRESS_ADD_TEST)
|
|
|
|
|
_set_tests_properties(${ARGV})
|
|
|
|
|
endif()
|
|
|
|
|
endfunction()
|
|
|
|
|
|
|
|
|
|
function(add_custom_target _name)
|
|
|
|
|
if(AKBASIC_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage")
|
|
|
|
|
_add_custom_target(akerror_coverage ${ARGN})
|
|
|
|
|
else()
|
|
|
|
|
_add_custom_target(${ARGV})
|
|
|
|
|
endif()
|
|
|
|
|
endfunction()
|
|
|
|
|
|
|
|
|
|
add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL)
|
|
|
|
|
add_subdirectory(deps/libakstdlib EXCLUDE_FROM_ALL)
|
|
|
|
|
if(AKBASIC_WITH_AKGL)
|
|
|
|
|
add_subdirectory(deps/libakgl EXCLUDE_FROM_ALL)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
set(AKBASIC_SUPPRESS_ADD_TEST FALSE)
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Instrumentation, applied per target so it never leaks into an exported
|
|
|
|
|
# interface.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
function(akbasic_instrument _target)
|
|
|
|
|
if(AKBASIC_COVERAGE)
|
|
|
|
|
target_compile_options(${_target} PRIVATE --coverage -O0 -g)
|
|
|
|
|
target_link_options(${_target} PRIVATE --coverage)
|
|
|
|
|
endif()
|
|
|
|
|
if(AKBASIC_SANITIZE)
|
|
|
|
|
target_compile_options(${_target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer -g)
|
|
|
|
|
target_link_options(${_target} PRIVATE -fsanitize=address,undefined)
|
|
|
|
|
endif()
|
|
|
|
|
endfunction()
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# The interpreter library. Everything here must be free of SDL, free of any
|
|
|
|
|
# process-terminating call, and buildable with no libakgl present.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
set(AKBASIC_SOURCES
|
|
|
|
|
src/convert.c
|
|
|
|
|
src/environment.c
|
|
|
|
|
src/error.c
|
|
|
|
|
src/grammar.c
|
|
|
|
|
src/parser.c
|
|
|
|
|
src/parser_commands.c
|
|
|
|
|
src/runtime.c
|
|
|
|
|
src/runtime_commands.c
|
|
|
|
|
src/runtime_functions.c
|
|
|
|
|
src/scanner.c
|
|
|
|
|
src/sink_stdio.c
|
|
|
|
|
src/symtab.c
|
|
|
|
|
src/value.c
|
|
|
|
|
src/variable.c
|
|
|
|
|
src/verbs.c
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
add_library(akbasic ${AKBASIC_SOURCES})
|
|
|
|
|
add_library(akbasic::akbasic ALIAS akbasic)
|
|
|
|
|
|
|
|
|
|
target_include_directories(akbasic PUBLIC
|
|
|
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
|
|
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
target_compile_options(akbasic PRIVATE -Wall -Wextra)
|
|
|
|
|
target_link_libraries(akbasic PUBLIC akstdlib::akstdlib akerror::akerror)
|
|
|
|
|
target_link_libraries(akbasic PRIVATE m)
|
|
|
|
|
|
|
|
|
|
set_target_properties(akbasic PROPERTIES
|
|
|
|
|
VERSION ${PROJECT_VERSION}
|
|
|
|
|
SOVERSION ${AKBASIC_SOVERSION}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
akbasic_instrument(akbasic)
|
|
|
|
|
|
|
|
|
|
# The libakgl-backed text sink, kept in its own target so the core library and
|
|
|
|
|
# the whole golden suite build with no SDL present.
|
|
|
|
|
if(AKBASIC_WITH_AKGL)
|
|
|
|
|
add_library(akbasic_akgl src/sink_akgl.c)
|
|
|
|
|
target_compile_options(akbasic_akgl PRIVATE -Wall -Wextra)
|
|
|
|
|
target_link_libraries(akbasic_akgl PUBLIC akbasic akgl)
|
|
|
|
|
akbasic_instrument(akbasic_akgl)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# The standalone driver. This is the only place FINISH_NORETURN may appear.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
add_executable(basic src/main.c)
|
|
|
|
|
target_compile_options(basic PRIVATE -Wall -Wextra)
|
|
|
|
|
target_link_libraries(basic PRIVATE akbasic)
|
|
|
|
|
if(AKBASIC_WITH_AKGL)
|
|
|
|
|
target_link_libraries(basic PRIVATE akbasic_akgl)
|
|
|
|
|
target_compile_definitions(basic PRIVATE AKBASIC_HAVE_AKGL=1)
|
|
|
|
|
endif()
|
|
|
|
|
akbasic_instrument(basic)
|
|
|
|
|
|
Port the README from the Go version and document embedding
Carries the reference README across, adjusted where C changes the answer: cmake
instead of make, the ak* libraries instead of the go-sdl2 bindings, and the
limits table now includes the three ceilings the Go version did not need because
it called make().
The new "Embedding the interpreter" section is the point of the rewrite, so it
states the four rules the library holds to -- nothing terminates the process,
nothing calls malloc, no file-scope mutable state, the host owns the loop -- and
each was checked against the tree rather than asserted.
Adds akbasic_runtime_load(). Writing the section turned up a real gap: a host
usually already holds its script as a string and wants the sink reserved for
output, and the only path that existed was AKBASIC_MODE_RUNSTREAM reading the
program through the sink's readline, which forces a game to point its output
device at its source text. The alternative was reaching into the header's
"internal API" block for store_line. Neither is something to put in a README.
Adds examples/embed.c, which is the code the README quotes -- a custom sink, a
bounded per-frame run, and the PASS-not-CATCH rule for a loop inside an ATTEMPT.
It is built by every build and registered as a CTest case, so a signature change
breaks the build instead of rotting the document. The README's own snippet is
compiled separately as a check; both were run before committing.
The "What Isn't Implemented / Isn't Working" section leads with the eleven
inherited defects rather than burying them, because five of them were found by
this port and a reader deserves to know that 1 - 2 - 3 computes 1 - 2 before
they hit it. Corrected two claims while verifying: the runtime is 10.1MB rather
than the ~8MB first written, and its largest single cost is the environment pool
at 4.1MB, not the source table.
ctest 60/60; ASan+UBSan 60/60; no warnings under -Wall -Wextra.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 06:42:08 -04:00
|
|
|
# The embedding example. It is built by default and registered as a test so the
|
|
|
|
|
# code README.md quotes cannot rot: a signature change breaks the build.
|
|
|
|
|
if(AKBASIC_BUILD_EXAMPLES)
|
|
|
|
|
add_executable(akbasic_example_embed examples/embed.c)
|
|
|
|
|
target_compile_options(akbasic_example_embed PRIVATE -Wall -Wextra)
|
|
|
|
|
target_link_libraries(akbasic_example_embed PRIVATE akbasic)
|
|
|
|
|
akbasic_instrument(akbasic_example_embed)
|
|
|
|
|
_add_test(NAME example_embed COMMAND akbasic_example_embed)
|
|
|
|
|
endif()
|
|
|
|
|
|
Port the BASIC interpreter from Go to C
Reproduces deps/basicinterpret in C, in the idiom of the ak* libraries. All 41
.bas files in the reference's corpus produce byte-identical stdout, including
the trailing double newline on an error line -- that comes from basicError
building a string ending in \n and handing it to Println, and
array_outofbounds.txt encodes it.
The corpus is driven in place from the submodule as 41 individual CTest cases
rather than copied, so it cannot drift from upstream. Eighteen unit tests cover
what the corpus cannot reach.
Three structural changes carry most of the work. Go's three reflection lookups
(Command*, Function*, ParseCommand*) become one sorted dispatch table in
src/verbs.c searched with bsearch; adding a verb is a row and two functions. The
five Go maps become one fixed open-addressed table over aksl_strhash_djb2. And
run(), which owned the process until MODE_QUIT, splits into step() plus a
bounded run() -- goal 3 requires a host game to be able to bound execution, and
nothing in the library now terminates the process or touches SDL.
Output goes through an akbasic_TextSink vtable. src/sink_stdio.c is what makes
the corpus runnable with no SDL present; the akgl-backed sink is still to come
and is blocked on libakgl having no text-measurement call.
src/convert.c exists because libakstdlib's aksl_ato* family cannot report a
conversion failure (its TODO.md 2.1.5). The reference checks strconv's error at
four sites and turns it into a BASIC error; routing those through aksl_atoi
would have turned four diagnosable errors into wrong answers, with VAL("garbage")
quietly returning 0. TODO.md 1.9 records which libakstdlib calls are cleared for
use here and which are not.
Reference defects are reproduced, not fixed: the golden files encode the observed
behaviour and a silent correction is a behaviour change. TODO.md section 6 lists
sixteen, and tests/known_reference_defects.c asserts the *correct* contract for
six of them under AKBASIC_KNOWN_FAILING_TESTS, so a fix shows up as
"unexpectedly passed". Five of the sixteen were found by this port and are new:
subtraction stops after one operator so 1-2-3 computes 1-2 and abandons the rest
of the line (a wrong answer, not a refused one); a unary-minus argument inflates
a function's arity so ABS(-9) is rejected; a comparison operator in a line's
final column is dropped; hex literals never survive the scanner; and the
"Reserved word in variable name" check is dead code.
Where the reference reaches undefined behaviour by a route that is defined in Go
-- an out-of-range shift, a negative string multiplier, integer division by zero
-- this raises instead of inheriting the UB. No golden case exercises any of
them.
The top-level CMakeLists shadows add_test, set_tests_properties and
add_custom_target around all three add_subdirectory calls. Without it libakerror's
tests land in our suite as Not Run, and its un-namespaced `coverage` target stops
a coverage build from configuring at all. Test targets are akbasic_test_<name>:
bare test_<name> collides with libakstdlib's, which is what broke libakgl's
configure in c2b16d3.
ctest 59/59; ASan+UBSan 59/59; 92.3% line and 96.9% function coverage; no
warnings under -Wall -Wextra. Branch coverage is not a target, for the reason
libakstdlib and libakgl both record: the akerror macros expand into large branch
trees at every call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 23:53:56 -04:00
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Tests.
|
|
|
|
|
#
|
|
|
|
|
# Three lists, and two of them invert the meaning of "Passed", exactly as
|
|
|
|
|
# libakstdlib does:
|
|
|
|
|
# AKBASIC_TESTS must exit 0
|
|
|
|
|
# AKBASIC_WILL_FAIL_TESTS abort by design
|
|
|
|
|
# AKBASIC_KNOWN_FAILING_TESTS assert the *correct* contract for a defect
|
|
|
|
|
# recorded in TODO.md and are expected to fail.
|
|
|
|
|
# When one starts passing CTest reports
|
|
|
|
|
# "unexpectedly passed" -- that is the cue to move
|
|
|
|
|
# it into AKBASIC_TESTS along with the fix.
|
|
|
|
|
#
|
|
|
|
|
# Test executables are named akbasic_test_<name>, never test_<name>: a
|
|
|
|
|
# dependency's targets are created even under EXCLUDE_FROM_ALL, and libakstdlib
|
|
|
|
|
# already ships test_version. The CTest name stays bare, so `ctest -R value`
|
|
|
|
|
# still selects it.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
set(AKBASIC_TESTS
|
|
|
|
|
convert
|
|
|
|
|
environment_scope
|
|
|
|
|
error_codes
|
|
|
|
|
grammar_leaves
|
|
|
|
|
parser_commands
|
|
|
|
|
parser_expressions
|
|
|
|
|
runtime_evaluate
|
|
|
|
|
runtime_verbs
|
|
|
|
|
scanner_tokens
|
|
|
|
|
sink_stdio
|
|
|
|
|
symtab
|
|
|
|
|
value_arithmetic
|
|
|
|
|
value_bitwise
|
|
|
|
|
value_compare
|
|
|
|
|
variable_subscript
|
|
|
|
|
verbs_table
|
|
|
|
|
version_check
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
set(AKBASIC_WILL_FAIL_TESTS
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
set(AKBASIC_KNOWN_FAILING_TESTS
|
|
|
|
|
known_reference_defects
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
foreach(_test IN LISTS AKBASIC_TESTS AKBASIC_WILL_FAIL_TESTS AKBASIC_KNOWN_FAILING_TESTS)
|
|
|
|
|
add_executable(akbasic_test_${_test} tests/${_test}.c)
|
|
|
|
|
target_compile_options(akbasic_test_${_test} PRIVATE -Wall -Wextra)
|
|
|
|
|
target_link_libraries(akbasic_test_${_test} PRIVATE akbasic)
|
|
|
|
|
target_include_directories(akbasic_test_${_test} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
|
|
|
|
akbasic_instrument(akbasic_test_${_test})
|
|
|
|
|
_add_test(NAME ${_test} COMMAND akbasic_test_${_test})
|
|
|
|
|
endforeach()
|
|
|
|
|
|
|
|
|
|
if(AKBASIC_TESTS OR AKBASIC_WILL_FAIL_TESTS OR AKBASIC_KNOWN_FAILING_TESTS)
|
|
|
|
|
_set_tests_properties(
|
|
|
|
|
${AKBASIC_TESTS} ${AKBASIC_WILL_FAIL_TESTS} ${AKBASIC_KNOWN_FAILING_TESTS}
|
|
|
|
|
PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" TIMEOUT 30
|
|
|
|
|
)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
if(AKBASIC_WILL_FAIL_TESTS OR AKBASIC_KNOWN_FAILING_TESTS)
|
|
|
|
|
_set_tests_properties(
|
|
|
|
|
${AKBASIC_WILL_FAIL_TESTS} ${AKBASIC_KNOWN_FAILING_TESTS}
|
|
|
|
|
PROPERTIES WILL_FAIL TRUE
|
|
|
|
|
)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
# The golden-file suite. It drives deps/basicinterpret/tests/**/*.bas in place --
|
|
|
|
|
# the corpus is a submodule and copying it guarantees drift -- and byte-compares
|
|
|
|
|
# stdout against the sibling .txt. One CTest case per .bas so a failure names the
|
|
|
|
|
# file.
|
|
|
|
|
file(GLOB_RECURSE AKBASIC_GOLDEN_CASES
|
|
|
|
|
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/deps/basicinterpret"
|
|
|
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/deps/basicinterpret/tests/*.bas"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
foreach(_case IN LISTS AKBASIC_GOLDEN_CASES)
|
|
|
|
|
string(REGEX REPLACE "^tests/" "" _name "${_case}")
|
|
|
|
|
string(REGEX REPLACE "\\.bas$" "" _name "${_name}")
|
|
|
|
|
string(REPLACE "/" "_" _name "${_name}")
|
|
|
|
|
_add_test(
|
|
|
|
|
NAME golden_${_name}
|
|
|
|
|
COMMAND ${CMAKE_COMMAND}
|
|
|
|
|
-DBASIC=$<TARGET_FILE:basic>
|
|
|
|
|
-DCASE=${CMAKE_CURRENT_SOURCE_DIR}/deps/basicinterpret/${_case}
|
|
|
|
|
-P ${CMAKE_CURRENT_SOURCE_DIR}/tests/golden.cmake
|
|
|
|
|
)
|
|
|
|
|
endforeach()
|
|
|
|
|
|
|
|
|
|
if(AKBASIC_GOLDEN_CASES)
|
|
|
|
|
set(AKBASIC_GOLDEN_NAMES)
|
|
|
|
|
foreach(_case IN LISTS AKBASIC_GOLDEN_CASES)
|
|
|
|
|
string(REGEX REPLACE "^tests/" "" _name "${_case}")
|
|
|
|
|
string(REGEX REPLACE "\\.bas$" "" _name "${_name}")
|
|
|
|
|
string(REPLACE "/" "_" _name "${_name}")
|
|
|
|
|
list(APPEND AKBASIC_GOLDEN_NAMES golden_${_name})
|
|
|
|
|
endforeach()
|
|
|
|
|
_set_tests_properties(${AKBASIC_GOLDEN_NAMES} PROPERTIES TIMEOUT 30)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
install(TARGETS akbasic basic
|
|
|
|
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
|
|
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
|
|
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
|
|
|
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
|
|
|
)
|
|
|
|
|
install(DIRECTORY include/akbasic DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|