Files
akbasic/CMakeLists.txt

277 lines
10 KiB
CMake
Raw Normal View History

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)
Document host/script variable exchange, and file the scope hazard it exposes The host can expose variables to a BASIC program, in both directions and for every type, with no marshalling layer -- akbasic_environment_get() finds or creates by name, the type comes from the suffix as it does for BASIC code, and host and script share the same akbasic_Value. Nothing new was needed to make that work. Testing it before writing it up turned up a hazard worth stating plainly, so the README documents the pattern and section 6 item 17 records the gap. Seeding before akbasic_runtime_start() and reading after the script stops is safe, and so is updating an existing global at any time, because the parent chain is searched. Creating one mid-run is not, in two ways, and both are silent. A script suspended part-way through a bounded run() is usually inside a FOR or GOSUB scope, so a variable created through obj->environment lands in that scope and dies when it pops -- the script reads it correctly inside the loop and gets 0 immediately after. And reaching for the root explicitly does not help: akbasic_environment_get only auto-creates when the environment it is given is the active one, so with a child active it returns NULL through dest without raising, and an unchecked host dereferences it. This one is ours rather than inherited. It falls out of the environment pool meeting the bounded run(), a combination the reference never had because its run() never returned. examples/hostvars.c demonstrates both hazards rather than describing them, and prints what it observes, so the day this is fixed that output changes and the example needs revisiting. Like examples/embed.c it is built and run by every build. Both README snippets were extracted and compiled as a check. Not fixed here: akbasic_runtime_global() would close it in about fifteen lines, but what it should do when the script is suspended inside a user function's scope is a design question worth settling deliberately rather than discovering. Filed with the proposed signature and the tests it wants. ctest 61/61; ASan+UBSan 61/61; no warnings under -Wall -Wextra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 06:50:55 -04:00
foreach(_example IN ITEMS embed hostvars)
add_executable(akbasic_example_${_example} examples/${_example}.c)
target_compile_options(akbasic_example_${_example} PRIVATE -Wall -Wextra)
target_link_libraries(akbasic_example_${_example} PRIVATE akbasic)
akbasic_instrument(akbasic_example_${_example})
_add_test(NAME example_${_example} COMMAND akbasic_example_${_example})
endforeach()
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
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})