From 8239a772720ccd51ae177b331ed1729219114dea Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 6 Dec 2023 17:29:45 +0100 Subject: [PATCH 01/29] Bump workflows with deprecation warning --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7865218..3b565fd2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: submodules: true # Download inklecate - - uses: suisei-cn/actions-download-file@v1 + - uses: suisei-cn/actions-download-file@v1.4.0 name: Download Inklecate id: download_inklecate with: @@ -62,7 +62,7 @@ jobs: # Setup CMake - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.9 + uses: jwlawson/actions-setup-cmake@v1.14.2 with: cmake-version: '3.21.x' From 9c862cc4bda833a4997c57ade8e5da17020840a8 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Tue, 12 Dec 2023 16:40:51 +0100 Subject: [PATCH 02/29] Add new test based on https://github.com/brwarner/inkcpp/issues/63 --- inkcpp_test/CMakeLists.txt | 1 + inkcpp_test/SpaceAfterBracketChoice.cpp | 48 +++++++++++++++++++++++++ inkcpp_test/ink/ChoiceBracketStory.ink | 7 ++++ 3 files changed, 56 insertions(+) create mode 100644 inkcpp_test/SpaceAfterBracketChoice.cpp create mode 100644 inkcpp_test/ink/ChoiceBracketStory.ink diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 7ffc8aff..4f5166d5 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(inkcpp_test catch.hpp Main.cpp LabelCondition.cpp Observer.cpp InkyJson.cpp + SpaceAfterBracketChoice.cpp ) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) diff --git a/inkcpp_test/SpaceAfterBracketChoice.cpp b/inkcpp_test/SpaceAfterBracketChoice.cpp new file mode 100644 index 00000000..9ee8715e --- /dev/null +++ b/inkcpp_test/SpaceAfterBracketChoice.cpp @@ -0,0 +1,48 @@ +#include "catch.hpp" +#include "../inkcpp_cl/test.h" + +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("a story with bracketed choices and spaces can choose correctly", "[choices]") +{ + GIVEN("a story with line breaks") + { + inklecate("ink/ChoiceBracketStory.ink", "ChoiceBracketStory.tmp"); + ink::compiler::run("ChoiceBracketStory.tmp", "ChoiceBracketStory.bin"); + auto ink = story::from_file("ChoiceBracketStory.bin"); + runner thread = ink->new_runner(); + thread->getall(); + WHEN("start thread") + { + THEN("thread has choices") + { + thread->getall(); + REQUIRE(thread->has_choices()); + } + WHEN("choose choice 1") + { + thread->choose(0); + thread->getall(); + THEN("still has choices") + { + thread->getall(); + REQUIRE(thread->has_choices()); + } + } + WHEN("choose choice 2") + { + thread->choose(1); + thread->getall(); + THEN("still has choices") + { + REQUIRE(thread->has_choices()); + } + } + } + } +} diff --git a/inkcpp_test/ink/ChoiceBracketStory.ink b/inkcpp_test/ink/ChoiceBracketStory.ink new file mode 100644 index 00000000..6658b24c --- /dev/null +++ b/inkcpp_test/ink/ChoiceBracketStory.ink @@ -0,0 +1,7 @@ +-> choices += choices + ++ [Choice 1] ++ [Choice 2] + +- -> choices \ No newline at end of file From e5d1c973051ae240dc82bb568e3501736885b196 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 10:32:10 +0100 Subject: [PATCH 03/29] Add check if end after begin for removing spaces --- inkcpp/output.cpp | 8 ++++++-- inkcpp/runner_impl.cpp | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 0c13452e..392ec3d3 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -140,8 +140,12 @@ namespace ink::runtime::internal std::string result = str.str(); if ( !result.empty() ) { auto end = clean_string( result.begin(), result.end() ); - _last_char = *( end - 1 ); - result.resize( end - result.begin() - ( _last_char == ' ' ? 1 : 0 ) ); + if (result.begin() == end) { + result.resize(0); + } else { + _last_char = *( end - 1 ); + result.resize( end - result.begin() - ( _last_char == ' ' ? 1 : 0 ) ); + } } return result; } diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 7ae1a136..5f4d829c 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -185,7 +185,7 @@ namespace ink::runtime::internal const uint32_t* iter = nullptr; container_t id; - ip_t offset; + ip_t offset = nullptr; size_t comm_end; bool reversed = _ptr > dest; From 810261d29afd0360c9e890be01c928b513eff94d Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 15:22:56 +0100 Subject: [PATCH 04/29] Clear data from managed_array on decstruction --- inkcpp/array.h | 6 ++++++ inkcpp/story_ptr.cpp | 2 +- inkcpp_cl/inkcpp_cl.cpp | 5 ++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index 5c3ac652..42a6a40f 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -20,6 +20,12 @@ namespace ink::runtime::internal { } } + ~managed_array() { + if constexpr (dynamic) { + delete[] _dynamic_data; + } + } + const T& operator[]( size_t i ) const { return data()[i]; } T& operator[]( size_t i ) { return data()[i]; } const T* data() const diff --git a/inkcpp/story_ptr.cpp b/inkcpp/story_ptr.cpp index 99f9b6dd..d425bacc 100644 --- a/inkcpp/story_ptr.cpp +++ b/inkcpp/story_ptr.cpp @@ -66,4 +66,4 @@ namespace ink::runtime::internal _instance_block = _story_block = nullptr; return is_destroyed; } -} \ No newline at end of file +} diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index f5d55f4b..1e800450 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -155,7 +155,7 @@ int main(int argc, const char** argv) using namespace ink::runtime; // Load story - story* myInk = story::from_file(outputFilename.c_str()); + std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; // Start runner runner thread; @@ -213,12 +213,11 @@ int main(int argc, const char** argv) // out of content break; } - - return 0; } catch (const std::exception& e) { std::cerr << "Unhandled ink runtime exception: " << e.what() << std::endl; return 1; } + return 0; } From 52cf7b9a4ea5fd9b91cf6d8ca6c8ebc3a5634332 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 15:50:42 +0100 Subject: [PATCH 05/29] Bump inkproof to master --- .github/workflows/build.yml | 2 +- proofing/ink-proof | 2 +- proofing/inkcpp_runtime_driver | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b565fd2..f8a469b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -144,7 +144,7 @@ jobs: shell: bash working-directory: proofing/ink-proof run: | - python3 proof.py --examples 'I...' inkcpp inklecate_v0.9.0 > run.out + python3 proof.py --examples 'I...' inklecate_v1.1.1 inkcpp_runtime > run.out echo -e "| ${{ matrix.name }} | $(cat run.out) |" > ${{ matrix.artifact }}.txt # Creates a "disabled" artifact if ink proofing is disabled diff --git a/proofing/ink-proof b/proofing/ink-proof index efdfb70e..6f22c543 160000 --- a/proofing/ink-proof +++ b/proofing/ink-proof @@ -1 +1 @@ -Subproject commit efdfb70e81350d94375e221b457197be140dc968 +Subproject commit 6f22c5430f98e76cf29f1d92d8e8b718207e407e diff --git a/proofing/inkcpp_runtime_driver b/proofing/inkcpp_runtime_driver index 5db41602..25022f95 100644 --- a/proofing/inkcpp_runtime_driver +++ b/proofing/inkcpp_runtime_driver @@ -6,7 +6,7 @@ import shutil ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PATH = os.path.join(ROOT, 'deps', 'inkcpp', 'inkcpp_cl') -ARGS = ["inkcpp_cl", "-p"] + sys.argv[1:] +ARGS = ["inkcpp_cl", "--ommit-choice-tags", "-p"] + sys.argv[1:] os.execv(PATH, ARGS) sleep(2) From 4c997768d53b735fd2a98091158557074b198df0 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 16:02:38 +0100 Subject: [PATCH 06/29] Fix pull request report condition --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8a469b3..bdab9d2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -172,7 +172,7 @@ jobs: reporting: name: "Pull Request Report" - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }} runs-on: ubuntu-latest needs: compilation steps: From d1dbb2ec18eec33933e1f346f91b13930c7d383b Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 16:17:40 +0100 Subject: [PATCH 07/29] Debug output for badge --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdab9d2d..68866b30 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -172,11 +172,14 @@ jobs: reporting: name: "Pull Request Report" - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }} + # if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }} runs-on: ubuntu-latest needs: compilation steps: # Download Ink Proof Results + - name: Debug + run: | + echo ${{github.event_name}} ${{github.event.pull_request.head.repo.full_name}} ${{github.event.pull_request.head.ref}} ${{github.event.pull_request.base.full_name}} ${{github.event.pull_request.base.ref}} - uses: actions/download-artifact@v2 with: name: results From 57476a855a51ebdd7f19f468440d062a5a9b34ba Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 16:33:00 +0100 Subject: [PATCH 08/29] Add permissions --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68866b30..6bf89e4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -173,8 +173,11 @@ jobs: reporting: name: "Pull Request Report" # if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }} + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} runs-on: ubuntu-latest needs: compilation + permissions: + pull-request: write steps: # Download Ink Proof Results - name: Debug @@ -203,7 +206,7 @@ jobs: done # Post Comment - - uses: marocchino/sticky-pull-request-comment@v2 + - uses: marocchino/sticky-pull-request-comment@v2.8 with: recreate: true path: comment.txt From 25940410f98e5ca7c75a74da78eda746d3d9e188 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 16:34:42 +0100 Subject: [PATCH 09/29] fix typo --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bf89e4e..3131c0f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -177,7 +177,7 @@ jobs: runs-on: ubuntu-latest needs: compilation permissions: - pull-request: write + pull-requests: write steps: # Download Ink Proof Results - name: Debug From 7b99da25b14efe78b4a1e9ac503cd6020bca139b Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Wed, 13 Dec 2023 16:39:32 +0100 Subject: [PATCH 10/29] extend tag label --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3131c0f5..9995ca40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,7 +206,7 @@ jobs: done # Post Comment - - uses: marocchino/sticky-pull-request-comment@v2.8 + - uses: marocchino/sticky-pull-request-comment@v2.8.0 with: recreate: true path: comment.txt From 030c5a7a575d7a721f6a5717ebfdea735899f4b5 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Thu, 14 Dec 2023 14:01:43 +0100 Subject: [PATCH 11/29] Disable fall through shortcut in jump() shortcut has corrupted container stack --- inkcpp/runner_impl.cpp | 9 +++-- inkcpp/story_impl.cpp | 4 -- inkcpp_test/CMakeLists.txt | 1 + inkcpp_test/ThirdTierChoiceAfterBrackets.cpp | 38 +++++++++++++++++++ .../ink/ThirdTierChoiceAfterBracketsStory.ink | 13 +++++++ 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 inkcpp_test/ThirdTierChoiceAfterBrackets.cpp create mode 100644 inkcpp_test/ink/ThirdTierChoiceAfterBracketsStory.ink diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 5f4d829c..3e7c3dee 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -177,7 +177,7 @@ namespace ink::runtime::internal // without entering any other containers // OR IF if target is same position do nothing // could happend if jumping to and of an unnamed container - if (_is_falling || dest ==_ptr) + if (dest ==_ptr) { _ptr = dest; return; @@ -1173,10 +1173,11 @@ namespace ink::runtime::internal case Command::END_CONTAINER_MARKER: { container_t index = read(); - inkAssert(_container.top().id == index, "Leaving container we are not in!"); - // Move up out of the current container - _container.pop(); + inkAssert(_container.top().id == index, "Leaving container we are not in!"); + + // Move up out of the current container + _container.pop(); // SPECIAL: If we've popped all containers, then there's an implied 'done' command or return if (_container.empty()) diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index 223d1407..64ee33b6 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -7,10 +7,6 @@ #include "snapshot_interface.h" #include "version.h" -#ifdef INK_ENABLE_STL -#include -#endif - namespace ink::runtime { #ifdef INK_ENABLE_STL diff --git a/inkcpp_test/CMakeLists.txt b/inkcpp_test/CMakeLists.txt index 4f5166d5..40efe207 100644 --- a/inkcpp_test/CMakeLists.txt +++ b/inkcpp_test/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(inkcpp_test catch.hpp Main.cpp Observer.cpp InkyJson.cpp SpaceAfterBracketChoice.cpp + ThirdTierChoiceAfterBrackets.cpp ) target_link_libraries(inkcpp_test PUBLIC inkcpp inkcpp_compiler inkcpp_shared) diff --git a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp new file mode 100644 index 00000000..6aa5fe20 --- /dev/null +++ b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp @@ -0,0 +1,38 @@ +#include "catch.hpp" +#include "../inkcpp_cl/test.h" + +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("a story with a bracketed choice as a second choice, and then a third choice, chooses properly", "[choices]") +{ + GIVEN("a story with brackets and nested choices") + { + inklecate("ink/ThirdTierChoiceAfterBracketsStory.ink", "ThirdTierChoiceAfterBracketsStory.tmp"); + ink::compiler::run("ThirdTierChoiceAfterBracketsStory.tmp", "ThirdTierChoiceAfterBracketsStory.bin"); + auto ink = story::from_file("ThirdTierChoiceAfterBracketsStory.bin"); + runner thread = ink->new_runner(); + + WHEN("start thread") + { + THEN("thread doesn't error") + { + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + } + } + } +} diff --git a/inkcpp_test/ink/ThirdTierChoiceAfterBracketsStory.ink b/inkcpp_test/ink/ThirdTierChoiceAfterBracketsStory.ink new file mode 100644 index 00000000..72e67814 --- /dev/null +++ b/inkcpp_test/ink/ThirdTierChoiceAfterBracketsStory.ink @@ -0,0 +1,13 @@ +-> content +== content + +* Some choice + blah blah + blah blah blah +* * [This is the killer!] Text can be here though, just not on the left. + Blah blah + Something something +* * * Encountering this choice causes an error. +- Weave... + +-> DONE From e2c28f0ce48da62283087e87b822276ef720a066 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Thu, 14 Dec 2023 14:42:09 +0100 Subject: [PATCH 12/29] Update Readme references --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c75dd990..2304c1b2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # inkcpp -![build](https://github.com/brwarner/inkcpp/workflows/build/badge.svg "Build Status") +![build](https://github.com/JBenda/inkcpp/workflows/build/badge.svg "Build Status") Inkle Ink C++ Runtime with JSON -> Binary Compiler. -Ink Proofing Test Results: https://brwarner.github.io/inkcpp +Ink Proofing Test Results: https://jbenda.github.io/inkcpp ## Project Goals * Fast, simple, clean syntax From 69765f1f28ce14ab4910b4ce619062611e6dcb32 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 08:41:22 +0100 Subject: [PATCH 13/29] add clang format --- .clang-format | 118 ++++++++++++++++++++++++++++++++++++ .github/workflows/build.yml | 19 +++++- 2 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..f553bd14 --- /dev/null +++ b/.clang-format @@ -0,0 +1,118 @@ +--- +AccessModifierOffset: -2 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: Right +AlignConsecutiveAssignments: + Enabled: true + AcrossComments: true + AcrossEmptyLines: true + AlignCompound: true + PadOperators: true +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCompound: true + PadOperators: true +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCompound: true + PadOperators: true +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCompound: true + PadOperators: true +AlignEscapedNewlines: Left +AlignOperands: AlignAfterOperator +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: {} +BreakBeforeBraces: Linux +BreakAfterAttributes: Always +BreakBeforeBinaryOperators: All +UseTab: ForIndentation +Standard: Latest +SpacesInSquareBrackets: false +SpacesInParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: 1 +SpacesInContainerLiterals: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: true +SpacesInAngles: Never +SpacesBeforeTrailingComments: 1 +SpaceInEmptyParentheses: false +SpaceInEmptyBlock: false +SpaceBeforeSquareBrackets: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeInheritanceColon: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCaseColon: false +SpaceBeforeAssignmentOperators: true +SpaceAroundPointerQualifiers: Before +PointerAlignment: Left +SpaceAfterTemplateKeyword: false +SpaceAfterLogicalNot: true +SpaceAfterCStyleCast: true +SortUsingDeclarations: Lexicographic +SortIncludes: CaseInsensitive +ShortNamespaceLines: 0 +SeparateDefinitionBlocks: Always +RequiresExpressionIndentation: OuterScope +BreakBeforeConceptDeclarations: Always +BreakBeforeInlineASMColon: Always +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 100 +CompactNamespaces: true +Cpp11BracedListStyle: true +EmptyLineBeforeAccessModifier: Always +FixNamespaceComments: true +IncludeBlocks: Preserve +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: Indent +IndentGotoLabels: false +IndentPPDirectives: AfterHash +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: true +InsertBraces: true +InsertNewlineAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +Language: Cpp +LineEnding: LF +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: Inner +QualifierAlignment: Left +ReferenceAlignment: Left +ReflowComments: true +RemoveBracesLLVM: false +RemoveSemicolon: false +RequiresClausePosition: OwnLine diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9995ca40..2bf24870 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -170,6 +170,22 @@ jobs: name: ${{ matrix.artifact }}-www path: proofing/ink-proof/out + clang-format: + name: "Check Formatting" + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} + steps: + - uses: actions/checkout@v3 + - name: Fetch master branch + run: | + git fetch origin master --depth 1 + - name: Check clang-format + run: | + diff=$(git clang-format-15 --style file --diff master) + if [ "$diff" != "" ]; then + exit 1 + fi + reporting: name: "Pull Request Report" # if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }} @@ -180,9 +196,6 @@ jobs: pull-requests: write steps: # Download Ink Proof Results - - name: Debug - run: | - echo ${{github.event_name}} ${{github.event.pull_request.head.repo.full_name}} ${{github.event.pull_request.head.ref}} ${{github.event.pull_request.base.full_name}} ${{github.event.pull_request.base.ref}} - uses: actions/download-artifact@v2 with: name: results From b3cf913116907f1430e0e51547d5f04d9cc6bd75 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 08:43:55 +0100 Subject: [PATCH 14/29] Formatting --- .clang-format | 2 +- inkcpp/array.h | 15 +-- inkcpp/output.cpp | 14 +-- inkcpp/runner_impl.cpp | 25 ++--- inkcpp/story_ptr.cpp | 2 +- inkcpp_cl/inkcpp_cl.cpp | 10 +- inkcpp_test/SpaceAfterBracketChoice.cpp | 96 ++++++++++---------- inkcpp_test/ThirdTierChoiceAfterBrackets.cpp | 81 +++++++++-------- 8 files changed, 126 insertions(+), 119 deletions(-) diff --git a/.clang-format b/.clang-format index f553bd14..b0adf2e2 100644 --- a/.clang-format +++ b/.clang-format @@ -77,7 +77,7 @@ SpaceAfterTemplateKeyword: false SpaceAfterLogicalNot: true SpaceAfterCStyleCast: true SortUsingDeclarations: Lexicographic -SortIncludes: CaseInsensitive +SortIncludes: Never ShortNamespaceLines: 0 SeparateDefinitionBlocks: Always RequiresExpressionIndentation: OuterScope diff --git a/inkcpp/array.h b/inkcpp/array.h index 42a6a40f..46fcfd30 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -18,15 +18,16 @@ namespace ink::runtime::internal { { _dynamic_data = new T[initialCapacity]; } - } + } - ~managed_array() { - if constexpr (dynamic) { - delete[] _dynamic_data; - } - } + ~managed_array() + { + if constexpr (dynamic) { + delete[] _dynamic_data; + } + } - const T& operator[]( size_t i ) const { return data()[i]; } + const T& operator[]( size_t i ) const { return data()[i]; } T& operator[]( size_t i ) { return data()[i]; } const T* data() const { diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 392ec3d3..99061918 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -140,13 +140,13 @@ namespace ink::runtime::internal std::string result = str.str(); if ( !result.empty() ) { auto end = clean_string( result.begin(), result.end() ); - if (result.begin() == end) { - result.resize(0); - } else { - _last_char = *( end - 1 ); - result.resize( end - result.begin() - ( _last_char == ' ' ? 1 : 0 ) ); - } - } + if (result.begin() == end) { + result.resize(0); + } else { + _last_char = *(end - 1); + result.resize(end - result.begin() - (_last_char == ' ' ? 1 : 0)); + } + } return result; } #endif diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 3e7c3dee..bbf7f70a 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -177,16 +177,15 @@ namespace ink::runtime::internal // without entering any other containers // OR IF if target is same position do nothing // could happend if jumping to and of an unnamed container - if (dest ==_ptr) - { - _ptr = dest; + if (dest == _ptr) { + _ptr = dest; return; - } + } - const uint32_t* iter = nullptr; + const uint32_t* iter = nullptr; container_t id; - ip_t offset = nullptr; - size_t comm_end; + ip_t offset = nullptr; + size_t comm_end; bool reversed = _ptr > dest; if (reversed) { @@ -1172,14 +1171,16 @@ namespace ink::runtime::internal } break; case Command::END_CONTAINER_MARKER: { - container_t index = read(); + container_t index = read(); - inkAssert(_container.top().id == index, "Leaving container we are not in!"); + inkAssert( + _container.top().id == index, "Leaving container we are not in!" + ); - // Move up out of the current container - _container.pop(); + // Move up out of the current container + _container.pop(); - // SPECIAL: If we've popped all containers, then there's an implied 'done' command or return + // SPECIAL: If we've popped all containers, then there's an implied 'done' command or return if (_container.empty()) { _is_falling = false; diff --git a/inkcpp/story_ptr.cpp b/inkcpp/story_ptr.cpp index d425bacc..c983f045 100644 --- a/inkcpp/story_ptr.cpp +++ b/inkcpp/story_ptr.cpp @@ -66,4 +66,4 @@ namespace ink::runtime::internal _instance_block = _story_block = nullptr; return is_destroyed; } -} + } // namespace ink::runtime::internal diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index 1e800450..de93742f 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -155,9 +155,9 @@ int main(int argc, const char** argv) using namespace ink::runtime; // Load story - std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; + std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; - // Start runner + // Start runner runner thread; if (snapshotFile.size()) { auto snap_ptr = snapshot::from_file( snapshotFile.c_str() ); @@ -212,12 +212,12 @@ int main(int argc, const char** argv) // out of content break; - } - } + } + } catch (const std::exception& e) { std::cerr << "Unhandled ink runtime exception: " << e.what() << std::endl; return 1; } - return 0; + return 0; } diff --git a/inkcpp_test/SpaceAfterBracketChoice.cpp b/inkcpp_test/SpaceAfterBracketChoice.cpp index 9ee8715e..55949b63 100644 --- a/inkcpp_test/SpaceAfterBracketChoice.cpp +++ b/inkcpp_test/SpaceAfterBracketChoice.cpp @@ -1,48 +1,48 @@ -#include "catch.hpp" -#include "../inkcpp_cl/test.h" - -#include -#include -#include -#include - -using namespace ink::runtime; - -SCENARIO("a story with bracketed choices and spaces can choose correctly", "[choices]") -{ - GIVEN("a story with line breaks") - { - inklecate("ink/ChoiceBracketStory.ink", "ChoiceBracketStory.tmp"); - ink::compiler::run("ChoiceBracketStory.tmp", "ChoiceBracketStory.bin"); - auto ink = story::from_file("ChoiceBracketStory.bin"); - runner thread = ink->new_runner(); - thread->getall(); - WHEN("start thread") - { - THEN("thread has choices") - { - thread->getall(); - REQUIRE(thread->has_choices()); - } - WHEN("choose choice 1") - { - thread->choose(0); - thread->getall(); - THEN("still has choices") - { - thread->getall(); - REQUIRE(thread->has_choices()); - } - } - WHEN("choose choice 2") - { - thread->choose(1); - thread->getall(); - THEN("still has choices") - { - REQUIRE(thread->has_choices()); - } - } - } - } -} +#include "catch.hpp" +#include "../inkcpp_cl/test.h" + +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO("a story with bracketed choices and spaces can choose correctly", "[choices]") +{ + GIVEN("a story with line breaks") + { + inklecate("ink/ChoiceBracketStory.ink", "ChoiceBracketStory.tmp"); + ink::compiler::run("ChoiceBracketStory.tmp", "ChoiceBracketStory.bin"); + auto ink = story::from_file("ChoiceBracketStory.bin"); + runner thread = ink->new_runner(); + thread->getall(); + WHEN("start thread") + { + THEN("thread has choices") + { + thread->getall(); + REQUIRE(thread->has_choices()); + } + WHEN("choose choice 1") + { + thread->choose(0); + thread->getall(); + THEN("still has choices") + { + thread->getall(); + REQUIRE(thread->has_choices()); + } + } + WHEN("choose choice 2") + { + thread->choose(1); + thread->getall(); + THEN("still has choices") + { + REQUIRE(thread->has_choices()); + } + } + } + } +} diff --git a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp index 6aa5fe20..99e54251 100644 --- a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp +++ b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp @@ -1,38 +1,43 @@ -#include "catch.hpp" -#include "../inkcpp_cl/test.h" - -#include -#include -#include -#include - -using namespace ink::runtime; - -SCENARIO("a story with a bracketed choice as a second choice, and then a third choice, chooses properly", "[choices]") -{ - GIVEN("a story with brackets and nested choices") - { - inklecate("ink/ThirdTierChoiceAfterBracketsStory.ink", "ThirdTierChoiceAfterBracketsStory.tmp"); - ink::compiler::run("ThirdTierChoiceAfterBracketsStory.tmp", "ThirdTierChoiceAfterBracketsStory.bin"); - auto ink = story::from_file("ThirdTierChoiceAfterBracketsStory.bin"); - runner thread = ink->new_runner(); - - WHEN("start thread") - { - THEN("thread doesn't error") - { - thread->getall(); - thread->has_choices(); - thread->choose(0); - thread->getall(); - thread->has_choices(); - thread->choose(0); - thread->getall(); - thread->has_choices(); - thread->choose(0); - thread->getall(); - thread->has_choices(); - } - } - } -} +#include "catch.hpp" +#include "../inkcpp_cl/test.h" + +#include +#include +#include +#include + +using namespace ink::runtime; + +SCENARIO( + "a story with a bracketed choice as a second choice, and then a third choice, chooses properly", + "[choices]" +) +{ + GIVEN("a story with brackets and nested choices") + { + inklecate("ink/ThirdTierChoiceAfterBracketsStory.ink", "ThirdTierChoiceAfterBracketsStory.tmp"); + ink::compiler::run( + "ThirdTierChoiceAfterBracketsStory.tmp", "ThirdTierChoiceAfterBracketsStory.bin" + ); + auto ink = story::from_file("ThirdTierChoiceAfterBracketsStory.bin"); + runner thread = ink->new_runner(); + + WHEN("start thread") + { + THEN("thread doesn't error") + { + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + } + } + } +} From a4a8688aa8c3b0f9d23cbff9becc35231b25a815 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 08:47:19 +0100 Subject: [PATCH 15/29] more formatting --- inkcpp/array.h | 927 +++++++------- inkcpp/output.cpp | 4 +- inkcpp/runner_impl.cpp | 2660 ++++++++++++++++++++------------------- inkcpp_cl/inkcpp_cl.cpp | 377 +++--- 4 files changed, 1981 insertions(+), 1987 deletions(-) diff --git a/inkcpp/array.h b/inkcpp/array.h index 46fcfd30..332b8c61 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -4,471 +4,462 @@ #include "system.h" #include "traits.h" -namespace ink::runtime::internal { - template - class managed_array : public snapshot_interface - { - public: - managed_array() - : _static_data{}, - _capacity{ initialCapacity }, - _size{ 0 } - { - if constexpr ( dynamic ) - { - _dynamic_data = new T[initialCapacity]; - } - } - - ~managed_array() - { - if constexpr (dynamic) { - delete[] _dynamic_data; - } - } - - const T& operator[]( size_t i ) const { return data()[i]; } - T& operator[]( size_t i ) { return data()[i]; } - const T* data() const - { - if constexpr ( dynamic ) - { - return _dynamic_data; - } - else - { - return _static_data; - } - } - T* data() - { - if constexpr ( dynamic ) - { - return _dynamic_data; - } - else - { - return _static_data; - } - } - const T* begin() const { return data(); } - T* begin() { return data(); } - const T* end() const { return data() + _size; } - T* end() { return data() + _size; } - const T& back() const { return end()[-1]; } - T& back() { return end()[-1]; } - - const size_t size() const { return _size; } - const size_t capacity() const { return _capacity; } - T& push() - { - if constexpr ( dynamic ) - { - if ( _size == _capacity ) - { - extend(); - } - } - else - { - inkAssert( _size <= _capacity, "Stack Overflow!" ); - /// FIXME silent fail!! - } - return data()[_size++]; - } - void clear() { _size = 0; } - void resize( size_t size ) - { - if constexpr ( dynamic ) - { - if ( size > _capacity ) - { - extend( size ); - } - } - else - { - inkAssert( size <= _size, "Only allow to reduce size" ); - } - _size = size; - } - - void extend( size_t capacity = 0 ); - - size_t snap( unsigned char* data, const snapper& snapper) const - { - inkAssert( !is_pointer{}(), "here is a special case oversight" ); - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write( ptr, _size, should_write ); - for (const T& e : *this) - { - if constexpr (is_base_of::value) { - ptr += e.snap(data == nullptr ? nullptr : ptr, snapper); - } else { - ptr = snap_write( ptr, e, should_write ); - } - } - return ptr - data; - } - const unsigned char* snap_load( const unsigned char* ptr, const loader& loader) - { - decltype( _size ) size; - ptr = snap_read( ptr, size ); - if constexpr ( dynamic ) - { - resize( size ); - } - else - { - inkAssert( size <= initialCapacity, "capacity of non dynamic array is to small vor snapshot!" ); - _size = size; - } - for ( T& e : *this ) - { - if constexpr (is_base_of::value) { - ptr = e.snap_load(ptr, loader); - } else { - ptr = snap_read( ptr, e ); - } - } - return ptr; - } - - private: - if_t _static_data[dynamic ? 1 : initialCapacity]; - T* _dynamic_data = nullptr; - size_t _capacity; - size_t _size; - }; - - template - class managed_restorable_array : public managed_array - { - using base = managed_array; - - public: - managed_restorable_array() - : base() {} - void restore() - { - base::resize( _last_size ); - } - void save() - { - _last_size = this->size(); - } - void forgett() - { - _last_size = 0; - } - bool has_changed() const - { - return base::size() != _last_size; - } - - size_t snap( unsigned char* data, const snapshot_interface::snapper& snapper ) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr += base::snap( ptr, snapper ); - ptr = base::snap_write( ptr, _last_size, should_write ); - return ptr - data; - } - - const unsigned char* snap_load(const unsigned char* ptr, const snapshot_interface::loader& loader) { - ptr = base::snap_load(ptr, loader); - ptr = base::snap_read(ptr, _last_size); - return ptr; - } - - private: - size_t _last_size = 0; - }; - - template - void managed_array::extend( size_t capacity ) - { - static_assert( dynamic, "Can only extend if array is dynamic!" ); - size_t new_capacity = capacity > _capacity - ? capacity - : 1.5f * _capacity; - if ( new_capacity < 5 ) - { - new_capacity = 5; - } - T* new_data = new T[new_capacity]; - - for ( size_t i = 0; i < _capacity; ++i ) - { - new_data[i] = _dynamic_data[i]; - } - - delete[] _dynamic_data; - _dynamic_data = new_data; - _capacity = new_capacity; - } - - template - class basic_restorable_array : public snapshot_interface - { - public: - basic_restorable_array( T* array, size_t capacity, T nullValue ) - : _saved( false ), - _array( array ), - _temp( array + capacity / 2 ), - _capacity( capacity / 2 ), - _null( nullValue ) - { - inkAssert( capacity % 2 == 0, "basic_restorable_array requires a datablock of even length to split into two arrays" ); - - // zero out main array and put 'nulls' in the clear_temp() - inkZeroMemory( _array, _capacity * sizeof( T ) ); - clear_temp(); - } - - // == Non-Copyable == - basic_restorable_array( const basic_restorable_array& ) = delete; - basic_restorable_array& operator=( const basic_restorable_array& ) = delete; - - // set value by index - void set( size_t index, const T& value ); - - // get value by index - const T& get( size_t index ) const; - - // size of the array - inline size_t capacity() const { return _capacity; } - - // only const indexing is supported due to save/restore system - inline const T& operator[]( size_t index ) const { return get( index ); } - - // == Save/Restore == - void save(); - void restore(); - void forget(); - - // Resets all values and clears any save points - void clear( const T& value ); - - // snapshot interface - virtual size_t snap( unsigned char* data, const snapper& ) const; - virtual const unsigned char* snap_load( const unsigned char* data, const loader& ); - - protected: - inline T* buffer() { return _array; } - void set_new_buffer( T* buffer, size_t capacity ) - { - _array = buffer; - _temp = buffer + capacity / 2; - _capacity = capacity / 2; - } - - private: - inline void check_index( size_t index ) const { inkAssert( index < capacity(), "Index out of range!" ); } - void clear_temp(); - - private: - bool _saved; - - // real values live here - T* _array; - - // we store values here when we're in save mode - // they're copied on a call to forget() - T* _temp; - - // size of both _array and _temp - size_t _capacity; - - // null - const T _null; - }; - - template - inline size_t basic_restorable_array::snap( unsigned char* data, const snapper& snapper ) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write( ptr, _saved, should_write ); - ptr = snap_write( ptr, _capacity, should_write ); - ptr = snap_write( ptr, _null, should_write ); - for ( size_t i = 0; i < _capacity; ++i ) - { - ptr = snap_write( ptr, _array[i], should_write ); - ptr = snap_write( ptr, _temp[i], should_write ); - } - return ptr - data; - } - - template - inline const unsigned char* basic_restorable_array::snap_load( const unsigned char* data, const loader& loader ) - { - auto ptr = data; - ptr = snap_read( ptr, _saved ); - ptr = snap_read( ptr, _capacity ); - T null; - ptr = snap_read( ptr, null ); - inkAssert( null == _null, "null value is different to snapshot!" ); - for ( size_t i = 0; i < _capacity; ++i ) - { - ptr = snap_read( ptr, _array[i] ); - ptr = snap_read( ptr, _temp[i] ); - } - return ptr; - } - - template - inline void basic_restorable_array::set( size_t index, const T& value ) - { - check_index( index ); - inkAssert( value != _null, "Can not add a value considered a 'null' to a restorable_array" ); - - // If we're saved, store in second half of the array - if ( _saved ) - _temp[index] = value; - else - // Otherwise, store in the main array - _array[index] = value; - } - - template - inline const T& basic_restorable_array::get( size_t index ) const - { - check_index( index ); - - // If we're in save mode and we have a value at that index, return that instead - if ( _saved && _temp[index] != _null ) - return _temp[index]; - - // Otherwise, fall back on the real array - return _array[index]; - } - - template - inline void basic_restorable_array::save() - { - // Put us into save/restore mode - _saved = true; - } - - template - inline void basic_restorable_array::restore() - { - clear_temp(); - - // Clear saved flag - _saved = false; - } - - template - inline void basic_restorable_array::forget() - { - // Run through the _temp array - for ( size_t i = 0; i < _capacity; i++ ) - { - // Copy if there's values - if ( _temp[i] != _null ) - _array[i] = _temp[i]; - - // Clear - _temp[i] = _null; - } - } - - template - inline void basic_restorable_array::clear_temp() - { - // Run through the _temp array - for ( size_t i = 0; i < _capacity; i++ ) - { - // Clear - _temp[i] = _null; - } - } - - template - inline void basic_restorable_array::clear( const T& value ) - { - _saved = false; - for ( size_t i = 0; i < _capacity; i++ ) - { - _temp[i] = _null; - _array[i] = value; - } - } - - template - class fixed_restorable_array : public basic_restorable_array - { - public: - fixed_restorable_array( const T& initial, const T& nullValue ) - : basic_restorable_array( _buffer, SIZE * 2, nullValue ) - { - basic_restorable_array::clear( initial ); - } - - private: - T _buffer[SIZE * 2]; - }; - - template - class allocated_restorable_array final : public basic_restorable_array - { - using base = basic_restorable_array; - - public: - allocated_restorable_array( const T& initial, const T& nullValue ) - : basic_restorable_array( 0, 0, nullValue ), - _initialValue{ initial }, - _nullValue{ nullValue }, - _buffer{ nullptr } - {} - allocated_restorable_array( size_t capacity, const T& initial, const T& nullValue ) - : basic_restorable_array( new T[capacity * 2], capacity * 2, nullValue ), - _initialValue{ initial }, - _nullValue{ nullValue } - { - _buffer = this->buffer(); - this->clear( _initialValue ); - } - - void resize( size_t n ) - { - size_t new_capacity = 2 * n; - T* new_buffer = new T[new_capacity]; - if ( _buffer ) - { - for ( size_t i = 0; i < base::capacity(); ++i ) - { - new_buffer[i] = _buffer[i]; - // copy temp - new_buffer[i + base::capacity()] = _buffer[i + base::capacity()]; - } - delete[] _buffer; - } - for ( size_t i = base::capacity(); i < new_capacity; ++i ) - { - new_buffer[i] = _initialValue; - new_buffer[i + base::capacity()] = _nullValue; - } - - _buffer = new_buffer; - this->set_new_buffer( _buffer, new_capacity ); - } - - virtual ~allocated_restorable_array() - { - if ( _buffer ) - { - delete[] _buffer; - _buffer = nullptr; - } - } - - private: - T _initialValue; - T _nullValue; - T* _buffer; - }; -} // namespace ink::runtime::internal +namespace ink::runtime::internal +{ +template +class managed_array : public snapshot_interface +{ +public: + managed_array() + : _static_data{} + , _capacity{initialCapacity} + , _size{0} + { + if constexpr (dynamic) { + _dynamic_data = new T[initialCapacity]; + } + } + + ~managed_array() + { + if constexpr (dynamic) { + delete[] _dynamic_data; + } + } + + const T& operator[](size_t i) const { return data()[i]; } + + T& operator[](size_t i) { return data()[i]; } + + const T* data() const + { + if constexpr (dynamic) { + return _dynamic_data; + } else { + return _static_data; + } + } + + T* data() + { + if constexpr (dynamic) { + return _dynamic_data; + } else { + return _static_data; + } + } + + const T* begin() const { return data(); } + + T* begin() { return data(); } + + const T* end() const { return data() + _size; } + + T* end() { return data() + _size; } + + const T& back() const { return end()[-1]; } + + T& back() { return end()[-1]; } + + const size_t size() const { return _size; } + + const size_t capacity() const { return _capacity; } + + T& push() + { + if constexpr (dynamic) { + if (_size == _capacity) { + extend(); + } + } else { + inkAssert(_size <= _capacity, "Stack Overflow!"); + /// FIXME silent fail!! + } + return data()[_size++]; + } + + void clear() { _size = 0; } + + void resize(size_t size) + { + if constexpr (dynamic) { + if (size > _capacity) { + extend(size); + } + } else { + inkAssert(size <= _size, "Only allow to reduce size"); + } + _size = size; + } + + void extend(size_t capacity = 0); + + size_t snap(unsigned char* data, const snapper& snapper) const + { + inkAssert(! is_pointer{}(), "here is a special case oversight"); + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _size, should_write); + for (const T& e : *this) { + if constexpr (is_base_of::value) { + ptr += e.snap(data == nullptr ? nullptr : ptr, snapper); + } else { + ptr = snap_write(ptr, e, should_write); + } + } + return ptr - data; + } + + const unsigned char* snap_load(const unsigned char* ptr, const loader& loader) + { + decltype(_size) size; + ptr = snap_read(ptr, size); + if constexpr (dynamic) { + resize(size); + } else { + inkAssert(size <= initialCapacity, "capacity of non dynamic array is to small vor snapshot!"); + _size = size; + } + for (T& e : *this) { + if constexpr (is_base_of::value) { + ptr = e.snap_load(ptr, loader); + } else { + ptr = snap_read(ptr, e); + } + } + return ptr; + } + +private: + if_t _static_data[dynamic ? 1 : initialCapacity]; + T* _dynamic_data = nullptr; + size_t _capacity; + size_t _size; +}; + +template +class managed_restorable_array : public managed_array +{ + using base = managed_array; + +public: + managed_restorable_array() + : base() + { + } + + void restore() { base::resize(_last_size); } + + void save() { _last_size = this->size(); } + + void forgett() { _last_size = 0; } + + bool has_changed() const { return base::size() != _last_size; } + + size_t snap(unsigned char* data, const snapshot_interface::snapper& snapper) const + { + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr += base::snap(ptr, snapper); + ptr = base::snap_write(ptr, _last_size, should_write); + return ptr - data; + } + + const unsigned char* snap_load(const unsigned char* ptr, const snapshot_interface::loader& loader) + { + ptr = base::snap_load(ptr, loader); + ptr = base::snap_read(ptr, _last_size); + return ptr; + } + +private: + size_t _last_size = 0; +}; + +template +void managed_array::extend(size_t capacity) +{ + static_assert(dynamic, "Can only extend if array is dynamic!"); + size_t new_capacity = capacity > _capacity ? capacity : 1.5f * _capacity; + if (new_capacity < 5) { + new_capacity = 5; + } + T* new_data = new T[new_capacity]; + + for (size_t i = 0; i < _capacity; ++i) { + new_data[i] = _dynamic_data[i]; + } + + delete[] _dynamic_data; + _dynamic_data = new_data; + _capacity = new_capacity; +} + +template +class basic_restorable_array : public snapshot_interface +{ +public: + basic_restorable_array(T* array, size_t capacity, T nullValue) + : _saved(false) + , _array(array) + , _temp(array + capacity / 2) + , _capacity(capacity / 2) + , _null(nullValue) + { + inkAssert( + capacity % 2 == 0, + "basic_restorable_array requires a datablock of even length to split into two arrays" + ); + + // zero out main array and put 'nulls' in the clear_temp() + inkZeroMemory(_array, _capacity * sizeof(T)); + clear_temp(); + } + + // == Non-Copyable == + basic_restorable_array(const basic_restorable_array&) = delete; + basic_restorable_array& operator=(const basic_restorable_array&) = delete; + + // set value by index + void set(size_t index, const T& value); + + // get value by index + const T& get(size_t index) const; + + // size of the array + inline size_t capacity() const { return _capacity; } + + // only const indexing is supported due to save/restore system + inline const T& operator[](size_t index) const { return get(index); } + + // == Save/Restore == + void save(); + void restore(); + void forget(); + + // Resets all values and clears any save points + void clear(const T& value); + + // snapshot interface + virtual size_t snap(unsigned char* data, const snapper&) const; + virtual const unsigned char* snap_load(const unsigned char* data, const loader&); + +protected: + inline T* buffer() { return _array; } + + void set_new_buffer(T* buffer, size_t capacity) + { + _array = buffer; + _temp = buffer + capacity / 2; + _capacity = capacity / 2; + } + +private: + inline void check_index(size_t index) const + { + inkAssert(index < capacity(), "Index out of range!"); + } + + void clear_temp(); + +private: + bool _saved; + + // real values live here + T* _array; + + // we store values here when we're in save mode + // they're copied on a call to forget() + T* _temp; + + // size of both _array and _temp + size_t _capacity; + + // null + const T _null; +}; + +template +inline size_t basic_restorable_array::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _saved, should_write); + ptr = snap_write(ptr, _capacity, should_write); + ptr = snap_write(ptr, _null, should_write); + for (size_t i = 0; i < _capacity; ++i) { + ptr = snap_write(ptr, _array[i], should_write); + ptr = snap_write(ptr, _temp[i], should_write); + } + return ptr - data; +} + +template +inline const unsigned char* + basic_restorable_array::snap_load(const unsigned char* data, const loader& loader) +{ + auto ptr = data; + ptr = snap_read(ptr, _saved); + ptr = snap_read(ptr, _capacity); + T null; + ptr = snap_read(ptr, null); + inkAssert(null == _null, "null value is different to snapshot!"); + for (size_t i = 0; i < _capacity; ++i) { + ptr = snap_read(ptr, _array[i]); + ptr = snap_read(ptr, _temp[i]); + } + return ptr; +} + +template +inline void basic_restorable_array::set(size_t index, const T& value) +{ + check_index(index); + inkAssert(value != _null, "Can not add a value considered a 'null' to a restorable_array"); + + // If we're saved, store in second half of the array + if (_saved) { + _temp[index] = value; + } else { + // Otherwise, store in the main array + _array[index] = value; + } +} + +template +inline const T& basic_restorable_array::get(size_t index) const +{ + check_index(index); + + // If we're in save mode and we have a value at that index, return that instead + if (_saved && _temp[index] != _null) { + return _temp[index]; + } + + // Otherwise, fall back on the real array + return _array[index]; +} + +template +inline void basic_restorable_array::save() +{ + // Put us into save/restore mode + _saved = true; +} + +template +inline void basic_restorable_array::restore() +{ + clear_temp(); + + // Clear saved flag + _saved = false; +} + +template +inline void basic_restorable_array::forget() +{ + // Run through the _temp array + for (size_t i = 0; i < _capacity; i++) { + // Copy if there's values + if (_temp[i] != _null) { + _array[i] = _temp[i]; + } + + // Clear + _temp[i] = _null; + } +} + +template +inline void basic_restorable_array::clear_temp() +{ + // Run through the _temp array + for (size_t i = 0; i < _capacity; i++) { + // Clear + _temp[i] = _null; + } +} + +template +inline void basic_restorable_array::clear(const T& value) +{ + _saved = false; + for (size_t i = 0; i < _capacity; i++) { + _temp[i] = _null; + _array[i] = value; + } +} + +template +class fixed_restorable_array : public basic_restorable_array +{ +public: + fixed_restorable_array(const T& initial, const T& nullValue) + : basic_restorable_array(_buffer, SIZE * 2, nullValue) + { + basic_restorable_array::clear(initial); + } + +private: + T _buffer[SIZE * 2]; +}; + +template +class allocated_restorable_array final : public basic_restorable_array +{ + using base = basic_restorable_array; + +public: + allocated_restorable_array(const T& initial, const T& nullValue) + : basic_restorable_array(0, 0, nullValue) + , _initialValue{initial} + , _nullValue{nullValue} + , _buffer{nullptr} + { + } + + allocated_restorable_array(size_t capacity, const T& initial, const T& nullValue) + : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue) + , _initialValue{initial} + , _nullValue{nullValue} + { + _buffer = this->buffer(); + this->clear(_initialValue); + } + + void resize(size_t n) + { + size_t new_capacity = 2 * n; + T* new_buffer = new T[new_capacity]; + if (_buffer) { + for (size_t i = 0; i < base::capacity(); ++i) { + new_buffer[i] = _buffer[i]; + // copy temp + new_buffer[i + base::capacity()] = _buffer[i + base::capacity()]; + } + delete[] _buffer; + } + for (size_t i = base::capacity(); i < new_capacity; ++i) { + new_buffer[i] = _initialValue; + new_buffer[i + base::capacity()] = _nullValue; + } + + _buffer = new_buffer; + this->set_new_buffer(_buffer, new_capacity); + } + + virtual ~allocated_restorable_array() + { + if (_buffer) { + delete[] _buffer; + _buffer = nullptr; + } + } + +private: + T _initialValue; + T _nullValue; + T* _buffer; +}; +} // namespace ink::runtime::internal diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 99061918..558c4b17 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -147,8 +147,8 @@ namespace ink::runtime::internal result.resize(end - result.begin() - (_last_char == ' ' ? 1 : 0)); } } - return result; - } + return result; + } #endif #ifdef INK_ENABLE_UNREAL FString basic_stream::get() diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index bbf7f70a..ef036745 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -11,1395 +11,1413 @@ namespace ink::runtime { - const choice* runner_interface::get_choice(size_t index) const - { - inkAssert(index < num_choices(), "Choice out of bounds!"); - return begin() + index; - } +const choice* runner_interface::get_choice(size_t index) const +{ + inkAssert(index < num_choices(), "Choice out of bounds!"); + return begin() + index; +} - size_t runner_interface::num_choices() const - { - return end() - begin(); - } +size_t runner_interface::num_choices() const +{ + return end() - begin(); } +} // namespace ink::runtime namespace ink::runtime::internal { - template<> - value* runner_impl::get_var(hash_t variableName) { - return _globals->get_variable(variableName); - } +template<> +value* runner_impl::get_var(hash_t variableName) +{ + return _globals->get_variable(variableName); +} - template<> - value* runner_impl::get_var(hash_t variableName) { - value* ret = _stack.get(variableName); - if(!ret) { return nullptr; } - if(ret->type() == value_type::value_pointer) { - auto [name, ci] = ret->get(); - inkAssert(ci == 0, "only Global pointer are allowd on the _stack!"); - return get_var(name); - } - return ret; - } +template<> +value* runner_impl::get_var(hash_t variableName) +{ + value* ret = _stack.get(variableName); + if (! ret) { + return nullptr; + } + if (ret->type() == value_type::value_pointer) { + auto [name, ci] = ret->get(); + inkAssert(ci == 0, "only Global pointer are allowd on the _stack!"); + return get_var(name); + } + return ret; +} - template<> - value* runner_impl::get_var(hash_t variableName) { - value* ret = get_var(variableName); - if(ret) { return ret; } - return get_var(variableName); - } - template - const value* runner_impl::get_var(hash_t variableName) const { - return const_cast(this)->get_var(variableName); - } +template<> +value* runner_impl::get_var(hash_t variableName) +{ + value* ret = get_var(variableName); + if (ret) { + return ret; + } + return get_var(variableName); +} - template<> - void runner_impl::set_var(hash_t variableName, const value& val, bool is_redef) { - if(is_redef) { - value* src = _globals->get_variable(variableName); - _globals->set_variable(variableName, src->redefine(val, _globals->lists())); - } else { - _globals->set_variable(variableName, val); - } - } +template +const value* runner_impl::get_var(hash_t variableName) const +{ + return const_cast(this)->get_var(variableName); +} - const value* runner_impl::dereference(const value& val) { - if(val.type() != value_type::value_pointer) { return &val; } - - auto [name, ci] = val.get(); - if(ci == 0) { return get_var(name); } - return _stack.get_from_frame(ci, name); - } +template<> +void runner_impl::set_var( + hash_t variableName, const value& val, bool is_redef +) +{ + if (is_redef) { + value* src = _globals->get_variable(variableName); + _globals->set_variable(variableName, src->redefine(val, _globals->lists())); + } else { + _globals->set_variable(variableName, val); + } +} - template<> - void runner_impl::set_var(hash_t variableName, const value& val, bool is_redef) { - if(val.type() == value_type::value_pointer) { - inkAssert(is_redef == false, "value pointer can only use to initelize variable!"); - auto [name, ci] = val.get(); - if(ci == 0) { _stack.set(variableName, val); } - else { - const value* dref = dereference(val); - if(dref == nullptr) { - value v = val; - auto ref = v.get(); - v.set(ref.name, 0); - _stack.set(variableName, v); - } else { - _ref_stack.set(variableName, val); - _stack.set(variableName, *dref); - } - } - } else { - if(is_redef) { - value* src = _stack.get(variableName); - if(src->type() == value_type::value_pointer) { - auto [name, ci] = src->get(); - inkAssert(ci == 0, "Only global pointer are allowed on _stack!"); - set_var( - name, - get_var(name)->redefine(val, _globals->lists()), - true); - } else { - _stack.set(variableName, src->redefine(val, _globals->lists())); - } - } else { - _stack.set(variableName, val); - } - } - } +const value* runner_impl::dereference(const value& val) +{ + if (val.type() != value_type::value_pointer) { + return &val; + } + + auto [name, ci] = val.get(); + if (ci == 0) { + return get_var(name); + } + return _stack.get_from_frame(ci, name); +} - template<> - void runner_impl::set_var(hash_t variableName, const value& val, bool is_redef) - { - inkAssert(is_redef, "define set scopeless variables!"); - if(_stack.get(variableName)) { - return set_var(variableName, val, is_redef); - } else { - return set_var(variableName, val, is_redef); - } - } +template<> +void runner_impl::set_var( + hash_t variableName, const value& val, bool is_redef +) +{ + if (val.type() == value_type::value_pointer) { + inkAssert(is_redef == false, "value pointer can only use to initelize variable!"); + auto [name, ci] = val.get(); + if (ci == 0) { + _stack.set(variableName, val); + } else { + const value* dref = dereference(val); + if (dref == nullptr) { + value v = val; + auto ref = v.get(); + v.set(ref.name, 0); + _stack.set(variableName, v); + } else { + _ref_stack.set(variableName, val); + _stack.set(variableName, *dref); + } + } + } else { + if (is_redef) { + value* src = _stack.get(variableName); + if (src->type() == value_type::value_pointer) { + auto [name, ci] = src->get(); + inkAssert(ci == 0, "Only global pointer are allowed on _stack!"); + set_var( + name, get_var(name)->redefine(val, _globals->lists()), true + ); + } else { + _stack.set(variableName, src->redefine(val, _globals->lists())); + } + } else { + _stack.set(variableName, val); + } + } +} +template<> +void runner_impl::set_var( + hash_t variableName, const value& val, bool is_redef +) +{ + inkAssert(is_redef, "define set scopeless variables!"); + if (_stack.get(variableName)) { + return set_var(variableName, val, is_redef); + } else { + return set_var(variableName, val, is_redef); + } +} +template +inline T runner_impl::read() +{ + using header = ink::internal::header; + // Sanity + inkAssert(_ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution"); - template - inline T runner_impl::read() - { - using header = ink::internal::header; - // Sanity - inkAssert(_ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution"); + // Read memory + T val = *( const T* ) _ptr; + if (_story->get_header().endien == header::endian_types::differ) { + val = header::swap_bytes(val); + } - // Read memory - T val = *(const T*)_ptr; - if (_story->get_header().endien == header::endian_types::differ) { - val = header::swap_bytes(val); - } + // Advance ip + _ptr += sizeof(T); - // Advance ip - _ptr += sizeof(T); + // Return + return val; +} - // Return - return val; - } +template<> +inline const char* runner_impl::read() +{ + offset_t str = read(); + return _story->string(str); +} - template<> - inline const char* runner_impl::read() - { - offset_t str = read(); - return _story->string(str); - } +choice& runner_impl::add_choice() +{ + inkAssert( + config::maxChoices < 0 || _choices.size() < config::maxChoices, "Ran out of choice storage!" + ); + return _choices.push(); +} - choice& runner_impl::add_choice() - { - inkAssert(config::maxChoices < 0 || _choices.size() < config::maxChoices, - "Ran out of choice storage!"); - return _choices.push(); - } +void runner_impl::clear_choices() +{ + // TODO: Garbage collection? ? which garbage ? + _fallback_choice = nullopt; + _choices.clear(); +} - void runner_impl::clear_choices() - { - // TODO: Garbage collection? ? which garbage ? - _fallback_choice = nullopt; - _choices.clear(); - } +void runner_impl::clear_tags() +{ + _tags.clear(); + _choice_tags_begin = -1; +} - void runner_impl::clear_tags() - { - _tags.clear(); - _choice_tags_begin = -1; - } +void runner_impl::jump(ip_t dest, bool record_visits) +{ + // Optimization: if we are _is_falling, then we can + // _should be_ able to safely assume that there is nothing to do here. A falling + // divert should only be taking us from a container to that same container's end point + // without entering any other containers + // OR IF if target is same position do nothing + // could happend if jumping to and of an unnamed container + if (dest == _ptr) { + _ptr = dest; + return; + } + + const uint32_t* iter = nullptr; + container_t id; + ip_t offset = nullptr; + size_t comm_end; + bool reversed = _ptr > dest; + + if (reversed) { + comm_end = 0; + iter = nullptr; + const ContainerData* old_iter = nullptr; + const uint32_t* last_comm_iter = nullptr; + _container.rev_iter(old_iter); + + // find commen part of old and new stack + while (_story->iterate_containers(iter, id, offset)) { + if (old_iter == nullptr || offset >= dest) { + break; + } + if (old_iter != nullptr && id == old_iter->id) { + last_comm_iter = iter; + _container.rev_iter(old_iter); + ++comm_end; + } + } + + // clear old part from stack + while (_container.size() > comm_end) { + _container.pop(); + } + iter = last_comm_iter; + + } else { + iter = nullptr; + comm_end = _container.size(); + // go to current possition in container list + while (_story->iterate_containers(iter, id, offset)) { + if (offset >= _ptr) { + break; + } + } + _story->iterate_containers(iter, id, offset, true); + } + + // move to destination and update container stack on the go + while (_story->iterate_containers(iter, id, offset)) { + if (offset >= dest) { + break; + } + if (_container.empty() || _container.top().id != id) { + _container.push({.id = id, .offset = offset}); + } else { + _container.pop(); + if (_container.size() < comm_end) { + comm_end = _container.size(); + } + } + } + _ptr = dest; + + // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container + // it will get visited in the next step + if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { + _ptr += 6; + _container.push({.id = id, .offset = offset}); + if (reversed && comm_end == _container.size() - 1) { + ++comm_end; + } + } + + // iff all container (until now) are entered at first position + bool allEnteredAtStart = true; + ip_t child_position = dest; + if (record_visits) { + const ContainerData* iData = nullptr; + size_t level = _container.size(); + while (_container.iter(iData) + && (level > comm_end + || _story->container_flag(iData->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST) + ) { + auto parrent_offset = iData->offset; + inkAssert(child_position >= parrent_offset, "Container stack order is broken"); + // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed + // subcontainers first child check if child_positino is the first child of current container + allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); + child_position = parrent_offset; + _globals->visit(iData->id, allEnteredAtStart); + } + } +} - void runner_impl::jump(ip_t dest, bool record_visits) - { - // Optimization: if we are _is_falling, then we can - // _should be_ able to safely assume that there is nothing to do here. A falling - // divert should only be taking us from a container to that same container's end point - // without entering any other containers - // OR IF if target is same position do nothing - // could happend if jumping to and of an unnamed container - if (dest == _ptr) { - _ptr = dest; - return; - } - - const uint32_t* iter = nullptr; - container_t id; - ip_t offset = nullptr; - size_t comm_end; - bool reversed = _ptr > dest; - - if (reversed) { - comm_end = 0; - iter = nullptr; - const ContainerData* old_iter = nullptr; - const uint32_t* last_comm_iter = nullptr; - _container.rev_iter(old_iter); - - // find commen part of old and new stack - while(_story->iterate_containers(iter, id, offset)) { - if(old_iter == nullptr || offset >= dest) { break; } - if(old_iter !=nullptr && id == old_iter->id) { - last_comm_iter = iter; - _container.rev_iter(old_iter); - ++comm_end; - } - } - - // clear old part from stack - while(_container.size() > comm_end) { _container.pop(); } - iter = last_comm_iter; - - } else { - iter = nullptr; - comm_end = _container.size(); - // go to current possition in container list - while(_story->iterate_containers(iter, id, offset)) { - if(offset >= _ptr) {break;} - } - _story->iterate_containers(iter, id, offset, true); - } - - // move to destination and update container stack on the go - while(_story->iterate_containers(iter, id, offset)) { - if (offset >= dest) { break; } - if(_container.empty() || _container.top().id != id) { - _container.push({.id = id, .offset = offset}); - } else { - _container.pop(); - if (_container.size() < comm_end) { - comm_end = _container.size(); - } - } - } - _ptr = dest; - - // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - // it will get visited in the next step - if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { - _ptr += 6; - _container.push({.id=id, .offset=offset}); - if (reversed && comm_end == _container.size() - 1) { ++comm_end; } - } - - // iff all container (until now) are entered at first position - bool allEnteredAtStart = true; - ip_t child_position = dest; - if(record_visits) { - const ContainerData* iData = nullptr; - size_t level = _container.size(); - while(_container.iter(iData) && - (level > comm_end || _story->container_flag(iData->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST )) - { - auto parrent_offset = iData->offset; - inkAssert(child_position >= parrent_offset, "Container stack order is broken"); - // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed subcontainers first child - // check if child_positino is the first child of current container - allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); - child_position = parrent_offset; - _globals->visit(iData->id, allEnteredAtStart); - } - } - - - - } - template - void runner_impl::start_frame(uint32_t target) { - if constexpr (type == frame_type::function) { - // add a function start marker - _output << values::func_start; - } - // Push next address onto the callstack - { - size_t address = _ptr - _story->instructions(); - _stack.push_frame(address, _evaluation_mode); - _ref_stack.push_frame(address, _evaluation_mode); - } - _evaluation_mode = false; // unset eval mode when enter function or tunnel - - // Do the jump - inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); - jump(_story->instructions() + target, true); - } +template +void runner_impl::start_frame(uint32_t target) +{ + if constexpr (type == frame_type::function) { + // add a function start marker + _output << values::func_start; + } + // Push next address onto the callstack + { + size_t address = _ptr - _story->instructions(); + _stack.push_frame(address, _evaluation_mode); + _ref_stack.push_frame(address, _evaluation_mode); + } + _evaluation_mode = false; // unset eval mode when enter function or tunnel + + // Do the jump + inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); + jump(_story->instructions() + target, true); +} - frame_type runner_impl::execute_return() - { - // Pop the callstack - _ref_stack.fetch_values(_stack); - frame_type type; - offset_t offset = _stack.pop_frame(&type,_evaluation_mode); - _ref_stack.push_values(_stack); - { frame_type t; bool eval; - // TODO: write all refs to new frame - offset_t o = _ref_stack.pop_frame(&t, eval); - inkAssert(o == offset && t == type && eval == _evaluation_mode, - "_ref_stack and _stack should be in frame sync!"); - } - - // SPECIAL: On function, do a trim - if (type == frame_type::function) - _output << values::func_end; - else if(type == frame_type::tunnel) { - // if we return but there is a divert target on top of - // the evaluation stack, we should follow this instead - // inkproof: I060 - if(!_eval.is_empty() && _eval.top().type() == value_type::divert) { - start_frame(_eval.pop().get()); - return type; - } - } - - - // Jump to the old offset - inkAssert(_story->instructions() + offset < _story->end(), "Callstack return is outside bounds of story!"); - jump(_story->instructions() + offset, false); - - // Return frame type - return type; - } +frame_type runner_impl::execute_return() +{ + // Pop the callstack + _ref_stack.fetch_values(_stack); + frame_type type; + offset_t offset = _stack.pop_frame(&type, _evaluation_mode); + _ref_stack.push_values(_stack); + { + frame_type t; + bool eval; + // TODO: write all refs to new frame + offset_t o = _ref_stack.pop_frame(&t, eval); + inkAssert( + o == offset && t == type && eval == _evaluation_mode, + "_ref_stack and _stack should be in frame sync!" + ); + } + + // SPECIAL: On function, do a trim + if (type == frame_type::function) { + _output << values::func_end; + } else if (type == frame_type::tunnel) { + // if we return but there is a divert target on top of + // the evaluation stack, we should follow this instead + // inkproof: I060 + if (! _eval.is_empty() && _eval.top().type() == value_type::divert) { + start_frame(_eval.pop().get()); + return type; + } + } + + + // Jump to the old offset + inkAssert( + _story->instructions() + offset < _story->end(), + "Callstack return is outside bounds of story!" + ); + jump(_story->instructions() + offset, false); + + // Return frame type + return type; +} - runner_impl::runner_impl(const story_impl* data, globals global) - : _story(data), _globals(global.cast()), - _operations( - global.cast()->strings(), - global.cast()->lists(), - _rng, - *global.cast(), - *data, - static_cast(*this)), - _backup(nullptr), _done(nullptr), _choices(), _container(ContainerData{}), _rng(time(NULL)) - { - _ptr = _story->instructions(); - _evaluation_mode = false; - _choice_tags_begin = -1; - - // register with globals - _globals->add_runner(this); - if(_globals->lists()) { - _output.set_list_meta(_globals->lists()); - } - - // initialize globals if necessary - if (!_globals->are_globals_initialized()) - { - _globals->initialize_globals(this); - - // Set us back to the beginning of the story - reset(); - _ptr = _story->instructions(); - } - } +runner_impl::runner_impl(const story_impl* data, globals global) + : _story(data) + , _globals(global.cast()) + , _operations( + global.cast()->strings(), global.cast()->lists(), _rng, + *global.cast(), *data, static_cast(*this) + ) + , _backup(nullptr) + , _done(nullptr) + , _choices() + , _container(ContainerData{}) + , _rng(time(NULL)) +{ + _ptr = _story->instructions(); + _evaluation_mode = false; + _choice_tags_begin = -1; + + // register with globals + _globals->add_runner(this); + if (_globals->lists()) { + _output.set_list_meta(_globals->lists()); + } + + // initialize globals if necessary + if (! _globals->are_globals_initialized()) { + _globals->initialize_globals(this); + + // Set us back to the beginning of the story + reset(); + _ptr = _story->instructions(); + } +} - runner_impl::~runner_impl() - { - // unregister with globals - _globals->remove_runner(this); - } +runner_impl::~runner_impl() +{ + // unregister with globals + _globals->remove_runner(this); +} #ifdef INK_ENABLE_STL - std::string runner_impl::getline() - { - std::string result{""}; - bool fill = false; - do { - if (fill) { - result += " "; - } - // Advance interpreter one line - advance_line(); - // Read line into std::string - result += _output.get(); - fill = _output.last_char() == ' '; - } while(_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice - if(!has_choices() && _fallback_choice) { choose(~0); } - - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getline!"); - return result; - } +std::string runner_impl::getline() +{ + std::string result{""}; + bool fill = false; + do { + if (fill) { + result += " "; + } + // Advance interpreter one line + advance_line(); + // Read line into std::string + result += _output.get(); + fill = _output.last_char() == ' '; + } while (_ptr != nullptr && _output.last_char() != '\n'); + + // TODO: fallback choice = no choice + if (! has_choices() && _fallback_choice) { + choose(~0); + } + + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getline!"); + return result; +} - void runner_impl::getline(std::ostream& out) - { - bool fill = false; - do { - if (fill) { out << " "; } - // Advance interpreter one line - advance_line(); - // Write into out - out << _output; - fill = _output.last_char() == ' '; - } while(_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice - if(!has_choices() && _fallback_choice) { choose(~0); } - - // Make sure we read everything - inkAssert(_output.is_empty(), "Output should be empty after getline!"); - } +void runner_impl::getline(std::ostream& out) +{ + bool fill = false; + do { + if (fill) { + out << " "; + } + // Advance interpreter one line + advance_line(); + // Write into out + out << _output; + fill = _output.last_char() == ' '; + } while (_ptr != nullptr && _output.last_char() != '\n'); + + // TODO: fallback choice = no choice + if (! has_choices() && _fallback_choice) { + choose(~0); + } + + // Make sure we read everything + inkAssert(_output.is_empty(), "Output should be empty after getline!"); +} - std::string runner_impl::getall() - { - // Advance interpreter until we're stopped - std::stringstream str; - while(can_continue()) { - getline(str); - } +std::string runner_impl::getall() +{ + // Advance interpreter until we're stopped + std::stringstream str; + while (can_continue()) { + getline(str); + } - // Read output into std::string + // Read output into std::string - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getall!"); - return str.str(); - } + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getall!"); + return str.str(); +} - void runner_impl::getall(std::ostream& out) - { - // Advance interpreter until we're stopped - while (can_continue()) - advance_line(); +void runner_impl::getall(std::ostream& out) +{ + // Advance interpreter until we're stopped + while (can_continue()) { + advance_line(); + } - // Send output into stream - out << _output; + // Send output into stream + out << _output; - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getall!"); - } + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getall!"); +} #endif #ifdef INK_ENABLE_UNREAL - FString runner_impl::getline() - { - clear_tags(); - FString result{}; - bool fill = false; - do { - if ( fill ) { - result += " "; - } - // Advance interpreter one line - advance_line(); - // Read lin ve into std::string - const char* str = _output.get_alloc(_globals->strings(), _globals->lists()); - result.Append( str, c_str_len( str ) ); - fill = _output.last_char() == ' '; - } while ( _ptr != nullptr && _output.last_char() != '\n' ); - - // TODO: fallback choice = no choice - if ( !has_choices() && _fallback_choice ) { choose( ~0 ); } - - // Return result - inkAssert( _output.is_empty(), "Output should be empty after getline!" ); - return result; - } +FString runner_impl::getline() +{ + clear_tags(); + FString result{}; + bool fill = false; + do { + if (fill) { + result += " "; + } + // Advance interpreter one line + advance_line(); + // Read lin ve into std::string + const char* str = _output.get_alloc(_globals->strings(), _globals->lists()); + result.Append(str, c_str_len(str)); + fill = _output.last_char() == ' '; + } while (_ptr != nullptr && _output.last_char() != '\n'); + + // TODO: fallback choice = no choice + if (! has_choices() && _fallback_choice) { + choose(~0); + } + + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getline!"); + return result; +} #endif - void runner_impl::advance_line() - { - // Step while we still have instructions to execute - while (_ptr != nullptr) - { - // Stop if we hit a new line - if (line_step()) - break; - } - - // can be in save state becaues of choice - // Garbage collection TODO: How often do we want to do this? - _globals->gc(); - } +void runner_impl::advance_line() +{ + // Step while we still have instructions to execute + while (_ptr != nullptr) { + // Stop if we hit a new line + if (line_step()) { + break; + } + } + + // can be in save state becaues of choice + // Garbage collection TODO: How often do we want to do this? + _globals->gc(); +} - bool runner_impl::can_continue() const - { - return _ptr != nullptr; - } +bool runner_impl::can_continue() const +{ + return _ptr != nullptr; +} - void runner_impl::choose(size_t index) - { - if(has_choices()) { - inkAssert(index < _choices.size(), "Choice index out of range"); - } - restore(); // restore to stack state when choice was maked - _globals->turn(); - // Get the choice - const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); - - // Get its thread - thread_t choiceThread = c._thread; - - // Figure out where our previous pointer was for that thread - ip_t prev = nullptr; - if (choiceThread == ~0) - prev = _done; - else - prev = _threads.get(choiceThread); - - // Make sure we have a previous pointer - inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output"); - - // Move to the previous pointer so we track our movements correctly - jump(prev, false); - _done = nullptr; - - // Collapse callstacks to the correct thread - _stack.collapse_to_thread(choiceThread); - _ref_stack.collapse_to_thread(choiceThread); - _threads.clear(); - - // Jump to destination and clear choice list - jump(_story->instructions() + c.path(), true); - clear_choices(); - clear_tags(); - } +void runner_impl::choose(size_t index) +{ + if (has_choices()) { + inkAssert(index < _choices.size(), "Choice index out of range"); + } + restore(); // restore to stack state when choice was maked + _globals->turn(); + // Get the choice + const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); + + // Get its thread + thread_t choiceThread = c._thread; + + // Figure out where our previous pointer was for that thread + ip_t prev = nullptr; + if (choiceThread == ~0) { + prev = _done; + } else { + prev = _threads.get(choiceThread); + } + + // Make sure we have a previous pointer + inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output"); + + // Move to the previous pointer so we track our movements correctly + jump(prev, false); + _done = nullptr; + + // Collapse callstacks to the correct thread + _stack.collapse_to_thread(choiceThread); + _ref_stack.collapse_to_thread(choiceThread); + _threads.clear(); + + // Jump to destination and clear choice list + jump(_story->instructions() + c.path(), true); + clear_choices(); + clear_tags(); +} - void runner_impl::getline_silent() - { - // advance and clear output stream - advance_line(); - _output.clear(); - } +void runner_impl::getline_silent() +{ + // advance and clear output stream + advance_line(); + _output.clear(); +} - bool runner_impl::has_tags() const - { - return num_tags() > 0; - } +bool runner_impl::has_tags() const +{ + return num_tags() > 0; +} - size_t runner_impl::num_tags() const - { - return _choice_tags_begin < 0 ? _tags.size() : _choice_tags_begin; - } +size_t runner_impl::num_tags() const +{ + return _choice_tags_begin < 0 ? _tags.size() : _choice_tags_begin; +} - const char* runner_impl::get_tag(size_t index) const - { - inkAssert(index < _tags.size(), "Tag index exceeds _num_tags"); - return _tags[index]; - } +const char* runner_impl::get_tag(size_t index) const +{ + inkAssert(index < _tags.size(), "Tag index exceeds _num_tags"); + return _tags[index]; +} - snapshot* runner_impl::create_snapshot() const - { - return _globals->create_snapshot(); - } +snapshot* runner_impl::create_snapshot() const +{ + return _globals->create_snapshot(); +} - size_t runner_impl::snap(unsigned char* data, snapper& snapper) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - snapper.current_runner_tags = _tags[0].ptr(); - std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; - ptr = snap_write(ptr, offset, should_write); - offset = _backup - _story->instructions(); - ptr = snap_write(ptr, offset, should_write); - offset = _done - _story->instructions(); - ptr = snap_write(ptr, offset, should_write); - ptr = snap_write(ptr, _rng.get_state(), should_write); - ptr = snap_write(ptr, _evaluation_mode, should_write); - ptr = snap_write(ptr, _string_mode, should_write); - ptr = snap_write(ptr, _saved_evaluation_mode, should_write); - ptr = snap_write(ptr, _saved, should_write); - ptr = snap_write(ptr, _is_falling, should_write); - ptr += _output.snap(data ? ptr : nullptr, snapper); - ptr += _stack.snap(data ? ptr : nullptr, snapper); - ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); - ptr += _eval.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _choice_tags_begin, should_write); - ptr += _tags.snap(data ? ptr : nullptr, snapper); - ptr += _container.snap(data ? ptr : nullptr, snapper); - ptr += _threads.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); - if (_fallback_choice) { - ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper); - } - ptr += _choices.snap(data ? ptr : nullptr, snapper); - return ptr - data; - } +size_t runner_impl::snap(unsigned char* data, snapper& snapper) const +{ + unsigned char* ptr = data; + bool should_write = data != nullptr; + snapper.current_runner_tags = _tags[0].ptr(); + std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; + ptr = snap_write(ptr, offset, should_write); + offset = _backup - _story->instructions(); + ptr = snap_write(ptr, offset, should_write); + offset = _done - _story->instructions(); + ptr = snap_write(ptr, offset, should_write); + ptr = snap_write(ptr, _rng.get_state(), should_write); + ptr = snap_write(ptr, _evaluation_mode, should_write); + ptr = snap_write(ptr, _string_mode, should_write); + ptr = snap_write(ptr, _saved_evaluation_mode, should_write); + ptr = snap_write(ptr, _saved, should_write); + ptr = snap_write(ptr, _is_falling, should_write); + ptr += _output.snap(data ? ptr : nullptr, snapper); + ptr += _stack.snap(data ? ptr : nullptr, snapper); + ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); + ptr += _eval.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _choice_tags_begin, should_write); + ptr += _tags.snap(data ? ptr : nullptr, snapper); + ptr += _container.snap(data ? ptr : nullptr, snapper); + ptr += _threads.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); + if (_fallback_choice) { + ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper); + } + ptr += _choices.snap(data ? ptr : nullptr, snapper); + return ptr - data; +} - const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader) - { - auto ptr = data; - std::uintptr_t offset; - ptr = snap_read(ptr, offset); - _ptr = offset == 0 ? nullptr : _story->instructions() + offset; - ptr = snap_read(ptr, offset); - _backup = _story->instructions() + offset; - ptr = snap_read(ptr, offset); - _done = _story->instructions() + offset; - int32_t seed; - ptr = snap_read(ptr, seed); - _rng.srand(seed); - ptr = snap_read(ptr, _evaluation_mode); - ptr = snap_read(ptr, _string_mode); - ptr = snap_read(ptr, _saved_evaluation_mode); - ptr = snap_read(ptr, _saved); - ptr = snap_read(ptr, _is_falling); - ptr = _output.snap_load(ptr, loader); - ptr = _stack.snap_load(ptr, loader); - ptr = _ref_stack.snap_load(ptr, loader); - ptr = _eval.snap_load(ptr, loader); - int choice_tags_begin; - ptr = snap_read(ptr, choice_tags_begin); - _choice_tags_begin = choice_tags_begin; - ptr = _tags.snap_load(ptr, loader); - loader.current_runner_tags = _tags[0].ptr(); - ptr = _container.snap_load(ptr, loader); - ptr = _threads.snap_load(ptr, loader); - bool has_fallback_choice; - ptr = snap_read(ptr, has_fallback_choice); - _fallback_choice = nullopt; - if (has_fallback_choice) { - _fallback_choice.emplace(); - ptr = _fallback_choice.value().snap_load(ptr, loader); - } - ptr = _choices.snap_load(ptr, loader); - return ptr; - } +const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader) +{ + auto ptr = data; + std::uintptr_t offset; + ptr = snap_read(ptr, offset); + _ptr = offset == 0 ? nullptr : _story->instructions() + offset; + ptr = snap_read(ptr, offset); + _backup = _story->instructions() + offset; + ptr = snap_read(ptr, offset); + _done = _story->instructions() + offset; + int32_t seed; + ptr = snap_read(ptr, seed); + _rng.srand(seed); + ptr = snap_read(ptr, _evaluation_mode); + ptr = snap_read(ptr, _string_mode); + ptr = snap_read(ptr, _saved_evaluation_mode); + ptr = snap_read(ptr, _saved); + ptr = snap_read(ptr, _is_falling); + ptr = _output.snap_load(ptr, loader); + ptr = _stack.snap_load(ptr, loader); + ptr = _ref_stack.snap_load(ptr, loader); + ptr = _eval.snap_load(ptr, loader); + int choice_tags_begin; + ptr = snap_read(ptr, choice_tags_begin); + _choice_tags_begin = choice_tags_begin; + ptr = _tags.snap_load(ptr, loader); + loader.current_runner_tags = _tags[0].ptr(); + ptr = _container.snap_load(ptr, loader); + ptr = _threads.snap_load(ptr, loader); + bool has_fallback_choice; + ptr = snap_read(ptr, has_fallback_choice); + _fallback_choice = nullopt; + if (has_fallback_choice) { + _fallback_choice.emplace(); + ptr = _fallback_choice.value().snap_load(ptr, loader); + } + ptr = _choices.snap_load(ptr, loader); + return ptr; +} #ifdef INK_ENABLE_CSTD - char* runner_impl::getline_alloc() - { - /// TODO - inkFail("Not implemented yet!"); - return nullptr; - } +char* runner_impl::getline_alloc() +{ + /// TODO + inkFail("Not implemented yet!"); + return nullptr; +} #endif - bool runner_impl::move_to(hash_t path) - { - // find the path - ip_t destination = _story->find_offset_for(path); - if (destination == nullptr) - { - // TODO: Error state? - return false; - } - - // Clear state and move to destination - reset(); - _ptr = _story->instructions(); - jump(destination, false); - - return true; - } - - void runner_impl::internal_bind(hash_t name, internal::function_base* function) - { - _functions.add(name, function); - } - - runner_impl::change_type runner_impl::detect_change() const - { - // Check if the old newline is still present (hasn't been glu'd) and - // if there is new text (non-whitespace) in the stream since saving - bool stillHasNewline = _output.saved_ends_with(value_type::newline); - bool hasAddedNewText = _output.text_past_save() || _tags.has_changed(); - - // Newline is still there and there's no new text - if (stillHasNewline && !hasAddedNewText) - return change_type::no_change; - - // If the newline is gone, we got glue'd. Continue as if we never had that newline - if (!stillHasNewline) - return change_type::newline_removed; - - // TODO New Tags -> extended +bool runner_impl::move_to(hash_t path) +{ + // find the path + ip_t destination = _story->find_offset_for(path); + if (destination == nullptr) { + // TODO: Error state? + return false; + } + + // Clear state and move to destination + reset(); + _ptr = _story->instructions(); + jump(destination, false); + + return true; +} - // If there's new text content, we went too far - if (hasAddedNewText) - return change_type::extended_past_newline; +void runner_impl::internal_bind(hash_t name, internal::function_base* function) +{ + _functions.add(name, function); +} - inkFail("Invalid change detction. Never should be here!"); - return change_type::no_change; - } +runner_impl::change_type runner_impl::detect_change() const +{ + // Check if the old newline is still present (hasn't been glu'd) and + // if there is new text (non-whitespace) in the stream since saving + bool stillHasNewline = _output.saved_ends_with(value_type::newline); + bool hasAddedNewText = _output.text_past_save() || _tags.has_changed(); + + // Newline is still there and there's no new text + if (stillHasNewline && ! hasAddedNewText) { + return change_type::no_change; + } + + // If the newline is gone, we got glue'd. Continue as if we never had that newline + if (! stillHasNewline) { + return change_type::newline_removed; + } + + // TODO New Tags -> extended + + // If there's new text content, we went too far + if (hasAddedNewText) { + return change_type::extended_past_newline; + } + + inkFail("Invalid change detction. Never should be here!"); + return change_type::no_change; +} - bool runner_impl::line_step() - { - // Step the interpreter - step(); - - // If we're not within string evaluation - if (!_output.has_marker()) - { - // If we have a saved state after a previous newline - // don't do this if we behind choice - if (_saved && !has_choices() && !_fallback_choice) - { - // Check for changes in the output stream - switch (detect_change()) - { - case change_type::extended_past_newline: - // We've gone too far. Restore to before we moved past the newline and return that we are done - restore(); - return true; - case change_type::newline_removed: - // Newline was removed. Proceed as if we never hit it - forget(); - break; - case change_type::no_change: - break; - } - } - - // If we're on a newline - if (_output.ends_with(value_type::newline)) - { - // Unless we are out of content, we are going to try - // to continue a little further. This is to check for - // glue (which means there is potentially more content - // in this line) OR for non-text content such as choices. - if (_ptr != nullptr) - { - // Save a snapshot of the current runtime state so we - // can return here if we end up hitting a new line - forget(); - save(); - } - // Otherwise, make sure we don't have any snapshots hanging around - // expect we are in choice handleing - else if( !has_choices() && !_fallback_choice) { - forget(); - } else { - _output.forget(); - } - } - } - - return false; - } +bool runner_impl::line_step() +{ + // Step the interpreter + step(); + + // If we're not within string evaluation + if (! _output.has_marker()) { + // If we have a saved state after a previous newline + // don't do this if we behind choice + if (_saved && ! has_choices() && ! _fallback_choice) { + // Check for changes in the output stream + switch (detect_change()) { + case change_type::extended_past_newline: + // We've gone too far. Restore to before we moved past the newline and return that we are + // done + restore(); + return true; + case change_type::newline_removed: + // Newline was removed. Proceed as if we never hit it + forget(); + break; + case change_type::no_change: break; + } + } + + // If we're on a newline + if (_output.ends_with(value_type::newline)) { + // Unless we are out of content, we are going to try + // to continue a little further. This is to check for + // glue (which means there is potentially more content + // in this line) OR for non-text content such as choices. + if (_ptr != nullptr) { + // Save a snapshot of the current runtime state so we + // can return here if we end up hitting a new line + forget(); + save(); + } + // Otherwise, make sure we don't have any snapshots hanging around + // expect we are in choice handleing + else if (! has_choices() && ! _fallback_choice) { + forget(); + } else { + _output.forget(); + } + } + } + + return false; +} - void runner_impl::step() - { +void runner_impl::step() +{ #ifndef INK_ENABLE_UNREAL - try + try #endif - { - inkAssert(_ptr != nullptr, "Can not step! Do not have a valid pointer"); - - // Load current command - Command cmd = read(); - CommandFlag flag = read(); - - // If we're falling and we hit a non-fallthrough command, stop the fall. - if (_is_falling && !((cmd == Command::DIVERT && flag & CommandFlag::DIVERT_IS_FALLTHROUGH) || cmd == Command::END_CONTAINER_MARKER)) - { - _is_falling = false; - set_done_ptr(nullptr); - } - if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) - { - _operations(cmd, _eval); - } - else switch (cmd) - { - // == Value Commands == - case Command::STR: - { - const char* str = read(); - if (_evaluation_mode) - _eval.push(value{}.set(str)); - else - _output << value{}.set(str); - } - break; - case Command::INT: - { - int val = read(); - if (_evaluation_mode) - _eval.push(value{}.set(val)); - // TEST-CASE B006 don't print integers - } - break; - case Command::BOOL: - { - bool val = read() ? true : false; - if(_evaluation_mode) - _eval.push(value{}.set(val)); - else - _output << value{}.set(val); - } - break; - case Command::FLOAT: - { - float val = read(); - if (_evaluation_mode) - _eval.push(value{}.set(val)); - // TEST-CASE B006 don't print floats - } break; - case Command::VALUE_POINTER: - { - hash_t val = read(); - if(_evaluation_mode) { - _eval.push(value{}.set(val, static_cast(flag) - 1)); - } else { - inkFail("never conciderd what should happend here! (value pointer print)"); - } - } - break; - case Command::LIST: - { - list_table::list list(read()); - if(_evaluation_mode) - _eval.push(value{}.set(list)); - else { - char* str = _globals->strings().create(_globals->lists().stringLen( - list)+1); - _globals->lists().toString(str, list)[0] = 0; - _output << value{}.set(str); - } - } - break; - case Command::DIVERT_VAL: - { - inkAssert(_evaluation_mode, "Can not push divert value into the output stream!"); - - // Push the divert target onto the stack - uint32_t target = read(); - _eval.push(value{}.set(target)); - } - break; - case Command::NEWLINE: - { - if (_evaluation_mode) - _eval.push(values::newline); - else - _output << values::newline; - } - break; - case Command::GLUE: - { - if (_evaluation_mode) - _eval.push(values::glue); - else - _output << values::glue; - } - break; - case Command::VOID: - { - if (_evaluation_mode) - _eval.push(values::null); // TODO: void type? - } - break; - - // == Divert commands - case Command::DIVERT: - { - // Find divert address - uint32_t target = read(); - - // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().truthy(_globals->lists())) - break; - - // SPECIAL: Fallthrough divert. We're starting to fall out of containers - if (flag & CommandFlag::DIVERT_IS_FALLTHROUGH && !_is_falling) - { - // Record the position of the instruction pointer at the first fallthrough. - // We'll use this if we run out of content and hit an implied "done" to restore - // our position when a choice is chosen. See ::choose - set_done_ptr(_ptr); - _is_falling = true; - } - - // If we're falling out of the story, then we're hitting an implied done - if (_is_falling && _story->instructions() + target == _story->end()) { - // Wait! We may be returning from a function! - frame_type type; - if (_stack.has_frame(&type) && type == frame_type::function) // implicit return is only for functions - { - // push null and return - _eval.push(values::null); - - // HACK - _ptr += sizeof(Command) + sizeof(CommandFlag); - execute_return(); - } - else - { - on_done(false); - } - break; - } - - // Do the jump - inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); - jump(_story->instructions() + target, true); - } - break; - case Command::DIVERT_TO_VARIABLE: - { - // Get variable value - hash_t variable = read(); - - // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && !_eval.pop().truthy(_globals->lists())) - break; - - const value* val = get_var(variable); - inkAssert(val, "Jump destiniation needs to be defined!"); - - // Move to location - jump(_story->instructions() + val->get(), true); - inkAssert(_ptr < _story->end(), "Diverted past end of story data!"); - } - break; - - // == Terminal commands - case Command::DONE: - on_done(true); - break; - - case Command::END: - _ptr = nullptr; - break; - - // == Tunneling - case Command::TUNNEL: - { - uint32_t target; - // Find divert address - if(flag & CommandFlag::TUNNEL_TO_VARIABLE) { - hash_t var_name = read(); - const value* val = get_var(var_name); - inkAssert(val != nullptr, "Variable containing tunnel target could not be found!"); - target = val->get(); - } else { - target = read(); - } - start_frame(target); - } - break; - case Command::FUNCTION: - { - uint32_t target; - // Find divert address - if(flag & CommandFlag::FUNCTION_TO_VARIABLE) { - hash_t var_name = read(); - const value* val = get_var(var_name); - inkAssert(val != nullptr, "Varibale containing function could not be found!"); - target = val->get(); - } else { - target = read(); - } - if (!(flag & CommandFlag::FALLBACK_FUNCTION)) { - start_frame(target); - } else { - inkAssert(!_eval.is_empty(), "fallback function but no function call before?"); - if(_eval.top_value().type() == value_type::ex_fn_not_found) { - _eval.pop(); - inkAssert(target != 0, "Exetrnal function was not binded, and no fallback function provided!"); - start_frame(target); - } - } - } - break; - case Command::TUNNEL_RETURN: - case Command::FUNCTION_RETURN: - { - execute_return(); - } - break; - - case Command::THREAD: - { - // Push a thread frame so we can return easily - // TODO We push ahead of a single divert. Is that correct in all cases....????? - auto returnTo = _ptr + CommandSize; - _stack.push_frame(returnTo - _story->instructions(), _evaluation_mode); - _ref_stack.push_frame(returnTo - _story->instructions(), _evaluation_mode); - - // Fork a new thread on the callstack - thread_t thread = _stack.fork_thread(); - { - thread_t t = _ref_stack.fork_thread(); - inkAssert(t == thread, "ref_stack and stack should be in sync!"); - } - - // Push that thread onto our thread stack - _threads.push(thread); - } - break; - - // == set temporärie variable - case Command::DEFINE_TEMP: - { - hash_t variableName = read(); - bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; - - // Get the top value and put it into the variable - value v = _eval.pop(); - set_var(variableName, v, is_redef); - } - break; - - case Command::SET_VARIABLE: - { - hash_t variableName = read(); - - // Check if it's a redefinition (not yet used, seems important for pointers later?) - bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; - - // If not, we're setting a global (temporary variables are explicitely defined as such, - // where globals are defined using SET_VARIABLE). - value val = _eval.pop(); - if(is_redef) { - set_var(variableName, val, is_redef); - } else { - set_var(variableName, val, is_redef); - } - } - break; - - // == Function calls - case Command::CALL_EXTERNAL: - { - // Read function name - hash_t functionName = read(); - - // Interpret flag as argument count - int numArguments = (int)flag; - - // find and execute. will automatically push a valid if applicable - bool success = _functions.call(functionName, &_eval, numArguments, _globals->strings(), _globals->lists()); - - // If we failed, notify a potential fallback function - if (!success) - { - _eval.push(values::ex_fn_not_found); - } - } - break; - - // == Evaluation stack - case Command::START_EVAL: - _evaluation_mode = true; - break; - case Command::END_EVAL: - _evaluation_mode = false; - - // Assert stack is empty? Is that necessary? - break; - case Command::OUTPUT: - { - value v = _eval.pop(); - _output << v; - } - break; - case Command::POP: - _eval.pop(); - break; - case Command::DUPLICATE: - _eval.push(_eval.top_value()); - break; - case Command::PUSH_VARIABLE_VALUE: - { - // Try to find in local stack - hash_t variableName = read(); - const value* val = get_var(variableName); - - inkAssert(val != nullptr, "Could not find variable!"); - _eval.push(*val); - break; - } - case Command::START_STR: - { - inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!"); - _string_mode = true; - _evaluation_mode = false; - _output << values::marker; - } break; - case Command::END_STR: - { - // TODO: Assert we really had a marker on there? - inkAssert(!_evaluation_mode, "Must be in evaluation mode"); - _string_mode = false; - _evaluation_mode = true; - - // Load value from output stream - // Push onto stack - _eval.push(value{}.set(_output.get_alloc( - _globals->strings(), - _globals->lists()))); - } break; - - case Command::START_TAG: - { - _output << values::marker; - } break; - - case Command::END_TAG: - { - auto tag = _output.get_alloc(_globals->strings(), _globals->lists()); - if(_string_mode && _choice_tags_begin < 0) { - _choice_tags_begin = _tags.size(); - } - _tags.push() = tag; - } break; - - // == Choice commands - case Command::CHOICE: - { - // Read path - uint32_t path = read(); - - // If we're a once only choice, make sure our destination hasn't - // been visited - if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { - // Need to convert offset to container index - container_t destination = -1; - if (_story->get_container_id(_story->instructions() + path, destination)) - { - // Ignore the choice if we've visited the destination before - if (_globals->visits(destination) > 0) - break; - } - else - { - inkAssert(false, "Destination for choice block does not have counting flags."); - } - } - - // Choice is conditional - if (flag & CommandFlag::CHOICE_HAS_CONDITION) { - // Only show if the top of the eval stack is 'truthy' - if(!_eval.pop().truthy(_globals->lists())) - break; - } - - // Use a marker to start compiling the choice text - _output << values::marker; - value stack[2]; - int sc = 0; - - if (flag & CommandFlag::CHOICE_HAS_START_CONTENT) { - stack[sc++] = _eval.pop(); - } - if (flag & CommandFlag::CHOICE_HAS_CHOICE_ONLY_CONTENT) { - stack[sc++] = _eval.pop(); - } - for(;sc;--sc) { _output << stack[sc-1]; } - - // fetch relevant tags - const snap_tag* tags = nullptr; - if (_choice_tags_begin >= 0 && _tags[_tags.size()-1] != nullptr) { - for(tags = _tags.end() - 1; *(tags-1) != nullptr && (tags - _tags.begin()) > _choice_tags_begin; --tags); - _tags.push() = nullptr; - } - - // Create choice and record it - if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) { - _fallback_choice - = choice{}.setup(_output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread(), tags->ptr()); - } else { - add_choice().setup(_output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread(), tags->ptr()); - } - // save stack at last choice - if(_saved) { forget(); } - save(); - } break; - case Command::START_CONTAINER_MARKER: - { - // Keep track of current container - auto index = read(); - // offset points to command, command has size 6 - _container.push({.id=index, .offset=_ptr - 6}); - - // Increment visit count - if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) - { - _globals->visit(_container.top().id, true); - } - - } break; - case Command::END_CONTAINER_MARKER: - { - container_t index = read(); - - inkAssert( - _container.top().id == index, "Leaving container we are not in!" - ); - - // Move up out of the current container - _container.pop(); - - // SPECIAL: If we've popped all containers, then there's an implied 'done' command or return - if (_container.empty()) - { - _is_falling = false; - - frame_type type; - if (!_threads.empty()) - { - on_done(false); - break; - } - else if (_stack.has_frame(&type) && type == frame_type::function) // implicit return is only for functions - { - // push null and return - _eval.push(values::null); - - // HACK - _ptr += sizeof(Command) + sizeof(CommandFlag); - execute_return(); - } else if (_ptr == _story->end()){ // check needed, because it colud exist an unnamed toplevel container (empty named container stack != empty container stack) - on_done(true); - } - } - } break; - case Command::VISIT: - { - // Push the visit count for the current container to the top - // is 0-indexed for some reason. idk why but this is what ink expects - _eval.push(value{}.set((int)_globals->visits(_container.top().id) - 1)); - } break; - case Command::TURN: - { - _eval.push(value{}.set((int)_globals->turns())); - } break; - case Command::SEQUENCE: - { - // TODO: The C# ink runtime does a bunch of fancy logic - // to make sure each element is picked at least once in every - // iteration loop. I don't feel like replicating that right now. - // So, let's just return a random number and *shrug* - int sequenceLength = _eval.pop().get(); - int index = _eval.pop().get(); - - _eval.push(value{}.set(static_cast(_rng.rand(sequenceLength)))); - } break; - case Command::SEED: - { - int32_t seed = _eval.pop().get(); - _rng.srand(seed); - - _eval.push(values::null); - } break; - - case Command::READ_COUNT: - { - // Get container index - container_t container = read(); - - // Push the read count for the requested container index - _eval.push(value{}.set((int)_globals->visits(container))); - } break; - case Command::TAG: - { - _tags.push() = read(); - } break; - default: - inkAssert(false, "Unrecognized command!"); - break; - } - - } + { + inkAssert(_ptr != nullptr, "Can not step! Do not have a valid pointer"); + + // Load current command + Command cmd = read(); + CommandFlag flag = read(); + + // If we're falling and we hit a non-fallthrough command, stop the fall. + if (_is_falling + && ! ( + (cmd == Command::DIVERT && flag & CommandFlag::DIVERT_IS_FALLTHROUGH) + || cmd == Command::END_CONTAINER_MARKER + )) { + _is_falling = false; + set_done_ptr(nullptr); + } + if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) { + _operations(cmd, _eval); + } else { + switch (cmd) { + // == Value Commands == + case Command::STR: { + const char* str = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(str)); + } else { + _output << value{}.set(str); + } + } break; + case Command::INT: { + int val = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(val)); + } + // TEST-CASE B006 don't print integers + } break; + case Command::BOOL: { + bool val = read() ? true : false; + if (_evaluation_mode) { + _eval.push(value{}.set(val)); + } else { + _output << value{}.set(val); + } + } break; + case Command::FLOAT: { + float val = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(val)); + } + // TEST-CASE B006 don't print floats + } break; + case Command::VALUE_POINTER: { + hash_t val = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(val, static_cast(flag) - 1)); + } else { + inkFail("never conciderd what should happend here! (value pointer print)"); + } + } break; + case Command::LIST: { + list_table::list list(read()); + if (_evaluation_mode) { + _eval.push(value{}.set(list)); + } else { + char* str = _globals->strings().create(_globals->lists().stringLen(list) + 1); + _globals->lists().toString(str, list)[0] = 0; + _output << value{}.set(str); + } + } break; + case Command::DIVERT_VAL: { + inkAssert(_evaluation_mode, "Can not push divert value into the output stream!"); + + // Push the divert target onto the stack + uint32_t target = read(); + _eval.push(value{}.set(target)); + } break; + case Command::NEWLINE: { + if (_evaluation_mode) { + _eval.push(values::newline); + } else { + _output << values::newline; + } + } break; + case Command::GLUE: { + if (_evaluation_mode) { + _eval.push(values::glue); + } else { + _output << values::glue; + } + } break; + case Command::VOID: { + if (_evaluation_mode) { + _eval.push(values::null); // TODO: void type? + } + } break; + + // == Divert commands + case Command::DIVERT: { + // Find divert address + uint32_t target = read(); + + // Check for condition + if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) { + break; + } + + // SPECIAL: Fallthrough divert. We're starting to fall out of containers + if (flag & CommandFlag::DIVERT_IS_FALLTHROUGH && ! _is_falling) { + // Record the position of the instruction pointer at the first fallthrough. + // We'll use this if we run out of content and hit an implied "done" to restore + // our position when a choice is chosen. See ::choose + set_done_ptr(_ptr); + _is_falling = true; + } + + // If we're falling out of the story, then we're hitting an implied done + if (_is_falling && _story->instructions() + target == _story->end()) { + // Wait! We may be returning from a function! + frame_type type; + if (_stack.has_frame(&type) + && type == frame_type::function) // implicit return is only for functions + { + // push null and return + _eval.push(values::null); + + // HACK + _ptr += sizeof(Command) + sizeof(CommandFlag); + execute_return(); + } else { + on_done(false); + } + break; + } + + // Do the jump + inkAssert( + _story->instructions() + target < _story->end(), "Diverting past end of story data!" + ); + jump(_story->instructions() + target, true); + } break; + case Command::DIVERT_TO_VARIABLE: { + // Get variable value + hash_t variable = read(); + + // Check for condition + if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) { + break; + } + + const value* val = get_var(variable); + inkAssert(val, "Jump destiniation needs to be defined!"); + + // Move to location + jump(_story->instructions() + val->get(), true); + inkAssert(_ptr < _story->end(), "Diverted past end of story data!"); + } break; + + // == Terminal commands + case Command::DONE: on_done(true); break; + + case Command::END: _ptr = nullptr; break; + + // == Tunneling + case Command::TUNNEL: { + uint32_t target; + // Find divert address + if (flag & CommandFlag::TUNNEL_TO_VARIABLE) { + hash_t var_name = read(); + const value* val = get_var(var_name); + inkAssert(val != nullptr, "Variable containing tunnel target could not be found!"); + target = val->get(); + } else { + target = read(); + } + start_frame(target); + } break; + case Command::FUNCTION: { + uint32_t target; + // Find divert address + if (flag & CommandFlag::FUNCTION_TO_VARIABLE) { + hash_t var_name = read(); + const value* val = get_var(var_name); + inkAssert(val != nullptr, "Varibale containing function could not be found!"); + target = val->get(); + } else { + target = read(); + } + if (! (flag & CommandFlag::FALLBACK_FUNCTION)) { + start_frame(target); + } else { + inkAssert(! _eval.is_empty(), "fallback function but no function call before?"); + if (_eval.top_value().type() == value_type::ex_fn_not_found) { + _eval.pop(); + inkAssert( + target != 0, + "Exetrnal function was not binded, and no fallback function provided!" + ); + start_frame(target); + } + } + } break; + case Command::TUNNEL_RETURN: + case Command::FUNCTION_RETURN: { + execute_return(); + } break; + + case Command::THREAD: { + // Push a thread frame so we can return easily + // TODO We push ahead of a single divert. Is that correct in all cases....????? + auto returnTo = _ptr + CommandSize; + _stack.push_frame( + returnTo - _story->instructions(), _evaluation_mode + ); + _ref_stack.push_frame( + returnTo - _story->instructions(), _evaluation_mode + ); + + // Fork a new thread on the callstack + thread_t thread = _stack.fork_thread(); + { + thread_t t = _ref_stack.fork_thread(); + inkAssert(t == thread, "ref_stack and stack should be in sync!"); + } + + // Push that thread onto our thread stack + _threads.push(thread); + } break; + + // == set temporärie variable + case Command::DEFINE_TEMP: { + hash_t variableName = read(); + bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; + + // Get the top value and put it into the variable + value v = _eval.pop(); + set_var(variableName, v, is_redef); + } break; + + case Command::SET_VARIABLE: { + hash_t variableName = read(); + + // Check if it's a redefinition (not yet used, seems important for pointers later?) + bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; + + // If not, we're setting a global (temporary variables are explicitely defined as such, + // where globals are defined using SET_VARIABLE). + value val = _eval.pop(); + if (is_redef) { + set_var(variableName, val, is_redef); + } else { + set_var(variableName, val, is_redef); + } + } break; + + // == Function calls + case Command::CALL_EXTERNAL: { + // Read function name + hash_t functionName = read(); + + // Interpret flag as argument count + int numArguments = ( int ) flag; + + // find and execute. will automatically push a valid if applicable + bool success = _functions.call( + functionName, &_eval, numArguments, _globals->strings(), _globals->lists() + ); + + // If we failed, notify a potential fallback function + if (! success) { + _eval.push(values::ex_fn_not_found); + } + } break; + + // == Evaluation stack + case Command::START_EVAL: _evaluation_mode = true; break; + case Command::END_EVAL: + _evaluation_mode = false; + + // Assert stack is empty? Is that necessary? + break; + case Command::OUTPUT: { + value v = _eval.pop(); + _output << v; + } break; + case Command::POP: _eval.pop(); break; + case Command::DUPLICATE: _eval.push(_eval.top_value()); break; + case Command::PUSH_VARIABLE_VALUE: { + // Try to find in local stack + hash_t variableName = read(); + const value* val = get_var(variableName); + + inkAssert(val != nullptr, "Could not find variable!"); + _eval.push(*val); + break; + } + case Command::START_STR: { + inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!"); + _string_mode = true; + _evaluation_mode = false; + _output << values::marker; + } break; + case Command::END_STR: { + // TODO: Assert we really had a marker on there? + inkAssert(! _evaluation_mode, "Must be in evaluation mode"); + _string_mode = false; + _evaluation_mode = true; + + // Load value from output stream + // Push onto stack + _eval.push(value{}.set( + _output.get_alloc(_globals->strings(), _globals->lists()) + )); + } break; + + case Command::START_TAG: { + _output << values::marker; + } break; + + case Command::END_TAG: { + auto tag = _output.get_alloc(_globals->strings(), _globals->lists()); + if (_string_mode && _choice_tags_begin < 0) { + _choice_tags_begin = _tags.size(); + } + _tags.push() = tag; + } break; + + // == Choice commands + case Command::CHOICE: { + // Read path + uint32_t path = read(); + + // If we're a once only choice, make sure our destination hasn't + // been visited + if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { + // Need to convert offset to container index + container_t destination = -1; + if (_story->get_container_id(_story->instructions() + path, destination)) { + // Ignore the choice if we've visited the destination before + if (_globals->visits(destination) > 0) { + break; + } + } else { + inkAssert(false, "Destination for choice block does not have counting flags."); + } + } + + // Choice is conditional + if (flag & CommandFlag::CHOICE_HAS_CONDITION) { + // Only show if the top of the eval stack is 'truthy' + if (! _eval.pop().truthy(_globals->lists())) { + break; + } + } + + // Use a marker to start compiling the choice text + _output << values::marker; + value stack[2]; + int sc = 0; + + if (flag & CommandFlag::CHOICE_HAS_START_CONTENT) { + stack[sc++] = _eval.pop(); + } + if (flag & CommandFlag::CHOICE_HAS_CHOICE_ONLY_CONTENT) { + stack[sc++] = _eval.pop(); + } + for (; sc; --sc) { + _output << stack[sc - 1]; + } + + // fetch relevant tags + const snap_tag* tags = nullptr; + if (_choice_tags_begin >= 0 && _tags[_tags.size() - 1] != nullptr) { + for (tags = _tags.end() - 1; + *(tags - 1) != nullptr && (tags - _tags.begin()) > _choice_tags_begin; --tags) + ; + _tags.push() = nullptr; + } + + // Create choice and record it + if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) { + _fallback_choice = choice{}.setup( + _output, _globals->strings(), _globals->lists(), _choices.size(), path, + current_thread(), tags->ptr() + ); + } else { + add_choice().setup( + _output, _globals->strings(), _globals->lists(), _choices.size(), path, + current_thread(), tags->ptr() + ); + } + // save stack at last choice + if (_saved) { + forget(); + } + save(); + } break; + case Command::START_CONTAINER_MARKER: { + // Keep track of current container + auto index = read(); + // offset points to command, command has size 6 + _container.push({.id = index, .offset = _ptr - 6}); + + // Increment visit count + if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS + || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { + _globals->visit(_container.top().id, true); + } + + } break; + case Command::END_CONTAINER_MARKER: { + container_t index = read(); + + inkAssert(_container.top().id == index, "Leaving container we are not in!"); + + // Move up out of the current container + _container.pop(); + + // SPECIAL: If we've popped all containers, then there's an implied + // 'done' command or return + if (_container.empty()) { + _is_falling = false; + + frame_type type; + if (! _threads.empty()) { + on_done(false); + break; + } else if (_stack.has_frame(&type) && type == frame_type::function) // implicit return + // is only for + // functions + { + // push null and return + _eval.push(values::null); + + // HACK + _ptr += sizeof(Command) + sizeof(CommandFlag); + execute_return(); + } else if (_ptr == _story->end()) { // check needed, because it colud exist an unnamed + // toplevel container (empty named container stack + // != empty container stack) + on_done(true); + } + } + } break; + case Command::VISIT: { + // Push the visit count for the current container to the top + // is 0-indexed for some reason. idk why but this is what ink expects + _eval.push( + value{}.set(( int ) _globals->visits(_container.top().id) - 1) + ); + } break; + case Command::TURN: { + _eval.push(value{}.set(( int ) _globals->turns())); + } break; + case Command::SEQUENCE: { + // TODO: The C# ink runtime does a bunch of fancy logic + // to make sure each element is picked at least once in every + // iteration loop. I don't feel like replicating that right now. + // So, let's just return a random number and *shrug* + int sequenceLength = _eval.pop().get(); + int index = _eval.pop().get(); + + _eval.push(value{}.set(static_cast(_rng.rand(sequenceLength))) + ); + } break; + case Command::SEED: { + int32_t seed = _eval.pop().get(); + _rng.srand(seed); + + _eval.push(values::null); + } break; + + case Command::READ_COUNT: { + // Get container index + container_t container = read(); + + // Push the read count for the requested container index + _eval.push(value{}.set(( int ) _globals->visits(container))); + } break; + case Command::TAG: { + _tags.push() = read(); + } break; + default: inkAssert(false, "Unrecognized command!"); break; + } + } + + } #ifndef INK_ENABLE_UNREAL - catch (...) - { - // Reset our whole state as it's probably corrupt - reset(); - throw; - } + catch (...) { + // Reset our whole state as it's probably corrupt + reset(); + throw; + } #endif - } +} - void runner_impl::on_done(bool setDone) - { - // If we're in a thread - if (!_threads.empty()) - { - // Get the thread ID of the current thread - thread_t completedThreadId = _threads.pop(); - - // Push in a complete marker - _stack.complete_thread(completedThreadId); - _ref_stack.complete_thread(completedThreadId); - - // Go to where the thread started - frame_type type = execute_return(); - inkAssert(type == frame_type::thread, "Expected thread frame marker to hold return to value but none found..."); - // if thread ends, move stave point with, else the thread end marker is missing - // and we can't collect the other threads - if(_saved) { forget(); save(); } - } - else - { - if (setDone) - set_done_ptr(_ptr); - _ptr = nullptr; - } - } +void runner_impl::on_done(bool setDone) +{ + // If we're in a thread + if (! _threads.empty()) { + // Get the thread ID of the current thread + thread_t completedThreadId = _threads.pop(); + + // Push in a complete marker + _stack.complete_thread(completedThreadId); + _ref_stack.complete_thread(completedThreadId); + + // Go to where the thread started + frame_type type = execute_return(); + inkAssert( + type == frame_type::thread, + "Expected thread frame marker to hold return to value but none found..." + ); + // if thread ends, move stave point with, else the thread end marker is missing + // and we can't collect the other threads + if (_saved) { + forget(); + save(); + } + } else { + if (setDone) { + set_done_ptr(_ptr); + } + _ptr = nullptr; + } +} - void runner_impl::set_done_ptr(ip_t ptr) - { - thread_t curr = current_thread(); - if (curr == ~0) { - _done = ptr; - } - else { - _threads.set(curr, ptr); - } - } +void runner_impl::set_done_ptr(ip_t ptr) +{ + thread_t curr = current_thread(); + if (curr == ~0) { + _done = ptr; + } else { + _threads.set(curr, ptr); + } +} - void runner_impl::reset() - { - _eval.clear(); - _output.clear(); - _stack.clear(); - _ref_stack.clear(); - _threads.clear(); - _evaluation_mode = false; - _saved = false; - _choices.clear(); - _ptr = nullptr; - _done = nullptr; - _container.clear(); - } +void runner_impl::reset() +{ + _eval.clear(); + _output.clear(); + _stack.clear(); + _ref_stack.clear(); + _threads.clear(); + _evaluation_mode = false; + _saved = false; + _choices.clear(); + _ptr = nullptr; + _done = nullptr; + _container.clear(); +} - void runner_impl::mark_used(string_table& strings, list_table& lists) const - { - // Find strings in output and stacks - _output.mark_used(strings, lists); - _stack.mark_used(strings, lists); - // ref_stack has no strings and lists! - _eval.mark_used(strings, lists); - - // Take into account tags - for (size_t i = 0; i < _tags.size(); ++i) { - strings.mark_used(_tags[i]); - } - // Take into account choice text - for (size_t i = 0; i < _choices.size(); i++) - strings.mark_used(_choices[i]._text); - } +void runner_impl::mark_used(string_table& strings, list_table& lists) const +{ + // Find strings in output and stacks + _output.mark_used(strings, lists); + _stack.mark_used(strings, lists); + // ref_stack has no strings and lists! + _eval.mark_used(strings, lists); + + // Take into account tags + for (size_t i = 0; i < _tags.size(); ++i) { + strings.mark_used(_tags[i]); + } + // Take into account choice text + for (size_t i = 0; i < _choices.size(); i++) { + strings.mark_used(_choices[i]._text); + } +} - void runner_impl::save() - { - inkAssert(!_saved, "Runner state already saved"); - - _saved = true; - _output.save(); - _stack.save(); - _ref_stack.save(); - _backup = _ptr; - _container.save(); - _globals->save(); - _eval.save(); - _threads.save(); - _choices.save(); - _tags.save(); - _saved_evaluation_mode = _evaluation_mode; - - // Not doing this anymore. There can be lingering stack entries from function returns - // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); - } +void runner_impl::save() +{ + inkAssert(! _saved, "Runner state already saved"); + + _saved = true; + _output.save(); + _stack.save(); + _ref_stack.save(); + _backup = _ptr; + _container.save(); + _globals->save(); + _eval.save(); + _threads.save(); + _choices.save(); + _tags.save(); + _saved_evaluation_mode = _evaluation_mode; + + // Not doing this anymore. There can be lingering stack entries from function returns + // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); +} - void runner_impl::restore() - { - inkAssert(_saved, "Can't restore. No runner state saved."); - // the output can be restored without the rest - if(_output.saved()) {_output.restore(); } - _stack.restore(); - _ref_stack.restore(); - _ptr = _backup; - _container.restore(); - _globals->restore(); - _eval.restore(); - _threads.restore(); - _choices.restore(); - _tags.restore(); - _evaluation_mode = _saved_evaluation_mode; - - // Not doing this anymore. There can be lingering stack entries from function returns - // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); - - _saved = false; - } +void runner_impl::restore() +{ + inkAssert(_saved, "Can't restore. No runner state saved."); + // the output can be restored without the rest + if (_output.saved()) { + _output.restore(); + } + _stack.restore(); + _ref_stack.restore(); + _ptr = _backup; + _container.restore(); + _globals->restore(); + _eval.restore(); + _threads.restore(); + _choices.restore(); + _tags.restore(); + _evaluation_mode = _saved_evaluation_mode; + + // Not doing this anymore. There can be lingering stack entries from function returns + // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); + + _saved = false; +} - void runner_impl::forget() - { - // Do nothing if we haven't saved - if (!_saved) - return; - - _output.forget(); - _stack.forget(); - _ref_stack.forget(); - _container.forget(); - _globals->forget(); - _eval.forget(); - _threads.forget(); - _choices.forgett(); - _tags.forgett(); - - // Nothing to do for eval stack. It should just stay as it is - - _saved = false; - } +void runner_impl::forget() +{ + // Do nothing if we haven't saved + if (! _saved) { + return; + } + + _output.forget(); + _stack.forget(); + _ref_stack.forget(); + _container.forget(); + _globals->forget(); + _eval.forget(); + _threads.forget(); + _choices.forgett(); + _tags.forgett(); + + // Nothing to do for eval stack. It should just stay as it is + + _saved = false; +} #ifdef INK_ENABLE_STL - std::ostream& operator<<(std::ostream& out, runner_impl& in) - { - in.getline(out); - return out; - } -#endif +std::ostream& operator<<(std::ostream& out, runner_impl& in) +{ + in.getline(out); + return out; } +#endif +} // namespace ink::runtime::internal diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index de93742f..cf4164b2 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -16,208 +16,193 @@ void usage() { - using namespace std; - cout - << "Usage: inkcpp_cl \n" - << "\t-o :\tOutput file name\n" - << "\t-p []:\tPlay mode\n\toptional snapshot file to load\n\tto create a snapshot file enter '-1' as choice\n" - << "\t--ommit-choice-tags:\tdo not print tags after choices, primarly used to be compatible with inkclecat output" - << endl; + using namespace std; + cout << "Usage: inkcpp_cl \n" + << "\t-o :\tOutput file name\n" + << "\t-p []:\tPlay mode\n\toptional snapshot file to load\n\tto create a " + "snapshot file enter '-1' as choice\n" + << "\t--ommit-choice-tags:\tdo not print tags after choices, primarly used to be compatible " + "with inkclecat output" + << endl; } int main(int argc, const char** argv) { - // Usage - if (argc == 1) - { - usage(); - return 1; + // Usage + if (argc == 1) { + usage(); + return 1; + } + + // Parse options + std::string outputFilename; + bool playMode = false, testMode = false, testDirectory = false, ommit_choice_tags = false; + std::string snapshotFile; + for (int i = 1; i < argc - 1; i++) { + std::string option = argv[i]; + if (option == "-o") { + outputFilename = argv[i + 1]; + i += 1; + } else if (option == "-p") { + playMode = true; + if (i + 1 < argc - 1 && argv[i + 1][0] != '-') { + ++i; + snapshotFile = argv[i]; + } + } else if (option == "--ommit-choice-tags") { + ommit_choice_tags = true; + } else if (option == "-t") { + testMode = true; + } else if (option == "-td") { + testMode = true; + testDirectory = true; + } else { + std::cerr << "Unrecognized option: '" << option << "'\n"; + } + } + + // Get input filename + std::string inputFilename = argv[argc - 1]; + + // Test mode + if (testMode) { + bool result; + if (testDirectory) { + result = test_directory(inputFilename); + } else { + result = test(inputFilename); + } + + return result ? 0 : -1; + } + + // If output filename not specified, use input filename as guideline + if (outputFilename.empty()) { + outputFilename = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".bin"); + } + + // If input filename is an .ink file + int val = inputFilename.find(".ink"); + bool json_file_is_tmp_file = false; + if (val == inputFilename.length() - 4) { + // Create temporary filename + std::string jsonFile = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".tmp"); + + // Then we need to do a compilation with inklecate + try { + inklecate(inputFilename, jsonFile); + } catch (const std::exception& e) { + std::cerr << "Inklecate Error: " << e.what() << std::endl; + return 1; + } + + // New input is the json file + json_file_is_tmp_file = true; + inputFilename = jsonFile; + } + + // Open file and compile + try { + ink::compiler::compilation_results results; + std::ofstream fout(outputFilename, std::ios::binary | std::ios::out); + ink::compiler::run(inputFilename.c_str(), fout, &results); + fout.close(); + if (json_file_is_tmp_file) { + remove(inputFilename.c_str()); + } + + // Report errors + for (auto& warn : results.warnings) { + std::cerr << "WARNING: " << warn << '\n'; + } + for (auto& err : results.errors) { + std::cerr << "ERROR: " << err << '\n'; + } + + if (results.errors.size() > 0 && playMode) { + std::cerr << "Cancelling play mode. Errors detected in compilation" << std::endl; + return -1; + } + } catch (std::exception& e) { + if (json_file_is_tmp_file) { + remove(inputFilename.c_str()); + } + std::cerr << "Unhandled InkBin compiler exception: " << e.what() << std::endl; + return 1; + } + + if (! playMode) { + return 0; + } + + // Run the story + try { + using namespace ink::runtime; + + // Load story + std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; + + // Start runner + runner thread; + if (snapshotFile.size()) { + auto snap_ptr = snapshot::from_file(snapshotFile.c_str()); + thread = myInk->new_runner_from_snapshot(*snap_ptr); + delete snap_ptr; + } else { + thread = myInk->new_runner(); + } + + while (true) { + while (thread->can_continue()) { + std::cout << thread->getline(); + } + if (thread->has_tags()) { + std::cout << "# tags: "; + for (int i = 0; i < thread->num_tags(); ++i) { + if (i != 0) { + std::cout << ", "; + } + std::cout << thread->get_tag(i); } - - // Parse options - std::string outputFilename; - bool playMode = false, - testMode = false, - testDirectory = false, - ommit_choice_tags = false; - std::string snapshotFile; - for (int i = 1; i < argc - 1; i++) - { - std::string option = argv[i]; - if (option == "-o") - { - outputFilename = argv[i + 1]; - i += 1; - } - else if (option == "-p") { - playMode = true; - if (i + 1 < argc - 1 && argv[i+1][0] != '-') { - ++i; - snapshotFile = argv[i]; - } - } - else if (option == "--ommit-choice-tags") - { - ommit_choice_tags = true; - } - else if (option == "-t") - testMode = true; - else if (option == "-td") - { - testMode = true; - testDirectory = true; - } - else - { - std::cerr << "Unrecognized option: '" << option << "'\n"; - } - } - - // Get input filename - std::string inputFilename = argv[argc - 1]; - - // Test mode - if (testMode) - { - bool result; - if (testDirectory) - result = test_directory(inputFilename); - else - result = test(inputFilename); - - return result ? 0 : -1; - } - - // If output filename not specified, use input filename as guideline - if (outputFilename.empty()) - { - outputFilename = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".bin"); - } - - // If input filename is an .ink file - int val = inputFilename.find(".ink"); - bool json_file_is_tmp_file = false; - if (val == inputFilename.length() - 4) - { - // Create temporary filename - std::string jsonFile = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".tmp"); - - // Then we need to do a compilation with inklecate - try - { - inklecate(inputFilename, jsonFile); - } - catch (const std::exception& e) - { - std::cerr << "Inklecate Error: " << e.what() << std::endl; - return 1; - } - - // New input is the json file - json_file_is_tmp_file = true; - inputFilename = jsonFile; - } - - // Open file and compile - try - { - ink::compiler::compilation_results results; - std::ofstream fout(outputFilename, std::ios::binary | std::ios::out); - ink::compiler::run(inputFilename.c_str(), fout, &results); - fout.close(); - if(json_file_is_tmp_file) { remove(inputFilename.c_str()); } - - // Report errors - for (auto& warn : results.warnings) - std::cerr << "WARNING: " << warn << '\n'; - for (auto& err : results.errors) - std::cerr << "ERROR: " << err << '\n'; - - if (results.errors.size() > 0 && playMode) - { - std::cerr << "Cancelling play mode. Errors detected in compilation" << std::endl; - return -1; - } - } - catch (std::exception& e) - { - if(json_file_is_tmp_file) { remove(inputFilename.c_str()); } - std::cerr << "Unhandled InkBin compiler exception: " << e.what() << std::endl; - return 1; + std::cout << std::endl; + } + if (thread->has_choices()) { + // Extra end line + std::cout << std::endl; + + int index = 1; + for (const ink::runtime::choice& c : *thread) { + std::cout << index++ << ": " << c.text(); + if (! ommit_choice_tags && c.has_tags()) { + std::cout << "\n\t"; + for (size_t i = 0; i < c.num_tags(); ++i) { + std::cout << "# " << c.get_tag(i) << " "; + } + } + std::cout << std::endl; } - if (!playMode) - return 0; - - // Run the story - try - { - using namespace ink::runtime; - - // Load story - std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; - - // Start runner - runner thread; - if (snapshotFile.size()) { - auto snap_ptr = snapshot::from_file( snapshotFile.c_str() ); - thread = myInk->new_runner_from_snapshot(*snap_ptr); - delete snap_ptr; - } else { - thread = myInk->new_runner(); - } - - while (true) - { - while (thread->can_continue()) - std::cout << thread->getline(); - if (thread->has_tags()){ - std::cout << "# tags: "; - for (int i = 0; i < thread->num_tags(); ++i) { - if(i != 0) std::cout << ", "; - std::cout << thread->get_tag(i); - } - std::cout << std::endl; - } - if (thread->has_choices()) - { - // Extra end line - std::cout << std::endl; - - int index = 1; - for (const ink::runtime::choice& c : *thread) - { - std::cout << index++ << ": " << c.text(); - if(!ommit_choice_tags && c.has_tags()) { - std::cout << "\n\t"; - for(size_t i = 0; i < c.num_tags(); ++i) { - std::cout << "# " << c.get_tag(i) << " "; - } - } - std::cout << std::endl; - } - - int c = 0; - std::cin >> c; - if (c == -1) { - snapshot* snap = thread->create_snapshot(); - snap->write_to_file(std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".snap").c_str()); - delete snap; - break; - } - thread->choose(c - 1); - std::cout << "?> "; - continue; - } - - // out of content - break; - } - } - catch (const std::exception& e) - { - std::cerr << "Unhandled ink runtime exception: " << e.what() << std::endl; - return 1; + int c = 0; + std::cin >> c; + if (c == -1) { + snapshot* snap = thread->create_snapshot(); + snap->write_to_file( + std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".snap").c_str() + ); + delete snap; + break; } - return 0; + thread->choose(c - 1); + std::cout << "?> "; + continue; + } + + // out of content + break; + } + } catch (const std::exception& e) { + std::cerr << "Unhandled ink runtime exception: " << e.what() << std::endl; + return 1; + } + return 0; } From d296516d5643745203b550e000e5a5c6677ebf1a Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 08:54:55 +0100 Subject: [PATCH 16/29] Change ref in format command --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bf24870..9ce0fe71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,7 +181,7 @@ jobs: git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git clang-format-15 --style file --diff master) + diff=$(git clang-format-15 --style file --diff origin/master) if [ "$diff" != "" ]; then exit 1 fi From 01ea0152b7fc34035e044d9cec0a6025b183200c Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:04:04 +0100 Subject: [PATCH 17/29] Install clang 16 on Ubuntu LTS 22.04 --- .github/workflows/build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ce0fe71..17d3d8b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,13 +175,18 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} steps: + - name: Install Clang16 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + ./llvm.sh 16 - uses: actions/checkout@v3 - name: Fetch master branch run: | git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git clang-format-15 --style file --diff origin/master) + diff=$(git clang-format-16 --style file --diff origin/master) if [ "$diff" != "" ]; then exit 1 fi From 9c3dd88f1f9e6cebbdf85c96a1d96f01fda5831d Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:04:56 +0100 Subject: [PATCH 18/29] add sudo rigths --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17d3d8b5..f2f27c50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -179,7 +179,7 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - ./llvm.sh 16 + sudo ./llvm.sh 16 - uses: actions/checkout@v3 - name: Fetch master branch run: | From 79df928cfc14bde1ae13ce9c10bb2d8362bab1ec Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:06:17 +0100 Subject: [PATCH 19/29] remove specifit identifier --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2f27c50..c56f5b6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -186,7 +186,7 @@ jobs: git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git clang-format-16 --style file --diff origin/master) + diff=$(git clang-format --style file --diff origin/master) if [ "$diff" != "" ]; then exit 1 fi From f02ac0c4d3b510723dac0b920103b153f203cb9a Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:12:35 +0100 Subject: [PATCH 20/29] update condition with -q --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c56f5b6f..f4023578 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -186,7 +186,8 @@ jobs: git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git clang-format --style file --diff origin/master) + diff=$(git clang-format --style file -q --diff origin/master) + echo $diff if [ "$diff" != "" ]; then exit 1 fi From fba989f3f8bdea6576101d851f30f1e2014b0eea Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:18:05 +0100 Subject: [PATCH 21/29] install all llvm --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4023578..1fed36bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -179,14 +179,14 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 16 all - uses: actions/checkout@v3 - name: Fetch master branch run: | git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git clang-format --style file -q --diff origin/master) + diff=$(git-clang-format --style file -q --diff origin/master) echo $diff if [ "$diff" != "" ]; then exit 1 From 4013484ef2685674d7c71e60df3ca6726e939f76 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:35:02 +0100 Subject: [PATCH 22/29] us format 15 --- .clang-format | 5 +---- .github/workflows/build.yml | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.clang-format b/.clang-format index b0adf2e2..0b6d3bef 100644 --- a/.clang-format +++ b/.clang-format @@ -28,9 +28,7 @@ AlignConsecutiveMacros: PadOperators: true AlignEscapedNewlines: Left AlignOperands: AlignAfterOperator -AlignTrailingComments: - Kind: Always - OverEmptyLines: 0 +AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Always @@ -45,7 +43,6 @@ AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true -BraceWrapping: {} BreakBeforeBraces: Linux BreakAfterAttributes: Always BreakBeforeBinaryOperators: All diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1fed36bf..0b27ba5a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,18 +175,13 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} steps: - - name: Install Clang16 - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 16 all - uses: actions/checkout@v3 - name: Fetch master branch run: | git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git-clang-format --style file -q --diff origin/master) + diff=$(git-clang-format-15 --style file -q --diff origin/master) echo $diff if [ "$diff" != "" ]; then exit 1 From f8da66e3a91c1fae0a373a50794f4e959b7e5c72 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:42:20 +0100 Subject: [PATCH 23/29] Fix format config for older version --- .clang-format | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.clang-format b/.clang-format index 0b6d3bef..d68b212a 100644 --- a/.clang-format +++ b/.clang-format @@ -44,7 +44,7 @@ AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BreakBeforeBraces: Linux -BreakAfterAttributes: Always +# BreakAfterAttributes: Always BreakBeforeBinaryOperators: All UseTab: ForIndentation Standard: Latest @@ -73,13 +73,13 @@ PointerAlignment: Left SpaceAfterTemplateKeyword: false SpaceAfterLogicalNot: true SpaceAfterCStyleCast: true -SortUsingDeclarations: Lexicographic +SortUsingDeclarations: false SortIncludes: Never ShortNamespaceLines: 0 SeparateDefinitionBlocks: Always -RequiresExpressionIndentation: OuterScope +# RequiresExpressionIndentation: OuterScope BreakBeforeConceptDeclarations: Always -BreakBeforeInlineASMColon: Always +# BreakBeforeInlineASMColon: Always BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakInheritanceList: BeforeComma @@ -100,16 +100,18 @@ IndentRequiresClause: true IndentWidth: 2 IndentWrappedFunctionNames: true InsertBraces: true -InsertNewlineAtEOF: true +# InsertNewlineAtEOF: true KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: Signature Language: Cpp -LineEnding: LF +# LineEnding: LF MaxEmptyLinesToKeep: 2 NamespaceIndentation: Inner QualifierAlignment: Left ReferenceAlignment: Left ReflowComments: true RemoveBracesLLVM: false -RemoveSemicolon: false +# RemoveSemicolon: false RequiresClausePosition: OwnLine +UseCRLF: false +DeriveLineEnding: false From 4eeaadc40d715215be47aabcd08f00643b181977 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:46:57 +0100 Subject: [PATCH 24/29] Installed version 15 explicit --- .github/workflows/build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b27ba5a..56de7dd2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -176,12 +176,17 @@ jobs: if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} steps: - uses: actions/checkout@v3 + - name: install clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 15 - name: Fetch master branch run: | git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git-clang-format-15 --style file -q --diff origin/master) + diff=$(git clang-format-15 --style file -q --diff origin/master) echo $diff if [ "$diff" != "" ]; then exit 1 From 13dfe68a31e2dab39b7d20b0485c8ed0ae12a0b3 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:51:40 +0100 Subject: [PATCH 25/29] downgrade again --- .clang-format | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/.clang-format b/.clang-format index d68b212a..3256552f 100644 --- a/.clang-format +++ b/.clang-format @@ -2,30 +2,11 @@ AccessModifierOffset: -2 AlignAfterOpenBracket: BlockIndent AlignArrayOfStructures: Right -AlignConsecutiveAssignments: - Enabled: true - AcrossComments: true - AcrossEmptyLines: true - AlignCompound: true - PadOperators: true -AlignConsecutiveBitFields: - Enabled: true - AcrossEmptyLines: true - AcrossComments: true - AlignCompound: true - PadOperators: true -AlignConsecutiveDeclarations: - Enabled: true - AcrossEmptyLines: true - AcrossComments: true - AlignCompound: true - PadOperators: true -AlignConsecutiveMacros: - Enabled: true - AcrossEmptyLines: true - AcrossComments: true - AlignCompound: true - PadOperators: true +# readd padOperation if bumped from clang 14 +AlignConsecutiveAssignments: AcrossComments +AlignConsecutiveBitFields: AcrossComments +AlignConsecutiveDeclarations: AcrossComments +AlignConsecutiveMacros: AcrossComments AlignEscapedNewlines: Left AlignOperands: AlignAfterOperator AlignTrailingComments: true @@ -78,7 +59,7 @@ SortIncludes: Never ShortNamespaceLines: 0 SeparateDefinitionBlocks: Always # RequiresExpressionIndentation: OuterScope -BreakBeforeConceptDeclarations: Always +# BreakBeforeConceptDeclarations: Always # BreakBeforeInlineASMColon: Always BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma @@ -96,10 +77,10 @@ IndentCaseLabels: true IndentExternBlock: Indent IndentGotoLabels: false IndentPPDirectives: AfterHash -IndentRequiresClause: true +# IndentRequiresClause: true IndentWidth: 2 IndentWrappedFunctionNames: true -InsertBraces: true +# InsertBraces: true # InsertNewlineAtEOF: true KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: Signature @@ -112,6 +93,6 @@ ReferenceAlignment: Left ReflowComments: true RemoveBracesLLVM: false # RemoveSemicolon: false -RequiresClausePosition: OwnLine +# RequiresClausePosition: OwnLine UseCRLF: false DeriveLineEnding: false From e6ba67e7a6fd247e39ce03bb43bcb90293d48b1b Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 09:57:39 +0100 Subject: [PATCH 26/29] format with new rules --- .github/workflows/build.yml | 9 ++--- inkcpp/array.h | 64 ++++++++++++++++----------------- inkcpp/runner_impl.cpp | 72 ++++++++++++++++++------------------- inkcpp_cl/inkcpp_cl.cpp | 6 ++-- 4 files changed, 74 insertions(+), 77 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56de7dd2..67cb5fdd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -176,19 +176,16 @@ jobs: if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} steps: - uses: actions/checkout@v3 - - name: install clang - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 15 - name: Fetch master branch run: | git fetch origin master --depth 1 - name: Check clang-format run: | diff=$(git clang-format-15 --style file -q --diff origin/master) - echo $diff + # echo $diff if [ "$diff" != "" ]; then + echo run git clang-format --style file master + echo or upstream/master depending on your setup exit 1 fi diff --git a/inkcpp/array.h b/inkcpp/array.h index 332b8c61..a72b2a6b 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -29,7 +29,7 @@ class managed_array : public snapshot_interface const T& operator[](size_t i) const { return data()[i]; } - T& operator[](size_t i) { return data()[i]; } + T& operator[](size_t i) { return data()[i]; } const T* data() const { @@ -49,23 +49,23 @@ class managed_array : public snapshot_interface } } - const T* begin() const { return data(); } + const T* begin() const { return data(); } - T* begin() { return data(); } + T* begin() { return data(); } - const T* end() const { return data() + _size; } + const T* end() const { return data() + _size; } - T* end() { return data() + _size; } + T* end() { return data() + _size; } - const T& back() const { return end()[-1]; } + const T& back() const { return end()[-1]; } - T& back() { return end()[-1]; } + T& back() { return end()[-1]; } const size_t size() const { return _size; } const size_t capacity() const { return _capacity; } - T& push() + T& push() { if constexpr (dynamic) { if (_size == _capacity) { @@ -92,7 +92,7 @@ class managed_array : public snapshot_interface _size = size; } - void extend(size_t capacity = 0); + void extend(size_t capacity = 0); size_t snap(unsigned char* data, const snapper& snapper) const { @@ -148,20 +148,20 @@ class managed_restorable_array : public managed_arraysize(); } + void save() { _last_size = this->size(); } - void forgett() { _last_size = 0; } + void forgett() { _last_size = 0; } - bool has_changed() const { return base::size() != _last_size; } + bool has_changed() const { return base::size() != _last_size; } size_t snap(unsigned char* data, const snapshot_interface::snapper& snapper) const { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr += base::snap(ptr, snapper); - ptr = base::snap_write(ptr, _last_size, should_write); + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr += base::snap(ptr, snapper); + ptr = base::snap_write(ptr, _last_size, should_write); return ptr - data; } @@ -217,28 +217,28 @@ class basic_restorable_array : public snapshot_interface } // == Non-Copyable == - basic_restorable_array(const basic_restorable_array&) = delete; - basic_restorable_array& operator=(const basic_restorable_array&) = delete; + basic_restorable_array(const basic_restorable_array&) = delete; + basic_restorable_array& operator=(const basic_restorable_array&) = delete; // set value by index - void set(size_t index, const T& value); + void set(size_t index, const T& value); // get value by index - const T& get(size_t index) const; + const T& get(size_t index) const; // size of the array - inline size_t capacity() const { return _capacity; } + inline size_t capacity() const { return _capacity; } // only const indexing is supported due to save/restore system - inline const T& operator[](size_t index) const { return get(index); } + inline const T& operator[](size_t index) const { return get(index); } // == Save/Restore == - void save(); - void restore(); - void forget(); + void save(); + void restore(); + void forget(); // Resets all values and clears any save points - void clear(const T& value); + void clear(const T& value); // snapshot interface virtual size_t snap(unsigned char* data, const snapper&) const; @@ -247,7 +247,7 @@ class basic_restorable_array : public snapshot_interface protected: inline T* buffer() { return _array; } - void set_new_buffer(T* buffer, size_t capacity) + void set_new_buffer(T* buffer, size_t capacity) { _array = buffer; _temp = buffer + capacity / 2; @@ -263,17 +263,17 @@ class basic_restorable_array : public snapshot_interface void clear_temp(); private: - bool _saved; + bool _saved; // real values live here - T* _array; + T* _array; // we store values here when we're in save mode // they're copied on a call to forget() - T* _temp; + T* _temp; // size of both _array and _temp - size_t _capacity; + size_t _capacity; // null const T _null; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index ef036745..d25fe36b 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -408,7 +408,7 @@ std::string runner_impl::getline() advance_line(); // Read line into std::string result += _output.get(); - fill = _output.last_char() == ' '; + fill = _output.last_char() == ' '; } while (_ptr != nullptr && _output.last_char() != '\n'); // TODO: fallback choice = no choice @@ -531,13 +531,13 @@ void runner_impl::choose(size_t index) restore(); // restore to stack state when choice was maked _globals->turn(); // Get the choice - const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); + const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); // Get its thread - thread_t choiceThread = c._thread; + thread_t choiceThread = c._thread; // Figure out where our previous pointer was for that thread - ip_t prev = nullptr; + ip_t prev = nullptr; if (choiceThread == ~0) { prev = _done; } else { @@ -592,30 +592,30 @@ snapshot* runner_impl::create_snapshot() const size_t runner_impl::snap(unsigned char* data, snapper& snapper) const { - unsigned char* ptr = data; - bool should_write = data != nullptr; - snapper.current_runner_tags = _tags[0].ptr(); - std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; - ptr = snap_write(ptr, offset, should_write); - offset = _backup - _story->instructions(); - ptr = snap_write(ptr, offset, should_write); - offset = _done - _story->instructions(); - ptr = snap_write(ptr, offset, should_write); - ptr = snap_write(ptr, _rng.get_state(), should_write); - ptr = snap_write(ptr, _evaluation_mode, should_write); - ptr = snap_write(ptr, _string_mode, should_write); - ptr = snap_write(ptr, _saved_evaluation_mode, should_write); - ptr = snap_write(ptr, _saved, should_write); - ptr = snap_write(ptr, _is_falling, should_write); - ptr += _output.snap(data ? ptr : nullptr, snapper); - ptr += _stack.snap(data ? ptr : nullptr, snapper); - ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); - ptr += _eval.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _choice_tags_begin, should_write); - ptr += _tags.snap(data ? ptr : nullptr, snapper); - ptr += _container.snap(data ? ptr : nullptr, snapper); - ptr += _threads.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); + unsigned char* ptr = data; + bool should_write = data != nullptr; + snapper.current_runner_tags = _tags[0].ptr(); + std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; + ptr = snap_write(ptr, offset, should_write); + offset = _backup - _story->instructions(); + ptr = snap_write(ptr, offset, should_write); + offset = _done - _story->instructions(); + ptr = snap_write(ptr, offset, should_write); + ptr = snap_write(ptr, _rng.get_state(), should_write); + ptr = snap_write(ptr, _evaluation_mode, should_write); + ptr = snap_write(ptr, _string_mode, should_write); + ptr = snap_write(ptr, _saved_evaluation_mode, should_write); + ptr = snap_write(ptr, _saved, should_write); + ptr = snap_write(ptr, _is_falling, should_write); + ptr += _output.snap(data ? ptr : nullptr, snapper); + ptr += _stack.snap(data ? ptr : nullptr, snapper); + ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); + ptr += _eval.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _choice_tags_begin, should_write); + ptr += _tags.snap(data ? ptr : nullptr, snapper); + ptr += _container.snap(data ? ptr : nullptr, snapper); + ptr += _threads.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); if (_fallback_choice) { ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper); } @@ -1011,7 +1011,7 @@ void runner_impl::step() bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; // Get the top value and put it into the variable - value v = _eval.pop(); + value v = _eval.pop(); set_var(variableName, v, is_redef); } break; @@ -1019,11 +1019,11 @@ void runner_impl::step() hash_t variableName = read(); // Check if it's a redefinition (not yet used, seems important for pointers later?) - bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; + bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; // If not, we're setting a global (temporary variables are explicitely defined as such, // where globals are defined using SET_VARIABLE). - value val = _eval.pop(); + value val = _eval.pop(); if (is_redef) { set_var(variableName, val, is_redef); } else { @@ -1037,12 +1037,12 @@ void runner_impl::step() hash_t functionName = read(); // Interpret flag as argument count - int numArguments = ( int ) flag; + int numArguments = ( int ) flag; // find and execute. will automatically push a valid if applicable - bool success = _functions.call( - functionName, &_eval, numArguments, _globals->strings(), _globals->lists() - ); + bool success = _functions.call( + functionName, &_eval, numArguments, _globals->strings(), _globals->lists() + ); // If we failed, notify a potential fallback function if (! success) { @@ -1388,7 +1388,7 @@ void runner_impl::restore() // Not doing this anymore. There can be lingering stack entries from function returns // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); - _saved = false; + _saved = false; } void runner_impl::forget() diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index cf4164b2..3a42cb5e 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -41,8 +41,8 @@ int main(int argc, const char** argv) for (int i = 1; i < argc - 1; i++) { std::string option = argv[i]; if (option == "-o") { - outputFilename = argv[i + 1]; - i += 1; + outputFilename = argv[i + 1]; + i += 1; } else if (option == "-p") { playMode = true; if (i + 1 < argc - 1 && argv[i + 1][0] != '-') { @@ -143,7 +143,7 @@ int main(int argc, const char** argv) std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; // Start runner - runner thread; + runner thread; if (snapshotFile.size()) { auto snap_ptr = snapshot::from_file(snapshotFile.c_str()); thread = myInk->new_runner_from_snapshot(*snap_ptr); From b211f20d7e160aa899fddb896c92ae706275f6b4 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 10:00:36 +0100 Subject: [PATCH 27/29] new break --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67cb5fdd..de3ae67c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -181,12 +181,12 @@ jobs: git fetch origin master --depth 1 - name: Check clang-format run: | - diff=$(git clang-format-15 --style file -q --diff origin/master) - # echo $diff + diff=$(git clang-format-14 --style file -q --diff origin/master) + echo $diff if [ "$diff" != "" ]; then echo run git clang-format --style file master echo or upstream/master depending on your setup - exit 1 + clang fi reporting: From a307589dd3dfea17bc517b9059387670ab108a67 Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 10:05:56 +0100 Subject: [PATCH 28/29] formatting --- inkcpp/output.cpp | 879 +++++++++++++++++++++++----------------------- 1 file changed, 434 insertions(+), 445 deletions(-) diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index 558c4b17..d2947d4b 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -5,501 +5,490 @@ #include "string_utils.h" #ifdef INK_ENABLE_STL -#include +# include #endif namespace ink::runtime::internal { - basic_stream::basic_stream(value* buffer, size_t len) - : _data(buffer), _max(len), _size(0), _save(~0) - {} - - void basic_stream::append(const value& in) - { - // SPECIAL: Incoming newline - if (in.type() == value_type::newline && _size > 1) - { - // If the end of the stream is a function start marker, we actually - // want to ignore this. Function start trimming. - if (_data[_size - 1].type() == value_type::func_start) - return; - } - - // Ignore leading newlines - if (in.type() == value_type::newline && _size == 0) - return; - - // Add to data stream - inkAssert(_size < _max, "Output stream overflow"); - _data[_size++] = in; - - // Special: Incoming glue. Trim whitespace/newlines prior - // This also applies when a function ends to trim trailing whitespace. - if ((in.type() == value_type::glue || in.type() == value_type::func_end) && _size > 1) - { - // Run backwards - size_t i = _size - 2; - while(true) - { - value& d = _data[i]; - - // Nullify newlines - if (d.type() == value_type::newline) { - d = value{}; - } - - // Nullify whitespace - else if ( d.type() == value_type::string - && is_whitespace(d.get())) - d = value{}; - - // If it's not a newline or whitespace, stop - else break; - - // If we've hit the end, break - if (i == 0) - break; - - // Move on to next element - i--; - } - } - } +basic_stream::basic_stream(value* buffer, size_t len) + : _data(buffer) + , _max(len) + , _size(0) + , _save(~0) +{ +} - void basic_stream::append(const value* in, unsigned int length) - { - // TODO: Better way to bulk while still executing glue checks? - for (size_t i = 0; i < length; i++) - append(in[i]); - } +void basic_stream::append(const value& in) +{ + // SPECIAL: Incoming newline + if (in.type() == value_type::newline && _size > 1) { + // If the end of the stream is a function start marker, we actually + // want to ignore this. Function start trimming. + if (_data[_size - 1].type() == value_type::func_start) + return; + } + + // Ignore leading newlines + if (in.type() == value_type::newline && _size == 0) + return; + + // Add to data stream + inkAssert(_size < _max, "Output stream overflow"); + _data[_size++] = in; + + // Special: Incoming glue. Trim whitespace/newlines prior + // This also applies when a function ends to trim trailing whitespace. + if ((in.type() == value_type::glue || in.type() == value_type::func_end) && _size > 1) { + // Run backwards + size_t i = _size - 2; + while (true) { + value& d = _data[i]; + + // Nullify newlines + if (d.type() == value_type::newline) { + d = value{}; + } + + // Nullify whitespace + else if (d.type() == value_type::string && is_whitespace(d.get())) + d = value{}; + + // If it's not a newline or whitespace, stop + else + break; + + // If we've hit the end, break + if (i == 0) + break; + + // Move on to next element + i--; + } + } +} - template - inline void write_char(T& output, char c) - { - static_assert(always_false::value, "Invalid output type"); - } +void basic_stream::append(const value* in, unsigned int length) +{ + // TODO: Better way to bulk while still executing glue checks? + for (size_t i = 0; i < length; i++) + append(in[i]); +} - template<> - inline void write_char(char*& output, char c) - { - (*output++) = c; - } +template +inline void write_char(T& output, char c) +{ + static_assert(always_false::value, "Invalid output type"); +} + +template<> +inline void write_char(char*& output, char c) +{ + (*output++) = c; +} #ifdef INK_ENABLE_STL - template<> - inline void write_char(std::stringstream& output, char c) - { - output.put(c); - } +template<> +inline void write_char(std::stringstream& output, char c) +{ + output.put(c); +} #endif - inline bool get_next(const value* list, size_t i, size_t size, const value** next) - { - while (i + 1 < size) - { - *next = &list[i + 1]; - value_type type = (*next)->type(); - if ((*next)->printable()) { return true; } - i++; - } - - return false; - } +inline bool get_next(const value* list, size_t i, size_t size, const value** next) +{ + while (i + 1 < size) { + *next = &list[i + 1]; + value_type type = (*next)->type(); + if ((*next)->printable()) { + return true; + } + i++; + } + + return false; +} - template - void basic_stream::copy_string(const char* str, size_t& dataIter, T& output) - { - while(*str != 0) { - write_char(output, *str++); - } - } +template +void basic_stream::copy_string(const char* str, size_t& dataIter, T& output) +{ + while (*str != 0) { + write_char(output, *str++); + } +} #ifdef INK_ENABLE_STL - std::string basic_stream::get() - { - size_t start = find_start(); - - // Move up from marker - bool hasGlue = false, lastNewline = false; - std::stringstream str; - for (size_t i = start; i < _size; i++) - { - if (should_skip(i, hasGlue, lastNewline)) - continue; - if (_data[i].printable()){ - _data[i].write(str, _lists_table); - } - - } - - // Reset stream size to where we last held the marker - _size = start; - - // Return processed string - // remove mulitple accourencies of ' ' - std::string result = str.str(); - if ( !result.empty() ) { - auto end = clean_string( result.begin(), result.end() ); - if (result.begin() == end) { - result.resize(0); - } else { - _last_char = *(end - 1); - result.resize(end - result.begin() - (_last_char == ' ' ? 1 : 0)); - } - } - return result; - } +std::string basic_stream::get() +{ + size_t start = find_start(); + + // Move up from marker + bool hasGlue = false, lastNewline = false; + std::stringstream str; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + if (_data[i].printable()) { + _data[i].write(str, _lists_table); + } + } + + // Reset stream size to where we last held the marker + _size = start; + + // Return processed string + // remove mulitple accourencies of ' ' + std::string result = str.str(); + if (! result.empty()) { + auto end = clean_string(result.begin(), result.end()); + if (result.begin() == end) { + result.resize(0); + } else { + _last_char = *(end - 1); + result.resize(end - result.begin() - (_last_char == ' ' ? 1 : 0)); + } + } + return result; +} #endif #ifdef INK_ENABLE_UNREAL - FString basic_stream::get() - { - UE_LOG( InkCpp, Warning, TEXT("Basic stream::get is not implemented correctly and should not be used implemented correctly!" ) ); - FString str; - return str; - } +FString basic_stream::get() +{ + UE_LOG( + InkCpp, Warning, + TEXT("Basic stream::get is not implemented correctly and should not be used implemented " + "correctly!") + ); + FString str; + return str; +} #endif - int basic_stream::queued() const - { - size_t start = find_start(); - return _size - start; - } - - const value& basic_stream::peek() const - { - inkAssert(_size > 0, "Attempting to peek empty stream!"); - return _data[_size - 1]; - } - - void basic_stream::discard(size_t length) - { - // discard elements - _size -= length; - if (_size < 0) - _size = 0; - } +int basic_stream::queued() const +{ + size_t start = find_start(); + return _size - start; +} - void basic_stream::get(value* ptr, size_t length) - { - // Find start - size_t start = find_start(); +const value& basic_stream::peek() const +{ + inkAssert(_size > 0, "Attempting to peek empty stream!"); + return _data[_size - 1]; +} - const value* end = ptr + length; - //inkAssert(_size - start < length, "Insufficient space in data array to store stream contents!"); +void basic_stream::discard(size_t length) +{ + // discard elements + _size -= length; + if (_size < 0) + _size = 0; +} - // Move up from marker - bool hasGlue = false, lastNewline = false; - for (size_t i = start; i < _size; i++) - { - if (should_skip(i, hasGlue, lastNewline)) - continue; +void basic_stream::get(value* ptr, size_t length) +{ + // Find start + size_t start = find_start(); + + const value* end = ptr + length; + // inkAssert(_size - start < length, "Insufficient space in data array to store stream + // contents!"); + + // Move up from marker + bool hasGlue = false, lastNewline = false; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + + // Make sure we can fit the next element + inkAssert(ptr < end, "Insufficient space in data array to store stream contents!"); + + // Copy any value elements + if (_data[i].printable()) { + *(ptr++) = _data[i]; + } + } + + // Reset stream size to where we last held the marker + _size = start; +} - // Make sure we can fit the next element - inkAssert(ptr < end, "Insufficient space in data array to store stream contents!"); +bool basic_stream::has_marker() const +{ + return entries_since_marker() >= 0; +} - // Copy any value elements - if (_data[i].printable()) { - *(ptr++) = _data[i]; - } - } +int basic_stream::entries_since_marker() const +{ + // TODO: Cache? + for (size_t i = 0; i < _size; i++) { + if (_data[i].type() == value_type::marker) + return i; + } - // Reset stream size to where we last held the marker - _size = start; - } + return -1; +} - bool basic_stream::has_marker() const { - return entries_since_marker() >= 0; - } +bool basic_stream::ends_with(value_type type) const +{ + if (_size == 0) + return false; - int basic_stream::entries_since_marker() const - { - // TODO: Cache? - for (size_t i = 0; i < _size; i++) - { - if (_data[i].type() == value_type::marker) - return i; - } + return _data[_size - 1].type() == type; +} - return -1; - } +bool basic_stream::saved_ends_with(value_type type) const +{ + inkAssert(_save != ~0, "Stream is not saved!"); - bool basic_stream::ends_with(value_type type) const - { - if (_size == 0) - return false; + if (_save == 0) + return false; - return _data[_size - 1].type() == type; - } + return _data[_save - 1].type() == type; +} - bool basic_stream::saved_ends_with(value_type type) const - { - inkAssert(_save != ~0, "Stream is not saved!"); +void basic_stream::save() +{ + inkAssert(_save == ~0, "Can not save over existing save point!"); - if (_save == 0) - return false; + // Save the current size + _save = _size; +} - return _data[_save - 1].type() == type; - } +void basic_stream::restore() +{ + inkAssert(_save != ~0, "No save point to restore!"); - void basic_stream::save() - { - inkAssert(_save == ~0, "Can not save over existing save point!"); + // Restore size to saved position + _size = _save; + _save = ~0; +} - // Save the current size - _save = _size; - } +void basic_stream::forget() +{ + inkAssert(_save != ~0, "No save point to forget!"); - void basic_stream::restore() - { - inkAssert(_save != ~0, "No save point to restore!"); + // Just null the save point and continue as normal + _save = ~0; +} - // Restore size to saved position - _size = _save; - _save = ~0; - } +template char* basic_stream::get_alloc(string_table& strings, list_table& lists); +template char* basic_stream::get_alloc(string_table& strings, list_table& lists); - void basic_stream::forget() - { - inkAssert(_save != ~0, "No save point to forget!"); +template +char* basic_stream::get_alloc(string_table& strings, list_table& lists) +{ + size_t start = find_start(); + + // Two passes. First for length + size_t length = 0; + bool hasGlue = false, lastNewline = false; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + ++length; // potenzial space to sperate + if (_data[i].printable()) { + switch (_data[i].type()) { + case value_type::list: length += lists.stringLen(_data[i].get()); break; + case value_type::list_flag: + length += lists.stringLen(_data[i].get()); + break; + default: length += value_length(_data[i]); + } + } + } + + // Allocate + char* buffer = strings.create(length + 1); + char* end = buffer + length + 1; + char* ptr = buffer; + hasGlue = false; + lastNewline = false; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + if (! _data[i].printable()) { + continue; + } + switch (_data[i].type()) { + case value_type::int32: + case value_type::float32: + case value_type::uint32: + // Convert to string and advance + toStr(ptr, end - ptr, _data[i]); + while (*ptr != 0) + ptr++; + + break; + case value_type::string: { + // Copy string and advance + const char* value = _data[i].get(); + copy_string(value, i, ptr); + } break; + case value_type::newline: + *ptr = '\n'; + ptr++; + break; + case value_type::list: ptr = lists.toString(ptr, _data[i].get()); break; + case value_type::list_flag: + ptr = lists.toString(ptr, _data[i].get()); + break; + default: inkFail("cant convert expression to string!"); + } + } + + // Make sure last character is a null + *ptr = 0; + + // Reset stream size to where we last held the marker + _size = start; + + // Return processed string + end = clean_string(buffer, buffer + c_str_len(buffer)); + *end = 0; + _last_char = end[-1]; + if constexpr (RemoveTail) { + if (_last_char == ' ') { + end[-1] = 0; + } + } + + return buffer; +} - // Just null the save point and continue as normal - _save = ~0; - } - - template char* basic_stream::get_alloc(string_table& strings, list_table& lists); - template char* basic_stream::get_alloc(string_table& strings, list_table& lists); - - template - char* basic_stream::get_alloc(string_table& strings, list_table& lists) - { - size_t start = find_start(); - - // Two passes. First for length - size_t length = 0; - bool hasGlue = false, lastNewline = false; - for (size_t i = start; i < _size; i++) - { - if (should_skip(i, hasGlue, lastNewline)) - continue; - ++length; // potenzial space to sperate - if (_data[i].printable()) { - switch(_data[i].type()) { - case value_type::list: - length += lists.stringLen(_data[i].get()); - break; - case value_type::list_flag: - length += lists.stringLen(_data[i].get()); - break; - default: length += value_length(_data[i]); - } - } - } - - // Allocate - char* buffer = strings.create(length + 1); - char* end = buffer + length + 1; - char* ptr = buffer; - hasGlue = false; lastNewline = false; - for (size_t i = start; i < _size; i++) - { - if (should_skip(i, hasGlue, lastNewline)) - continue; - if(!_data[i].printable()) { continue; } - switch (_data[i].type()) - { - case value_type::int32: - case value_type::float32: - case value_type::uint32: - // Convert to string and advance - toStr(ptr, end - ptr, _data[i]); - while (*ptr != 0) ptr++; - - break; - case value_type::string: - { - // Copy string and advance - const char* value = _data[i].get(); - copy_string(value, i, ptr); - } break; - case value_type::newline: - *ptr = '\n'; ptr++; - break; - case value_type::list: - ptr = lists.toString(ptr, _data[i].get()); - break; - case value_type::list_flag: - ptr = lists.toString(ptr, _data[i].get()); - break; - default: inkFail("cant convert expression to string!"); - } - } - - // Make sure last character is a null - *ptr = 0; - - // Reset stream size to where we last held the marker - _size = start; - - // Return processed string - end = clean_string(buffer, buffer + c_str_len(buffer)); - *end = 0; - _last_char = end[-1]; - if constexpr (RemoveTail) { - if (_last_char == ' ') { end[-1] = 0; } - } - - return buffer; - } +size_t basic_stream::find_start() const +{ + // Find marker (or start) + size_t start = _size; + while (start > 0) { + start--; + if (_data[start].type() == value_type::marker) + break; + } + + // Make sure we're not violating a save point + if (_save != ~0 && start < _save) { + // TODO: check if we don't reset save correct + // at some point we can modifiy the output even behind save (probally discard?) and push a new + // element -> invalid save point inkAssert(false, "Trying to access output stream prior to save + // point!"); + const_cast(*this).clear(); + } + + return start; +} - size_t basic_stream::find_start() const - { - // Find marker (or start) - size_t start = _size; - while (start > 0) - { - start--; - if (_data[start].type() == value_type::marker) - break; - } - - // Make sure we're not violating a save point - if (_save != ~0 && start < _save) { - // TODO: check if we don't reset save correct - // at some point we can modifiy the output even behind save (probally discard?) and push a new element -> invalid save point - // inkAssert(false, "Trying to access output stream prior to save point!"); - const_cast(*this).clear(); - } - - return start; +bool basic_stream::should_skip(size_t iter, bool& hasGlue, bool& lastNewline) const +{ + if (_data[iter].printable() && _data[iter].type() != value_type::newline + && _data[iter].type() != value_type::string) { + lastNewline = false; + hasGlue = false; + } else { + switch (_data[iter].type()) { + case value_type::newline: + if (lastNewline) + return true; + if (hasGlue) + return true; + lastNewline = true; + break; + case value_type::glue: hasGlue = true; break; + case value_type::string: { + lastNewline = false; + // an empty string don't count as glued I095 + for (const char* i = _data[iter].get(); *i; ++i) { + // isspace only supports characters in [0, UCHAR_MAX] + if (! isspace(static_cast(*i))) { + hasGlue = false; + break; + } } + } break; + default: break; + } + } - bool basic_stream::should_skip(size_t iter, bool& hasGlue, bool& lastNewline) const - { - if (_data[iter].printable() - && _data[iter].type() != value_type::newline - && _data[iter].type() != value_type::string) { - lastNewline = false; - hasGlue = false; - } else { - switch (_data[iter].type()) - { - case value_type::newline: - if (lastNewline) - return true; - if (hasGlue) - return true; - lastNewline = true; - break; - case value_type::glue: - hasGlue = true; - break; - case value_type::string: - { - lastNewline = false; - // an empty string don't count as glued I095 - for(const char* i=_data[iter].get(); - *i; ++i) - { - // isspace only supports characters in [0, UCHAR_MAX] - if (!isspace(static_cast(*i))) { - hasGlue = false; - break; - } - } - } break; - default: - break; - } - } - - return false; - } + return false; +} - bool basic_stream::text_past_save() const - { - // Check if there is text past the save - for (size_t i = _save; i < _size; i++) - { - const value& d = _data[i]; - if (d.type() == value_type::string) - { - // TODO: Cache what counts as whitespace? - if (!is_whitespace(d.get(), false)) - return true; - } - } - - // No text - return false; - } +bool basic_stream::text_past_save() const +{ + // Check if there is text past the save + for (size_t i = _save; i < _size; i++) { + const value& d = _data[i]; + if (d.type() == value_type::string) { + // TODO: Cache what counts as whitespace? + if (! is_whitespace(d.get(), false)) + return true; + } + } + + // No text + return false; +} - void basic_stream::clear() - { - _save = ~0; - _size = 0; - } +void basic_stream::clear() +{ + _save = ~0; + _size = 0; +} - void basic_stream::mark_used(string_table& strings, list_table& lists) const - { - // Find all allocated strings and mark them as used - for (size_t i = 0; i < _size; i++) - { - if (_data[i].type() == value_type::string) { - string_type str = _data[i].get(); - if (str.allocated) { - strings.mark_used(str.str); - } - } else if (_data[i].type() == value_type::list) { - lists.mark_used(_data[i].get()); - } - } - } +void basic_stream::mark_used(string_table& strings, list_table& lists) const +{ + // Find all allocated strings and mark them as used + for (size_t i = 0; i < _size; i++) { + if (_data[i].type() == value_type::string) { + string_type str = _data[i].get(); + if (str.allocated) { + strings.mark_used(str.str); + } + } else if (_data[i].type() == value_type::list) { + lists.mark_used(_data[i].get()); + } + } +} #ifdef INK_ENABLE_STL - std::ostream& operator<<(std::ostream& out, basic_stream& in) - { - out << in.get(); - return out; - } +std::ostream& operator<<(std::ostream& out, basic_stream& in) +{ + out << in.get(); + return out; +} - basic_stream& operator>>(basic_stream& in, std::string& out) - { - out = in.get(); - return in; - } +basic_stream& operator>>(basic_stream& in, std::string& out) +{ + out = in.get(); + return in; +} #endif #ifdef INK_ENABLE_UNREAL - basic_stream& operator>>(basic_stream& in, FString& out) - { - out = in.get(); - return in; - } +basic_stream& operator>>(basic_stream& in, FString& out) +{ + out = in.get(); + return in; +} #endif - size_t basic_stream::snap(unsigned char* data, const snapper& snapper) const - { - unsigned char* ptr = data; - ptr = snap_write(ptr, _last_char, data != nullptr); - ptr = snap_write(ptr, _size, data != nullptr); - ptr = snap_write(ptr, _save, data != nullptr); - for(auto itr = _data; itr != _data + _size; ++itr) - { - ptr += itr->snap(data ? ptr : nullptr, snapper); - } - return ptr - data; - } - - const unsigned char* basic_stream::snap_load(const unsigned char* ptr, const loader& loader) - { - ptr = snap_read(ptr, _last_char); - ptr = snap_read(ptr, _size); - ptr = snap_read(ptr, _save); - inkAssert(_max >= _size, "output is to small to hold stored data"); - for(auto itr = _data; itr != _data + _size; ++itr) - { - ptr = itr->snap_load(ptr, loader); - } - return ptr; - } +size_t basic_stream::snap(unsigned char* data, const snapper& snapper) const +{ + unsigned char* ptr = data; + ptr = snap_write(ptr, _last_char, data != nullptr); + ptr = snap_write(ptr, _size, data != nullptr); + ptr = snap_write(ptr, _save, data != nullptr); + for (auto itr = _data; itr != _data + _size; ++itr) { + ptr += itr->snap(data ? ptr : nullptr, snapper); + } + return ptr - data; } +const unsigned char* basic_stream::snap_load(const unsigned char* ptr, const loader& loader) +{ + ptr = snap_read(ptr, _last_char); + ptr = snap_read(ptr, _size); + ptr = snap_read(ptr, _save); + inkAssert(_max >= _size, "output is to small to hold stored data"); + for (auto itr = _data; itr != _data + _size; ++itr) { + ptr = itr->snap_load(ptr, loader); + } + return ptr; +} +} // namespace ink::runtime::internal From eceb5f880c4ca707d45de37c2974109fc00aa4af Mon Sep 17 00:00:00 2001 From: Julian Benda Date: Fri, 15 Dec 2023 10:17:09 +0100 Subject: [PATCH 29/29] formatting bug --- .clang-format | 1 + .github/workflows/build.yml | 2 +- inkcpp/array.h | 690 +++--- inkcpp/output.cpp | 634 ++--- inkcpp/runner_impl.cpp | 2326 +++++++++--------- inkcpp/story_ptr.cpp | 2 +- inkcpp_cl/inkcpp_cl.cpp | 362 +-- inkcpp_test/SpaceAfterBracketChoice.cpp | 66 +- inkcpp_test/ThirdTierChoiceAfterBrackets.cpp | 52 +- 9 files changed, 2068 insertions(+), 2067 deletions(-) diff --git a/.clang-format b/.clang-format index 3256552f..b5be3ec7 100644 --- a/.clang-format +++ b/.clang-format @@ -79,6 +79,7 @@ IndentGotoLabels: false IndentPPDirectives: AfterHash # IndentRequiresClause: true IndentWidth: 2 +TabWidth: 2 IndentWrappedFunctionNames: true # InsertBraces: true # InsertNewlineAtEOF: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de3ae67c..92af0cac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -194,7 +194,7 @@ jobs: # if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'brwarner/inkcpp' }} if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'master' }} runs-on: ubuntu-latest - needs: compilation + needs: [compilation, clang-format] permissions: pull-requests: write steps: diff --git a/inkcpp/array.h b/inkcpp/array.h index a72b2a6b..0be3484b 100644 --- a/inkcpp/array.h +++ b/inkcpp/array.h @@ -10,456 +10,456 @@ template class managed_array : public snapshot_interface { public: - managed_array() - : _static_data{} - , _capacity{initialCapacity} - , _size{0} - { - if constexpr (dynamic) { - _dynamic_data = new T[initialCapacity]; - } - } - - ~managed_array() - { - if constexpr (dynamic) { - delete[] _dynamic_data; - } - } - - const T& operator[](size_t i) const { return data()[i]; } - - T& operator[](size_t i) { return data()[i]; } - - const T* data() const - { - if constexpr (dynamic) { - return _dynamic_data; - } else { - return _static_data; - } - } - - T* data() - { - if constexpr (dynamic) { - return _dynamic_data; - } else { - return _static_data; - } - } - - const T* begin() const { return data(); } - - T* begin() { return data(); } - - const T* end() const { return data() + _size; } - - T* end() { return data() + _size; } - - const T& back() const { return end()[-1]; } - - T& back() { return end()[-1]; } - - const size_t size() const { return _size; } - - const size_t capacity() const { return _capacity; } - - T& push() - { - if constexpr (dynamic) { - if (_size == _capacity) { - extend(); - } - } else { - inkAssert(_size <= _capacity, "Stack Overflow!"); - /// FIXME silent fail!! - } - return data()[_size++]; - } - - void clear() { _size = 0; } - - void resize(size_t size) - { - if constexpr (dynamic) { - if (size > _capacity) { - extend(size); - } - } else { - inkAssert(size <= _size, "Only allow to reduce size"); - } - _size = size; - } - - void extend(size_t capacity = 0); - - size_t snap(unsigned char* data, const snapper& snapper) const - { - inkAssert(! is_pointer{}(), "here is a special case oversight"); - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write(ptr, _size, should_write); - for (const T& e : *this) { - if constexpr (is_base_of::value) { - ptr += e.snap(data == nullptr ? nullptr : ptr, snapper); - } else { - ptr = snap_write(ptr, e, should_write); - } - } - return ptr - data; - } - - const unsigned char* snap_load(const unsigned char* ptr, const loader& loader) - { - decltype(_size) size; - ptr = snap_read(ptr, size); - if constexpr (dynamic) { - resize(size); - } else { - inkAssert(size <= initialCapacity, "capacity of non dynamic array is to small vor snapshot!"); - _size = size; - } - for (T& e : *this) { - if constexpr (is_base_of::value) { - ptr = e.snap_load(ptr, loader); - } else { - ptr = snap_read(ptr, e); - } - } - return ptr; - } + managed_array() + : _static_data{} + , _capacity{initialCapacity} + , _size{0} + { + if constexpr (dynamic) { + _dynamic_data = new T[initialCapacity]; + } + } + + ~managed_array() + { + if constexpr (dynamic) { + delete[] _dynamic_data; + } + } + + const T& operator[](size_t i) const { return data()[i]; } + + T& operator[](size_t i) { return data()[i]; } + + const T* data() const + { + if constexpr (dynamic) { + return _dynamic_data; + } else { + return _static_data; + } + } + + T* data() + { + if constexpr (dynamic) { + return _dynamic_data; + } else { + return _static_data; + } + } + + const T* begin() const { return data(); } + + T* begin() { return data(); } + + const T* end() const { return data() + _size; } + + T* end() { return data() + _size; } + + const T& back() const { return end()[-1]; } + + T& back() { return end()[-1]; } + + const size_t size() const { return _size; } + + const size_t capacity() const { return _capacity; } + + T& push() + { + if constexpr (dynamic) { + if (_size == _capacity) { + extend(); + } + } else { + inkAssert(_size <= _capacity, "Stack Overflow!"); + /// FIXME silent fail!! + } + return data()[_size++]; + } + + void clear() { _size = 0; } + + void resize(size_t size) + { + if constexpr (dynamic) { + if (size > _capacity) { + extend(size); + } + } else { + inkAssert(size <= _size, "Only allow to reduce size"); + } + _size = size; + } + + void extend(size_t capacity = 0); + + size_t snap(unsigned char* data, const snapper& snapper) const + { + inkAssert(! is_pointer{}(), "here is a special case oversight"); + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _size, should_write); + for (const T& e : *this) { + if constexpr (is_base_of::value) { + ptr += e.snap(data == nullptr ? nullptr : ptr, snapper); + } else { + ptr = snap_write(ptr, e, should_write); + } + } + return ptr - data; + } + + const unsigned char* snap_load(const unsigned char* ptr, const loader& loader) + { + decltype(_size) size; + ptr = snap_read(ptr, size); + if constexpr (dynamic) { + resize(size); + } else { + inkAssert(size <= initialCapacity, "capacity of non dynamic array is to small vor snapshot!"); + _size = size; + } + for (T& e : *this) { + if constexpr (is_base_of::value) { + ptr = e.snap_load(ptr, loader); + } else { + ptr = snap_read(ptr, e); + } + } + return ptr; + } private: - if_t _static_data[dynamic ? 1 : initialCapacity]; - T* _dynamic_data = nullptr; - size_t _capacity; - size_t _size; + if_t _static_data[dynamic ? 1 : initialCapacity]; + T* _dynamic_data = nullptr; + size_t _capacity; + size_t _size; }; template class managed_restorable_array : public managed_array { - using base = managed_array; + using base = managed_array; public: - managed_restorable_array() - : base() - { - } + managed_restorable_array() + : base() + { + } - void restore() { base::resize(_last_size); } + void restore() { base::resize(_last_size); } - void save() { _last_size = this->size(); } + void save() { _last_size = this->size(); } - void forgett() { _last_size = 0; } + void forgett() { _last_size = 0; } - bool has_changed() const { return base::size() != _last_size; } + bool has_changed() const { return base::size() != _last_size; } - size_t snap(unsigned char* data, const snapshot_interface::snapper& snapper) const - { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr += base::snap(ptr, snapper); - ptr = base::snap_write(ptr, _last_size, should_write); - return ptr - data; - } + size_t snap(unsigned char* data, const snapshot_interface::snapper& snapper) const + { + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr += base::snap(ptr, snapper); + ptr = base::snap_write(ptr, _last_size, should_write); + return ptr - data; + } - const unsigned char* snap_load(const unsigned char* ptr, const snapshot_interface::loader& loader) - { - ptr = base::snap_load(ptr, loader); - ptr = base::snap_read(ptr, _last_size); - return ptr; - } + const unsigned char* snap_load(const unsigned char* ptr, const snapshot_interface::loader& loader) + { + ptr = base::snap_load(ptr, loader); + ptr = base::snap_read(ptr, _last_size); + return ptr; + } private: - size_t _last_size = 0; + size_t _last_size = 0; }; template void managed_array::extend(size_t capacity) { - static_assert(dynamic, "Can only extend if array is dynamic!"); - size_t new_capacity = capacity > _capacity ? capacity : 1.5f * _capacity; - if (new_capacity < 5) { - new_capacity = 5; - } - T* new_data = new T[new_capacity]; - - for (size_t i = 0; i < _capacity; ++i) { - new_data[i] = _dynamic_data[i]; - } - - delete[] _dynamic_data; - _dynamic_data = new_data; - _capacity = new_capacity; + static_assert(dynamic, "Can only extend if array is dynamic!"); + size_t new_capacity = capacity > _capacity ? capacity : 1.5f * _capacity; + if (new_capacity < 5) { + new_capacity = 5; + } + T* new_data = new T[new_capacity]; + + for (size_t i = 0; i < _capacity; ++i) { + new_data[i] = _dynamic_data[i]; + } + + delete[] _dynamic_data; + _dynamic_data = new_data; + _capacity = new_capacity; } template class basic_restorable_array : public snapshot_interface { public: - basic_restorable_array(T* array, size_t capacity, T nullValue) - : _saved(false) - , _array(array) - , _temp(array + capacity / 2) - , _capacity(capacity / 2) - , _null(nullValue) - { - inkAssert( - capacity % 2 == 0, - "basic_restorable_array requires a datablock of even length to split into two arrays" - ); - - // zero out main array and put 'nulls' in the clear_temp() - inkZeroMemory(_array, _capacity * sizeof(T)); - clear_temp(); - } - - // == Non-Copyable == - basic_restorable_array(const basic_restorable_array&) = delete; - basic_restorable_array& operator=(const basic_restorable_array&) = delete; - - // set value by index - void set(size_t index, const T& value); - - // get value by index - const T& get(size_t index) const; - - // size of the array - inline size_t capacity() const { return _capacity; } - - // only const indexing is supported due to save/restore system - inline const T& operator[](size_t index) const { return get(index); } - - // == Save/Restore == - void save(); - void restore(); - void forget(); - - // Resets all values and clears any save points - void clear(const T& value); - - // snapshot interface - virtual size_t snap(unsigned char* data, const snapper&) const; - virtual const unsigned char* snap_load(const unsigned char* data, const loader&); + basic_restorable_array(T* array, size_t capacity, T nullValue) + : _saved(false) + , _array(array) + , _temp(array + capacity / 2) + , _capacity(capacity / 2) + , _null(nullValue) + { + inkAssert( + capacity % 2 == 0, + "basic_restorable_array requires a datablock of even length to split into two arrays" + ); + + // zero out main array and put 'nulls' in the clear_temp() + inkZeroMemory(_array, _capacity * sizeof(T)); + clear_temp(); + } + + // == Non-Copyable == + basic_restorable_array(const basic_restorable_array&) = delete; + basic_restorable_array& operator=(const basic_restorable_array&) = delete; + + // set value by index + void set(size_t index, const T& value); + + // get value by index + const T& get(size_t index) const; + + // size of the array + inline size_t capacity() const { return _capacity; } + + // only const indexing is supported due to save/restore system + inline const T& operator[](size_t index) const { return get(index); } + + // == Save/Restore == + void save(); + void restore(); + void forget(); + + // Resets all values and clears any save points + void clear(const T& value); + + // snapshot interface + virtual size_t snap(unsigned char* data, const snapper&) const; + virtual const unsigned char* snap_load(const unsigned char* data, const loader&); protected: - inline T* buffer() { return _array; } + inline T* buffer() { return _array; } - void set_new_buffer(T* buffer, size_t capacity) - { - _array = buffer; - _temp = buffer + capacity / 2; - _capacity = capacity / 2; - } + void set_new_buffer(T* buffer, size_t capacity) + { + _array = buffer; + _temp = buffer + capacity / 2; + _capacity = capacity / 2; + } private: - inline void check_index(size_t index) const - { - inkAssert(index < capacity(), "Index out of range!"); - } + inline void check_index(size_t index) const + { + inkAssert(index < capacity(), "Index out of range!"); + } - void clear_temp(); + void clear_temp(); private: - bool _saved; + bool _saved; - // real values live here - T* _array; + // real values live here + T* _array; - // we store values here when we're in save mode - // they're copied on a call to forget() - T* _temp; + // we store values here when we're in save mode + // they're copied on a call to forget() + T* _temp; - // size of both _array and _temp - size_t _capacity; + // size of both _array and _temp + size_t _capacity; - // null - const T _null; + // null + const T _null; }; template inline size_t basic_restorable_array::snap(unsigned char* data, const snapper& snapper) const { - unsigned char* ptr = data; - bool should_write = data != nullptr; - ptr = snap_write(ptr, _saved, should_write); - ptr = snap_write(ptr, _capacity, should_write); - ptr = snap_write(ptr, _null, should_write); - for (size_t i = 0; i < _capacity; ++i) { - ptr = snap_write(ptr, _array[i], should_write); - ptr = snap_write(ptr, _temp[i], should_write); - } - return ptr - data; + unsigned char* ptr = data; + bool should_write = data != nullptr; + ptr = snap_write(ptr, _saved, should_write); + ptr = snap_write(ptr, _capacity, should_write); + ptr = snap_write(ptr, _null, should_write); + for (size_t i = 0; i < _capacity; ++i) { + ptr = snap_write(ptr, _array[i], should_write); + ptr = snap_write(ptr, _temp[i], should_write); + } + return ptr - data; } template inline const unsigned char* basic_restorable_array::snap_load(const unsigned char* data, const loader& loader) { - auto ptr = data; - ptr = snap_read(ptr, _saved); - ptr = snap_read(ptr, _capacity); - T null; - ptr = snap_read(ptr, null); - inkAssert(null == _null, "null value is different to snapshot!"); - for (size_t i = 0; i < _capacity; ++i) { - ptr = snap_read(ptr, _array[i]); - ptr = snap_read(ptr, _temp[i]); - } - return ptr; + auto ptr = data; + ptr = snap_read(ptr, _saved); + ptr = snap_read(ptr, _capacity); + T null; + ptr = snap_read(ptr, null); + inkAssert(null == _null, "null value is different to snapshot!"); + for (size_t i = 0; i < _capacity; ++i) { + ptr = snap_read(ptr, _array[i]); + ptr = snap_read(ptr, _temp[i]); + } + return ptr; } template inline void basic_restorable_array::set(size_t index, const T& value) { - check_index(index); - inkAssert(value != _null, "Can not add a value considered a 'null' to a restorable_array"); - - // If we're saved, store in second half of the array - if (_saved) { - _temp[index] = value; - } else { - // Otherwise, store in the main array - _array[index] = value; - } + check_index(index); + inkAssert(value != _null, "Can not add a value considered a 'null' to a restorable_array"); + + // If we're saved, store in second half of the array + if (_saved) { + _temp[index] = value; + } else { + // Otherwise, store in the main array + _array[index] = value; + } } template inline const T& basic_restorable_array::get(size_t index) const { - check_index(index); + check_index(index); - // If we're in save mode and we have a value at that index, return that instead - if (_saved && _temp[index] != _null) { - return _temp[index]; - } + // If we're in save mode and we have a value at that index, return that instead + if (_saved && _temp[index] != _null) { + return _temp[index]; + } - // Otherwise, fall back on the real array - return _array[index]; + // Otherwise, fall back on the real array + return _array[index]; } template inline void basic_restorable_array::save() { - // Put us into save/restore mode - _saved = true; + // Put us into save/restore mode + _saved = true; } template inline void basic_restorable_array::restore() { - clear_temp(); + clear_temp(); - // Clear saved flag - _saved = false; + // Clear saved flag + _saved = false; } template inline void basic_restorable_array::forget() { - // Run through the _temp array - for (size_t i = 0; i < _capacity; i++) { - // Copy if there's values - if (_temp[i] != _null) { - _array[i] = _temp[i]; - } - - // Clear - _temp[i] = _null; - } + // Run through the _temp array + for (size_t i = 0; i < _capacity; i++) { + // Copy if there's values + if (_temp[i] != _null) { + _array[i] = _temp[i]; + } + + // Clear + _temp[i] = _null; + } } template inline void basic_restorable_array::clear_temp() { - // Run through the _temp array - for (size_t i = 0; i < _capacity; i++) { - // Clear - _temp[i] = _null; - } + // Run through the _temp array + for (size_t i = 0; i < _capacity; i++) { + // Clear + _temp[i] = _null; + } } template inline void basic_restorable_array::clear(const T& value) { - _saved = false; - for (size_t i = 0; i < _capacity; i++) { - _temp[i] = _null; - _array[i] = value; - } + _saved = false; + for (size_t i = 0; i < _capacity; i++) { + _temp[i] = _null; + _array[i] = value; + } } template class fixed_restorable_array : public basic_restorable_array { public: - fixed_restorable_array(const T& initial, const T& nullValue) - : basic_restorable_array(_buffer, SIZE * 2, nullValue) - { - basic_restorable_array::clear(initial); - } + fixed_restorable_array(const T& initial, const T& nullValue) + : basic_restorable_array(_buffer, SIZE * 2, nullValue) + { + basic_restorable_array::clear(initial); + } private: - T _buffer[SIZE * 2]; + T _buffer[SIZE * 2]; }; template class allocated_restorable_array final : public basic_restorable_array { - using base = basic_restorable_array; + using base = basic_restorable_array; public: - allocated_restorable_array(const T& initial, const T& nullValue) - : basic_restorable_array(0, 0, nullValue) - , _initialValue{initial} - , _nullValue{nullValue} - , _buffer{nullptr} - { - } - - allocated_restorable_array(size_t capacity, const T& initial, const T& nullValue) - : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue) - , _initialValue{initial} - , _nullValue{nullValue} - { - _buffer = this->buffer(); - this->clear(_initialValue); - } - - void resize(size_t n) - { - size_t new_capacity = 2 * n; - T* new_buffer = new T[new_capacity]; - if (_buffer) { - for (size_t i = 0; i < base::capacity(); ++i) { - new_buffer[i] = _buffer[i]; - // copy temp - new_buffer[i + base::capacity()] = _buffer[i + base::capacity()]; - } - delete[] _buffer; - } - for (size_t i = base::capacity(); i < new_capacity; ++i) { - new_buffer[i] = _initialValue; - new_buffer[i + base::capacity()] = _nullValue; - } - - _buffer = new_buffer; - this->set_new_buffer(_buffer, new_capacity); - } - - virtual ~allocated_restorable_array() - { - if (_buffer) { - delete[] _buffer; - _buffer = nullptr; - } - } + allocated_restorable_array(const T& initial, const T& nullValue) + : basic_restorable_array(0, 0, nullValue) + , _initialValue{initial} + , _nullValue{nullValue} + , _buffer{nullptr} + { + } + + allocated_restorable_array(size_t capacity, const T& initial, const T& nullValue) + : basic_restorable_array(new T[capacity * 2], capacity * 2, nullValue) + , _initialValue{initial} + , _nullValue{nullValue} + { + _buffer = this->buffer(); + this->clear(_initialValue); + } + + void resize(size_t n) + { + size_t new_capacity = 2 * n; + T* new_buffer = new T[new_capacity]; + if (_buffer) { + for (size_t i = 0; i < base::capacity(); ++i) { + new_buffer[i] = _buffer[i]; + // copy temp + new_buffer[i + base::capacity()] = _buffer[i + base::capacity()]; + } + delete[] _buffer; + } + for (size_t i = base::capacity(); i < new_capacity; ++i) { + new_buffer[i] = _initialValue; + new_buffer[i + base::capacity()] = _nullValue; + } + + _buffer = new_buffer; + this->set_new_buffer(_buffer, new_capacity); + } + + virtual ~allocated_restorable_array() + { + if (_buffer) { + delete[] _buffer; + _buffer = nullptr; + } + } private: - T _initialValue; - T _nullValue; - T* _buffer; + T _initialValue; + T _nullValue; + T* _buffer; }; } // namespace ink::runtime::internal diff --git a/inkcpp/output.cpp b/inkcpp/output.cpp index d2947d4b..09075bf6 100644 --- a/inkcpp/output.cpp +++ b/inkcpp/output.cpp @@ -5,7 +5,7 @@ #include "string_utils.h" #ifdef INK_ENABLE_STL -# include +# include #endif namespace ink::runtime::internal @@ -20,254 +20,254 @@ basic_stream::basic_stream(value* buffer, size_t len) void basic_stream::append(const value& in) { - // SPECIAL: Incoming newline - if (in.type() == value_type::newline && _size > 1) { - // If the end of the stream is a function start marker, we actually - // want to ignore this. Function start trimming. - if (_data[_size - 1].type() == value_type::func_start) - return; - } - - // Ignore leading newlines - if (in.type() == value_type::newline && _size == 0) - return; - - // Add to data stream - inkAssert(_size < _max, "Output stream overflow"); - _data[_size++] = in; - - // Special: Incoming glue. Trim whitespace/newlines prior - // This also applies when a function ends to trim trailing whitespace. - if ((in.type() == value_type::glue || in.type() == value_type::func_end) && _size > 1) { - // Run backwards - size_t i = _size - 2; - while (true) { - value& d = _data[i]; - - // Nullify newlines - if (d.type() == value_type::newline) { - d = value{}; - } - - // Nullify whitespace - else if (d.type() == value_type::string && is_whitespace(d.get())) - d = value{}; - - // If it's not a newline or whitespace, stop - else - break; - - // If we've hit the end, break - if (i == 0) - break; - - // Move on to next element - i--; - } - } + // SPECIAL: Incoming newline + if (in.type() == value_type::newline && _size > 1) { + // If the end of the stream is a function start marker, we actually + // want to ignore this. Function start trimming. + if (_data[_size - 1].type() == value_type::func_start) + return; + } + + // Ignore leading newlines + if (in.type() == value_type::newline && _size == 0) + return; + + // Add to data stream + inkAssert(_size < _max, "Output stream overflow"); + _data[_size++] = in; + + // Special: Incoming glue. Trim whitespace/newlines prior + // This also applies when a function ends to trim trailing whitespace. + if ((in.type() == value_type::glue || in.type() == value_type::func_end) && _size > 1) { + // Run backwards + size_t i = _size - 2; + while (true) { + value& d = _data[i]; + + // Nullify newlines + if (d.type() == value_type::newline) { + d = value{}; + } + + // Nullify whitespace + else if (d.type() == value_type::string && is_whitespace(d.get())) + d = value{}; + + // If it's not a newline or whitespace, stop + else + break; + + // If we've hit the end, break + if (i == 0) + break; + + // Move on to next element + i--; + } + } } void basic_stream::append(const value* in, unsigned int length) { - // TODO: Better way to bulk while still executing glue checks? - for (size_t i = 0; i < length; i++) - append(in[i]); + // TODO: Better way to bulk while still executing glue checks? + for (size_t i = 0; i < length; i++) + append(in[i]); } template inline void write_char(T& output, char c) { - static_assert(always_false::value, "Invalid output type"); + static_assert(always_false::value, "Invalid output type"); } template<> inline void write_char(char*& output, char c) { - (*output++) = c; + (*output++) = c; } #ifdef INK_ENABLE_STL template<> inline void write_char(std::stringstream& output, char c) { - output.put(c); + output.put(c); } #endif inline bool get_next(const value* list, size_t i, size_t size, const value** next) { - while (i + 1 < size) { - *next = &list[i + 1]; - value_type type = (*next)->type(); - if ((*next)->printable()) { - return true; - } - i++; - } + while (i + 1 < size) { + *next = &list[i + 1]; + value_type type = (*next)->type(); + if ((*next)->printable()) { + return true; + } + i++; + } - return false; + return false; } template void basic_stream::copy_string(const char* str, size_t& dataIter, T& output) { - while (*str != 0) { - write_char(output, *str++); - } + while (*str != 0) { + write_char(output, *str++); + } } #ifdef INK_ENABLE_STL std::string basic_stream::get() { - size_t start = find_start(); - - // Move up from marker - bool hasGlue = false, lastNewline = false; - std::stringstream str; - for (size_t i = start; i < _size; i++) { - if (should_skip(i, hasGlue, lastNewline)) - continue; - if (_data[i].printable()) { - _data[i].write(str, _lists_table); - } - } - - // Reset stream size to where we last held the marker - _size = start; - - // Return processed string - // remove mulitple accourencies of ' ' - std::string result = str.str(); - if (! result.empty()) { - auto end = clean_string(result.begin(), result.end()); - if (result.begin() == end) { - result.resize(0); - } else { - _last_char = *(end - 1); - result.resize(end - result.begin() - (_last_char == ' ' ? 1 : 0)); - } - } - return result; + size_t start = find_start(); + + // Move up from marker + bool hasGlue = false, lastNewline = false; + std::stringstream str; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + if (_data[i].printable()) { + _data[i].write(str, _lists_table); + } + } + + // Reset stream size to where we last held the marker + _size = start; + + // Return processed string + // remove mulitple accourencies of ' ' + std::string result = str.str(); + if (! result.empty()) { + auto end = clean_string(result.begin(), result.end()); + if (result.begin() == end) { + result.resize(0); + } else { + _last_char = *(end - 1); + result.resize(end - result.begin() - (_last_char == ' ' ? 1 : 0)); + } + } + return result; } #endif #ifdef INK_ENABLE_UNREAL FString basic_stream::get() { - UE_LOG( - InkCpp, Warning, - TEXT("Basic stream::get is not implemented correctly and should not be used implemented " - "correctly!") - ); - FString str; - return str; + UE_LOG( + InkCpp, Warning, + TEXT("Basic stream::get is not implemented correctly and should not be used implemented " + "correctly!") + ); + FString str; + return str; } #endif int basic_stream::queued() const { - size_t start = find_start(); - return _size - start; + size_t start = find_start(); + return _size - start; } const value& basic_stream::peek() const { - inkAssert(_size > 0, "Attempting to peek empty stream!"); - return _data[_size - 1]; + inkAssert(_size > 0, "Attempting to peek empty stream!"); + return _data[_size - 1]; } void basic_stream::discard(size_t length) { - // discard elements - _size -= length; - if (_size < 0) - _size = 0; + // discard elements + _size -= length; + if (_size < 0) + _size = 0; } void basic_stream::get(value* ptr, size_t length) { - // Find start - size_t start = find_start(); + // Find start + size_t start = find_start(); - const value* end = ptr + length; - // inkAssert(_size - start < length, "Insufficient space in data array to store stream - // contents!"); + const value* end = ptr + length; + // inkAssert(_size - start < length, "Insufficient space in data array to store stream + // contents!"); - // Move up from marker - bool hasGlue = false, lastNewline = false; - for (size_t i = start; i < _size; i++) { - if (should_skip(i, hasGlue, lastNewline)) - continue; + // Move up from marker + bool hasGlue = false, lastNewline = false; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; - // Make sure we can fit the next element - inkAssert(ptr < end, "Insufficient space in data array to store stream contents!"); + // Make sure we can fit the next element + inkAssert(ptr < end, "Insufficient space in data array to store stream contents!"); - // Copy any value elements - if (_data[i].printable()) { - *(ptr++) = _data[i]; - } - } + // Copy any value elements + if (_data[i].printable()) { + *(ptr++) = _data[i]; + } + } - // Reset stream size to where we last held the marker - _size = start; + // Reset stream size to where we last held the marker + _size = start; } bool basic_stream::has_marker() const { - return entries_since_marker() >= 0; + return entries_since_marker() >= 0; } int basic_stream::entries_since_marker() const { - // TODO: Cache? - for (size_t i = 0; i < _size; i++) { - if (_data[i].type() == value_type::marker) - return i; - } + // TODO: Cache? + for (size_t i = 0; i < _size; i++) { + if (_data[i].type() == value_type::marker) + return i; + } - return -1; + return -1; } bool basic_stream::ends_with(value_type type) const { - if (_size == 0) - return false; + if (_size == 0) + return false; - return _data[_size - 1].type() == type; + return _data[_size - 1].type() == type; } bool basic_stream::saved_ends_with(value_type type) const { - inkAssert(_save != ~0, "Stream is not saved!"); + inkAssert(_save != ~0, "Stream is not saved!"); - if (_save == 0) - return false; + if (_save == 0) + return false; - return _data[_save - 1].type() == type; + return _data[_save - 1].type() == type; } void basic_stream::save() { - inkAssert(_save == ~0, "Can not save over existing save point!"); + inkAssert(_save == ~0, "Can not save over existing save point!"); - // Save the current size - _save = _size; + // Save the current size + _save = _size; } void basic_stream::restore() { - inkAssert(_save != ~0, "No save point to restore!"); + inkAssert(_save != ~0, "No save point to restore!"); - // Restore size to saved position - _size = _save; - _save = ~0; + // Restore size to saved position + _size = _save; + _save = ~0; } void basic_stream::forget() { - inkAssert(_save != ~0, "No save point to forget!"); + inkAssert(_save != ~0, "No save point to forget!"); - // Just null the save point and continue as normal - _save = ~0; + // Just null the save point and continue as normal + _save = ~0; } template char* basic_stream::get_alloc(string_table& strings, list_table& lists); @@ -276,219 +276,219 @@ template char* basic_stream::get_alloc(string_table& strings, list_table& template char* basic_stream::get_alloc(string_table& strings, list_table& lists) { - size_t start = find_start(); - - // Two passes. First for length - size_t length = 0; - bool hasGlue = false, lastNewline = false; - for (size_t i = start; i < _size; i++) { - if (should_skip(i, hasGlue, lastNewline)) - continue; - ++length; // potenzial space to sperate - if (_data[i].printable()) { - switch (_data[i].type()) { - case value_type::list: length += lists.stringLen(_data[i].get()); break; - case value_type::list_flag: - length += lists.stringLen(_data[i].get()); - break; - default: length += value_length(_data[i]); - } - } - } - - // Allocate - char* buffer = strings.create(length + 1); - char* end = buffer + length + 1; - char* ptr = buffer; - hasGlue = false; - lastNewline = false; - for (size_t i = start; i < _size; i++) { - if (should_skip(i, hasGlue, lastNewline)) - continue; - if (! _data[i].printable()) { - continue; - } - switch (_data[i].type()) { - case value_type::int32: - case value_type::float32: - case value_type::uint32: - // Convert to string and advance - toStr(ptr, end - ptr, _data[i]); - while (*ptr != 0) - ptr++; - - break; - case value_type::string: { - // Copy string and advance - const char* value = _data[i].get(); - copy_string(value, i, ptr); - } break; - case value_type::newline: - *ptr = '\n'; - ptr++; - break; - case value_type::list: ptr = lists.toString(ptr, _data[i].get()); break; - case value_type::list_flag: - ptr = lists.toString(ptr, _data[i].get()); - break; - default: inkFail("cant convert expression to string!"); - } - } - - // Make sure last character is a null - *ptr = 0; - - // Reset stream size to where we last held the marker - _size = start; - - // Return processed string - end = clean_string(buffer, buffer + c_str_len(buffer)); - *end = 0; - _last_char = end[-1]; - if constexpr (RemoveTail) { - if (_last_char == ' ') { - end[-1] = 0; - } - } - - return buffer; + size_t start = find_start(); + + // Two passes. First for length + size_t length = 0; + bool hasGlue = false, lastNewline = false; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + ++length; // potenzial space to sperate + if (_data[i].printable()) { + switch (_data[i].type()) { + case value_type::list: length += lists.stringLen(_data[i].get()); break; + case value_type::list_flag: + length += lists.stringLen(_data[i].get()); + break; + default: length += value_length(_data[i]); + } + } + } + + // Allocate + char* buffer = strings.create(length + 1); + char* end = buffer + length + 1; + char* ptr = buffer; + hasGlue = false; + lastNewline = false; + for (size_t i = start; i < _size; i++) { + if (should_skip(i, hasGlue, lastNewline)) + continue; + if (! _data[i].printable()) { + continue; + } + switch (_data[i].type()) { + case value_type::int32: + case value_type::float32: + case value_type::uint32: + // Convert to string and advance + toStr(ptr, end - ptr, _data[i]); + while (*ptr != 0) + ptr++; + + break; + case value_type::string: { + // Copy string and advance + const char* value = _data[i].get(); + copy_string(value, i, ptr); + } break; + case value_type::newline: + *ptr = '\n'; + ptr++; + break; + case value_type::list: ptr = lists.toString(ptr, _data[i].get()); break; + case value_type::list_flag: + ptr = lists.toString(ptr, _data[i].get()); + break; + default: inkFail("cant convert expression to string!"); + } + } + + // Make sure last character is a null + *ptr = 0; + + // Reset stream size to where we last held the marker + _size = start; + + // Return processed string + end = clean_string(buffer, buffer + c_str_len(buffer)); + *end = 0; + _last_char = end[-1]; + if constexpr (RemoveTail) { + if (_last_char == ' ') { + end[-1] = 0; + } + } + + return buffer; } size_t basic_stream::find_start() const { - // Find marker (or start) - size_t start = _size; - while (start > 0) { - start--; - if (_data[start].type() == value_type::marker) - break; - } + // Find marker (or start) + size_t start = _size; + while (start > 0) { + start--; + if (_data[start].type() == value_type::marker) + break; + } - // Make sure we're not violating a save point - if (_save != ~0 && start < _save) { - // TODO: check if we don't reset save correct - // at some point we can modifiy the output even behind save (probally discard?) and push a new - // element -> invalid save point inkAssert(false, "Trying to access output stream prior to save - // point!"); - const_cast(*this).clear(); - } + // Make sure we're not violating a save point + if (_save != ~0 && start < _save) { + // TODO: check if we don't reset save correct + // at some point we can modifiy the output even behind save (probally discard?) and push a new + // element -> invalid save point inkAssert(false, "Trying to access output stream prior to save + // point!"); + const_cast(*this).clear(); + } - return start; + return start; } bool basic_stream::should_skip(size_t iter, bool& hasGlue, bool& lastNewline) const { - if (_data[iter].printable() && _data[iter].type() != value_type::newline - && _data[iter].type() != value_type::string) { - lastNewline = false; - hasGlue = false; - } else { - switch (_data[iter].type()) { - case value_type::newline: - if (lastNewline) - return true; - if (hasGlue) - return true; - lastNewline = true; - break; - case value_type::glue: hasGlue = true; break; - case value_type::string: { - lastNewline = false; - // an empty string don't count as glued I095 - for (const char* i = _data[iter].get(); *i; ++i) { - // isspace only supports characters in [0, UCHAR_MAX] - if (! isspace(static_cast(*i))) { - hasGlue = false; - break; - } + if (_data[iter].printable() && _data[iter].type() != value_type::newline + && _data[iter].type() != value_type::string) { + lastNewline = false; + hasGlue = false; + } else { + switch (_data[iter].type()) { + case value_type::newline: + if (lastNewline) + return true; + if (hasGlue) + return true; + lastNewline = true; + break; + case value_type::glue: hasGlue = true; break; + case value_type::string: { + lastNewline = false; + // an empty string don't count as glued I095 + for (const char* i = _data[iter].get(); *i; ++i) { + // isspace only supports characters in [0, UCHAR_MAX] + if (! isspace(static_cast(*i))) { + hasGlue = false; + break; + } + } + } break; + default: break; + } } - } break; - default: break; - } - } - return false; + return false; } bool basic_stream::text_past_save() const { - // Check if there is text past the save - for (size_t i = _save; i < _size; i++) { - const value& d = _data[i]; - if (d.type() == value_type::string) { - // TODO: Cache what counts as whitespace? - if (! is_whitespace(d.get(), false)) - return true; - } - } + // Check if there is text past the save + for (size_t i = _save; i < _size; i++) { + const value& d = _data[i]; + if (d.type() == value_type::string) { + // TODO: Cache what counts as whitespace? + if (! is_whitespace(d.get(), false)) + return true; + } + } - // No text - return false; + // No text + return false; } void basic_stream::clear() { - _save = ~0; - _size = 0; + _save = ~0; + _size = 0; } void basic_stream::mark_used(string_table& strings, list_table& lists) const { - // Find all allocated strings and mark them as used - for (size_t i = 0; i < _size; i++) { - if (_data[i].type() == value_type::string) { - string_type str = _data[i].get(); - if (str.allocated) { - strings.mark_used(str.str); - } - } else if (_data[i].type() == value_type::list) { - lists.mark_used(_data[i].get()); - } - } + // Find all allocated strings and mark them as used + for (size_t i = 0; i < _size; i++) { + if (_data[i].type() == value_type::string) { + string_type str = _data[i].get(); + if (str.allocated) { + strings.mark_used(str.str); + } + } else if (_data[i].type() == value_type::list) { + lists.mark_used(_data[i].get()); + } + } } #ifdef INK_ENABLE_STL std::ostream& operator<<(std::ostream& out, basic_stream& in) { - out << in.get(); - return out; + out << in.get(); + return out; } basic_stream& operator>>(basic_stream& in, std::string& out) { - out = in.get(); - return in; + out = in.get(); + return in; } #endif #ifdef INK_ENABLE_UNREAL basic_stream& operator>>(basic_stream& in, FString& out) { - out = in.get(); - return in; + out = in.get(); + return in; } #endif size_t basic_stream::snap(unsigned char* data, const snapper& snapper) const { - unsigned char* ptr = data; - ptr = snap_write(ptr, _last_char, data != nullptr); - ptr = snap_write(ptr, _size, data != nullptr); - ptr = snap_write(ptr, _save, data != nullptr); - for (auto itr = _data; itr != _data + _size; ++itr) { - ptr += itr->snap(data ? ptr : nullptr, snapper); - } - return ptr - data; + unsigned char* ptr = data; + ptr = snap_write(ptr, _last_char, data != nullptr); + ptr = snap_write(ptr, _size, data != nullptr); + ptr = snap_write(ptr, _save, data != nullptr); + for (auto itr = _data; itr != _data + _size; ++itr) { + ptr += itr->snap(data ? ptr : nullptr, snapper); + } + return ptr - data; } const unsigned char* basic_stream::snap_load(const unsigned char* ptr, const loader& loader) { - ptr = snap_read(ptr, _last_char); - ptr = snap_read(ptr, _size); - ptr = snap_read(ptr, _save); - inkAssert(_max >= _size, "output is to small to hold stored data"); - for (auto itr = _data; itr != _data + _size; ++itr) { - ptr = itr->snap_load(ptr, loader); - } - return ptr; + ptr = snap_read(ptr, _last_char); + ptr = snap_read(ptr, _size); + ptr = snap_read(ptr, _save); + inkAssert(_max >= _size, "output is to small to hold stored data"); + for (auto itr = _data; itr != _data + _size; ++itr) { + ptr = itr->snap_load(ptr, loader); + } + return ptr; } } // namespace ink::runtime::internal diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index d25fe36b..22881d50 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -13,13 +13,13 @@ namespace ink::runtime { const choice* runner_interface::get_choice(size_t index) const { - inkAssert(index < num_choices(), "Choice out of bounds!"); - return begin() + index; + inkAssert(index < num_choices(), "Choice out of bounds!"); + return begin() + index; } size_t runner_interface::num_choices() const { - return end() - begin(); + return end() - begin(); } } // namespace ink::runtime @@ -29,38 +29,38 @@ namespace ink::runtime::internal template<> value* runner_impl::get_var(hash_t variableName) { - return _globals->get_variable(variableName); + return _globals->get_variable(variableName); } template<> value* runner_impl::get_var(hash_t variableName) { - value* ret = _stack.get(variableName); - if (! ret) { - return nullptr; - } - if (ret->type() == value_type::value_pointer) { - auto [name, ci] = ret->get(); - inkAssert(ci == 0, "only Global pointer are allowd on the _stack!"); - return get_var(name); - } - return ret; + value* ret = _stack.get(variableName); + if (! ret) { + return nullptr; + } + if (ret->type() == value_type::value_pointer) { + auto [name, ci] = ret->get(); + inkAssert(ci == 0, "only Global pointer are allowd on the _stack!"); + return get_var(name); + } + return ret; } template<> value* runner_impl::get_var(hash_t variableName) { - value* ret = get_var(variableName); - if (ret) { - return ret; - } - return get_var(variableName); + value* ret = get_var(variableName); + if (ret) { + return ret; + } + return get_var(variableName); } template const value* runner_impl::get_var(hash_t variableName) const { - return const_cast(this)->get_var(variableName); + return const_cast(this)->get_var(variableName); } template<> @@ -68,25 +68,25 @@ void runner_impl::set_var( hash_t variableName, const value& val, bool is_redef ) { - if (is_redef) { - value* src = _globals->get_variable(variableName); - _globals->set_variable(variableName, src->redefine(val, _globals->lists())); - } else { - _globals->set_variable(variableName, val); - } + if (is_redef) { + value* src = _globals->get_variable(variableName); + _globals->set_variable(variableName, src->redefine(val, _globals->lists())); + } else { + _globals->set_variable(variableName, val); + } } const value* runner_impl::dereference(const value& val) { - if (val.type() != value_type::value_pointer) { - return &val; - } - - auto [name, ci] = val.get(); - if (ci == 0) { - return get_var(name); - } - return _stack.get_from_frame(ci, name); + if (val.type() != value_type::value_pointer) { + return &val; + } + + auto [name, ci] = val.get(); + if (ci == 0) { + return get_var(name); + } + return _stack.get_from_frame(ci, name); } template<> @@ -94,39 +94,39 @@ void runner_impl::set_var( hash_t variableName, const value& val, bool is_redef ) { - if (val.type() == value_type::value_pointer) { - inkAssert(is_redef == false, "value pointer can only use to initelize variable!"); - auto [name, ci] = val.get(); - if (ci == 0) { - _stack.set(variableName, val); - } else { - const value* dref = dereference(val); - if (dref == nullptr) { - value v = val; - auto ref = v.get(); - v.set(ref.name, 0); - _stack.set(variableName, v); - } else { - _ref_stack.set(variableName, val); - _stack.set(variableName, *dref); - } - } - } else { - if (is_redef) { - value* src = _stack.get(variableName); - if (src->type() == value_type::value_pointer) { - auto [name, ci] = src->get(); - inkAssert(ci == 0, "Only global pointer are allowed on _stack!"); - set_var( - name, get_var(name)->redefine(val, _globals->lists()), true - ); - } else { - _stack.set(variableName, src->redefine(val, _globals->lists())); - } - } else { - _stack.set(variableName, val); - } - } + if (val.type() == value_type::value_pointer) { + inkAssert(is_redef == false, "value pointer can only use to initelize variable!"); + auto [name, ci] = val.get(); + if (ci == 0) { + _stack.set(variableName, val); + } else { + const value* dref = dereference(val); + if (dref == nullptr) { + value v = val; + auto ref = v.get(); + v.set(ref.name, 0); + _stack.set(variableName, v); + } else { + _ref_stack.set(variableName, val); + _stack.set(variableName, *dref); + } + } + } else { + if (is_redef) { + value* src = _stack.get(variableName); + if (src->type() == value_type::value_pointer) { + auto [name, ci] = src->get(); + inkAssert(ci == 0, "Only global pointer are allowed on _stack!"); + set_var( + name, get_var(name)->redefine(val, _globals->lists()), true + ); + } else { + _stack.set(variableName, src->redefine(val, _globals->lists())); + } + } else { + _stack.set(variableName, val); + } + } } template<> @@ -134,226 +134,226 @@ void runner_impl::set_var( hash_t variableName, const value& val, bool is_redef ) { - inkAssert(is_redef, "define set scopeless variables!"); - if (_stack.get(variableName)) { - return set_var(variableName, val, is_redef); - } else { - return set_var(variableName, val, is_redef); - } + inkAssert(is_redef, "define set scopeless variables!"); + if (_stack.get(variableName)) { + return set_var(variableName, val, is_redef); + } else { + return set_var(variableName, val, is_redef); + } } template inline T runner_impl::read() { - using header = ink::internal::header; - // Sanity - inkAssert(_ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution"); - - // Read memory - T val = *( const T* ) _ptr; - if (_story->get_header().endien == header::endian_types::differ) { - val = header::swap_bytes(val); - } + using header = ink::internal::header; + // Sanity + inkAssert(_ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution"); + + // Read memory + T val = *( const T* ) _ptr; + if (_story->get_header().endien == header::endian_types::differ) { + val = header::swap_bytes(val); + } - // Advance ip - _ptr += sizeof(T); + // Advance ip + _ptr += sizeof(T); - // Return - return val; + // Return + return val; } template<> inline const char* runner_impl::read() { - offset_t str = read(); - return _story->string(str); + offset_t str = read(); + return _story->string(str); } choice& runner_impl::add_choice() { - inkAssert( - config::maxChoices < 0 || _choices.size() < config::maxChoices, "Ran out of choice storage!" - ); - return _choices.push(); + inkAssert( + config::maxChoices < 0 || _choices.size() < config::maxChoices, "Ran out of choice storage!" + ); + return _choices.push(); } void runner_impl::clear_choices() { - // TODO: Garbage collection? ? which garbage ? - _fallback_choice = nullopt; - _choices.clear(); + // TODO: Garbage collection? ? which garbage ? + _fallback_choice = nullopt; + _choices.clear(); } void runner_impl::clear_tags() { - _tags.clear(); - _choice_tags_begin = -1; + _tags.clear(); + _choice_tags_begin = -1; } void runner_impl::jump(ip_t dest, bool record_visits) { - // Optimization: if we are _is_falling, then we can - // _should be_ able to safely assume that there is nothing to do here. A falling - // divert should only be taking us from a container to that same container's end point - // without entering any other containers - // OR IF if target is same position do nothing - // could happend if jumping to and of an unnamed container - if (dest == _ptr) { - _ptr = dest; - return; - } - - const uint32_t* iter = nullptr; - container_t id; - ip_t offset = nullptr; - size_t comm_end; - bool reversed = _ptr > dest; - - if (reversed) { - comm_end = 0; - iter = nullptr; - const ContainerData* old_iter = nullptr; - const uint32_t* last_comm_iter = nullptr; - _container.rev_iter(old_iter); - - // find commen part of old and new stack - while (_story->iterate_containers(iter, id, offset)) { - if (old_iter == nullptr || offset >= dest) { - break; - } - if (old_iter != nullptr && id == old_iter->id) { - last_comm_iter = iter; - _container.rev_iter(old_iter); - ++comm_end; - } - } - - // clear old part from stack - while (_container.size() > comm_end) { - _container.pop(); - } - iter = last_comm_iter; - - } else { - iter = nullptr; - comm_end = _container.size(); - // go to current possition in container list - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= _ptr) { - break; - } - } - _story->iterate_containers(iter, id, offset, true); - } - - // move to destination and update container stack on the go - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= dest) { - break; - } - if (_container.empty() || _container.top().id != id) { - _container.push({.id = id, .offset = offset}); - } else { - _container.pop(); - if (_container.size() < comm_end) { - comm_end = _container.size(); - } - } - } - _ptr = dest; - - // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - // it will get visited in the next step - if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { - _ptr += 6; - _container.push({.id = id, .offset = offset}); - if (reversed && comm_end == _container.size() - 1) { - ++comm_end; - } - } - - // iff all container (until now) are entered at first position - bool allEnteredAtStart = true; - ip_t child_position = dest; - if (record_visits) { - const ContainerData* iData = nullptr; - size_t level = _container.size(); - while (_container.iter(iData) - && (level > comm_end - || _story->container_flag(iData->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST) - ) { - auto parrent_offset = iData->offset; - inkAssert(child_position >= parrent_offset, "Container stack order is broken"); - // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed - // subcontainers first child check if child_positino is the first child of current container - allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); - child_position = parrent_offset; - _globals->visit(iData->id, allEnteredAtStart); - } - } + // Optimization: if we are _is_falling, then we can + // _should be_ able to safely assume that there is nothing to do here. A falling + // divert should only be taking us from a container to that same container's end point + // without entering any other containers + // OR IF if target is same position do nothing + // could happend if jumping to and of an unnamed container + if (dest == _ptr) { + _ptr = dest; + return; + } + + const uint32_t* iter = nullptr; + container_t id; + ip_t offset = nullptr; + size_t comm_end; + bool reversed = _ptr > dest; + + if (reversed) { + comm_end = 0; + iter = nullptr; + const ContainerData* old_iter = nullptr; + const uint32_t* last_comm_iter = nullptr; + _container.rev_iter(old_iter); + + // find commen part of old and new stack + while (_story->iterate_containers(iter, id, offset)) { + if (old_iter == nullptr || offset >= dest) { + break; + } + if (old_iter != nullptr && id == old_iter->id) { + last_comm_iter = iter; + _container.rev_iter(old_iter); + ++comm_end; + } + } + + // clear old part from stack + while (_container.size() > comm_end) { + _container.pop(); + } + iter = last_comm_iter; + + } else { + iter = nullptr; + comm_end = _container.size(); + // go to current possition in container list + while (_story->iterate_containers(iter, id, offset)) { + if (offset >= _ptr) { + break; + } + } + _story->iterate_containers(iter, id, offset, true); + } + + // move to destination and update container stack on the go + while (_story->iterate_containers(iter, id, offset)) { + if (offset >= dest) { + break; + } + if (_container.empty() || _container.top().id != id) { + _container.push({.id = id, .offset = offset}); + } else { + _container.pop(); + if (_container.size() < comm_end) { + comm_end = _container.size(); + } + } + } + _ptr = dest; + + // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container + // it will get visited in the next step + if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { + _ptr += 6; + _container.push({.id = id, .offset = offset}); + if (reversed && comm_end == _container.size() - 1) { + ++comm_end; + } + } + + // iff all container (until now) are entered at first position + bool allEnteredAtStart = true; + ip_t child_position = dest; + if (record_visits) { + const ContainerData* iData = nullptr; + size_t level = _container.size(); + while (_container.iter(iData) + && (level > comm_end + || _story->container_flag(iData->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST) + ) { + auto parrent_offset = iData->offset; + inkAssert(child_position >= parrent_offset, "Container stack order is broken"); + // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed + // subcontainers first child check if child_positino is the first child of current container + allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); + child_position = parrent_offset; + _globals->visit(iData->id, allEnteredAtStart); + } + } } template void runner_impl::start_frame(uint32_t target) { - if constexpr (type == frame_type::function) { - // add a function start marker - _output << values::func_start; - } - // Push next address onto the callstack - { - size_t address = _ptr - _story->instructions(); - _stack.push_frame(address, _evaluation_mode); - _ref_stack.push_frame(address, _evaluation_mode); - } - _evaluation_mode = false; // unset eval mode when enter function or tunnel - - // Do the jump - inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); - jump(_story->instructions() + target, true); + if constexpr (type == frame_type::function) { + // add a function start marker + _output << values::func_start; + } + // Push next address onto the callstack + { + size_t address = _ptr - _story->instructions(); + _stack.push_frame(address, _evaluation_mode); + _ref_stack.push_frame(address, _evaluation_mode); + } + _evaluation_mode = false; // unset eval mode when enter function or tunnel + + // Do the jump + inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!"); + jump(_story->instructions() + target, true); } frame_type runner_impl::execute_return() { - // Pop the callstack - _ref_stack.fetch_values(_stack); - frame_type type; - offset_t offset = _stack.pop_frame(&type, _evaluation_mode); - _ref_stack.push_values(_stack); - { - frame_type t; - bool eval; - // TODO: write all refs to new frame - offset_t o = _ref_stack.pop_frame(&t, eval); - inkAssert( - o == offset && t == type && eval == _evaluation_mode, - "_ref_stack and _stack should be in frame sync!" - ); - } - - // SPECIAL: On function, do a trim - if (type == frame_type::function) { - _output << values::func_end; - } else if (type == frame_type::tunnel) { - // if we return but there is a divert target on top of - // the evaluation stack, we should follow this instead - // inkproof: I060 - if (! _eval.is_empty() && _eval.top().type() == value_type::divert) { - start_frame(_eval.pop().get()); - return type; - } - } - - - // Jump to the old offset - inkAssert( - _story->instructions() + offset < _story->end(), - "Callstack return is outside bounds of story!" - ); - jump(_story->instructions() + offset, false); - - // Return frame type - return type; + // Pop the callstack + _ref_stack.fetch_values(_stack); + frame_type type; + offset_t offset = _stack.pop_frame(&type, _evaluation_mode); + _ref_stack.push_values(_stack); + { + frame_type t; + bool eval; + // TODO: write all refs to new frame + offset_t o = _ref_stack.pop_frame(&t, eval); + inkAssert( + o == offset && t == type && eval == _evaluation_mode, + "_ref_stack and _stack should be in frame sync!" + ); + } + + // SPECIAL: On function, do a trim + if (type == frame_type::function) { + _output << values::func_end; + } else if (type == frame_type::tunnel) { + // if we return but there is a divert target on top of + // the evaluation stack, we should follow this instead + // inkproof: I060 + if (! _eval.is_empty() && _eval.top().type() == value_type::divert) { + start_frame(_eval.pop().get()); + return type; + } + } + + + // Jump to the old offset + inkAssert( + _story->instructions() + offset < _story->end(), + "Callstack return is outside bounds of story!" + ); + jump(_story->instructions() + offset, false); + + // Return frame type + return type; } runner_impl::runner_impl(const story_impl* data, globals global) @@ -369,1055 +369,1055 @@ runner_impl::runner_impl(const story_impl* data, globals global) , _container(ContainerData{}) , _rng(time(NULL)) { - _ptr = _story->instructions(); - _evaluation_mode = false; - _choice_tags_begin = -1; - - // register with globals - _globals->add_runner(this); - if (_globals->lists()) { - _output.set_list_meta(_globals->lists()); - } - - // initialize globals if necessary - if (! _globals->are_globals_initialized()) { - _globals->initialize_globals(this); - - // Set us back to the beginning of the story - reset(); - _ptr = _story->instructions(); - } + _ptr = _story->instructions(); + _evaluation_mode = false; + _choice_tags_begin = -1; + + // register with globals + _globals->add_runner(this); + if (_globals->lists()) { + _output.set_list_meta(_globals->lists()); + } + + // initialize globals if necessary + if (! _globals->are_globals_initialized()) { + _globals->initialize_globals(this); + + // Set us back to the beginning of the story + reset(); + _ptr = _story->instructions(); + } } runner_impl::~runner_impl() { - // unregister with globals - _globals->remove_runner(this); + // unregister with globals + _globals->remove_runner(this); } #ifdef INK_ENABLE_STL std::string runner_impl::getline() { - std::string result{""}; - bool fill = false; - do { - if (fill) { - result += " "; - } - // Advance interpreter one line - advance_line(); - // Read line into std::string - result += _output.get(); - fill = _output.last_char() == ' '; - } while (_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice - if (! has_choices() && _fallback_choice) { - choose(~0); - } - - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getline!"); - return result; + std::string result{""}; + bool fill = false; + do { + if (fill) { + result += " "; + } + // Advance interpreter one line + advance_line(); + // Read line into std::string + result += _output.get(); + fill = _output.last_char() == ' '; + } while (_ptr != nullptr && _output.last_char() != '\n'); + + // TODO: fallback choice = no choice + if (! has_choices() && _fallback_choice) { + choose(~0); + } + + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getline!"); + return result; } void runner_impl::getline(std::ostream& out) { - bool fill = false; - do { - if (fill) { - out << " "; - } - // Advance interpreter one line - advance_line(); - // Write into out - out << _output; - fill = _output.last_char() == ' '; - } while (_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice - if (! has_choices() && _fallback_choice) { - choose(~0); - } - - // Make sure we read everything - inkAssert(_output.is_empty(), "Output should be empty after getline!"); + bool fill = false; + do { + if (fill) { + out << " "; + } + // Advance interpreter one line + advance_line(); + // Write into out + out << _output; + fill = _output.last_char() == ' '; + } while (_ptr != nullptr && _output.last_char() != '\n'); + + // TODO: fallback choice = no choice + if (! has_choices() && _fallback_choice) { + choose(~0); + } + + // Make sure we read everything + inkAssert(_output.is_empty(), "Output should be empty after getline!"); } std::string runner_impl::getall() { - // Advance interpreter until we're stopped - std::stringstream str; - while (can_continue()) { - getline(str); - } + // Advance interpreter until we're stopped + std::stringstream str; + while (can_continue()) { + getline(str); + } - // Read output into std::string + // Read output into std::string - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getall!"); - return str.str(); + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getall!"); + return str.str(); } void runner_impl::getall(std::ostream& out) { - // Advance interpreter until we're stopped - while (can_continue()) { - advance_line(); - } + // Advance interpreter until we're stopped + while (can_continue()) { + advance_line(); + } - // Send output into stream - out << _output; + // Send output into stream + out << _output; - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getall!"); + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getall!"); } #endif #ifdef INK_ENABLE_UNREAL FString runner_impl::getline() { - clear_tags(); - FString result{}; - bool fill = false; - do { - if (fill) { - result += " "; - } - // Advance interpreter one line - advance_line(); - // Read lin ve into std::string - const char* str = _output.get_alloc(_globals->strings(), _globals->lists()); - result.Append(str, c_str_len(str)); - fill = _output.last_char() == ' '; - } while (_ptr != nullptr && _output.last_char() != '\n'); - - // TODO: fallback choice = no choice - if (! has_choices() && _fallback_choice) { - choose(~0); - } - - // Return result - inkAssert(_output.is_empty(), "Output should be empty after getline!"); - return result; + clear_tags(); + FString result{}; + bool fill = false; + do { + if (fill) { + result += " "; + } + // Advance interpreter one line + advance_line(); + // Read lin ve into std::string + const char* str = _output.get_alloc(_globals->strings(), _globals->lists()); + result.Append(str, c_str_len(str)); + fill = _output.last_char() == ' '; + } while (_ptr != nullptr && _output.last_char() != '\n'); + + // TODO: fallback choice = no choice + if (! has_choices() && _fallback_choice) { + choose(~0); + } + + // Return result + inkAssert(_output.is_empty(), "Output should be empty after getline!"); + return result; } #endif void runner_impl::advance_line() { - // Step while we still have instructions to execute - while (_ptr != nullptr) { - // Stop if we hit a new line - if (line_step()) { - break; - } - } - - // can be in save state becaues of choice - // Garbage collection TODO: How often do we want to do this? - _globals->gc(); + // Step while we still have instructions to execute + while (_ptr != nullptr) { + // Stop if we hit a new line + if (line_step()) { + break; + } + } + + // can be in save state becaues of choice + // Garbage collection TODO: How often do we want to do this? + _globals->gc(); } bool runner_impl::can_continue() const { - return _ptr != nullptr; + return _ptr != nullptr; } void runner_impl::choose(size_t index) { - if (has_choices()) { - inkAssert(index < _choices.size(), "Choice index out of range"); - } - restore(); // restore to stack state when choice was maked - _globals->turn(); - // Get the choice - const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); - - // Get its thread - thread_t choiceThread = c._thread; - - // Figure out where our previous pointer was for that thread - ip_t prev = nullptr; - if (choiceThread == ~0) { - prev = _done; - } else { - prev = _threads.get(choiceThread); - } - - // Make sure we have a previous pointer - inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output"); - - // Move to the previous pointer so we track our movements correctly - jump(prev, false); - _done = nullptr; - - // Collapse callstacks to the correct thread - _stack.collapse_to_thread(choiceThread); - _ref_stack.collapse_to_thread(choiceThread); - _threads.clear(); - - // Jump to destination and clear choice list - jump(_story->instructions() + c.path(), true); - clear_choices(); - clear_tags(); + if (has_choices()) { + inkAssert(index < _choices.size(), "Choice index out of range"); + } + restore(); // restore to stack state when choice was maked + _globals->turn(); + // Get the choice + const auto& c = has_choices() ? _choices[index] : _fallback_choice.value(); + + // Get its thread + thread_t choiceThread = c._thread; + + // Figure out where our previous pointer was for that thread + ip_t prev = nullptr; + if (choiceThread == ~0) { + prev = _done; + } else { + prev = _threads.get(choiceThread); + } + + // Make sure we have a previous pointer + inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output"); + + // Move to the previous pointer so we track our movements correctly + jump(prev, false); + _done = nullptr; + + // Collapse callstacks to the correct thread + _stack.collapse_to_thread(choiceThread); + _ref_stack.collapse_to_thread(choiceThread); + _threads.clear(); + + // Jump to destination and clear choice list + jump(_story->instructions() + c.path(), true); + clear_choices(); + clear_tags(); } void runner_impl::getline_silent() { - // advance and clear output stream - advance_line(); - _output.clear(); + // advance and clear output stream + advance_line(); + _output.clear(); } bool runner_impl::has_tags() const { - return num_tags() > 0; + return num_tags() > 0; } size_t runner_impl::num_tags() const { - return _choice_tags_begin < 0 ? _tags.size() : _choice_tags_begin; + return _choice_tags_begin < 0 ? _tags.size() : _choice_tags_begin; } const char* runner_impl::get_tag(size_t index) const { - inkAssert(index < _tags.size(), "Tag index exceeds _num_tags"); - return _tags[index]; + inkAssert(index < _tags.size(), "Tag index exceeds _num_tags"); + return _tags[index]; } snapshot* runner_impl::create_snapshot() const { - return _globals->create_snapshot(); + return _globals->create_snapshot(); } size_t runner_impl::snap(unsigned char* data, snapper& snapper) const { - unsigned char* ptr = data; - bool should_write = data != nullptr; - snapper.current_runner_tags = _tags[0].ptr(); - std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; - ptr = snap_write(ptr, offset, should_write); - offset = _backup - _story->instructions(); - ptr = snap_write(ptr, offset, should_write); - offset = _done - _story->instructions(); - ptr = snap_write(ptr, offset, should_write); - ptr = snap_write(ptr, _rng.get_state(), should_write); - ptr = snap_write(ptr, _evaluation_mode, should_write); - ptr = snap_write(ptr, _string_mode, should_write); - ptr = snap_write(ptr, _saved_evaluation_mode, should_write); - ptr = snap_write(ptr, _saved, should_write); - ptr = snap_write(ptr, _is_falling, should_write); - ptr += _output.snap(data ? ptr : nullptr, snapper); - ptr += _stack.snap(data ? ptr : nullptr, snapper); - ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); - ptr += _eval.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _choice_tags_begin, should_write); - ptr += _tags.snap(data ? ptr : nullptr, snapper); - ptr += _container.snap(data ? ptr : nullptr, snapper); - ptr += _threads.snap(data ? ptr : nullptr, snapper); - ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); - if (_fallback_choice) { - ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper); - } - ptr += _choices.snap(data ? ptr : nullptr, snapper); - return ptr - data; + unsigned char* ptr = data; + bool should_write = data != nullptr; + snapper.current_runner_tags = _tags[0].ptr(); + std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0; + ptr = snap_write(ptr, offset, should_write); + offset = _backup - _story->instructions(); + ptr = snap_write(ptr, offset, should_write); + offset = _done - _story->instructions(); + ptr = snap_write(ptr, offset, should_write); + ptr = snap_write(ptr, _rng.get_state(), should_write); + ptr = snap_write(ptr, _evaluation_mode, should_write); + ptr = snap_write(ptr, _string_mode, should_write); + ptr = snap_write(ptr, _saved_evaluation_mode, should_write); + ptr = snap_write(ptr, _saved, should_write); + ptr = snap_write(ptr, _is_falling, should_write); + ptr += _output.snap(data ? ptr : nullptr, snapper); + ptr += _stack.snap(data ? ptr : nullptr, snapper); + ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); + ptr += _eval.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _choice_tags_begin, should_write); + ptr += _tags.snap(data ? ptr : nullptr, snapper); + ptr += _container.snap(data ? ptr : nullptr, snapper); + ptr += _threads.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _fallback_choice.has_value(), should_write); + if (_fallback_choice) { + ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper); + } + ptr += _choices.snap(data ? ptr : nullptr, snapper); + return ptr - data; } const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader) { - auto ptr = data; - std::uintptr_t offset; - ptr = snap_read(ptr, offset); - _ptr = offset == 0 ? nullptr : _story->instructions() + offset; - ptr = snap_read(ptr, offset); - _backup = _story->instructions() + offset; - ptr = snap_read(ptr, offset); - _done = _story->instructions() + offset; - int32_t seed; - ptr = snap_read(ptr, seed); - _rng.srand(seed); - ptr = snap_read(ptr, _evaluation_mode); - ptr = snap_read(ptr, _string_mode); - ptr = snap_read(ptr, _saved_evaluation_mode); - ptr = snap_read(ptr, _saved); - ptr = snap_read(ptr, _is_falling); - ptr = _output.snap_load(ptr, loader); - ptr = _stack.snap_load(ptr, loader); - ptr = _ref_stack.snap_load(ptr, loader); - ptr = _eval.snap_load(ptr, loader); - int choice_tags_begin; - ptr = snap_read(ptr, choice_tags_begin); - _choice_tags_begin = choice_tags_begin; - ptr = _tags.snap_load(ptr, loader); - loader.current_runner_tags = _tags[0].ptr(); - ptr = _container.snap_load(ptr, loader); - ptr = _threads.snap_load(ptr, loader); - bool has_fallback_choice; - ptr = snap_read(ptr, has_fallback_choice); - _fallback_choice = nullopt; - if (has_fallback_choice) { - _fallback_choice.emplace(); - ptr = _fallback_choice.value().snap_load(ptr, loader); - } - ptr = _choices.snap_load(ptr, loader); - return ptr; + auto ptr = data; + std::uintptr_t offset; + ptr = snap_read(ptr, offset); + _ptr = offset == 0 ? nullptr : _story->instructions() + offset; + ptr = snap_read(ptr, offset); + _backup = _story->instructions() + offset; + ptr = snap_read(ptr, offset); + _done = _story->instructions() + offset; + int32_t seed; + ptr = snap_read(ptr, seed); + _rng.srand(seed); + ptr = snap_read(ptr, _evaluation_mode); + ptr = snap_read(ptr, _string_mode); + ptr = snap_read(ptr, _saved_evaluation_mode); + ptr = snap_read(ptr, _saved); + ptr = snap_read(ptr, _is_falling); + ptr = _output.snap_load(ptr, loader); + ptr = _stack.snap_load(ptr, loader); + ptr = _ref_stack.snap_load(ptr, loader); + ptr = _eval.snap_load(ptr, loader); + int choice_tags_begin; + ptr = snap_read(ptr, choice_tags_begin); + _choice_tags_begin = choice_tags_begin; + ptr = _tags.snap_load(ptr, loader); + loader.current_runner_tags = _tags[0].ptr(); + ptr = _container.snap_load(ptr, loader); + ptr = _threads.snap_load(ptr, loader); + bool has_fallback_choice; + ptr = snap_read(ptr, has_fallback_choice); + _fallback_choice = nullopt; + if (has_fallback_choice) { + _fallback_choice.emplace(); + ptr = _fallback_choice.value().snap_load(ptr, loader); + } + ptr = _choices.snap_load(ptr, loader); + return ptr; } #ifdef INK_ENABLE_CSTD char* runner_impl::getline_alloc() { - /// TODO - inkFail("Not implemented yet!"); - return nullptr; + /// TODO + inkFail("Not implemented yet!"); + return nullptr; } #endif bool runner_impl::move_to(hash_t path) { - // find the path - ip_t destination = _story->find_offset_for(path); - if (destination == nullptr) { - // TODO: Error state? - return false; - } - - // Clear state and move to destination - reset(); - _ptr = _story->instructions(); - jump(destination, false); - - return true; + // find the path + ip_t destination = _story->find_offset_for(path); + if (destination == nullptr) { + // TODO: Error state? + return false; + } + + // Clear state and move to destination + reset(); + _ptr = _story->instructions(); + jump(destination, false); + + return true; } void runner_impl::internal_bind(hash_t name, internal::function_base* function) { - _functions.add(name, function); + _functions.add(name, function); } runner_impl::change_type runner_impl::detect_change() const { - // Check if the old newline is still present (hasn't been glu'd) and - // if there is new text (non-whitespace) in the stream since saving - bool stillHasNewline = _output.saved_ends_with(value_type::newline); - bool hasAddedNewText = _output.text_past_save() || _tags.has_changed(); - - // Newline is still there and there's no new text - if (stillHasNewline && ! hasAddedNewText) { - return change_type::no_change; - } - - // If the newline is gone, we got glue'd. Continue as if we never had that newline - if (! stillHasNewline) { - return change_type::newline_removed; - } - - // TODO New Tags -> extended - - // If there's new text content, we went too far - if (hasAddedNewText) { - return change_type::extended_past_newline; - } - - inkFail("Invalid change detction. Never should be here!"); - return change_type::no_change; + // Check if the old newline is still present (hasn't been glu'd) and + // if there is new text (non-whitespace) in the stream since saving + bool stillHasNewline = _output.saved_ends_with(value_type::newline); + bool hasAddedNewText = _output.text_past_save() || _tags.has_changed(); + + // Newline is still there and there's no new text + if (stillHasNewline && ! hasAddedNewText) { + return change_type::no_change; + } + + // If the newline is gone, we got glue'd. Continue as if we never had that newline + if (! stillHasNewline) { + return change_type::newline_removed; + } + + // TODO New Tags -> extended + + // If there's new text content, we went too far + if (hasAddedNewText) { + return change_type::extended_past_newline; + } + + inkFail("Invalid change detction. Never should be here!"); + return change_type::no_change; } bool runner_impl::line_step() { - // Step the interpreter - step(); - - // If we're not within string evaluation - if (! _output.has_marker()) { - // If we have a saved state after a previous newline - // don't do this if we behind choice - if (_saved && ! has_choices() && ! _fallback_choice) { - // Check for changes in the output stream - switch (detect_change()) { - case change_type::extended_past_newline: - // We've gone too far. Restore to before we moved past the newline and return that we are - // done - restore(); - return true; - case change_type::newline_removed: - // Newline was removed. Proceed as if we never hit it - forget(); - break; - case change_type::no_change: break; - } - } - - // If we're on a newline - if (_output.ends_with(value_type::newline)) { - // Unless we are out of content, we are going to try - // to continue a little further. This is to check for - // glue (which means there is potentially more content - // in this line) OR for non-text content such as choices. - if (_ptr != nullptr) { - // Save a snapshot of the current runtime state so we - // can return here if we end up hitting a new line - forget(); - save(); - } - // Otherwise, make sure we don't have any snapshots hanging around - // expect we are in choice handleing - else if (! has_choices() && ! _fallback_choice) { - forget(); - } else { - _output.forget(); - } - } - } + // Step the interpreter + step(); + + // If we're not within string evaluation + if (! _output.has_marker()) { + // If we have a saved state after a previous newline + // don't do this if we behind choice + if (_saved && ! has_choices() && ! _fallback_choice) { + // Check for changes in the output stream + switch (detect_change()) { + case change_type::extended_past_newline: + // We've gone too far. Restore to before we moved past the newline and return that we are + // done + restore(); + return true; + case change_type::newline_removed: + // Newline was removed. Proceed as if we never hit it + forget(); + break; + case change_type::no_change: break; + } + } + + // If we're on a newline + if (_output.ends_with(value_type::newline)) { + // Unless we are out of content, we are going to try + // to continue a little further. This is to check for + // glue (which means there is potentially more content + // in this line) OR for non-text content such as choices. + if (_ptr != nullptr) { + // Save a snapshot of the current runtime state so we + // can return here if we end up hitting a new line + forget(); + save(); + } + // Otherwise, make sure we don't have any snapshots hanging around + // expect we are in choice handleing + else if (! has_choices() && ! _fallback_choice) { + forget(); + } else { + _output.forget(); + } + } + } - return false; + return false; } void runner_impl::step() { #ifndef INK_ENABLE_UNREAL - try + try #endif - { - inkAssert(_ptr != nullptr, "Can not step! Do not have a valid pointer"); - - // Load current command - Command cmd = read(); - CommandFlag flag = read(); - - // If we're falling and we hit a non-fallthrough command, stop the fall. - if (_is_falling - && ! ( - (cmd == Command::DIVERT && flag & CommandFlag::DIVERT_IS_FALLTHROUGH) - || cmd == Command::END_CONTAINER_MARKER - )) { - _is_falling = false; - set_done_ptr(nullptr); - } - if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) { - _operations(cmd, _eval); - } else { - switch (cmd) { - // == Value Commands == - case Command::STR: { - const char* str = read(); - if (_evaluation_mode) { - _eval.push(value{}.set(str)); - } else { - _output << value{}.set(str); - } - } break; - case Command::INT: { - int val = read(); - if (_evaluation_mode) { - _eval.push(value{}.set(val)); - } - // TEST-CASE B006 don't print integers - } break; - case Command::BOOL: { - bool val = read() ? true : false; - if (_evaluation_mode) { - _eval.push(value{}.set(val)); - } else { - _output << value{}.set(val); - } - } break; - case Command::FLOAT: { - float val = read(); - if (_evaluation_mode) { - _eval.push(value{}.set(val)); - } - // TEST-CASE B006 don't print floats - } break; - case Command::VALUE_POINTER: { - hash_t val = read(); - if (_evaluation_mode) { - _eval.push(value{}.set(val, static_cast(flag) - 1)); - } else { - inkFail("never conciderd what should happend here! (value pointer print)"); - } - } break; - case Command::LIST: { - list_table::list list(read()); - if (_evaluation_mode) { - _eval.push(value{}.set(list)); - } else { - char* str = _globals->strings().create(_globals->lists().stringLen(list) + 1); - _globals->lists().toString(str, list)[0] = 0; - _output << value{}.set(str); - } - } break; - case Command::DIVERT_VAL: { - inkAssert(_evaluation_mode, "Can not push divert value into the output stream!"); - - // Push the divert target onto the stack - uint32_t target = read(); - _eval.push(value{}.set(target)); - } break; - case Command::NEWLINE: { - if (_evaluation_mode) { - _eval.push(values::newline); - } else { - _output << values::newline; - } - } break; - case Command::GLUE: { - if (_evaluation_mode) { - _eval.push(values::glue); - } else { - _output << values::glue; - } - } break; - case Command::VOID: { - if (_evaluation_mode) { - _eval.push(values::null); // TODO: void type? - } - } break; - - // == Divert commands - case Command::DIVERT: { - // Find divert address - uint32_t target = read(); - - // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) { - break; - } - - // SPECIAL: Fallthrough divert. We're starting to fall out of containers - if (flag & CommandFlag::DIVERT_IS_FALLTHROUGH && ! _is_falling) { - // Record the position of the instruction pointer at the first fallthrough. - // We'll use this if we run out of content and hit an implied "done" to restore - // our position when a choice is chosen. See ::choose - set_done_ptr(_ptr); - _is_falling = true; - } - - // If we're falling out of the story, then we're hitting an implied done - if (_is_falling && _story->instructions() + target == _story->end()) { - // Wait! We may be returning from a function! - frame_type type; - if (_stack.has_frame(&type) - && type == frame_type::function) // implicit return is only for functions - { - // push null and return - _eval.push(values::null); - - // HACK - _ptr += sizeof(Command) + sizeof(CommandFlag); - execute_return(); - } else { - on_done(false); - } - break; - } - - // Do the jump - inkAssert( - _story->instructions() + target < _story->end(), "Diverting past end of story data!" - ); - jump(_story->instructions() + target, true); - } break; - case Command::DIVERT_TO_VARIABLE: { - // Get variable value - hash_t variable = read(); - - // Check for condition - if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) { - break; - } - - const value* val = get_var(variable); - inkAssert(val, "Jump destiniation needs to be defined!"); - - // Move to location - jump(_story->instructions() + val->get(), true); - inkAssert(_ptr < _story->end(), "Diverted past end of story data!"); - } break; - - // == Terminal commands - case Command::DONE: on_done(true); break; - - case Command::END: _ptr = nullptr; break; - - // == Tunneling - case Command::TUNNEL: { - uint32_t target; - // Find divert address - if (flag & CommandFlag::TUNNEL_TO_VARIABLE) { - hash_t var_name = read(); - const value* val = get_var(var_name); - inkAssert(val != nullptr, "Variable containing tunnel target could not be found!"); - target = val->get(); - } else { - target = read(); - } - start_frame(target); - } break; - case Command::FUNCTION: { - uint32_t target; - // Find divert address - if (flag & CommandFlag::FUNCTION_TO_VARIABLE) { - hash_t var_name = read(); - const value* val = get_var(var_name); - inkAssert(val != nullptr, "Varibale containing function could not be found!"); - target = val->get(); - } else { - target = read(); - } - if (! (flag & CommandFlag::FALLBACK_FUNCTION)) { - start_frame(target); - } else { - inkAssert(! _eval.is_empty(), "fallback function but no function call before?"); - if (_eval.top_value().type() == value_type::ex_fn_not_found) { - _eval.pop(); - inkAssert( - target != 0, - "Exetrnal function was not binded, and no fallback function provided!" - ); - start_frame(target); - } - } - } break; - case Command::TUNNEL_RETURN: - case Command::FUNCTION_RETURN: { - execute_return(); - } break; - - case Command::THREAD: { - // Push a thread frame so we can return easily - // TODO We push ahead of a single divert. Is that correct in all cases....????? - auto returnTo = _ptr + CommandSize; - _stack.push_frame( - returnTo - _story->instructions(), _evaluation_mode - ); - _ref_stack.push_frame( - returnTo - _story->instructions(), _evaluation_mode - ); - - // Fork a new thread on the callstack - thread_t thread = _stack.fork_thread(); - { - thread_t t = _ref_stack.fork_thread(); - inkAssert(t == thread, "ref_stack and stack should be in sync!"); - } - - // Push that thread onto our thread stack - _threads.push(thread); - } break; - - // == set temporärie variable - case Command::DEFINE_TEMP: { - hash_t variableName = read(); - bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; - - // Get the top value and put it into the variable - value v = _eval.pop(); - set_var(variableName, v, is_redef); - } break; - - case Command::SET_VARIABLE: { - hash_t variableName = read(); - - // Check if it's a redefinition (not yet used, seems important for pointers later?) - bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; - - // If not, we're setting a global (temporary variables are explicitely defined as such, - // where globals are defined using SET_VARIABLE). - value val = _eval.pop(); - if (is_redef) { - set_var(variableName, val, is_redef); - } else { - set_var(variableName, val, is_redef); - } - } break; - - // == Function calls - case Command::CALL_EXTERNAL: { - // Read function name - hash_t functionName = read(); - - // Interpret flag as argument count - int numArguments = ( int ) flag; - - // find and execute. will automatically push a valid if applicable - bool success = _functions.call( - functionName, &_eval, numArguments, _globals->strings(), _globals->lists() - ); - - // If we failed, notify a potential fallback function - if (! success) { - _eval.push(values::ex_fn_not_found); - } - } break; - - // == Evaluation stack - case Command::START_EVAL: _evaluation_mode = true; break; - case Command::END_EVAL: - _evaluation_mode = false; - - // Assert stack is empty? Is that necessary? - break; - case Command::OUTPUT: { - value v = _eval.pop(); - _output << v; - } break; - case Command::POP: _eval.pop(); break; - case Command::DUPLICATE: _eval.push(_eval.top_value()); break; - case Command::PUSH_VARIABLE_VALUE: { - // Try to find in local stack - hash_t variableName = read(); - const value* val = get_var(variableName); - - inkAssert(val != nullptr, "Could not find variable!"); - _eval.push(*val); - break; + { + inkAssert(_ptr != nullptr, "Can not step! Do not have a valid pointer"); + + // Load current command + Command cmd = read(); + CommandFlag flag = read(); + + // If we're falling and we hit a non-fallthrough command, stop the fall. + if (_is_falling + && ! ( + (cmd == Command::DIVERT && flag & CommandFlag::DIVERT_IS_FALLTHROUGH) + || cmd == Command::END_CONTAINER_MARKER + )) { + _is_falling = false; + set_done_ptr(nullptr); + } + if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) { + _operations(cmd, _eval); + } else { + switch (cmd) { + // == Value Commands == + case Command::STR: { + const char* str = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(str)); + } else { + _output << value{}.set(str); + } + } break; + case Command::INT: { + int val = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(val)); + } + // TEST-CASE B006 don't print integers + } break; + case Command::BOOL: { + bool val = read() ? true : false; + if (_evaluation_mode) { + _eval.push(value{}.set(val)); + } else { + _output << value{}.set(val); + } + } break; + case Command::FLOAT: { + float val = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(val)); + } + // TEST-CASE B006 don't print floats + } break; + case Command::VALUE_POINTER: { + hash_t val = read(); + if (_evaluation_mode) { + _eval.push(value{}.set(val, static_cast(flag) - 1)); + } else { + inkFail("never conciderd what should happend here! (value pointer print)"); + } + } break; + case Command::LIST: { + list_table::list list(read()); + if (_evaluation_mode) { + _eval.push(value{}.set(list)); + } else { + char* str = _globals->strings().create(_globals->lists().stringLen(list) + 1); + _globals->lists().toString(str, list)[0] = 0; + _output << value{}.set(str); + } + } break; + case Command::DIVERT_VAL: { + inkAssert(_evaluation_mode, "Can not push divert value into the output stream!"); + + // Push the divert target onto the stack + uint32_t target = read(); + _eval.push(value{}.set(target)); + } break; + case Command::NEWLINE: { + if (_evaluation_mode) { + _eval.push(values::newline); + } else { + _output << values::newline; + } + } break; + case Command::GLUE: { + if (_evaluation_mode) { + _eval.push(values::glue); + } else { + _output << values::glue; + } + } break; + case Command::VOID: { + if (_evaluation_mode) { + _eval.push(values::null); // TODO: void type? + } + } break; + + // == Divert commands + case Command::DIVERT: { + // Find divert address + uint32_t target = read(); + + // Check for condition + if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) { + break; + } + + // SPECIAL: Fallthrough divert. We're starting to fall out of containers + if (flag & CommandFlag::DIVERT_IS_FALLTHROUGH && ! _is_falling) { + // Record the position of the instruction pointer at the first fallthrough. + // We'll use this if we run out of content and hit an implied "done" to restore + // our position when a choice is chosen. See ::choose + set_done_ptr(_ptr); + _is_falling = true; + } + + // If we're falling out of the story, then we're hitting an implied done + if (_is_falling && _story->instructions() + target == _story->end()) { + // Wait! We may be returning from a function! + frame_type type; + if (_stack.has_frame(&type) + && type == frame_type::function) // implicit return is only for functions + { + // push null and return + _eval.push(values::null); + + // HACK + _ptr += sizeof(Command) + sizeof(CommandFlag); + execute_return(); + } else { + on_done(false); + } + break; + } + + // Do the jump + inkAssert( + _story->instructions() + target < _story->end(), "Diverting past end of story data!" + ); + jump(_story->instructions() + target, true); + } break; + case Command::DIVERT_TO_VARIABLE: { + // Get variable value + hash_t variable = read(); + + // Check for condition + if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) { + break; + } + + const value* val = get_var(variable); + inkAssert(val, "Jump destiniation needs to be defined!"); + + // Move to location + jump(_story->instructions() + val->get(), true); + inkAssert(_ptr < _story->end(), "Diverted past end of story data!"); + } break; + + // == Terminal commands + case Command::DONE: on_done(true); break; + + case Command::END: _ptr = nullptr; break; + + // == Tunneling + case Command::TUNNEL: { + uint32_t target; + // Find divert address + if (flag & CommandFlag::TUNNEL_TO_VARIABLE) { + hash_t var_name = read(); + const value* val = get_var(var_name); + inkAssert(val != nullptr, "Variable containing tunnel target could not be found!"); + target = val->get(); + } else { + target = read(); + } + start_frame(target); + } break; + case Command::FUNCTION: { + uint32_t target; + // Find divert address + if (flag & CommandFlag::FUNCTION_TO_VARIABLE) { + hash_t var_name = read(); + const value* val = get_var(var_name); + inkAssert(val != nullptr, "Varibale containing function could not be found!"); + target = val->get(); + } else { + target = read(); + } + if (! (flag & CommandFlag::FALLBACK_FUNCTION)) { + start_frame(target); + } else { + inkAssert(! _eval.is_empty(), "fallback function but no function call before?"); + if (_eval.top_value().type() == value_type::ex_fn_not_found) { + _eval.pop(); + inkAssert( + target != 0, + "Exetrnal function was not binded, and no fallback function provided!" + ); + start_frame(target); + } + } + } break; + case Command::TUNNEL_RETURN: + case Command::FUNCTION_RETURN: { + execute_return(); + } break; + + case Command::THREAD: { + // Push a thread frame so we can return easily + // TODO We push ahead of a single divert. Is that correct in all cases....????? + auto returnTo = _ptr + CommandSize; + _stack.push_frame( + returnTo - _story->instructions(), _evaluation_mode + ); + _ref_stack.push_frame( + returnTo - _story->instructions(), _evaluation_mode + ); + + // Fork a new thread on the callstack + thread_t thread = _stack.fork_thread(); + { + thread_t t = _ref_stack.fork_thread(); + inkAssert(t == thread, "ref_stack and stack should be in sync!"); + } + + // Push that thread onto our thread stack + _threads.push(thread); + } break; + + // == set temporärie variable + case Command::DEFINE_TEMP: { + hash_t variableName = read(); + bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; + + // Get the top value and put it into the variable + value v = _eval.pop(); + set_var(variableName, v, is_redef); + } break; + + case Command::SET_VARIABLE: { + hash_t variableName = read(); + + // Check if it's a redefinition (not yet used, seems important for pointers later?) + bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE; + + // If not, we're setting a global (temporary variables are explicitely defined as such, + // where globals are defined using SET_VARIABLE). + value val = _eval.pop(); + if (is_redef) { + set_var(variableName, val, is_redef); + } else { + set_var(variableName, val, is_redef); + } + } break; + + // == Function calls + case Command::CALL_EXTERNAL: { + // Read function name + hash_t functionName = read(); + + // Interpret flag as argument count + int numArguments = ( int ) flag; + + // find and execute. will automatically push a valid if applicable + bool success = _functions.call( + functionName, &_eval, numArguments, _globals->strings(), _globals->lists() + ); + + // If we failed, notify a potential fallback function + if (! success) { + _eval.push(values::ex_fn_not_found); + } + } break; + + // == Evaluation stack + case Command::START_EVAL: _evaluation_mode = true; break; + case Command::END_EVAL: + _evaluation_mode = false; + + // Assert stack is empty? Is that necessary? + break; + case Command::OUTPUT: { + value v = _eval.pop(); + _output << v; + } break; + case Command::POP: _eval.pop(); break; + case Command::DUPLICATE: _eval.push(_eval.top_value()); break; + case Command::PUSH_VARIABLE_VALUE: { + // Try to find in local stack + hash_t variableName = read(); + const value* val = get_var(variableName); + + inkAssert(val != nullptr, "Could not find variable!"); + _eval.push(*val); + break; + } + case Command::START_STR: { + inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!"); + _string_mode = true; + _evaluation_mode = false; + _output << values::marker; + } break; + case Command::END_STR: { + // TODO: Assert we really had a marker on there? + inkAssert(! _evaluation_mode, "Must be in evaluation mode"); + _string_mode = false; + _evaluation_mode = true; + + // Load value from output stream + // Push onto stack + _eval.push(value{}.set( + _output.get_alloc(_globals->strings(), _globals->lists()) + )); + } break; + + case Command::START_TAG: { + _output << values::marker; + } break; + + case Command::END_TAG: { + auto tag = _output.get_alloc(_globals->strings(), _globals->lists()); + if (_string_mode && _choice_tags_begin < 0) { + _choice_tags_begin = _tags.size(); + } + _tags.push() = tag; + } break; + + // == Choice commands + case Command::CHOICE: { + // Read path + uint32_t path = read(); + + // If we're a once only choice, make sure our destination hasn't + // been visited + if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { + // Need to convert offset to container index + container_t destination = -1; + if (_story->get_container_id(_story->instructions() + path, destination)) { + // Ignore the choice if we've visited the destination before + if (_globals->visits(destination) > 0) { + break; + } + } else { + inkAssert(false, "Destination for choice block does not have counting flags."); + } + } + + // Choice is conditional + if (flag & CommandFlag::CHOICE_HAS_CONDITION) { + // Only show if the top of the eval stack is 'truthy' + if (! _eval.pop().truthy(_globals->lists())) { + break; + } + } + + // Use a marker to start compiling the choice text + _output << values::marker; + value stack[2]; + int sc = 0; + + if (flag & CommandFlag::CHOICE_HAS_START_CONTENT) { + stack[sc++] = _eval.pop(); + } + if (flag & CommandFlag::CHOICE_HAS_CHOICE_ONLY_CONTENT) { + stack[sc++] = _eval.pop(); + } + for (; sc; --sc) { + _output << stack[sc - 1]; + } + + // fetch relevant tags + const snap_tag* tags = nullptr; + if (_choice_tags_begin >= 0 && _tags[_tags.size() - 1] != nullptr) { + for (tags = _tags.end() - 1; + *(tags - 1) != nullptr && (tags - _tags.begin()) > _choice_tags_begin; --tags) + ; + _tags.push() = nullptr; + } + + // Create choice and record it + if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) { + _fallback_choice = choice{}.setup( + _output, _globals->strings(), _globals->lists(), _choices.size(), path, + current_thread(), tags->ptr() + ); + } else { + add_choice().setup( + _output, _globals->strings(), _globals->lists(), _choices.size(), path, + current_thread(), tags->ptr() + ); + } + // save stack at last choice + if (_saved) { + forget(); + } + save(); + } break; + case Command::START_CONTAINER_MARKER: { + // Keep track of current container + auto index = read(); + // offset points to command, command has size 6 + _container.push({.id = index, .offset = _ptr - 6}); + + // Increment visit count + if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS + || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { + _globals->visit(_container.top().id, true); + } + + } break; + case Command::END_CONTAINER_MARKER: { + container_t index = read(); + + inkAssert(_container.top().id == index, "Leaving container we are not in!"); + + // Move up out of the current container + _container.pop(); + + // SPECIAL: If we've popped all containers, then there's an implied + // 'done' command or return + if (_container.empty()) { + _is_falling = false; + + frame_type type; + if (! _threads.empty()) { + on_done(false); + break; + } else if (_stack.has_frame(&type) && type == frame_type::function) // implicit return + // is only for + // functions + { + // push null and return + _eval.push(values::null); + + // HACK + _ptr += sizeof(Command) + sizeof(CommandFlag); + execute_return(); + } else if (_ptr == _story->end()) { // check needed, because it colud exist an unnamed + // toplevel container (empty named container stack + // != empty container stack) + on_done(true); + } + } + } break; + case Command::VISIT: { + // Push the visit count for the current container to the top + // is 0-indexed for some reason. idk why but this is what ink expects + _eval.push( + value{}.set(( int ) _globals->visits(_container.top().id) - 1) + ); + } break; + case Command::TURN: { + _eval.push(value{}.set(( int ) _globals->turns())); + } break; + case Command::SEQUENCE: { + // TODO: The C# ink runtime does a bunch of fancy logic + // to make sure each element is picked at least once in every + // iteration loop. I don't feel like replicating that right now. + // So, let's just return a random number and *shrug* + int sequenceLength = _eval.pop().get(); + int index = _eval.pop().get(); + + _eval.push(value{}.set(static_cast(_rng.rand(sequenceLength))) + ); + } break; + case Command::SEED: { + int32_t seed = _eval.pop().get(); + _rng.srand(seed); + + _eval.push(values::null); + } break; + + case Command::READ_COUNT: { + // Get container index + container_t container = read(); + + // Push the read count for the requested container index + _eval.push(value{}.set(( int ) _globals->visits(container))); + } break; + case Command::TAG: { + _tags.push() = read(); + } break; + default: inkAssert(false, "Unrecognized command!"); break; + } + } + } - case Command::START_STR: { - inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!"); - _string_mode = true; - _evaluation_mode = false; - _output << values::marker; - } break; - case Command::END_STR: { - // TODO: Assert we really had a marker on there? - inkAssert(! _evaluation_mode, "Must be in evaluation mode"); - _string_mode = false; - _evaluation_mode = true; - - // Load value from output stream - // Push onto stack - _eval.push(value{}.set( - _output.get_alloc(_globals->strings(), _globals->lists()) - )); - } break; - - case Command::START_TAG: { - _output << values::marker; - } break; - - case Command::END_TAG: { - auto tag = _output.get_alloc(_globals->strings(), _globals->lists()); - if (_string_mode && _choice_tags_begin < 0) { - _choice_tags_begin = _tags.size(); - } - _tags.push() = tag; - } break; - - // == Choice commands - case Command::CHOICE: { - // Read path - uint32_t path = read(); - - // If we're a once only choice, make sure our destination hasn't - // been visited - if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { - // Need to convert offset to container index - container_t destination = -1; - if (_story->get_container_id(_story->instructions() + path, destination)) { - // Ignore the choice if we've visited the destination before - if (_globals->visits(destination) > 0) { - break; - } - } else { - inkAssert(false, "Destination for choice block does not have counting flags."); - } - } - - // Choice is conditional - if (flag & CommandFlag::CHOICE_HAS_CONDITION) { - // Only show if the top of the eval stack is 'truthy' - if (! _eval.pop().truthy(_globals->lists())) { - break; - } - } - - // Use a marker to start compiling the choice text - _output << values::marker; - value stack[2]; - int sc = 0; - - if (flag & CommandFlag::CHOICE_HAS_START_CONTENT) { - stack[sc++] = _eval.pop(); - } - if (flag & CommandFlag::CHOICE_HAS_CHOICE_ONLY_CONTENT) { - stack[sc++] = _eval.pop(); - } - for (; sc; --sc) { - _output << stack[sc - 1]; - } - - // fetch relevant tags - const snap_tag* tags = nullptr; - if (_choice_tags_begin >= 0 && _tags[_tags.size() - 1] != nullptr) { - for (tags = _tags.end() - 1; - *(tags - 1) != nullptr && (tags - _tags.begin()) > _choice_tags_begin; --tags) - ; - _tags.push() = nullptr; - } - - // Create choice and record it - if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) { - _fallback_choice = choice{}.setup( - _output, _globals->strings(), _globals->lists(), _choices.size(), path, - current_thread(), tags->ptr() - ); - } else { - add_choice().setup( - _output, _globals->strings(), _globals->lists(), _choices.size(), path, - current_thread(), tags->ptr() - ); - } - // save stack at last choice - if (_saved) { - forget(); - } - save(); - } break; - case Command::START_CONTAINER_MARKER: { - // Keep track of current container - auto index = read(); - // offset points to command, command has size 6 - _container.push({.id = index, .offset = _ptr - 6}); - - // Increment visit count - if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS - || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { - _globals->visit(_container.top().id, true); - } - - } break; - case Command::END_CONTAINER_MARKER: { - container_t index = read(); - - inkAssert(_container.top().id == index, "Leaving container we are not in!"); - - // Move up out of the current container - _container.pop(); - - // SPECIAL: If we've popped all containers, then there's an implied - // 'done' command or return - if (_container.empty()) { - _is_falling = false; - - frame_type type; - if (! _threads.empty()) { - on_done(false); - break; - } else if (_stack.has_frame(&type) && type == frame_type::function) // implicit return - // is only for - // functions - { - // push null and return - _eval.push(values::null); - - // HACK - _ptr += sizeof(Command) + sizeof(CommandFlag); - execute_return(); - } else if (_ptr == _story->end()) { // check needed, because it colud exist an unnamed - // toplevel container (empty named container stack - // != empty container stack) - on_done(true); - } - } - } break; - case Command::VISIT: { - // Push the visit count for the current container to the top - // is 0-indexed for some reason. idk why but this is what ink expects - _eval.push( - value{}.set(( int ) _globals->visits(_container.top().id) - 1) - ); - } break; - case Command::TURN: { - _eval.push(value{}.set(( int ) _globals->turns())); - } break; - case Command::SEQUENCE: { - // TODO: The C# ink runtime does a bunch of fancy logic - // to make sure each element is picked at least once in every - // iteration loop. I don't feel like replicating that right now. - // So, let's just return a random number and *shrug* - int sequenceLength = _eval.pop().get(); - int index = _eval.pop().get(); - - _eval.push(value{}.set(static_cast(_rng.rand(sequenceLength))) - ); - } break; - case Command::SEED: { - int32_t seed = _eval.pop().get(); - _rng.srand(seed); - - _eval.push(values::null); - } break; - - case Command::READ_COUNT: { - // Get container index - container_t container = read(); - - // Push the read count for the requested container index - _eval.push(value{}.set(( int ) _globals->visits(container))); - } break; - case Command::TAG: { - _tags.push() = read(); - } break; - default: inkAssert(false, "Unrecognized command!"); break; - } - } - - } #ifndef INK_ENABLE_UNREAL - catch (...) { - // Reset our whole state as it's probably corrupt - reset(); - throw; - } + catch (...) { + // Reset our whole state as it's probably corrupt + reset(); + throw; + } #endif } void runner_impl::on_done(bool setDone) { - // If we're in a thread - if (! _threads.empty()) { - // Get the thread ID of the current thread - thread_t completedThreadId = _threads.pop(); - - // Push in a complete marker - _stack.complete_thread(completedThreadId); - _ref_stack.complete_thread(completedThreadId); - - // Go to where the thread started - frame_type type = execute_return(); - inkAssert( - type == frame_type::thread, - "Expected thread frame marker to hold return to value but none found..." - ); - // if thread ends, move stave point with, else the thread end marker is missing - // and we can't collect the other threads - if (_saved) { - forget(); - save(); - } - } else { - if (setDone) { - set_done_ptr(_ptr); - } - _ptr = nullptr; - } + // If we're in a thread + if (! _threads.empty()) { + // Get the thread ID of the current thread + thread_t completedThreadId = _threads.pop(); + + // Push in a complete marker + _stack.complete_thread(completedThreadId); + _ref_stack.complete_thread(completedThreadId); + + // Go to where the thread started + frame_type type = execute_return(); + inkAssert( + type == frame_type::thread, + "Expected thread frame marker to hold return to value but none found..." + ); + // if thread ends, move stave point with, else the thread end marker is missing + // and we can't collect the other threads + if (_saved) { + forget(); + save(); + } + } else { + if (setDone) { + set_done_ptr(_ptr); + } + _ptr = nullptr; + } } void runner_impl::set_done_ptr(ip_t ptr) { - thread_t curr = current_thread(); - if (curr == ~0) { - _done = ptr; - } else { - _threads.set(curr, ptr); - } + thread_t curr = current_thread(); + if (curr == ~0) { + _done = ptr; + } else { + _threads.set(curr, ptr); + } } void runner_impl::reset() { - _eval.clear(); - _output.clear(); - _stack.clear(); - _ref_stack.clear(); - _threads.clear(); - _evaluation_mode = false; - _saved = false; - _choices.clear(); - _ptr = nullptr; - _done = nullptr; - _container.clear(); + _eval.clear(); + _output.clear(); + _stack.clear(); + _ref_stack.clear(); + _threads.clear(); + _evaluation_mode = false; + _saved = false; + _choices.clear(); + _ptr = nullptr; + _done = nullptr; + _container.clear(); } void runner_impl::mark_used(string_table& strings, list_table& lists) const { - // Find strings in output and stacks - _output.mark_used(strings, lists); - _stack.mark_used(strings, lists); - // ref_stack has no strings and lists! - _eval.mark_used(strings, lists); - - // Take into account tags - for (size_t i = 0; i < _tags.size(); ++i) { - strings.mark_used(_tags[i]); - } - // Take into account choice text - for (size_t i = 0; i < _choices.size(); i++) { - strings.mark_used(_choices[i]._text); - } + // Find strings in output and stacks + _output.mark_used(strings, lists); + _stack.mark_used(strings, lists); + // ref_stack has no strings and lists! + _eval.mark_used(strings, lists); + + // Take into account tags + for (size_t i = 0; i < _tags.size(); ++i) { + strings.mark_used(_tags[i]); + } + // Take into account choice text + for (size_t i = 0; i < _choices.size(); i++) { + strings.mark_used(_choices[i]._text); + } } void runner_impl::save() { - inkAssert(! _saved, "Runner state already saved"); - - _saved = true; - _output.save(); - _stack.save(); - _ref_stack.save(); - _backup = _ptr; - _container.save(); - _globals->save(); - _eval.save(); - _threads.save(); - _choices.save(); - _tags.save(); - _saved_evaluation_mode = _evaluation_mode; - - // Not doing this anymore. There can be lingering stack entries from function returns - // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); + inkAssert(! _saved, "Runner state already saved"); + + _saved = true; + _output.save(); + _stack.save(); + _ref_stack.save(); + _backup = _ptr; + _container.save(); + _globals->save(); + _eval.save(); + _threads.save(); + _choices.save(); + _tags.save(); + _saved_evaluation_mode = _evaluation_mode; + + // Not doing this anymore. There can be lingering stack entries from function returns + // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); } void runner_impl::restore() { - inkAssert(_saved, "Can't restore. No runner state saved."); - // the output can be restored without the rest - if (_output.saved()) { - _output.restore(); - } - _stack.restore(); - _ref_stack.restore(); - _ptr = _backup; - _container.restore(); - _globals->restore(); - _eval.restore(); - _threads.restore(); - _choices.restore(); - _tags.restore(); - _evaluation_mode = _saved_evaluation_mode; - - // Not doing this anymore. There can be lingering stack entries from function returns - // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); - - _saved = false; + inkAssert(_saved, "Can't restore. No runner state saved."); + // the output can be restored without the rest + if (_output.saved()) { + _output.restore(); + } + _stack.restore(); + _ref_stack.restore(); + _ptr = _backup; + _container.restore(); + _globals->restore(); + _eval.restore(); + _threads.restore(); + _choices.restore(); + _tags.restore(); + _evaluation_mode = _saved_evaluation_mode; + + // Not doing this anymore. There can be lingering stack entries from function returns + // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty"); + + _saved = false; } void runner_impl::forget() { - // Do nothing if we haven't saved - if (! _saved) { - return; - } - - _output.forget(); - _stack.forget(); - _ref_stack.forget(); - _container.forget(); - _globals->forget(); - _eval.forget(); - _threads.forget(); - _choices.forgett(); - _tags.forgett(); - - // Nothing to do for eval stack. It should just stay as it is - - _saved = false; + // Do nothing if we haven't saved + if (! _saved) { + return; + } + + _output.forget(); + _stack.forget(); + _ref_stack.forget(); + _container.forget(); + _globals->forget(); + _eval.forget(); + _threads.forget(); + _choices.forgett(); + _tags.forgett(); + + // Nothing to do for eval stack. It should just stay as it is + + _saved = false; } #ifdef INK_ENABLE_STL std::ostream& operator<<(std::ostream& out, runner_impl& in) { - in.getline(out); - return out; + in.getline(out); + return out; } #endif } // namespace ink::runtime::internal diff --git a/inkcpp/story_ptr.cpp b/inkcpp/story_ptr.cpp index c983f045..780aa8e8 100644 --- a/inkcpp/story_ptr.cpp +++ b/inkcpp/story_ptr.cpp @@ -66,4 +66,4 @@ namespace ink::runtime::internal _instance_block = _story_block = nullptr; return is_destroyed; } - } // namespace ink::runtime::internal + } // namespace ink::runtime::internal diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index 3a42cb5e..ab429508 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -16,193 +16,193 @@ void usage() { - using namespace std; - cout << "Usage: inkcpp_cl \n" - << "\t-o :\tOutput file name\n" - << "\t-p []:\tPlay mode\n\toptional snapshot file to load\n\tto create a " - "snapshot file enter '-1' as choice\n" - << "\t--ommit-choice-tags:\tdo not print tags after choices, primarly used to be compatible " - "with inkclecat output" - << endl; + using namespace std; + cout << "Usage: inkcpp_cl \n" + << "\t-o :\tOutput file name\n" + << "\t-p []:\tPlay mode\n\toptional snapshot file to load\n\tto create a " + "snapshot file enter '-1' as choice\n" + << "\t--ommit-choice-tags:\tdo not print tags after choices, primarly used to be compatible " + "with inkclecat output" + << endl; } int main(int argc, const char** argv) { - // Usage - if (argc == 1) { - usage(); - return 1; - } - - // Parse options - std::string outputFilename; - bool playMode = false, testMode = false, testDirectory = false, ommit_choice_tags = false; - std::string snapshotFile; - for (int i = 1; i < argc - 1; i++) { - std::string option = argv[i]; - if (option == "-o") { - outputFilename = argv[i + 1]; - i += 1; - } else if (option == "-p") { - playMode = true; - if (i + 1 < argc - 1 && argv[i + 1][0] != '-') { - ++i; - snapshotFile = argv[i]; - } - } else if (option == "--ommit-choice-tags") { - ommit_choice_tags = true; - } else if (option == "-t") { - testMode = true; - } else if (option == "-td") { - testMode = true; - testDirectory = true; - } else { - std::cerr << "Unrecognized option: '" << option << "'\n"; - } - } - - // Get input filename - std::string inputFilename = argv[argc - 1]; - - // Test mode - if (testMode) { - bool result; - if (testDirectory) { - result = test_directory(inputFilename); - } else { - result = test(inputFilename); - } - - return result ? 0 : -1; - } - - // If output filename not specified, use input filename as guideline - if (outputFilename.empty()) { - outputFilename = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".bin"); - } - - // If input filename is an .ink file - int val = inputFilename.find(".ink"); - bool json_file_is_tmp_file = false; - if (val == inputFilename.length() - 4) { - // Create temporary filename - std::string jsonFile = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".tmp"); - - // Then we need to do a compilation with inklecate - try { - inklecate(inputFilename, jsonFile); - } catch (const std::exception& e) { - std::cerr << "Inklecate Error: " << e.what() << std::endl; - return 1; - } - - // New input is the json file - json_file_is_tmp_file = true; - inputFilename = jsonFile; - } - - // Open file and compile - try { - ink::compiler::compilation_results results; - std::ofstream fout(outputFilename, std::ios::binary | std::ios::out); - ink::compiler::run(inputFilename.c_str(), fout, &results); - fout.close(); - if (json_file_is_tmp_file) { - remove(inputFilename.c_str()); - } - - // Report errors - for (auto& warn : results.warnings) { - std::cerr << "WARNING: " << warn << '\n'; - } - for (auto& err : results.errors) { - std::cerr << "ERROR: " << err << '\n'; - } - - if (results.errors.size() > 0 && playMode) { - std::cerr << "Cancelling play mode. Errors detected in compilation" << std::endl; - return -1; - } - } catch (std::exception& e) { - if (json_file_is_tmp_file) { - remove(inputFilename.c_str()); - } - std::cerr << "Unhandled InkBin compiler exception: " << e.what() << std::endl; - return 1; - } - - if (! playMode) { - return 0; - } - - // Run the story - try { - using namespace ink::runtime; - - // Load story - std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; - - // Start runner - runner thread; - if (snapshotFile.size()) { - auto snap_ptr = snapshot::from_file(snapshotFile.c_str()); - thread = myInk->new_runner_from_snapshot(*snap_ptr); - delete snap_ptr; - } else { - thread = myInk->new_runner(); - } - - while (true) { - while (thread->can_continue()) { - std::cout << thread->getline(); - } - if (thread->has_tags()) { - std::cout << "# tags: "; - for (int i = 0; i < thread->num_tags(); ++i) { - if (i != 0) { - std::cout << ", "; - } - std::cout << thread->get_tag(i); + // Usage + if (argc == 1) { + usage(); + return 1; } - std::cout << std::endl; - } - if (thread->has_choices()) { - // Extra end line - std::cout << std::endl; - - int index = 1; - for (const ink::runtime::choice& c : *thread) { - std::cout << index++ << ": " << c.text(); - if (! ommit_choice_tags && c.has_tags()) { - std::cout << "\n\t"; - for (size_t i = 0; i < c.num_tags(); ++i) { - std::cout << "# " << c.get_tag(i) << " "; - } - } - std::cout << std::endl; + + // Parse options + std::string outputFilename; + bool playMode = false, testMode = false, testDirectory = false, ommit_choice_tags = false; + std::string snapshotFile; + for (int i = 1; i < argc - 1; i++) { + std::string option = argv[i]; + if (option == "-o") { + outputFilename = argv[i + 1]; + i += 1; + } else if (option == "-p") { + playMode = true; + if (i + 1 < argc - 1 && argv[i + 1][0] != '-') { + ++i; + snapshotFile = argv[i]; + } + } else if (option == "--ommit-choice-tags") { + ommit_choice_tags = true; + } else if (option == "-t") { + testMode = true; + } else if (option == "-td") { + testMode = true; + testDirectory = true; + } else { + std::cerr << "Unrecognized option: '" << option << "'\n"; + } + } + + // Get input filename + std::string inputFilename = argv[argc - 1]; + + // Test mode + if (testMode) { + bool result; + if (testDirectory) { + result = test_directory(inputFilename); + } else { + result = test(inputFilename); + } + + return result ? 0 : -1; + } + + // If output filename not specified, use input filename as guideline + if (outputFilename.empty()) { + outputFilename = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".bin"); + } + + // If input filename is an .ink file + int val = inputFilename.find(".ink"); + bool json_file_is_tmp_file = false; + if (val == inputFilename.length() - 4) { + // Create temporary filename + std::string jsonFile = std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".tmp"); + + // Then we need to do a compilation with inklecate + try { + inklecate(inputFilename, jsonFile); + } catch (const std::exception& e) { + std::cerr << "Inklecate Error: " << e.what() << std::endl; + return 1; + } + + // New input is the json file + json_file_is_tmp_file = true; + inputFilename = jsonFile; + } + + // Open file and compile + try { + ink::compiler::compilation_results results; + std::ofstream fout(outputFilename, std::ios::binary | std::ios::out); + ink::compiler::run(inputFilename.c_str(), fout, &results); + fout.close(); + if (json_file_is_tmp_file) { + remove(inputFilename.c_str()); + } + + // Report errors + for (auto& warn : results.warnings) { + std::cerr << "WARNING: " << warn << '\n'; + } + for (auto& err : results.errors) { + std::cerr << "ERROR: " << err << '\n'; + } + + if (results.errors.size() > 0 && playMode) { + std::cerr << "Cancelling play mode. Errors detected in compilation" << std::endl; + return -1; + } + } catch (std::exception& e) { + if (json_file_is_tmp_file) { + remove(inputFilename.c_str()); + } + std::cerr << "Unhandled InkBin compiler exception: " << e.what() << std::endl; + return 1; + } + + if (! playMode) { + return 0; } - int c = 0; - std::cin >> c; - if (c == -1) { - snapshot* snap = thread->create_snapshot(); - snap->write_to_file( - std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".snap").c_str() - ); - delete snap; - break; + // Run the story + try { + using namespace ink::runtime; + + // Load story + std::unique_ptr myInk{story::from_file(outputFilename.c_str())}; + + // Start runner + runner thread; + if (snapshotFile.size()) { + auto snap_ptr = snapshot::from_file(snapshotFile.c_str()); + thread = myInk->new_runner_from_snapshot(*snap_ptr); + delete snap_ptr; + } else { + thread = myInk->new_runner(); + } + + while (true) { + while (thread->can_continue()) { + std::cout << thread->getline(); + } + if (thread->has_tags()) { + std::cout << "# tags: "; + for (int i = 0; i < thread->num_tags(); ++i) { + if (i != 0) { + std::cout << ", "; + } + std::cout << thread->get_tag(i); + } + std::cout << std::endl; + } + if (thread->has_choices()) { + // Extra end line + std::cout << std::endl; + + int index = 1; + for (const ink::runtime::choice& c : *thread) { + std::cout << index++ << ": " << c.text(); + if (! ommit_choice_tags && c.has_tags()) { + std::cout << "\n\t"; + for (size_t i = 0; i < c.num_tags(); ++i) { + std::cout << "# " << c.get_tag(i) << " "; + } + } + std::cout << std::endl; + } + + int c = 0; + std::cin >> c; + if (c == -1) { + snapshot* snap = thread->create_snapshot(); + snap->write_to_file( + std::regex_replace(inputFilename, std::regex("\\.[^\\.]+$"), ".snap").c_str() + ); + delete snap; + break; + } + thread->choose(c - 1); + std::cout << "?> "; + continue; + } + + // out of content + break; + } + } catch (const std::exception& e) { + std::cerr << "Unhandled ink runtime exception: " << e.what() << std::endl; + return 1; } - thread->choose(c - 1); - std::cout << "?> "; - continue; - } - - // out of content - break; - } - } catch (const std::exception& e) { - std::cerr << "Unhandled ink runtime exception: " << e.what() << std::endl; - return 1; - } - return 0; + return 0; } diff --git a/inkcpp_test/SpaceAfterBracketChoice.cpp b/inkcpp_test/SpaceAfterBracketChoice.cpp index 55949b63..923a81a0 100644 --- a/inkcpp_test/SpaceAfterBracketChoice.cpp +++ b/inkcpp_test/SpaceAfterBracketChoice.cpp @@ -10,39 +10,39 @@ using namespace ink::runtime; SCENARIO("a story with bracketed choices and spaces can choose correctly", "[choices]") { - GIVEN("a story with line breaks") - { - inklecate("ink/ChoiceBracketStory.ink", "ChoiceBracketStory.tmp"); - ink::compiler::run("ChoiceBracketStory.tmp", "ChoiceBracketStory.bin"); - auto ink = story::from_file("ChoiceBracketStory.bin"); - runner thread = ink->new_runner(); - thread->getall(); - WHEN("start thread") - { - THEN("thread has choices") - { - thread->getall(); - REQUIRE(thread->has_choices()); - } - WHEN("choose choice 1") - { - thread->choose(0); - thread->getall(); - THEN("still has choices") + GIVEN("a story with line breaks") { - thread->getall(); - REQUIRE(thread->has_choices()); + inklecate("ink/ChoiceBracketStory.ink", "ChoiceBracketStory.tmp"); + ink::compiler::run("ChoiceBracketStory.tmp", "ChoiceBracketStory.bin"); + auto ink = story::from_file("ChoiceBracketStory.bin"); + runner thread = ink->new_runner(); + thread->getall(); + WHEN("start thread") + { + THEN("thread has choices") + { + thread->getall(); + REQUIRE(thread->has_choices()); + } + WHEN("choose choice 1") + { + thread->choose(0); + thread->getall(); + THEN("still has choices") + { + thread->getall(); + REQUIRE(thread->has_choices()); + } + } + WHEN("choose choice 2") + { + thread->choose(1); + thread->getall(); + THEN("still has choices") + { + REQUIRE(thread->has_choices()); + } + } + } } - } - WHEN("choose choice 2") - { - thread->choose(1); - thread->getall(); - THEN("still has choices") - { - REQUIRE(thread->has_choices()); - } - } - } - } } diff --git a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp index 99e54251..71fbfce6 100644 --- a/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp +++ b/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp @@ -13,31 +13,31 @@ SCENARIO( "[choices]" ) { - GIVEN("a story with brackets and nested choices") - { - inklecate("ink/ThirdTierChoiceAfterBracketsStory.ink", "ThirdTierChoiceAfterBracketsStory.tmp"); - ink::compiler::run( - "ThirdTierChoiceAfterBracketsStory.tmp", "ThirdTierChoiceAfterBracketsStory.bin" - ); - auto ink = story::from_file("ThirdTierChoiceAfterBracketsStory.bin"); - runner thread = ink->new_runner(); + GIVEN("a story with brackets and nested choices") + { + inklecate("ink/ThirdTierChoiceAfterBracketsStory.ink", "ThirdTierChoiceAfterBracketsStory.tmp"); + ink::compiler::run( + "ThirdTierChoiceAfterBracketsStory.tmp", "ThirdTierChoiceAfterBracketsStory.bin" + ); + auto ink = story::from_file("ThirdTierChoiceAfterBracketsStory.bin"); + runner thread = ink->new_runner(); - WHEN("start thread") - { - THEN("thread doesn't error") - { - thread->getall(); - thread->has_choices(); - thread->choose(0); - thread->getall(); - thread->has_choices(); - thread->choose(0); - thread->getall(); - thread->has_choices(); - thread->choose(0); - thread->getall(); - thread->has_choices(); - } - } - } + WHEN("start thread") + { + THEN("thread doesn't error") + { + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + thread->choose(0); + thread->getall(); + thread->has_choices(); + } + } + } }