Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: CI

on:
push:
branches:
- master
- arithmetic
- stack-ptr
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
checks: write
pull-requests: write

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
shell: bash
run: |
set -o pipefail
cmake --build build 2>&1 | tee build.log

- name: Test
shell: bash
run: |
set -o pipefail
ctest --test-dir build --output-on-failure --output-junit test-results.xml 2>&1 | tee test.log

- name: Publish Test Results
if: ${{ !cancelled() }}
uses: EnricoMi/publish-unit-test-result-action@v2
with:
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()
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
build/test-results.xml
43 changes: 42 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,45 @@ add_executable(${PROJECT_NAME}
${TYPES_SOURCE_FILES}
)

define_file_basename_for_sources(${PROJECT_NAME})
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=$<TARGET_FILE:${PROJECT_NAME}>
-DSCRIPT_FILE=${script_file}
-DEXPECTED_FILE=${expected_file}
-P ${PROJECT_SOURCE_DIR}/tests/RunScriptOutputTest.cmake
)
endfunction()

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()

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()
4 changes: 4 additions & 0 deletions examples/echoMultipleArgs.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var greeting Hello
var target World
echo $greeting, $target!
ret 0
17 changes: 9 additions & 8 deletions examples/nestedFor.script
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ 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
# 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
2 changes: 1 addition & 1 deletion src/commands/command
Original file line number Diff line number Diff line change
Expand Up @@ -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);}
13 changes: 13 additions & 0 deletions src/commands/comments.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
#include <command>
#include <no_op.h>

/*
NAME
comments - register comment tokens as no-op commands

SYNOPSIS
# <text>
// <text>
/// <text>

DESCRIPTION
Treats comment-prefixed lines as valid commands that do nothing when
executed.
*/

namespace
{
Expand Down
17 changes: 15 additions & 2 deletions src/commands/echo.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
#include <command>

/*
NAME
echo - write text to standard output

SYNOPSIS
echo <text>

DESCRIPTION
Expands variables in the provided text and writes the result without
appending a trailing newline.
*/
REGISTER_COMMAND
{
std::cout << args;
auto output = args;
DEREFERENCE(output);
std::cout << output;
std::cout.flush();
};
};
11 changes: 11 additions & 0 deletions src/commands/end_for.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
#include <command>

/*
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
Expand Down
12 changes: 11 additions & 1 deletion src/commands/endl.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
#include <command>

/*
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();
};
};
12 changes: 12 additions & 0 deletions src/commands/for.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
#include <command>
#include <findMatch.h>

/*
NAME
for - execute a counted loop

SYNOPSIS
for <loop_var> <start> <end> <unary_func>

DESCRIPTION
Iterates from <start> toward the exclusive <end> bound using unary_func,
which must currently be inc or dec. The current value is stored in
<loop_var> for the body of the loop.
*/
REGISTER_COMMAND
{
auto splitArgs = utils::split(args);
Expand Down
33 changes: 33 additions & 0 deletions src/commands/mul.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <command>

/*
NAME
mul - multiply two integers and store the result

SYNOPSIS
mul <output> <a> <b>

DESCRIPTION
Expands variables in the numeric inputs, multiplies the two integer values,
and stores the result in <output>.
*/
REGISTER_COMMAND
{
auto vars = args;
DEREFERENCE(vars);
const auto& splitVars = utils::split(vars);

if (splitVars.size() != 3)
ERROR("requires 3 arguments <output> <a> <b>");

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");
}
};
19 changes: 16 additions & 3 deletions src/commands/ret.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@
#include <format>
#include <split.h>

/*
NAME
ret - return from the current script frame

SYNOPSIS
ret <value>

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
{
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 <value>");
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);
Expand Down
11 changes: 11 additions & 0 deletions src/commands/scope.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
#include <command>
#include <no_op.h>

/*
NAME
scope - register brace tokens as no-op commands

SYNOPSIS
{
}

DESCRIPTION
Treats brace-only lines as valid commands that do nothing when executed.
*/

namespace
{
Expand Down
33 changes: 33 additions & 0 deletions src/commands/sum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <command>

/*
NAME
sum - add two integers and store the result

SYNOPSIS
sum <output> <a> <b>

DESCRIPTION
Expands variables in the numeric inputs, adds the two integer values, and
stores the result in <output>.
*/
REGISTER_COMMAND
{
auto vars = args;
DEREFERENCE(vars);
const auto& splitVars = utils::split(vars);

if (splitVars.size() != 3)
ERROR("requires 3 arguments <output> <a> <b>");

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");
}
};
Loading
Loading