From 9299e98a8ad496968abfc605c0dcdad91e624b4f Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sat, 4 Apr 2026 18:28:22 -0400 Subject: [PATCH 1/8] feat: add CI workflow, implement argument dereferencing, and introduce script output tests --- .github/workflows/ci.yml | 29 +++++++++++++++++++++ CMakeLists.txt | 19 +++++++++++++- examples/echoMultipleArgs.script | 4 +++ src/commands/echo.cpp | 6 +++-- src/commands/ret.cpp | 8 +++--- src/commands/var.cpp | 5 ++-- src/commands/wait.cpp | 16 +++++++++--- tests/RunScriptOutputTest.cmake | 39 +++++++++++++++++++++++++++++ tests/expected/echoMultipleArgs.txt | 1 + tests/expected/helloWorld.txt | 1 + tests/expected/nestedFor.txt | 10 ++++++++ 11 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 examples/echoMultipleArgs.script create mode 100644 tests/RunScriptOutputTest.cmake create mode 100644 tests/expected/echoMultipleArgs.txt create mode 100644 tests/expected/helloWorld.txt create mode 100644 tests/expected/nestedFor.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ad141f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: + - master + - arithmetic + - stack-ptr + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Install Ninja + run: sudo apt-get update && sudo apt-get install -y ninja-build + + - name: Configure + run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cf709a..411a659 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,4 +130,21 @@ add_executable(${PROJECT_NAME} ${TYPES_SOURCE_FILES} ) -define_file_basename_for_sources(${PROJECT_NAME}) \ No newline at end of file +define_file_basename_for_sources(${PROJECT_NAME}) + +enable_testing() + +function(add_script_output_test test_name script_file expected_file) + add_test( + NAME ${test_name} + COMMAND ${CMAKE_COMMAND} + -DSCRIPT_EXECUTABLE=$ + -DSCRIPT_FILE=${PROJECT_SOURCE_DIR}/${script_file} + -DEXPECTED_FILE=${PROJECT_SOURCE_DIR}/${expected_file} + -P ${PROJECT_SOURCE_DIR}/tests/RunScriptOutputTest.cmake + ) +endfunction() + +add_script_output_test(script_hello_world examples/helloWorld.script tests/expected/helloWorld.txt) +add_script_output_test(script_nested_for examples/nestedFor.script tests/expected/nestedFor.txt) +add_script_output_test(script_echo_multiple_args examples/echoMultipleArgs.script tests/expected/echoMultipleArgs.txt) diff --git a/examples/echoMultipleArgs.script b/examples/echoMultipleArgs.script new file mode 100644 index 0000000..b00d61e --- /dev/null +++ b/examples/echoMultipleArgs.script @@ -0,0 +1,4 @@ +var greeting Hello +var target World +echo $greeting, $target! +ret 0 diff --git a/src/commands/echo.cpp b/src/commands/echo.cpp index cff01f4..38ea08e 100644 --- a/src/commands/echo.cpp +++ b/src/commands/echo.cpp @@ -2,6 +2,8 @@ REGISTER_COMMAND { - std::cout << args; + auto output = args; + DEREFERENCE(output); + std::cout << output; std::cout.flush(); -}; \ No newline at end of file +}; diff --git a/src/commands/ret.cpp b/src/commands/ret.cpp index 43c1c91..46d764e 100644 --- a/src/commands/ret.cpp +++ b/src/commands/ret.cpp @@ -6,13 +6,15 @@ REGISTER_COMMAND { try { - size_t numArgs = utils::split(args).size(); - if (args.empty()) + auto expandedArgs = args; + DEREFERENCE(expandedArgs); + size_t numArgs = utils::split(expandedArgs).size(); + if (expandedArgs.empty()) throw std::invalid_argument("requires a single integer argument "); if (numArgs > 1) throw std::invalid_argument(std::format("Too many arguments. Expected 1 got {}", numArgs)); - const int value = std::stoi(args); + const int value = std::stoi(expandedArgs); if (!POP_STACK) std::exit(value); diff --git a/src/commands/var.cpp b/src/commands/var.cpp index ab8efaa..df5cd47 100644 --- a/src/commands/var.cpp +++ b/src/commands/var.cpp @@ -3,11 +3,12 @@ REGISTER_COMMAND { - const auto splitArgs = utils::splitQuoted(args); + auto splitArgs = utils::splitQuoted(args); if (splitArgs.size() != 2) { ERROR("requires arguments"); } + DEREFERENCE(splitArgs[1]); SET_VARIABLE(splitArgs[0], splitArgs[1]); -}; \ No newline at end of file +}; diff --git a/src/commands/wait.cpp b/src/commands/wait.cpp index 26fea2e..cbda569 100644 --- a/src/commands/wait.cpp +++ b/src/commands/wait.cpp @@ -1,4 +1,5 @@ #include +#include #include REGISTER_COMMAND @@ -6,14 +7,23 @@ REGISTER_COMMAND int value = 0; try { - if (args.size() != 1) + auto expandedArgs = args; + DEREFERENCE(expandedArgs); + if (expandedArgs.size() != 1) throw std::invalid_argument("Too many arguments"); - value = std::stoi(args); + value = std::stoi(expandedArgs); } catch (...) { ERROR("requires a single integer argument "); } + // Tests can disable real sleeps to keep script output checks fast. + if (const char* disableWait = std::getenv("SCRIPT_DISABLE_WAIT")) + { + if (disableWait[0] != '\0' && disableWait[0] != '0') + return; + } + std::this_thread::sleep_for(std::chrono::seconds(value)); -}; \ No newline at end of file +}; diff --git a/tests/RunScriptOutputTest.cmake b/tests/RunScriptOutputTest.cmake new file mode 100644 index 0000000..76e6035 --- /dev/null +++ b/tests/RunScriptOutputTest.cmake @@ -0,0 +1,39 @@ +if (NOT DEFINED SCRIPT_EXECUTABLE) + message(FATAL_ERROR "SCRIPT_EXECUTABLE is required") +endif() + +if (NOT DEFINED SCRIPT_FILE) + message(FATAL_ERROR "SCRIPT_FILE is required") +endif() + +if (NOT DEFINED EXPECTED_FILE) + message(FATAL_ERROR "EXPECTED_FILE is required") +endif() + +execute_process( + COMMAND "${CMAKE_COMMAND}" -E env "SCRIPT_DISABLE_WAIT=1" "${SCRIPT_EXECUTABLE}" "${SCRIPT_FILE}" + RESULT_VARIABLE script_result + OUTPUT_VARIABLE script_stdout + ERROR_VARIABLE script_stderr +) + +if (NOT script_result EQUAL 0) + message(FATAL_ERROR + "Script exited with ${script_result}\n" + "stderr:\n${script_stderr}\n" + "stdout:\n${script_stdout}") +endif() + +file(READ "${EXPECTED_FILE}" expected_stdout) + +string(REPLACE "\r\n" "\n" script_stdout "${script_stdout}") +string(REPLACE "\r\n" "\n" expected_stdout "${expected_stdout}") +string(REGEX REPLACE "\n$" "" script_stdout "${script_stdout}") +string(REGEX REPLACE "\n$" "" expected_stdout "${expected_stdout}") + +if (NOT script_stdout STREQUAL expected_stdout) + message(FATAL_ERROR + "Output mismatch for ${SCRIPT_FILE}\n" + "Expected:\n${expected_stdout}\n" + "Actual:\n${script_stdout}\n") +endif() diff --git a/tests/expected/echoMultipleArgs.txt b/tests/expected/echoMultipleArgs.txt new file mode 100644 index 0000000..8ab686e --- /dev/null +++ b/tests/expected/echoMultipleArgs.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/tests/expected/helloWorld.txt b/tests/expected/helloWorld.txt new file mode 100644 index 0000000..ce4ae4d --- /dev/null +++ b/tests/expected/helloWorld.txt @@ -0,0 +1 @@ +Hello...World! diff --git a/tests/expected/nestedFor.txt b/tests/expected/nestedFor.txt new file mode 100644 index 0000000..c24e925 --- /dev/null +++ b/tests/expected/nestedFor.txt @@ -0,0 +1,10 @@ +.......... +.......... +.......... +.......... +.......... +.......... +.......... +.......... +.......... +.......... From 102b9dd8debef1dd66d87f39860989106c710a53 Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sat, 4 Apr 2026 18:30:53 -0400 Subject: [PATCH 2/8] ci: publish build and test logs in PRs --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad141f5..5d0d724 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ on: jobs: build-and-test: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Check out repository @@ -23,7 +25,38 @@ jobs: run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug - name: Build - run: cmake --build build + shell: bash + run: | + set -o pipefail + cmake --build build 2>&1 | tee build.log - name: Test - run: ctest --test-dir build --output-on-failure + shell: bash + run: | + set -o pipefail + ctest --test-dir build --output-on-failure 2>&1 | tee test.log + + - name: Publish Test Summary + if: always() + shell: bash + run: | + { + echo "## Build Output" + echo '```text' + cat build.log + echo '```' + echo + echo "## Test Output" + echo '```text' + cat test.log + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload Logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: ci-logs + path: | + build.log + test.log From 878c59fefb52221c0cc2d9360e100fccb9be138b Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sat, 4 Apr 2026 18:34:47 -0400 Subject: [PATCH 3/8] ci: publish junit test results in PRs --- .github/workflows/ci.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d0d724..f8a0206 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + actions: read + checks: write steps: - name: Check out repository @@ -34,7 +36,19 @@ jobs: shell: bash run: | set -o pipefail - ctest --test-dir build --output-on-failure 2>&1 | tee test.log + ctest --test-dir build --output-on-failure --output-junit test-results.xml 2>&1 | tee test.log + + - name: Publish Test Report + if: ${{ !cancelled() }} + uses: dorny/test-reporter@v2 + with: + name: CTest Results + path: test-results.xml + reporter: java-junit + use-actions-summary: true + list-suites: all + list-tests: all + fail-on-empty: true - name: Publish Test Summary if: always() @@ -60,3 +74,4 @@ jobs: path: | build.log test.log + test-results.xml From 2d14b87b1a87f361b5de285e00019a1dc92f30ff Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sat, 4 Apr 2026 18:40:13 -0400 Subject: [PATCH 4/8] ci: fix junit report path --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8a0206..7a9fb88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: uses: dorny/test-reporter@v2 with: name: CTest Results - path: test-results.xml + path: build/test-results.xml reporter: java-junit use-actions-summary: true list-suites: all @@ -74,4 +74,4 @@ jobs: path: | build.log test.log - test-results.xml + build/test-results.xml From aa01554f134f6a2c8fb5359ff3c58919fd6e606b Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sat, 4 Apr 2026 18:44:46 -0400 Subject: [PATCH 5/8] ci: publish test results directly in PRs --- .github/workflows/ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a9fb88..3e6f300 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: contents: read actions: read checks: write + pull-requests: write steps: - name: Check out repository @@ -38,17 +39,16 @@ jobs: set -o pipefail ctest --test-dir build --output-on-failure --output-junit test-results.xml 2>&1 | tee test.log - - name: Publish Test Report + - name: Publish Test Results if: ${{ !cancelled() }} - uses: dorny/test-reporter@v2 + uses: EnricoMi/publish-unit-test-result-action@v2 with: - name: CTest Results - path: build/test-results.xml - reporter: java-junit - use-actions-summary: true - list-suites: all - list-tests: all - fail-on-empty: true + files: build/test-results.xml + check_name: Test Results + comment_title: Test Results + comment_mode: ${{ github.event_name == 'pull_request' && 'always' || 'off' }} + report_individual_runs: true + check_run_annotations: all tests, skipped tests - name: Publish Test Summary if: always() From 60343b8f8211892b3f0712609e9809f8e80fe5b5 Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sun, 5 Apr 2026 20:09:32 -0400 Subject: [PATCH 6/8] feat: add command documentation comments and implement `sum` command --- examples/nestedFor.script | 5 ++--- src/commands/command | 2 +- src/commands/comments.cpp | 13 +++++++++++++ src/commands/echo.cpp | 11 +++++++++++ src/commands/end_for.cpp | 11 +++++++++++ src/commands/endl.cpp | 12 +++++++++++- src/commands/for.cpp | 12 ++++++++++++ src/commands/ret.cpp | 11 +++++++++++ src/commands/scope.cpp | 11 +++++++++++ src/commands/sum.cpp | 33 +++++++++++++++++++++++++++++++++ src/commands/var.cpp | 11 +++++++++++ src/commands/wait.cpp | 11 +++++++++++ 12 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/commands/sum.cpp diff --git a/examples/nestedFor.script b/examples/nestedFor.script index 08c2581..b29bafe 100644 --- a/examples/nestedFor.script +++ b/examples/nestedFor.script @@ -7,9 +7,8 @@ var count 99 for i 0 10 inc for j 10 0 dec - echo . -# sum neg_j_minus_one -1 $j -# sum low 10 $neg_j_minus_one + sum neg_j_minus_one -1 $j + sum low 10 $neg_j_minus_one # mul high $i 10 # sum value $high $low # if_lte $value $count diff --git a/src/commands/command b/src/commands/command index 9875015..1c7a13e 100644 --- a/src/commands/command +++ b/src/commands/command @@ -87,4 +87,4 @@ #define REMOVE_METADATA(name) ScriptInstance::removeMetadata(name) // Writes a standard command error message with the command name and line number. -#define ERROR(msg) std::cerr << std::endl << "ERROR: '" << COMMAND << "' " << msg << " : " << SOURCE; std::exit(1); +#define ERROR(msg) {std::cerr << std::endl << "ERROR: '" << COMMAND << "' " << msg << " : " << SOURCE; std::exit(1);} diff --git a/src/commands/comments.cpp b/src/commands/comments.cpp index 9c9d15d..d6a67f4 100644 --- a/src/commands/comments.cpp +++ b/src/commands/comments.cpp @@ -1,6 +1,19 @@ #include #include +/* +NAME + comments - register comment tokens as no-op commands + +SYNOPSIS + # + // + /// + +DESCRIPTION + Treats comment-prefixed lines as valid commands that do nothing when + executed. +*/ namespace { diff --git a/src/commands/echo.cpp b/src/commands/echo.cpp index 38ea08e..929466c 100644 --- a/src/commands/echo.cpp +++ b/src/commands/echo.cpp @@ -1,5 +1,16 @@ #include +/* +NAME + echo - write text to standard output + +SYNOPSIS + echo + +DESCRIPTION + Expands variables in the provided text and writes the result without + appending a trailing newline. +*/ REGISTER_COMMAND { auto output = args; diff --git a/src/commands/end_for.cpp b/src/commands/end_for.cpp index 4b0340f..3ed8b7a 100644 --- a/src/commands/end_for.cpp +++ b/src/commands/end_for.cpp @@ -1,5 +1,16 @@ #include +/* +NAME + end_for - jump back to the matching for loop + +SYNOPSIS + end_for + +DESCRIPTION + Uses loop metadata recorded by for to continue iteration or fall through + when the loop is complete. +*/ REGISTER_COMMAND { try diff --git a/src/commands/endl.cpp b/src/commands/endl.cpp index 125cacf..c4b5f86 100644 --- a/src/commands/endl.cpp +++ b/src/commands/endl.cpp @@ -1,7 +1,17 @@ #include +/* +NAME + endl - write a newline to standard output + +SYNOPSIS + endl + +DESCRIPTION + Flushes a single line ending to standard output. +*/ REGISTER_COMMAND { std::cout << std::endl; std::cout.flush(); -}; \ No newline at end of file +}; diff --git a/src/commands/for.cpp b/src/commands/for.cpp index 8d9288e..29c64ca 100644 --- a/src/commands/for.cpp +++ b/src/commands/for.cpp @@ -1,6 +1,18 @@ #include #include +/* +NAME + for - execute a counted loop + +SYNOPSIS + for + +DESCRIPTION + Iterates from toward the exclusive bound using unary_func, + which must currently be inc or dec. The current value is stored in + for the body of the loop. +*/ REGISTER_COMMAND { auto splitArgs = utils::split(args); diff --git a/src/commands/ret.cpp b/src/commands/ret.cpp index 46d764e..95eb0d7 100644 --- a/src/commands/ret.cpp +++ b/src/commands/ret.cpp @@ -2,6 +2,17 @@ #include #include +/* +NAME + ret - return from the current script frame + +SYNOPSIS + ret + +DESCRIPTION + Returns control to the previous stack frame with the given integer status. + If there is no previous frame, the process exits with that status code. +*/ REGISTER_COMMAND { try diff --git a/src/commands/scope.cpp b/src/commands/scope.cpp index 6978493..ccd4204 100644 --- a/src/commands/scope.cpp +++ b/src/commands/scope.cpp @@ -1,6 +1,17 @@ #include #include +/* +NAME + scope - register brace tokens as no-op commands + +SYNOPSIS + { + } + +DESCRIPTION + Treats brace-only lines as valid commands that do nothing when executed. +*/ namespace { diff --git a/src/commands/sum.cpp b/src/commands/sum.cpp new file mode 100644 index 0000000..a6c8943 --- /dev/null +++ b/src/commands/sum.cpp @@ -0,0 +1,33 @@ +#include + +/* +NAME + sum - add two integers and store the result + +SYNOPSIS + sum + +DESCRIPTION + Expands variables in the numeric inputs, adds the two integer values, and + stores the result in . +*/ +REGISTER_COMMAND +{ + auto vars = args; + DEREFERENCE(vars); + const auto& splitVars = utils::split(vars); + + if (splitVars.size() != 3) + ERROR("requires 3 arguments "); + + try + { + const int a = std::stoi(splitVars[1].c_str()); + const int b = std::stoi(splitVars[2].c_str()); + SET_VARIABLE(splitVars[0], std::to_string(a + b)); + } + catch (std::exception& e) + { + ERROR("Input values must be integers"); + } +}; diff --git a/src/commands/var.cpp b/src/commands/var.cpp index df5cd47..288e938 100644 --- a/src/commands/var.cpp +++ b/src/commands/var.cpp @@ -1,6 +1,17 @@ #include #include +/* +NAME + var - define or overwrite a variable + +SYNOPSIS + var + +DESCRIPTION + Stores in . The value argument is quote-aware and variables + inside the value are expanded before assignment. +*/ REGISTER_COMMAND { auto splitArgs = utils::splitQuoted(args); diff --git a/src/commands/wait.cpp b/src/commands/wait.cpp index cbda569..1fd36b4 100644 --- a/src/commands/wait.cpp +++ b/src/commands/wait.cpp @@ -2,6 +2,17 @@ #include #include +/* +NAME + wait - pause execution for a number of seconds + +SYNOPSIS + wait + +DESCRIPTION + Expands the argument, parses it as an integer second count, and sleeps for + that duration unless test mode disables real waits. +*/ REGISTER_COMMAND { int value = 0; From b17adc0c84db59536c9996155bb1eeeeabb81f11 Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sun, 5 Apr 2026 20:27:46 -0400 Subject: [PATCH 7/8] feat: implement `mul` command, update tests, and enhance script handling automation --- CMakeLists.txt | 21 +++++++++++++----- examples/nestedFor.script | 16 ++++++++------ src/commands/mul.cpp | 33 +++++++++++++++++++++++++++++ tests/expected/comments.txt | 1 + tests/expected/comments_scope.txt | 1 + tests/expected/echo.txt | 1 + tests/expected/echoMultipleArgs.txt | 1 - tests/expected/endl.txt | 2 ++ tests/expected/for_dec.txt | 1 + tests/expected/for_inc.txt | 1 + tests/expected/helloWorld.txt | 1 - tests/expected/mul.txt | 1 + tests/expected/nestedFor.txt | 10 --------- tests/expected/ret.txt | 1 + tests/expected/sum.txt | 1 + tests/expected/var.txt | 1 + tests/expected/wait.txt | 1 + tests/scripts/comments.script | 4 ++++ tests/scripts/comments_scope.script | 3 +++ tests/scripts/echo.script | 1 + tests/scripts/endl.script | 3 +++ tests/scripts/for_dec.script | 3 +++ tests/scripts/for_inc.script | 3 +++ tests/scripts/mul.script | 2 ++ tests/scripts/ret.script | 3 +++ tests/scripts/sum.script | 2 ++ tests/scripts/var.script | 2 ++ tests/scripts/wait.script | 2 ++ 28 files changed, 98 insertions(+), 24 deletions(-) create mode 100644 src/commands/mul.cpp create mode 100644 tests/expected/comments.txt create mode 100644 tests/expected/comments_scope.txt create mode 100644 tests/expected/echo.txt delete mode 100644 tests/expected/echoMultipleArgs.txt create mode 100644 tests/expected/endl.txt create mode 100644 tests/expected/for_dec.txt create mode 100644 tests/expected/for_inc.txt delete mode 100644 tests/expected/helloWorld.txt create mode 100644 tests/expected/mul.txt delete mode 100644 tests/expected/nestedFor.txt create mode 100644 tests/expected/ret.txt create mode 100644 tests/expected/sum.txt create mode 100644 tests/expected/var.txt create mode 100644 tests/expected/wait.txt create mode 100644 tests/scripts/comments.script create mode 100644 tests/scripts/comments_scope.script create mode 100644 tests/scripts/echo.script create mode 100644 tests/scripts/endl.script create mode 100644 tests/scripts/for_dec.script create mode 100644 tests/scripts/for_inc.script create mode 100644 tests/scripts/mul.script create mode 100644 tests/scripts/ret.script create mode 100644 tests/scripts/sum.script create mode 100644 tests/scripts/var.script create mode 100644 tests/scripts/wait.script diff --git a/CMakeLists.txt b/CMakeLists.txt index 411a659..5871217 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,12 +139,23 @@ function(add_script_output_test test_name script_file expected_file) NAME ${test_name} COMMAND ${CMAKE_COMMAND} -DSCRIPT_EXECUTABLE=$ - -DSCRIPT_FILE=${PROJECT_SOURCE_DIR}/${script_file} - -DEXPECTED_FILE=${PROJECT_SOURCE_DIR}/${expected_file} + -DSCRIPT_FILE=${script_file} + -DEXPECTED_FILE=${expected_file} -P ${PROJECT_SOURCE_DIR}/tests/RunScriptOutputTest.cmake ) endfunction() -add_script_output_test(script_hello_world examples/helloWorld.script tests/expected/helloWorld.txt) -add_script_output_test(script_nested_for examples/nestedFor.script tests/expected/nestedFor.txt) -add_script_output_test(script_echo_multiple_args examples/echoMultipleArgs.script tests/expected/echoMultipleArgs.txt) +file(GLOB TEST_SCRIPT_FILES CONFIGURE_DEPENDS + "${PROJECT_SOURCE_DIR}/tests/scripts/*.script" +) + +foreach(test_script ${TEST_SCRIPT_FILES}) + get_filename_component(test_name "${test_script}" NAME_WE) + set(expected_file "${PROJECT_SOURCE_DIR}/tests/expected/${test_name}.txt") + + if (NOT EXISTS "${expected_file}") + message(FATAL_ERROR "Missing expected output for ${test_script}: ${expected_file}") + endif() + + add_script_output_test("script_${test_name}" "${test_script}" "${expected_file}") +endforeach() diff --git a/examples/nestedFor.script b/examples/nestedFor.script index b29bafe..10a8074 100644 --- a/examples/nestedFor.script +++ b/examples/nestedFor.script @@ -7,13 +7,15 @@ var count 99 for i 0 10 inc for j 10 0 dec - sum neg_j_minus_one -1 $j - sum low 10 $neg_j_minus_one -# mul high $i 10 -# sum value $high $low -# if_lte $value $count -# echo $value -# end_if + mul neg_j -1 $j + sum low 10 $neg_j + mul high $i 10 + sum value $high $low + if_lte $value $count + echo $value + else + ret 0 + end_if end_for endl end_for diff --git a/src/commands/mul.cpp b/src/commands/mul.cpp new file mode 100644 index 0000000..574f2f2 --- /dev/null +++ b/src/commands/mul.cpp @@ -0,0 +1,33 @@ +#include + +/* +NAME + mul - multiply two integers and store the result + +SYNOPSIS + mul + +DESCRIPTION + Expands variables in the numeric inputs, multiplies the two integer values, + and stores the result in . +*/ +REGISTER_COMMAND +{ + auto vars = args; + DEREFERENCE(vars); + const auto& splitVars = utils::split(vars); + + if (splitVars.size() != 3) + ERROR("requires 3 arguments "); + + try + { + const int a = std::stoi(splitVars[1].c_str()); + const int b = std::stoi(splitVars[2].c_str()); + SET_VARIABLE(splitVars[0], std::to_string(a * b)); + } + catch (std::exception& e) + { + ERROR("Input values must be integers"); + } +}; diff --git a/tests/expected/comments.txt b/tests/expected/comments.txt new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/tests/expected/comments.txt @@ -0,0 +1 @@ +ok diff --git a/tests/expected/comments_scope.txt b/tests/expected/comments_scope.txt new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/tests/expected/comments_scope.txt @@ -0,0 +1 @@ +ok diff --git a/tests/expected/echo.txt b/tests/expected/echo.txt new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/tests/expected/echo.txt @@ -0,0 +1 @@ +hello diff --git a/tests/expected/echoMultipleArgs.txt b/tests/expected/echoMultipleArgs.txt deleted file mode 100644 index 8ab686e..0000000 --- a/tests/expected/echoMultipleArgs.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, World! diff --git a/tests/expected/endl.txt b/tests/expected/endl.txt new file mode 100644 index 0000000..94954ab --- /dev/null +++ b/tests/expected/endl.txt @@ -0,0 +1,2 @@ +hello +world diff --git a/tests/expected/for_dec.txt b/tests/expected/for_dec.txt new file mode 100644 index 0000000..3ae0b93 --- /dev/null +++ b/tests/expected/for_dec.txt @@ -0,0 +1 @@ +321 diff --git a/tests/expected/for_inc.txt b/tests/expected/for_inc.txt new file mode 100644 index 0000000..de97a6d --- /dev/null +++ b/tests/expected/for_inc.txt @@ -0,0 +1 @@ +012 diff --git a/tests/expected/helloWorld.txt b/tests/expected/helloWorld.txt deleted file mode 100644 index ce4ae4d..0000000 --- a/tests/expected/helloWorld.txt +++ /dev/null @@ -1 +0,0 @@ -Hello...World! diff --git a/tests/expected/mul.txt b/tests/expected/mul.txt new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/tests/expected/mul.txt @@ -0,0 +1 @@ +42 diff --git a/tests/expected/nestedFor.txt b/tests/expected/nestedFor.txt deleted file mode 100644 index c24e925..0000000 --- a/tests/expected/nestedFor.txt +++ /dev/null @@ -1,10 +0,0 @@ -.......... -.......... -.......... -.......... -.......... -.......... -.......... -.......... -.......... -.......... diff --git a/tests/expected/ret.txt b/tests/expected/ret.txt new file mode 100644 index 0000000..90be1f3 --- /dev/null +++ b/tests/expected/ret.txt @@ -0,0 +1 @@ +before diff --git a/tests/expected/sum.txt b/tests/expected/sum.txt new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/tests/expected/sum.txt @@ -0,0 +1 @@ +42 diff --git a/tests/expected/var.txt b/tests/expected/var.txt new file mode 100644 index 0000000..557db03 --- /dev/null +++ b/tests/expected/var.txt @@ -0,0 +1 @@ +Hello World diff --git a/tests/expected/wait.txt b/tests/expected/wait.txt new file mode 100644 index 0000000..19f86f4 --- /dev/null +++ b/tests/expected/wait.txt @@ -0,0 +1 @@ +done diff --git a/tests/scripts/comments.script b/tests/scripts/comments.script new file mode 100644 index 0000000..0a0585f --- /dev/null +++ b/tests/scripts/comments.script @@ -0,0 +1,4 @@ +# this should be ignored +// this too +/// and this +echo ok diff --git a/tests/scripts/comments_scope.script b/tests/scripts/comments_scope.script new file mode 100644 index 0000000..b252821 --- /dev/null +++ b/tests/scripts/comments_scope.script @@ -0,0 +1,3 @@ +{ +} +echo ok diff --git a/tests/scripts/echo.script b/tests/scripts/echo.script new file mode 100644 index 0000000..2f08be9 --- /dev/null +++ b/tests/scripts/echo.script @@ -0,0 +1 @@ +echo hello diff --git a/tests/scripts/endl.script b/tests/scripts/endl.script new file mode 100644 index 0000000..de67f7f --- /dev/null +++ b/tests/scripts/endl.script @@ -0,0 +1,3 @@ +echo hello +endl +echo world diff --git a/tests/scripts/for_dec.script b/tests/scripts/for_dec.script new file mode 100644 index 0000000..ce6e35b --- /dev/null +++ b/tests/scripts/for_dec.script @@ -0,0 +1,3 @@ +for i 3 0 dec + echo $i +end_for diff --git a/tests/scripts/for_inc.script b/tests/scripts/for_inc.script new file mode 100644 index 0000000..382f728 --- /dev/null +++ b/tests/scripts/for_inc.script @@ -0,0 +1,3 @@ +for i 0 3 inc + echo $i +end_for diff --git a/tests/scripts/mul.script b/tests/scripts/mul.script new file mode 100644 index 0000000..b688115 --- /dev/null +++ b/tests/scripts/mul.script @@ -0,0 +1,2 @@ +mul product 6 7 +echo $product diff --git a/tests/scripts/ret.script b/tests/scripts/ret.script new file mode 100644 index 0000000..5e4c8e5 --- /dev/null +++ b/tests/scripts/ret.script @@ -0,0 +1,3 @@ +echo before +ret 0 +echo after diff --git a/tests/scripts/sum.script b/tests/scripts/sum.script new file mode 100644 index 0000000..ac54a20 --- /dev/null +++ b/tests/scripts/sum.script @@ -0,0 +1,2 @@ +sum total 19 23 +echo $total diff --git a/tests/scripts/var.script b/tests/scripts/var.script new file mode 100644 index 0000000..4031eac --- /dev/null +++ b/tests/scripts/var.script @@ -0,0 +1,2 @@ +var greeting "Hello World" +echo $greeting diff --git a/tests/scripts/wait.script b/tests/scripts/wait.script new file mode 100644 index 0000000..a7b16b0 --- /dev/null +++ b/tests/scripts/wait.script @@ -0,0 +1,2 @@ +wait 1 +echo done From dd1160bef9f0d80499c019cb54f1f3288f9d8a9f Mon Sep 17 00:00:00 2001 From: Nic Holthaus Date: Sun, 5 Apr 2026 20:34:39 -0400 Subject: [PATCH 8/8] feat: add expected output tests for example scripts and automate example script testing in CMake --- CMakeLists.txt | 13 +++++++++++++ tests/expected/examples/echoMultipleArgs.txt | 1 + tests/expected/examples/helloWorld.txt | 1 + 3 files changed, 15 insertions(+) create mode 100644 tests/expected/examples/echoMultipleArgs.txt create mode 100644 tests/expected/examples/helloWorld.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 5871217..45a4afb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,3 +159,16 @@ foreach(test_script ${TEST_SCRIPT_FILES}) add_script_output_test("script_${test_name}" "${test_script}" "${expected_file}") endforeach() + +file(GLOB EXAMPLE_SCRIPT_FILES CONFIGURE_DEPENDS + "${PROJECT_SOURCE_DIR}/examples/*.script" +) + +foreach(example_script ${EXAMPLE_SCRIPT_FILES}) + get_filename_component(example_name "${example_script}" NAME_WE) + set(example_expected_file "${PROJECT_SOURCE_DIR}/tests/expected/examples/${example_name}.txt") + + if (EXISTS "${example_expected_file}") + add_script_output_test("example_${example_name}" "${example_script}" "${example_expected_file}") + endif() +endforeach() diff --git a/tests/expected/examples/echoMultipleArgs.txt b/tests/expected/examples/echoMultipleArgs.txt new file mode 100644 index 0000000..8ab686e --- /dev/null +++ b/tests/expected/examples/echoMultipleArgs.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/tests/expected/examples/helloWorld.txt b/tests/expected/examples/helloWorld.txt new file mode 100644 index 0000000..ce4ae4d --- /dev/null +++ b/tests/expected/examples/helloWorld.txt @@ -0,0 +1 @@ +Hello...World!