inkcpp_rb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +1 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +84 -0
  6. data/LICENSE +7 -0
  7. data/README.md +3 -0
  8. data/Rakefile +16 -0
  9. data/bin/console +15 -0
  10. data/bin/setup +10 -0
  11. data/bin/tapioca +29 -0
  12. data/ext/inkcpp_rb/extconf.rb +19 -0
  13. data/ext/inkcpp_rb/inkcpp/.clang-format +99 -0
  14. data/ext/inkcpp_rb/inkcpp/.github/FUNDING.yml +1 -0
  15. data/ext/inkcpp_rb/inkcpp/.github/workflows/build.yml +344 -0
  16. data/ext/inkcpp_rb/inkcpp/.github/workflows/release.yml +49 -0
  17. data/ext/inkcpp_rb/inkcpp/.gitignore +25 -0
  18. data/ext/inkcpp_rb/inkcpp/.gitmodules +9 -0
  19. data/ext/inkcpp_rb/inkcpp/CMakeLists.txt +170 -0
  20. data/ext/inkcpp_rb/inkcpp/CODE_OF_CONDUCT.md +76 -0
  21. data/ext/inkcpp_rb/inkcpp/CONTRIBUTING.md +55 -0
  22. data/ext/inkcpp_rb/inkcpp/Config.cmake.in +2 -0
  23. data/ext/inkcpp_rb/inkcpp/Documentation/cmake_example/CMakeLists.txt +13 -0
  24. data/ext/inkcpp_rb/inkcpp/Documentation/cmake_example/main.c +38 -0
  25. data/ext/inkcpp_rb/inkcpp/Documentation/cmake_example/main.cpp +40 -0
  26. data/ext/inkcpp_rb/inkcpp/Documentation/cmake_example/test.ink +8 -0
  27. data/ext/inkcpp_rb/inkcpp/Documentation/cmake_example/test.ink.json +1 -0
  28. data/ext/inkcpp_rb/inkcpp/Documentation/cmake_example.zip +0 -0
  29. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/InkCPP_DEMO.zip +0 -0
  30. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/CreateThread.png +0 -0
  31. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/HandleChoice.png +0 -0
  32. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/ListElementOf.png +0 -0
  33. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/MinimalRuntime.png +0 -0
  34. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/MinimalThread.png +0 -0
  35. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/ObseverChange.png +0 -0
  36. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/TagListGetValue.png +0 -0
  37. data/ext/inkcpp_rb/inkcpp/Documentation/unreal/imgs/YieldResume.png +0 -0
  38. data/ext/inkcpp_rb/inkcpp/Doxyfile +2825 -0
  39. data/ext/inkcpp_rb/inkcpp/LICENSE.txt +22 -0
  40. data/ext/inkcpp_rb/inkcpp/Minimal.runsettings +8 -0
  41. data/ext/inkcpp_rb/inkcpp/README.md +192 -0
  42. data/ext/inkcpp_rb/inkcpp/inkcpp/CMakeLists.txt +67 -0
  43. data/ext/inkcpp_rb/inkcpp/inkcpp/array.h +481 -0
  44. data/ext/inkcpp_rb/inkcpp/inkcpp/avl_array.h +833 -0
  45. data/ext/inkcpp_rb/inkcpp/inkcpp/casting.h +93 -0
  46. data/ext/inkcpp_rb/inkcpp/inkcpp/choice.cpp +54 -0
  47. data/ext/inkcpp_rb/inkcpp/inkcpp/collections/restorable.cpp +124 -0
  48. data/ext/inkcpp_rb/inkcpp/inkcpp/collections/restorable.h +406 -0
  49. data/ext/inkcpp_rb/inkcpp/inkcpp/container_operations.cpp +52 -0
  50. data/ext/inkcpp_rb/inkcpp/inkcpp/container_operations.h +34 -0
  51. data/ext/inkcpp_rb/inkcpp/inkcpp/executioner.h +179 -0
  52. data/ext/inkcpp_rb/inkcpp/inkcpp/functional.cpp +86 -0
  53. data/ext/inkcpp_rb/inkcpp/inkcpp/functions.cpp +54 -0
  54. data/ext/inkcpp_rb/inkcpp/inkcpp/functions.h +40 -0
  55. data/ext/inkcpp_rb/inkcpp/inkcpp/globals_impl.cpp +289 -0
  56. data/ext/inkcpp_rb/inkcpp/inkcpp/globals_impl.h +149 -0
  57. data/ext/inkcpp_rb/inkcpp/inkcpp/header.cpp +44 -0
  58. data/ext/inkcpp_rb/inkcpp/inkcpp/include/choice.h +106 -0
  59. data/ext/inkcpp_rb/inkcpp/inkcpp/include/functional.h +327 -0
  60. data/ext/inkcpp_rb/inkcpp/inkcpp/include/globals.h +196 -0
  61. data/ext/inkcpp_rb/inkcpp/inkcpp/include/list.h +187 -0
  62. data/ext/inkcpp_rb/inkcpp/inkcpp/include/runner.h +291 -0
  63. data/ext/inkcpp_rb/inkcpp/inkcpp/include/snapshot.h +61 -0
  64. data/ext/inkcpp_rb/inkcpp/inkcpp/include/story.h +219 -0
  65. data/ext/inkcpp_rb/inkcpp/inkcpp/include/story_ptr.h +233 -0
  66. data/ext/inkcpp_rb/inkcpp/inkcpp/include/traits.h +270 -0
  67. data/ext/inkcpp_rb/inkcpp/inkcpp/include/types.h +169 -0
  68. data/ext/inkcpp_rb/inkcpp/inkcpp/list_impl.cpp +79 -0
  69. data/ext/inkcpp_rb/inkcpp/inkcpp/list_impl.h +39 -0
  70. data/ext/inkcpp_rb/inkcpp/inkcpp/list_operations.cpp +276 -0
  71. data/ext/inkcpp_rb/inkcpp/inkcpp/list_operations.h +356 -0
  72. data/ext/inkcpp_rb/inkcpp/inkcpp/list_table.cpp +841 -0
  73. data/ext/inkcpp_rb/inkcpp/inkcpp/list_table.h +450 -0
  74. data/ext/inkcpp_rb/inkcpp/inkcpp/numeric_operations.cpp +40 -0
  75. data/ext/inkcpp_rb/inkcpp/inkcpp/numeric_operations.h +529 -0
  76. data/ext/inkcpp_rb/inkcpp/inkcpp/operation_bases.h +164 -0
  77. data/ext/inkcpp_rb/inkcpp/inkcpp/operations.h +100 -0
  78. data/ext/inkcpp_rb/inkcpp/inkcpp/output.cpp +528 -0
  79. data/ext/inkcpp_rb/inkcpp/inkcpp/output.h +153 -0
  80. data/ext/inkcpp_rb/inkcpp/inkcpp/platform.h +22 -0
  81. data/ext/inkcpp_rb/inkcpp/inkcpp/random.h +38 -0
  82. data/ext/inkcpp_rb/inkcpp/inkcpp/runner_impl.cpp +1396 -0
  83. data/ext/inkcpp_rb/inkcpp/inkcpp/runner_impl.h +336 -0
  84. data/ext/inkcpp_rb/inkcpp/inkcpp/simple_restorable_stack.h +335 -0
  85. data/ext/inkcpp_rb/inkcpp/inkcpp/snapshot_impl.cpp +182 -0
  86. data/ext/inkcpp_rb/inkcpp/inkcpp/snapshot_impl.h +91 -0
  87. data/ext/inkcpp_rb/inkcpp/inkcpp/snapshot_interface.h +57 -0
  88. data/ext/inkcpp_rb/inkcpp/inkcpp/stack.cpp +618 -0
  89. data/ext/inkcpp_rb/inkcpp/inkcpp/stack.h +243 -0
  90. data/ext/inkcpp_rb/inkcpp/inkcpp/story_impl.cpp +361 -0
  91. data/ext/inkcpp_rb/inkcpp/inkcpp/story_impl.h +92 -0
  92. data/ext/inkcpp_rb/inkcpp/inkcpp/story_ptr.cpp +75 -0
  93. data/ext/inkcpp_rb/inkcpp/inkcpp/string_operations.cpp +125 -0
  94. data/ext/inkcpp_rb/inkcpp/inkcpp/string_operations.h +67 -0
  95. data/ext/inkcpp_rb/inkcpp/inkcpp/string_table.cpp +149 -0
  96. data/ext/inkcpp_rb/inkcpp/inkcpp/string_table.h +47 -0
  97. data/ext/inkcpp_rb/inkcpp/inkcpp/string_utils.h +207 -0
  98. data/ext/inkcpp_rb/inkcpp/inkcpp/system.cpp +39 -0
  99. data/ext/inkcpp_rb/inkcpp/inkcpp/tuple.hpp +151 -0
  100. data/ext/inkcpp_rb/inkcpp/inkcpp/value.cpp +279 -0
  101. data/ext/inkcpp_rb/inkcpp/inkcpp/value.h +666 -0
  102. data/ext/inkcpp_rb/inkcpp/inkcpp_c/CMakeLists.txt +62 -0
  103. data/ext/inkcpp_rb/inkcpp/inkcpp_c/include/inkcpp.h +393 -0
  104. data/ext/inkcpp_rb/inkcpp/inkcpp_c/inkcpp.cpp +344 -0
  105. data/ext/inkcpp_rb/inkcpp/inkcpp_c/inkcpp_c.pc.in +10 -0
  106. data/ext/inkcpp_rb/inkcpp/inkcpp_c/tests/ExternalFunction.c +56 -0
  107. data/ext/inkcpp_rb/inkcpp/inkcpp_c/tests/Globals.c +98 -0
  108. data/ext/inkcpp_rb/inkcpp/inkcpp_c/tests/Lists.c +73 -0
  109. data/ext/inkcpp_rb/inkcpp/inkcpp_c/tests/Observer.c +36 -0
  110. data/ext/inkcpp_rb/inkcpp/inkcpp_c/tests/Snapshot.c +65 -0
  111. data/ext/inkcpp_rb/inkcpp/inkcpp_cl/CMakeLists.txt +49 -0
  112. data/ext/inkcpp_rb/inkcpp/inkcpp_cl/inkcpp_cl.cpp +215 -0
  113. data/ext/inkcpp_rb/inkcpp/inkcpp_cl/test.cpp +209 -0
  114. data/ext/inkcpp_rb/inkcpp/inkcpp_cl/test.h +8 -0
  115. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/CMakeLists.txt +37 -0
  116. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/binary_emitter.cpp +446 -0
  117. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/binary_emitter.h +70 -0
  118. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/binary_stream.cpp +166 -0
  119. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/binary_stream.h +79 -0
  120. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/command.cpp +107 -0
  121. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/compiler.cpp +96 -0
  122. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/emitter.cpp +62 -0
  123. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/emitter.h +104 -0
  124. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/include/compilation_results.h +22 -0
  125. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/include/compiler.h +44 -0
  126. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/json.hpp +24596 -0
  127. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/json_compiler.cpp +411 -0
  128. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/json_compiler.h +62 -0
  129. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/list_data.cpp +47 -0
  130. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/list_data.h +70 -0
  131. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/reporter.cpp +107 -0
  132. data/ext/inkcpp_rb/inkcpp/inkcpp_compiler/reporter.h +55 -0
  133. data/ext/inkcpp_rb/inkcpp/inkcpp_py/CMakeLists.txt +19 -0
  134. data/ext/inkcpp_rb/inkcpp/inkcpp_py/example.py +78 -0
  135. data/ext/inkcpp_rb/inkcpp/inkcpp_py/src/module.cpp +317 -0
  136. data/ext/inkcpp_rb/inkcpp/inkcpp_py/tests/conftest.py +53 -0
  137. data/ext/inkcpp_rb/inkcpp/inkcpp_py/tests/test_ExternalFunctions.py +35 -0
  138. data/ext/inkcpp_rb/inkcpp/inkcpp_py/tests/test_Globals.py +40 -0
  139. data/ext/inkcpp_rb/inkcpp/inkcpp_py/tests/test_Lists.py +43 -0
  140. data/ext/inkcpp_rb/inkcpp/inkcpp_py/tests/test_Observer.py +27 -0
  141. data/ext/inkcpp_rb/inkcpp/inkcpp_py/tests/test_Snapshot.py +57 -0
  142. data/ext/inkcpp_rb/inkcpp/inkcpp_py/unreal_example.ink +71 -0
  143. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Array.cpp +115 -0
  144. data/ext/inkcpp_rb/inkcpp/inkcpp_test/CMakeLists.txt +117 -0
  145. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Callstack.cpp +392 -0
  146. data/ext/inkcpp_rb/inkcpp/inkcpp_test/EmptyStringForDivert.cpp +36 -0
  147. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ExternalFunctionsExecuteProperly.cpp +34 -0
  148. data/ext/inkcpp_rb/inkcpp/inkcpp_test/FallbackFunction.cpp +77 -0
  149. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Globals.cpp +73 -0
  150. data/ext/inkcpp_rb/inkcpp/inkcpp_test/InkyJson.cpp +34 -0
  151. data/ext/inkcpp_rb/inkcpp/inkcpp_test/LabelCondition.cpp +60 -0
  152. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Lists.cpp +144 -0
  153. data/ext/inkcpp_rb/inkcpp/inkcpp_test/LookaheadSafe.cpp +46 -0
  154. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Main.cpp +7 -0
  155. data/ext/inkcpp_rb/inkcpp/inkcpp_test/MoveTo.cpp +95 -0
  156. data/ext/inkcpp_rb/inkcpp/inkcpp_test/NewLines.cpp +76 -0
  157. data/ext/inkcpp_rb/inkcpp/inkcpp_test/NoEarlyTags.cpp +33 -0
  158. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Observer.cpp +245 -0
  159. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Pointer.cpp +191 -0
  160. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Restorable.cpp +294 -0
  161. data/ext/inkcpp_rb/inkcpp/inkcpp_test/SpaceAfterBracketChoice.cpp +45 -0
  162. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Stack.cpp +224 -0
  163. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Tags.cpp +131 -0
  164. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ThirdTierChoiceAfterBrackets.cpp +38 -0
  165. data/ext/inkcpp_rb/inkcpp/inkcpp_test/UTF8.cpp +56 -0
  166. data/ext/inkcpp_rb/inkcpp/inkcpp_test/Value.cpp +210 -0
  167. data/ext/inkcpp_rb/inkcpp/inkcpp_test/catch.hpp +17970 -0
  168. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/AHF.ink +7 -0
  169. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/ChoiceBracketStory.ink +7 -0
  170. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/EmptyStringForDivert.ink +13 -0
  171. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/ExternalFunctionsExecuteProperly.ink +11 -0
  172. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/FallBack.ink +15 -0
  173. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/GlobalStory.ink +9 -0
  174. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/LabelConditionStory.ink +5 -0
  175. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/LinesStory.ink +42 -0
  176. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/ListLogicStory.ink +40 -0
  177. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/ListStory.ink +8 -0
  178. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/LookaheadSafe.ink +14 -0
  179. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/MoveTo.ink +36 -0
  180. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/NoEarlyTags.ink +19 -0
  181. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/ObserverStory.ink +8 -0
  182. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/SimpleStoryFlow.ink +65 -0
  183. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/TagsStory.ink +22 -0
  184. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/TheIntercept.ink +1686 -0
  185. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/ThirdTierChoiceAfterBracketsStory.ink +13 -0
  186. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/UTF-8-demo.txt +212 -0
  187. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/UTF8Story.ink +218 -0
  188. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/simple-1.1.1-inklecate.json +154 -0
  189. data/ext/inkcpp_rb/inkcpp/inkcpp_test/ink/simple-1.1.1-inky.json +160 -0
  190. data/ext/inkcpp_rb/inkcpp/notes/ArchitectureNotes.md +54 -0
  191. data/ext/inkcpp_rb/inkcpp/notes/ListNotes.md +69 -0
  192. data/ext/inkcpp_rb/inkcpp/notes/OperationNotes.md +35 -0
  193. data/ext/inkcpp_rb/inkcpp/notes/TagsNotes.md +24 -0
  194. data/ext/inkcpp_rb/inkcpp/notes/WhitespaceNotes.md +28 -0
  195. data/ext/inkcpp_rb/inkcpp/proofing/README.md +3 -0
  196. data/ext/inkcpp_rb/inkcpp/proofing/inkcpp_runtime_driver +12 -0
  197. data/ext/inkcpp_rb/inkcpp/pyproject.toml +63 -0
  198. data/ext/inkcpp_rb/inkcpp/setup.py +166 -0
  199. data/ext/inkcpp_rb/inkcpp/shared/CMakeLists.txt +14 -0
  200. data/ext/inkcpp_rb/inkcpp/shared/private/command.h +172 -0
  201. data/ext/inkcpp_rb/inkcpp/shared/private/header.h +46 -0
  202. data/ext/inkcpp_rb/inkcpp/shared/public/config.h +53 -0
  203. data/ext/inkcpp_rb/inkcpp/shared/public/system.h +307 -0
  204. data/ext/inkcpp_rb/inkcpp/shared/public/version.h +14 -0
  205. data/ext/inkcpp_rb/inkcpp/tests/TestAllSequenceTypes.ink +59 -0
  206. data/ext/inkcpp_rb/inkcpp/tests/TestArithmetic.ink +17 -0
  207. data/ext/inkcpp_rb/inkcpp/tests/TestBasicStringLiterals.ink +8 -0
  208. data/ext/inkcpp_rb/inkcpp/tests/TestBasicTunnel.ink +10 -0
  209. data/ext/inkcpp_rb/inkcpp/tests/TestBlanksInInlineSequences.ink +51 -0
  210. data/ext/inkcpp_rb/inkcpp/tests/TestCallStackEvaluation.ink +15 -0
  211. data/ext/inkcpp_rb/inkcpp/tests/TestChoiceCount.ink +15 -0
  212. data/ext/inkcpp_rb/inkcpp/tests/TestChoiceDivertsToDone.ink +6 -0
  213. data/ext/inkcpp_rb/inkcpp/tests/TestChoiceWithBracketsOnly.ink +9 -0
  214. data/ext/inkcpp_rb/inkcpp/tests/TestCompareDivertTargets.ink +26 -0
  215. data/ext/inkcpp_rb/inkcpp/tests/TestComplexTunnels.ink +22 -0
  216. data/ext/inkcpp_rb/inkcpp/tests/TestConditionalChoiceInWeave.ink +19 -0
  217. data/ext/inkcpp_rb/inkcpp/tests/TestTunnelOnwardsAfterTunnel.ink +17 -0
  218. data/ext/inkcpp_rb/inkcpp/unreal/CMakeLists.txt +51 -0
  219. data/ext/inkcpp_rb/inkcpp/unreal/UE_example.ink +92 -0
  220. data/ext/inkcpp_rb/inkcpp/unreal/blueprint_filter.js +377 -0
  221. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Resources/Icon128.png +0 -0
  222. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/InkAsset.cpp +47 -0
  223. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/InkChoice.cpp +40 -0
  224. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/InkList.cpp +86 -0
  225. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/InkRuntime.cpp +265 -0
  226. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/InkThread.cpp +239 -0
  227. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/InkVar.cpp +143 -0
  228. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/TagList.cpp +95 -0
  229. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Private/inkcpp.cpp +13 -0
  230. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkAsset.h +50 -0
  231. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkChoice.h +58 -0
  232. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkDelegates.h +139 -0
  233. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkList.h +102 -0
  234. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkRuntime.h +177 -0
  235. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkSnapshot.h +30 -0
  236. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkThread.h +215 -0
  237. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/InkVar.h +245 -0
  238. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/TagList.h +77 -0
  239. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/Public/inkcpp.h +217 -0
  240. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp/inkcpp.Build.cs +62 -0
  241. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp_editor/Private/InkAssetFactory.cpp +237 -0
  242. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp_editor/Private/InkAssetFactory.h +43 -0
  243. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp_editor/Private/inkcpp_editor.cpp +13 -0
  244. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp_editor/Private/inklecate_cmd.cpp.in +24 -0
  245. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp_editor/Public/inkcpp_editor.h +9 -0
  246. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/Source/inkcpp_editor/inkcpp_editor.Build.cs +61 -0
  247. data/ext/inkcpp_rb/inkcpp/unreal/inkcpp/inkcpp.uplugin +44 -0
  248. data/ext/inkcpp_rb/inkcpp/unreal/render.css +1 -0
  249. data/ext/inkcpp_rb/inkcpp_rb.cpp +321 -0
  250. data/inkcpp_rb.gemspec +54 -0
  251. data/rbi/inkcpp_rb.rbi +211 -0
  252. data/sorbet/config +4 -0
  253. data/sorbet/rbi/annotations/.gitattributes +1 -0
  254. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  255. data/sorbet/rbi/gems/.gitattributes +1 -0
  256. data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  257. data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  258. data/sorbet/rbi/gems/minitest@5.25.4.rbi +1547 -0
  259. data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
  260. data/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  261. data/sorbet/rbi/gems/prism@1.3.0.rbi +40040 -0
  262. data/sorbet/rbi/gems/rake-compiler@1.2.8.rbi +9 -0
  263. data/sorbet/rbi/gems/rake@13.2.1.rbi +3033 -0
  264. data/sorbet/rbi/gems/rbi@0.2.2.rbi +4527 -0
  265. data/sorbet/rbi/gems/rice@4.3.3.rbi +44 -0
  266. data/sorbet/rbi/gems/spoom@1.5.0.rbi +4932 -0
  267. data/sorbet/rbi/gems/tapioca@0.16.7.rbi +3611 -0
  268. data/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  269. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  270. data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
  271. data/sorbet/tapioca/config.yml +13 -0
  272. data/sorbet/tapioca/require.rb +4 -0
  273. metadata +400 -0
@@ -0,0 +1,1396 @@
1
+ /* Copyright (c) 2024 Julian Benda
2
+ *
3
+ * This file is part of inkCPP which is released under MIT license.
4
+ * See file LICENSE.txt or go to
5
+ * https://github.com/JBenda/inkcpp for full license details.
6
+ */
7
+ #include "runner_impl.h"
8
+ #include "story_impl.h"
9
+ #include "command.h"
10
+ #include "choice.h"
11
+ #include "globals_impl.h"
12
+ #include "header.h"
13
+ #include "string_utils.h"
14
+ #include "snapshot_impl.h"
15
+ #include "system.h"
16
+ #include "value.h"
17
+
18
+ namespace ink::runtime
19
+ {
20
+ const choice* runner_interface::get_choice(size_t index) const
21
+ {
22
+ inkAssert(index < num_choices(), "Choice out of bounds!");
23
+ return begin() + index;
24
+ }
25
+
26
+ size_t runner_interface::num_choices() const { return end() - begin(); }
27
+ } // namespace ink::runtime
28
+
29
+ namespace ink::runtime::internal
30
+ {
31
+
32
+ template<>
33
+ value* runner_impl::get_var<runner_impl::Scope::GLOBAL>(hash_t variableName)
34
+ {
35
+ return _globals->get_variable(variableName);
36
+ }
37
+
38
+ template<>
39
+ value* runner_impl::get_var<runner_impl::Scope::LOCAL>(hash_t variableName)
40
+ {
41
+ value* ret = _stack.get(variableName);
42
+ if (! ret) {
43
+ return nullptr;
44
+ }
45
+ if (ret->type() == value_type::value_pointer) {
46
+ auto [name, ci] = ret->get<value_type::value_pointer>();
47
+ inkAssert(ci == 0, "only Global pointer are allowd on the _stack!");
48
+ return get_var<runner_impl::Scope::GLOBAL>(name);
49
+ }
50
+ return ret;
51
+ }
52
+
53
+ template<>
54
+ value* runner_impl::get_var<runner_impl::Scope::NONE>(hash_t variableName)
55
+ {
56
+ value* ret = get_var<Scope::LOCAL>(variableName);
57
+ if (ret) {
58
+ return ret;
59
+ }
60
+ return get_var<Scope::GLOBAL>(variableName);
61
+ }
62
+
63
+ template<runner_impl::Scope Hint>
64
+ const value* runner_impl::get_var(hash_t variableName) const
65
+ {
66
+ return const_cast<runner_impl*>(this)->get_var<Hint>(variableName);
67
+ }
68
+
69
+ template<>
70
+ void runner_impl::set_var<runner_impl::Scope::GLOBAL>(
71
+ hash_t variableName, const value& val, bool is_redef
72
+ )
73
+ {
74
+ if (is_redef) {
75
+ value* src = _globals->get_variable(variableName);
76
+ _globals->set_variable(variableName, src->redefine(val, _globals->lists()));
77
+ } else {
78
+ _globals->set_variable(variableName, val);
79
+ }
80
+ }
81
+
82
+ const value* runner_impl::dereference(const value& val)
83
+ {
84
+ if (val.type() != value_type::value_pointer) {
85
+ return &val;
86
+ }
87
+
88
+ auto [name, ci] = val.get<value_type::value_pointer>();
89
+ if (ci == 0) {
90
+ return get_var<Scope::GLOBAL>(name);
91
+ }
92
+ return _stack.get_from_frame(ci, name);
93
+ }
94
+
95
+ template<>
96
+ void runner_impl::set_var<runner_impl::Scope::LOCAL>(
97
+ hash_t variableName, const value& val, bool is_redef
98
+ )
99
+ {
100
+ if (val.type() == value_type::value_pointer) {
101
+ inkAssert(is_redef == false, "value pointer can only use to initelize variable!");
102
+ auto [name, ci] = val.get<value_type::value_pointer>();
103
+ if (ci == 0) {
104
+ _stack.set(variableName, val);
105
+ } else {
106
+ const value* dref = dereference(val);
107
+ if (dref == nullptr) {
108
+ value v = val;
109
+ auto ref = v.get<value_type::value_pointer>();
110
+ v.set<value_type::value_pointer>(ref.name, 0);
111
+ _stack.set(variableName, v);
112
+ } else {
113
+ _ref_stack.set(variableName, val);
114
+ _stack.set(variableName, *dref);
115
+ }
116
+ }
117
+ } else {
118
+ if (is_redef) {
119
+ value* src = _stack.get(variableName);
120
+ if (src->type() == value_type::value_pointer) {
121
+ auto [name, ci] = src->get<value_type::value_pointer>();
122
+ inkAssert(ci == 0, "Only global pointer are allowed on _stack!");
123
+ set_var<Scope::GLOBAL>(
124
+ name, get_var<Scope::GLOBAL>(name)->redefine(val, _globals->lists()), true
125
+ );
126
+ } else {
127
+ _stack.set(variableName, src->redefine(val, _globals->lists()));
128
+ }
129
+ } else {
130
+ _stack.set(variableName, val);
131
+ }
132
+ }
133
+ }
134
+
135
+ template<>
136
+ void runner_impl::set_var<runner_impl::Scope::NONE>(
137
+ hash_t variableName, const value& val, bool is_redef
138
+ )
139
+ {
140
+ inkAssert(is_redef, "define set scopeless variables!");
141
+ if (_stack.get(variableName)) {
142
+ return set_var<Scope::LOCAL>(variableName, val, is_redef);
143
+ } else {
144
+ return set_var<Scope::GLOBAL>(variableName, val, is_redef);
145
+ }
146
+ }
147
+
148
+ template<typename T>
149
+ inline T runner_impl::read()
150
+ {
151
+ using header = ink::internal::header;
152
+ // Sanity
153
+ inkAssert(_ptr + sizeof(T) <= _story->end(), "Unexpected EOF in Ink execution");
154
+
155
+ // Read memory
156
+ T val = *( const T* ) _ptr;
157
+ if (_story->get_header().endien == header::endian_types::differ) {
158
+ val = header::swap_bytes(val);
159
+ }
160
+
161
+ // Advance ip
162
+ _ptr += sizeof(T);
163
+
164
+ // Return
165
+ return val;
166
+ }
167
+
168
+ template<>
169
+ inline const char* runner_impl::read()
170
+ {
171
+ offset_t str = read<offset_t>();
172
+ return _story->string(str);
173
+ }
174
+
175
+ choice& runner_impl::add_choice()
176
+ {
177
+ inkAssert(
178
+ config::maxChoices < 0 || _choices.size() < static_cast<size_t>(config::maxChoices),
179
+ "Ran out of choice storage!"
180
+ );
181
+ return _choices.push();
182
+ }
183
+
184
+ void runner_impl::clear_choices()
185
+ {
186
+ // TODO: Garbage collection? ? which garbage ?
187
+ _fallback_choice = nullopt;
188
+ _choices.clear();
189
+ }
190
+
191
+ void runner_impl::clear_tags()
192
+ {
193
+ _tags.clear();
194
+ _choice_tags_begin = -1;
195
+ }
196
+
197
+ void runner_impl::jump(ip_t dest, bool record_visits)
198
+ {
199
+ // Optimization: if we are _is_falling, then we can
200
+ // _should be_ able to safely assume that there is nothing to do here. A falling
201
+ // divert should only be taking us from a container to that same container's end point
202
+ // without entering any other containers
203
+ // OR IF if target is same position do nothing
204
+ // could happend if jumping to and of an unnamed container
205
+ if (dest == _ptr) {
206
+ _ptr = dest;
207
+ return;
208
+ }
209
+
210
+ const uint32_t* iter = nullptr;
211
+ container_t id;
212
+ ip_t offset = nullptr;
213
+ size_t comm_end;
214
+ bool reversed = _ptr > dest;
215
+
216
+ if (reversed) {
217
+ comm_end = 0;
218
+ iter = nullptr;
219
+ const ContainerData* old_iter = nullptr;
220
+ const uint32_t* last_comm_iter = nullptr;
221
+ _container.rev_iter(old_iter);
222
+
223
+ // find commen part of old and new stack
224
+ while (_story->iterate_containers(iter, id, offset)) {
225
+ if (old_iter == nullptr || offset >= dest) {
226
+ break;
227
+ }
228
+ if (old_iter != nullptr && id == old_iter->id) {
229
+ last_comm_iter = iter;
230
+ _container.rev_iter(old_iter);
231
+ ++comm_end;
232
+ }
233
+ }
234
+
235
+ // clear old part from stack
236
+ while (_container.size() > comm_end) {
237
+ _container.pop();
238
+ }
239
+ iter = last_comm_iter;
240
+
241
+ } else {
242
+ iter = nullptr;
243
+ comm_end = _container.size();
244
+ // go to current possition in container list
245
+ while (_story->iterate_containers(iter, id, offset)) {
246
+ if (offset >= _ptr) {
247
+ break;
248
+ }
249
+ }
250
+ _story->iterate_containers(iter, id, offset, true);
251
+ }
252
+
253
+ // move to destination and update container stack on the go
254
+ while (_story->iterate_containers(iter, id, offset)) {
255
+ if (offset >= dest) {
256
+ break;
257
+ }
258
+ if (_container.empty() || _container.top().id != id) {
259
+ _container.push({.id = id, .offset = offset});
260
+ } else {
261
+ _container.pop();
262
+ if (_container.size() < comm_end) {
263
+ comm_end = _container.size();
264
+ }
265
+ }
266
+ }
267
+ _ptr = dest;
268
+
269
+ // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container
270
+ // it will get visited in the next step
271
+ if (offset == dest && static_cast<Command>(offset[0]) == Command::START_CONTAINER_MARKER) {
272
+ _ptr += 6;
273
+ _container.push({.id = id, .offset = offset});
274
+ if (reversed && comm_end == _container.size() - 1) {
275
+ ++comm_end;
276
+ }
277
+ }
278
+
279
+ // iff all container (until now) are entered at first position
280
+ bool allEnteredAtStart = true;
281
+ ip_t child_position = dest;
282
+ if (record_visits) {
283
+ const ContainerData* iData = nullptr;
284
+ size_t level = _container.size();
285
+ while (_container.iter(iData)
286
+ && (level > comm_end
287
+ || _story->container_flag(iData->offset) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)
288
+ ) {
289
+ auto parrent_offset = iData->offset;
290
+ inkAssert(child_position >= parrent_offset, "Container stack order is broken");
291
+ // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed
292
+ // subcontainers first child check if child_positino is the first child of current container
293
+ allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6);
294
+ child_position = parrent_offset;
295
+ _globals->visit(iData->id, allEnteredAtStart);
296
+ }
297
+ }
298
+ }
299
+
300
+ template<frame_type type>
301
+ void runner_impl::start_frame(uint32_t target)
302
+ {
303
+ if constexpr (type == frame_type::function) {
304
+ // add a function start marker
305
+ _output << values::func_start;
306
+ }
307
+ // Push next address onto the callstack
308
+ {
309
+ size_t address = _ptr - _story->instructions();
310
+ _stack.push_frame<type>(address, _evaluation_mode);
311
+ _ref_stack.push_frame<type>(address, _evaluation_mode);
312
+ }
313
+ _evaluation_mode = false; // unset eval mode when enter function or tunnel
314
+
315
+ // Do the jump
316
+ inkAssert(_story->instructions() + target < _story->end(), "Diverting past end of story data!");
317
+ jump(_story->instructions() + target, true);
318
+ }
319
+
320
+ frame_type runner_impl::execute_return()
321
+ {
322
+ // Pop the callstack
323
+ _ref_stack.fetch_values(_stack);
324
+ frame_type type;
325
+ offset_t offset = _stack.pop_frame(&type, _evaluation_mode);
326
+ _ref_stack.push_values(_stack);
327
+ {
328
+ frame_type t;
329
+ bool eval;
330
+ // TODO: write all refs to new frame
331
+ offset_t o = _ref_stack.pop_frame(&t, eval);
332
+ inkAssert(
333
+ o == offset && t == type && eval == _evaluation_mode,
334
+ "_ref_stack and _stack should be in frame sync!"
335
+ );
336
+ }
337
+
338
+ // SPECIAL: On function, do a trim
339
+ if (type == frame_type::function) {
340
+ _output << values::func_end;
341
+ } else if (type == frame_type::tunnel) {
342
+ // if we return but there is a divert target on top of
343
+ // the evaluation stack, we should follow this instead
344
+ // inkproof: I060
345
+ if (! _eval.is_empty() && _eval.top().type() == value_type::divert) {
346
+ start_frame<frame_type::tunnel>(_eval.pop().get<value_type::divert>());
347
+ return type;
348
+ }
349
+ }
350
+
351
+
352
+ // Jump to the old offset
353
+ inkAssert(
354
+ _story->instructions() + offset < _story->end(),
355
+ "Callstack return is outside bounds of story!"
356
+ );
357
+ jump(_story->instructions() + offset, false);
358
+
359
+ // Return frame type
360
+ return type;
361
+ }
362
+
363
+ runner_impl::runner_impl(const story_impl* data, globals global)
364
+ : _story(data)
365
+ , _globals(global.cast<globals_impl>())
366
+ , _operations(
367
+ global.cast<globals_impl>()->strings(), global.cast<globals_impl>()->lists(), _rng,
368
+ *global.cast<globals_impl>(), *data, static_cast<const runner_interface&>(*this)
369
+ )
370
+ , _backup(nullptr)
371
+ , _done(nullptr)
372
+ , _choices()
373
+ , _container(ContainerData{})
374
+ , _rng(time(NULL))
375
+ {
376
+ _ptr = _story->instructions();
377
+ _evaluation_mode = false;
378
+ _choice_tags_begin = -1;
379
+
380
+ // register with globals
381
+ _globals->add_runner(this);
382
+ if (_globals->lists()) {
383
+ _output.set_list_meta(_globals->lists());
384
+ }
385
+
386
+ // initialize globals if necessary
387
+ if (! _globals->are_globals_initialized()) {
388
+ _globals->initialize_globals(this);
389
+
390
+ // Set us back to the beginning of the story
391
+ reset();
392
+ _ptr = _story->instructions();
393
+ }
394
+ }
395
+
396
+ runner_impl::~runner_impl()
397
+ {
398
+ // unregister with globals
399
+ if (_globals.is_valid()) {
400
+ _globals->remove_runner(this);
401
+ }
402
+ }
403
+
404
+ #ifdef INK_ENABLE_STL
405
+ std::string runner_impl::getline()
406
+ {
407
+ advance_line();
408
+ std::string result = _output.get();
409
+ if (! has_choices() && _fallback_choice) {
410
+ choose(~0);
411
+ }
412
+ // Return result
413
+ inkAssert(_output.is_empty(), "Output should be empty after getline!");
414
+ return result;
415
+ }
416
+
417
+ void runner_impl::getline(std::ostream& out)
418
+ {
419
+ // Advance interpreter one line
420
+ advance_line();
421
+ // Write into out
422
+ out << _output;
423
+ if (! has_choices() && _fallback_choice) {
424
+ choose(~0);
425
+ }
426
+
427
+ // Make sure we read everything
428
+ inkAssert(_output.is_empty(), "Output should be empty after getline!");
429
+ }
430
+
431
+ std::string runner_impl::getall()
432
+ {
433
+ // Advance interpreter until we're stopped
434
+ std::stringstream str;
435
+ while (can_continue()) {
436
+ getline(str);
437
+ }
438
+
439
+ // Read output into std::string
440
+
441
+ // Return result
442
+ inkAssert(_output.is_empty(), "Output should be empty after getall!");
443
+ return str.str();
444
+ }
445
+
446
+ void runner_impl::getall(std::ostream& out)
447
+ {
448
+ // Advance interpreter until we're stopped
449
+ while (can_continue()) {
450
+ advance_line();
451
+ // Send output into stream
452
+ out << _output;
453
+ }
454
+ // Return result
455
+ inkAssert(_output.is_empty(), "Output should be empty after getall!");
456
+ }
457
+
458
+ #endif
459
+ #ifdef INK_ENABLE_UNREAL
460
+ FString runner_impl::getline()
461
+ {
462
+ clear_tags();
463
+ advance_line();
464
+ FString result(ANSI_TO_TCHAR(_output.get_alloc(_globals->strings(), _globals->lists())));
465
+
466
+ if (! has_choices() && _fallback_choice) {
467
+ choose(~0);
468
+ }
469
+ // Return result
470
+ inkAssert(_output.is_empty(), "Output should be empty after getline!");
471
+ return result;
472
+ }
473
+ #endif
474
+
475
+ void runner_impl::advance_line()
476
+ {
477
+ // Step while we still have instructions to execute
478
+ while (_ptr != nullptr) {
479
+ // Stop if we hit a new line
480
+ if (line_step()) {
481
+ break;
482
+ }
483
+ }
484
+
485
+ // can be in save state becaues of choice
486
+ // Garbage collection TODO: How often do we want to do this?
487
+ if (_saved) {
488
+ restore();
489
+ }
490
+ _globals->gc();
491
+ if (_output.saved()) {
492
+ _output.restore();
493
+ }
494
+ }
495
+
496
+ bool runner_impl::can_continue() const { return _ptr != nullptr && ! has_choices(); }
497
+
498
+ void runner_impl::choose(size_t index)
499
+ {
500
+ if (has_choices()) {
501
+ inkAssert(index < _choices.size(), "Choice index out of range");
502
+ }
503
+ _globals->turn();
504
+ // Get the choice
505
+ const auto& c = has_choices() ? _choices[index] : _fallback_choice.value();
506
+
507
+ // Get its thread
508
+ thread_t choiceThread = c._thread;
509
+
510
+ // Figure out where our previous pointer was for that thread
511
+ ip_t prev = nullptr;
512
+ if (choiceThread == ~0) {
513
+ prev = _done;
514
+ } else {
515
+ prev = _threads.get(choiceThread);
516
+ }
517
+
518
+ // Make sure we have a previous pointer
519
+ inkAssert(prev != nullptr, "No 'done' point recorded before finishing choice output");
520
+
521
+ // Move to the previous pointer so we track our movements correctly
522
+ jump(prev, false);
523
+ _done = nullptr;
524
+
525
+ // Collapse callstacks to the correct thread
526
+ _stack.collapse_to_thread(choiceThread);
527
+ _ref_stack.collapse_to_thread(choiceThread);
528
+ _threads.clear();
529
+
530
+ // Jump to destination and clear choice list
531
+ jump(_story->instructions() + c.path(), true);
532
+ clear_choices();
533
+ clear_tags();
534
+ }
535
+
536
+ void runner_impl::getline_silent()
537
+ {
538
+ // advance and clear output stream
539
+ advance_line();
540
+ _output.clear();
541
+ }
542
+
543
+ bool runner_impl::has_tags() const { return num_tags() > 0; }
544
+
545
+ size_t runner_impl::num_tags() const
546
+ {
547
+ return _choice_tags_begin < 0 ? _tags.size() : _choice_tags_begin;
548
+ }
549
+
550
+ const char* runner_impl::get_tag(size_t index) const
551
+ {
552
+ inkAssert(index < _tags.size(), "Tag index exceeds _num_tags");
553
+ return _tags[index];
554
+ }
555
+
556
+ snapshot* runner_impl::create_snapshot() const { return _globals->create_snapshot(); }
557
+
558
+ size_t runner_impl::snap(unsigned char* data, snapper& snapper) const
559
+ {
560
+ unsigned char* ptr = data;
561
+ bool should_write = data != nullptr;
562
+ snapper.current_runner_tags = _tags[0].ptr();
563
+ std::uintptr_t offset = _ptr != nullptr ? _ptr - _story->instructions() : 0;
564
+ ptr = snap_write(ptr, offset, should_write);
565
+ offset = _backup - _story->instructions();
566
+ ptr = snap_write(ptr, offset, should_write);
567
+ offset = _done - _story->instructions();
568
+ ptr = snap_write(ptr, offset, should_write);
569
+ ptr = snap_write(ptr, _rng.get_state(), should_write);
570
+ ptr = snap_write(ptr, _evaluation_mode, should_write);
571
+ ptr = snap_write(ptr, _string_mode, should_write);
572
+ ptr = snap_write(ptr, _saved_evaluation_mode, should_write);
573
+ ptr = snap_write(ptr, _saved, should_write);
574
+ ptr = snap_write(ptr, _is_falling, should_write);
575
+ ptr += _output.snap(data ? ptr : nullptr, snapper);
576
+ ptr += _stack.snap(data ? ptr : nullptr, snapper);
577
+ ptr += _ref_stack.snap(data ? ptr : nullptr, snapper);
578
+ ptr += _eval.snap(data ? ptr : nullptr, snapper);
579
+ ptr = snap_write(ptr, _choice_tags_begin, should_write);
580
+ ptr += _tags.snap(data ? ptr : nullptr, snapper);
581
+ ptr += _container.snap(data ? ptr : nullptr, snapper);
582
+ ptr += _threads.snap(data ? ptr : nullptr, snapper);
583
+ ptr = snap_write(ptr, _fallback_choice.has_value(), should_write);
584
+ if (_fallback_choice) {
585
+ ptr += _fallback_choice.value().snap(data ? ptr : nullptr, snapper);
586
+ }
587
+ ptr += _choices.snap(data ? ptr : nullptr, snapper);
588
+ return ptr - data;
589
+ }
590
+
591
+ const unsigned char* runner_impl::snap_load(const unsigned char* data, loader& loader)
592
+ {
593
+ auto ptr = data;
594
+ std::uintptr_t offset;
595
+ ptr = snap_read(ptr, offset);
596
+ _ptr = offset == 0 ? nullptr : _story->instructions() + offset;
597
+ ptr = snap_read(ptr, offset);
598
+ _backup = _story->instructions() + offset;
599
+ ptr = snap_read(ptr, offset);
600
+ _done = _story->instructions() + offset;
601
+ int32_t seed;
602
+ ptr = snap_read(ptr, seed);
603
+ _rng.srand(seed);
604
+ ptr = snap_read(ptr, _evaluation_mode);
605
+ ptr = snap_read(ptr, _string_mode);
606
+ ptr = snap_read(ptr, _saved_evaluation_mode);
607
+ ptr = snap_read(ptr, _saved);
608
+ ptr = snap_read(ptr, _is_falling);
609
+ ptr = _output.snap_load(ptr, loader);
610
+ ptr = _stack.snap_load(ptr, loader);
611
+ ptr = _ref_stack.snap_load(ptr, loader);
612
+ ptr = _eval.snap_load(ptr, loader);
613
+ int choice_tags_begin;
614
+ ptr = snap_read(ptr, choice_tags_begin);
615
+ _choice_tags_begin = choice_tags_begin;
616
+ ptr = _tags.snap_load(ptr, loader);
617
+ loader.current_runner_tags = _tags[0].ptr();
618
+ ptr = _container.snap_load(ptr, loader);
619
+ ptr = _threads.snap_load(ptr, loader);
620
+ bool has_fallback_choice;
621
+ ptr = snap_read(ptr, has_fallback_choice);
622
+ _fallback_choice = nullopt;
623
+ if (has_fallback_choice) {
624
+ _fallback_choice.emplace();
625
+ ptr = _fallback_choice.value().snap_load(ptr, loader);
626
+ }
627
+ ptr = _choices.snap_load(ptr, loader);
628
+ return ptr;
629
+ }
630
+
631
+ #ifdef INK_ENABLE_CSTD
632
+ const char* runner_impl::getline_alloc()
633
+ {
634
+ advance_line();
635
+ const char* res = _output.get_alloc(_globals->strings(), _globals->lists());
636
+ if (! has_choices() && _fallback_choice) {
637
+ choose(~0);
638
+ }
639
+ inkAssert(_output.is_empty(), "Output should be empty after getline!");
640
+ return res;
641
+ }
642
+ #endif
643
+
644
+ bool runner_impl::move_to(hash_t path)
645
+ {
646
+ // find the path
647
+ ip_t destination = _story->find_offset_for(path);
648
+ if (destination == nullptr) {
649
+ // TODO: Error state?
650
+ return false;
651
+ }
652
+
653
+ // Clear state and move to destination
654
+ reset();
655
+ _ptr = _story->instructions();
656
+ jump(destination, false);
657
+
658
+ return true;
659
+ }
660
+
661
+ void runner_impl::internal_bind(hash_t name, internal::function_base* function)
662
+ {
663
+ _functions.add(name, function);
664
+ }
665
+
666
+ runner_impl::change_type runner_impl::detect_change() const
667
+ {
668
+ // Check if the old newline is still present (hasn't been glu'd) and
669
+ // if there is new text (non-whitespace) in the stream since saving
670
+ bool stillHasNewline = _output.saved_ends_with(value_type::newline);
671
+ bool hasAddedNewText = _output.text_past_save() || _tags.last_size() < num_tags();
672
+
673
+ // Newline is still there and there's no new text
674
+ if (stillHasNewline && ! hasAddedNewText) {
675
+ return change_type::no_change;
676
+ }
677
+
678
+ // If the newline is gone, we got glue'd. Continue as if we never had that newline
679
+ if (! stillHasNewline) {
680
+ return change_type::newline_removed;
681
+ }
682
+
683
+ // If there's new text content, we went too far
684
+ if (hasAddedNewText) {
685
+ return change_type::extended_past_newline;
686
+ }
687
+
688
+ inkFail("Invalid change detction. Never should be here!");
689
+ return change_type::no_change;
690
+ }
691
+
692
+ bool runner_impl::line_step()
693
+ {
694
+ // Step the interpreter
695
+ step();
696
+
697
+ // If we're not within string evaluation
698
+ if (! _output.has_marker()) {
699
+ // If we have a saved state after a previous newline
700
+ // don't do this if we behind choice
701
+ if (_saved && ! has_choices() && ! _fallback_choice) {
702
+ // Check for changes in the output stream
703
+ switch (detect_change()) {
704
+ case change_type::extended_past_newline:
705
+ // We've gone too far. Restore to before we moved past the newline and return that we are
706
+ // done
707
+ restore();
708
+ return true;
709
+ case change_type::newline_removed:
710
+ // Newline was removed. Proceed as if we never hit it
711
+ forget();
712
+ break;
713
+ case change_type::no_change: break;
714
+ }
715
+ }
716
+
717
+ // If we're on a newline
718
+ if (_output.ends_with(value_type::newline)) {
719
+ // Unless we are out of content, we are going to try
720
+ // to continue a little further. This is to check for
721
+ // glue (which means there is potentially more content
722
+ // in this line) OR for non-text content such as choices.
723
+ if (_ptr != nullptr) {
724
+ // Save a snapshot of the current runtime state so we
725
+ // can return here if we end up hitting a new line
726
+ // forget();
727
+ if (! _saved) {
728
+ save();
729
+ }
730
+ }
731
+ // Otherwise, make sure we don't have any snapshots hanging around
732
+ // expect we are in choice handleing
733
+ else if (! has_choices() && ! _fallback_choice) {
734
+ forget();
735
+ } else {
736
+ _output.forget();
737
+ }
738
+ }
739
+ }
740
+
741
+ return false;
742
+ }
743
+
744
+ void runner_impl::step()
745
+ {
746
+ #ifndef INK_ENABLE_UNREAL
747
+ try
748
+ #endif
749
+ {
750
+ inkAssert(_ptr != nullptr, "Can not step! Do not have a valid pointer");
751
+
752
+ // Load current command
753
+ Command cmd = read<Command>();
754
+ CommandFlag flag = read<CommandFlag>();
755
+
756
+ // If we're falling and we hit a non-fallthrough command, stop the fall.
757
+ if (_is_falling
758
+ && ! (
759
+ (cmd == Command::DIVERT && flag & CommandFlag::DIVERT_IS_FALLTHROUGH)
760
+ || cmd == Command::END_CONTAINER_MARKER
761
+ )) {
762
+ _is_falling = false;
763
+ set_done_ptr(nullptr);
764
+ }
765
+ if (cmd >= Command::OP_BEGIN && cmd < Command::OP_END) {
766
+ _operations(cmd, _eval);
767
+ } else {
768
+ switch (cmd) {
769
+ // == Value Commands ==
770
+ case Command::STR: {
771
+ const char* str = read<const char*>();
772
+ if (_evaluation_mode) {
773
+ _eval.push(value{}.set<value_type::string>(str));
774
+ } else {
775
+ _output << value{}.set<value_type::string>(str);
776
+ }
777
+ } break;
778
+ case Command::INT: {
779
+ int val = read<int>();
780
+ if (_evaluation_mode) {
781
+ _eval.push(value{}.set<value_type::int32>(val));
782
+ }
783
+ // TEST-CASE B006 don't print integers
784
+ } break;
785
+ case Command::BOOL: {
786
+ bool val = read<int>() ? true : false;
787
+ if (_evaluation_mode) {
788
+ _eval.push(value{}.set<value_type::boolean>(val));
789
+ } else {
790
+ _output << value{}.set<value_type::boolean>(val);
791
+ }
792
+ } break;
793
+ case Command::FLOAT: {
794
+ float val = read<float>();
795
+ if (_evaluation_mode) {
796
+ _eval.push(value{}.set<value_type::float32>(val));
797
+ }
798
+ // TEST-CASE B006 don't print floats
799
+ } break;
800
+ case Command::VALUE_POINTER: {
801
+ hash_t val = read<hash_t>();
802
+ if (_evaluation_mode) {
803
+ _eval.push(value{}.set<value_type::value_pointer>(val, static_cast<char>(flag) - 1));
804
+ } else {
805
+ inkFail("never conciderd what should happend here! (value pointer print)");
806
+ }
807
+ } break;
808
+ case Command::LIST: {
809
+ list_table::list list(read<int>());
810
+ if (_evaluation_mode) {
811
+ _eval.push(value{}.set<value_type::list>(list));
812
+ } else {
813
+ char* str = _globals->strings().create(_globals->lists().stringLen(list) + 1);
814
+ _globals->lists().toString(str, list)[0] = 0;
815
+ _output << value{}.set<value_type::string>(str);
816
+ }
817
+ } break;
818
+ case Command::DIVERT_VAL: {
819
+ inkAssert(_evaluation_mode, "Can not push divert value into the output stream!");
820
+
821
+ // Push the divert target onto the stack
822
+ uint32_t target = read<uint32_t>();
823
+ _eval.push(value{}.set<value_type::divert>(target));
824
+ } break;
825
+ case Command::NEWLINE: {
826
+ if (_evaluation_mode) {
827
+ _eval.push(values::newline);
828
+ } else {
829
+ if (! _output.ends_with(value_type::newline)) {
830
+ _output << values::newline;
831
+ }
832
+ }
833
+ } break;
834
+ case Command::GLUE: {
835
+ if (_evaluation_mode) {
836
+ _eval.push(values::glue);
837
+ } else {
838
+ _output << values::glue;
839
+ }
840
+ } break;
841
+ case Command::VOID: {
842
+ if (_evaluation_mode) {
843
+ _eval.push(values::null); // TODO: void type?
844
+ }
845
+ } break;
846
+
847
+ // == Divert commands
848
+ case Command::DIVERT: {
849
+ // Find divert address
850
+ uint32_t target = read<uint32_t>();
851
+
852
+ // Check for condition
853
+ if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) {
854
+ break;
855
+ }
856
+
857
+ // SPECIAL: Fallthrough divert. We're starting to fall out of containers
858
+ if (flag & CommandFlag::DIVERT_IS_FALLTHROUGH && ! _is_falling) {
859
+ // Record the position of the instruction pointer at the first fallthrough.
860
+ // We'll use this if we run out of content and hit an implied "done" to restore
861
+ // our position when a choice is chosen. See ::choose
862
+ set_done_ptr(_ptr);
863
+ _is_falling = true;
864
+ }
865
+
866
+ // If we're falling out of the story, then we're hitting an implied done
867
+ if (_is_falling && _story->instructions() + target == _story->end()) {
868
+ // Wait! We may be returning from a function!
869
+ frame_type type;
870
+ if (_stack.has_frame(&type)
871
+ && type == frame_type::function) // implicit return is only for functions
872
+ {
873
+ // push null and return
874
+ _eval.push(values::null);
875
+
876
+ // HACK
877
+ _ptr += sizeof(Command) + sizeof(CommandFlag);
878
+ execute_return();
879
+ } else {
880
+ on_done(false);
881
+ }
882
+ break;
883
+ }
884
+
885
+ // Do the jump
886
+ inkAssert(
887
+ _story->instructions() + target < _story->end(), "Diverting past end of story data!"
888
+ );
889
+ jump(_story->instructions() + target, true);
890
+ } break;
891
+ case Command::DIVERT_TO_VARIABLE: {
892
+ // Get variable value
893
+ hash_t variable = read<hash_t>();
894
+
895
+ // Check for condition
896
+ if (flag & CommandFlag::DIVERT_HAS_CONDITION && ! _eval.pop().truthy(_globals->lists())) {
897
+ break;
898
+ }
899
+
900
+ const value* val = get_var(variable);
901
+ inkAssert(val, "Jump destiniation needs to be defined!");
902
+
903
+ // Move to location
904
+ jump(_story->instructions() + val->get<value_type::divert>(), true);
905
+ inkAssert(_ptr < _story->end(), "Diverted past end of story data!");
906
+ } break;
907
+
908
+ // == Terminal commands
909
+ case Command::DONE: on_done(true); break;
910
+
911
+ case Command::END: _ptr = nullptr; break;
912
+
913
+ // == Tunneling
914
+ case Command::TUNNEL: {
915
+ uint32_t target;
916
+ // Find divert address
917
+ if (flag & CommandFlag::TUNNEL_TO_VARIABLE) {
918
+ hash_t var_name = read<hash_t>();
919
+ const value* val = get_var(var_name);
920
+ inkAssert(val != nullptr, "Variable containing tunnel target could not be found!");
921
+ target = val->get<value_type::divert>();
922
+ } else {
923
+ target = read<uint32_t>();
924
+ }
925
+ start_frame<frame_type::tunnel>(target);
926
+ } break;
927
+ case Command::FUNCTION: {
928
+ uint32_t target;
929
+ // Find divert address
930
+ if (flag & CommandFlag::FUNCTION_TO_VARIABLE) {
931
+ hash_t var_name = read<hash_t>();
932
+ const value* val = get_var(var_name);
933
+ inkAssert(val != nullptr, "Varibale containing function could not be found!");
934
+ target = val->get<value_type::divert>();
935
+ } else {
936
+ target = read<uint32_t>();
937
+ }
938
+ if (! (flag & CommandFlag::FALLBACK_FUNCTION)) {
939
+ start_frame<frame_type::function>(target);
940
+ } else {
941
+ inkAssert(! _eval.is_empty(), "fallback function but no function call before?");
942
+ if (_eval.top_value().type() == value_type::ex_fn_not_found) {
943
+ _eval.pop();
944
+ inkAssert(
945
+ target != 0,
946
+ "Exetrnal function was not binded, and no fallback function provided!"
947
+ );
948
+ start_frame<frame_type::function>(target);
949
+ }
950
+ }
951
+ } break;
952
+ case Command::TUNNEL_RETURN:
953
+ case Command::FUNCTION_RETURN: {
954
+ execute_return();
955
+ } break;
956
+
957
+ case Command::THREAD: {
958
+ // Push a thread frame so we can return easily
959
+ // TODO We push ahead of a single divert. Is that correct in all cases....?????
960
+ auto returnTo = _ptr + CommandSize<uint32_t>;
961
+ _stack.push_frame<frame_type::thread>(
962
+ returnTo - _story->instructions(), _evaluation_mode
963
+ );
964
+ _ref_stack.push_frame<frame_type::thread>(
965
+ returnTo - _story->instructions(), _evaluation_mode
966
+ );
967
+
968
+ // Fork a new thread on the callstack
969
+ thread_t thread = _stack.fork_thread();
970
+ {
971
+ thread_t t = _ref_stack.fork_thread();
972
+ inkAssert(t == thread, "ref_stack and stack should be in sync!");
973
+ }
974
+
975
+ // Push that thread onto our thread stack
976
+ _threads.push(thread);
977
+ } break;
978
+
979
+ // == set temporärie variable
980
+ case Command::DEFINE_TEMP: {
981
+ hash_t variableName = read<hash_t>();
982
+ bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE;
983
+
984
+ // Get the top value and put it into the variable
985
+ value v = _eval.pop();
986
+ set_var<Scope::LOCAL>(variableName, v, is_redef);
987
+ } break;
988
+
989
+ case Command::SET_VARIABLE: {
990
+ hash_t variableName = read<hash_t>();
991
+
992
+ // Check if it's a redefinition (not yet used, seems important for pointers later?)
993
+ bool is_redef = flag & CommandFlag::ASSIGNMENT_IS_REDEFINE;
994
+
995
+ // If not, we're setting a global (temporary variables are explicitely defined as such,
996
+ // where globals are defined using SET_VARIABLE).
997
+ value val = _eval.pop();
998
+ if (is_redef) {
999
+ set_var(variableName, val, is_redef);
1000
+ } else {
1001
+ set_var<Scope::GLOBAL>(variableName, val, is_redef);
1002
+ }
1003
+ } break;
1004
+
1005
+ // == Function calls
1006
+ case Command::CALL_EXTERNAL: {
1007
+ // Read function name
1008
+ hash_t functionName = read<hash_t>();
1009
+
1010
+ // Interpret flag as argument count
1011
+ int numArguments = ( int ) flag;
1012
+
1013
+ // find and execute. will automatically push a valid if applicable
1014
+ auto* fn = _functions.find(functionName);
1015
+ if (fn == nullptr) {
1016
+ _eval.push(values::ex_fn_not_found);
1017
+ } else if (_output.saved() && _output.saved_ends_with(value_type::newline) && ! fn->lookaheadSafe()) {
1018
+ // TODO: seperate token?
1019
+ _output.append(values::null);
1020
+ } else {
1021
+ fn->call(&_eval, numArguments, _globals->strings(), _globals->lists());
1022
+ }
1023
+ } break;
1024
+
1025
+ // == Evaluation stack
1026
+ case Command::START_EVAL: _evaluation_mode = true; break;
1027
+ case Command::END_EVAL:
1028
+ _evaluation_mode = false;
1029
+
1030
+ // Assert stack is empty? Is that necessary?
1031
+ break;
1032
+ case Command::OUTPUT: {
1033
+ value v = _eval.pop();
1034
+ _output << v;
1035
+ } break;
1036
+ case Command::POP: _eval.pop(); break;
1037
+ case Command::DUPLICATE: _eval.push(_eval.top_value()); break;
1038
+ case Command::PUSH_VARIABLE_VALUE: {
1039
+ // Try to find in local stack
1040
+ hash_t variableName = read<hash_t>();
1041
+ const value* val = get_var(variableName);
1042
+
1043
+ inkAssert(val != nullptr, "Could not find variable!");
1044
+ _eval.push(*val);
1045
+ break;
1046
+ }
1047
+ case Command::START_STR: {
1048
+ inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!");
1049
+ _string_mode = true;
1050
+ _evaluation_mode = false;
1051
+ _output << values::marker;
1052
+ } break;
1053
+ case Command::END_STR: {
1054
+ // TODO: Assert we really had a marker on there?
1055
+ inkAssert(! _evaluation_mode, "Must be in evaluation mode");
1056
+ _string_mode = false;
1057
+ _evaluation_mode = true;
1058
+
1059
+ // Load value from output stream
1060
+ // Push onto stack
1061
+ _eval.push(value{}.set<value_type::string>(
1062
+ _output.get_alloc<false>(_globals->strings(), _globals->lists())
1063
+ ));
1064
+ } break;
1065
+
1066
+ case Command::START_TAG: {
1067
+ _output << values::marker;
1068
+ } break;
1069
+
1070
+ case Command::END_TAG: {
1071
+ auto tag = _output.get_alloc<true>(_globals->strings(), _globals->lists());
1072
+ if (_string_mode && _choice_tags_begin < 0) {
1073
+ _choice_tags_begin = _tags.size();
1074
+ }
1075
+ _tags.push() = tag;
1076
+ } break;
1077
+
1078
+ // == Choice commands
1079
+ case Command::CHOICE: {
1080
+ // Read path
1081
+ uint32_t path = read<uint32_t>();
1082
+
1083
+ // If we're a once only choice, make sure our destination hasn't
1084
+ // been visited
1085
+ if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) {
1086
+ // Need to convert offset to container index
1087
+ container_t destination = -1;
1088
+ if (_story->get_container_id(_story->instructions() + path, destination)) {
1089
+ // Ignore the choice if we've visited the destination before
1090
+ if (_globals->visits(destination) > 0) {
1091
+ break;
1092
+ }
1093
+ } else {
1094
+ inkAssert(false, "Destination for choice block does not have counting flags.");
1095
+ }
1096
+ }
1097
+
1098
+ // Choice is conditional
1099
+ if (flag & CommandFlag::CHOICE_HAS_CONDITION) {
1100
+ // Only show if the top of the eval stack is 'truthy'
1101
+ if (! _eval.pop().truthy(_globals->lists())) {
1102
+ break;
1103
+ }
1104
+ }
1105
+
1106
+ // Use a marker to start compiling the choice text
1107
+ _output << values::marker;
1108
+ value stack[2];
1109
+ int sc = 0;
1110
+
1111
+ if (flag & CommandFlag::CHOICE_HAS_START_CONTENT) {
1112
+ stack[sc++] = _eval.pop();
1113
+ }
1114
+ if (flag & CommandFlag::CHOICE_HAS_CHOICE_ONLY_CONTENT) {
1115
+ stack[sc++] = _eval.pop();
1116
+ }
1117
+ for (; sc; --sc) {
1118
+ _output << stack[sc - 1];
1119
+ }
1120
+
1121
+ // fetch relevant tags
1122
+ const snap_tag* tags = nullptr;
1123
+ if (_choice_tags_begin >= 0 && _tags[_tags.size() - 1] != nullptr) {
1124
+ for (tags = _tags.end() - 1;
1125
+ *(tags - 1) != nullptr && (tags - _tags.begin()) > _choice_tags_begin; --tags)
1126
+ ;
1127
+ _tags.push() = nullptr;
1128
+ }
1129
+
1130
+ // Create choice and record it
1131
+ if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) {
1132
+ _fallback_choice.emplace();
1133
+ _fallback_choice.value().setup(
1134
+ _output, _globals->strings(), _globals->lists(), _choices.size(), path,
1135
+ current_thread(), tags->ptr()
1136
+ );
1137
+ } else {
1138
+ add_choice().setup(
1139
+ _output, _globals->strings(), _globals->lists(), _choices.size(), path,
1140
+ current_thread(), tags->ptr()
1141
+ );
1142
+ }
1143
+ // save stack at last choice
1144
+ if (_saved) {
1145
+ forget();
1146
+ }
1147
+ save();
1148
+ } break;
1149
+ case Command::START_CONTAINER_MARKER: {
1150
+ // Keep track of current container
1151
+ auto index = read<uint32_t>();
1152
+ // offset points to command, command has size 6
1153
+ _container.push({.id = index, .offset = _ptr - 6});
1154
+
1155
+ // Increment visit count
1156
+ if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS
1157
+ || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) {
1158
+ _globals->visit(_container.top().id, true);
1159
+ }
1160
+
1161
+ } break;
1162
+ case Command::END_CONTAINER_MARKER: {
1163
+ container_t index = read<container_t>();
1164
+
1165
+ inkAssert(_container.top().id == index, "Leaving container we are not in!");
1166
+
1167
+ // Move up out of the current container
1168
+ _container.pop();
1169
+
1170
+ // SPECIAL: If we've popped all containers, then there's an implied
1171
+ // 'done' command or return
1172
+ if (_container.empty()) {
1173
+ _is_falling = false;
1174
+
1175
+ frame_type type;
1176
+ if (! _threads.empty()) {
1177
+ on_done(false);
1178
+ break;
1179
+ } else if (_stack.has_frame(&type) && type == frame_type::function) // implicit return
1180
+ // is only for
1181
+ // functions
1182
+ {
1183
+ // push null and return
1184
+ _eval.push(values::null);
1185
+
1186
+ // HACK
1187
+ _ptr += sizeof(Command) + sizeof(CommandFlag);
1188
+ execute_return();
1189
+ } else if (_ptr == _story->end()) { // check needed, because it colud exist an unnamed
1190
+ // toplevel container (empty named container stack
1191
+ // != empty container stack)
1192
+ on_done(true);
1193
+ }
1194
+ }
1195
+ } break;
1196
+ case Command::VISIT: {
1197
+ // Push the visit count for the current container to the top
1198
+ // is 0-indexed for some reason. idk why but this is what ink expects
1199
+ _eval.push(
1200
+ value{}.set<value_type::int32>(( int ) _globals->visits(_container.top().id) - 1)
1201
+ );
1202
+ } break;
1203
+ case Command::TURN: {
1204
+ _eval.push(value{}.set<value_type::int32>(( int ) _globals->turns()));
1205
+ } break;
1206
+ case Command::SEQUENCE: {
1207
+ // TODO: The C# ink runtime does a bunch of fancy logic
1208
+ // to make sure each element is picked at least once in every
1209
+ // iteration loop. I don't feel like replicating that right now.
1210
+ // So, let's just return a random number and *shrug*
1211
+ int sequenceLength = _eval.pop().get<value_type::int32>();
1212
+ int index = _eval.pop().get<value_type::int32>();
1213
+
1214
+ _eval.push(value{}.set<value_type::int32>(static_cast<int32_t>(_rng.rand(sequenceLength)))
1215
+ );
1216
+ } break;
1217
+ case Command::SEED: {
1218
+ int32_t seed = _eval.pop().get<value_type::int32>();
1219
+ _rng.srand(seed);
1220
+
1221
+ _eval.push(values::null);
1222
+ } break;
1223
+
1224
+ case Command::READ_COUNT: {
1225
+ // Get container index
1226
+ container_t container = read<container_t>();
1227
+
1228
+ // Push the read count for the requested container index
1229
+ _eval.push(value{}.set<value_type::int32>(( int ) _globals->visits(container)));
1230
+ } break;
1231
+ case Command::TAG: {
1232
+ _tags.push() = read<const char*>();
1233
+ } break;
1234
+ default: inkAssert(false, "Unrecognized command!"); break;
1235
+ }
1236
+ }
1237
+
1238
+ }
1239
+ #ifndef INK_ENABLE_UNREAL
1240
+ catch (...) {
1241
+ // Reset our whole state as it's probably corrupt
1242
+ reset();
1243
+ throw;
1244
+ }
1245
+ #endif
1246
+ }
1247
+
1248
+ void runner_impl::on_done(bool setDone)
1249
+ {
1250
+ // If we're in a thread
1251
+ if (! _threads.empty()) {
1252
+ // Get the thread ID of the current thread
1253
+ thread_t completedThreadId = _threads.pop();
1254
+
1255
+ // Push in a complete marker
1256
+ _stack.complete_thread(completedThreadId);
1257
+ _ref_stack.complete_thread(completedThreadId);
1258
+
1259
+ // Go to where the thread started
1260
+ frame_type type = execute_return();
1261
+ inkAssert(
1262
+ type == frame_type::thread,
1263
+ "Expected thread frame marker to hold return to value but none found..."
1264
+ );
1265
+ // if thread ends, move stave point with, else the thread end marker is missing
1266
+ // and we can't collect the other threads
1267
+ if (_saved) {
1268
+ forget();
1269
+ save();
1270
+ }
1271
+ } else {
1272
+ if (setDone) {
1273
+ set_done_ptr(_ptr);
1274
+ }
1275
+ _ptr = nullptr;
1276
+ }
1277
+ }
1278
+
1279
+ void runner_impl::set_done_ptr(ip_t ptr)
1280
+ {
1281
+ thread_t curr = current_thread();
1282
+ if (curr == ~0) {
1283
+ _done = ptr;
1284
+ } else {
1285
+ _threads.set(curr, ptr);
1286
+ }
1287
+ }
1288
+
1289
+ void runner_impl::reset()
1290
+ {
1291
+ _eval.clear();
1292
+ _output.clear();
1293
+ _stack.clear();
1294
+ _ref_stack.clear();
1295
+ _threads.clear();
1296
+ _evaluation_mode = false;
1297
+ _saved = false;
1298
+ _choices.clear();
1299
+ _ptr = nullptr;
1300
+ _done = nullptr;
1301
+ _container.clear();
1302
+ }
1303
+
1304
+ void runner_impl::mark_used(string_table& strings, list_table& lists) const
1305
+ {
1306
+ // Find strings in output and stacks
1307
+ _output.mark_used(strings, lists);
1308
+ _stack.mark_used(strings, lists);
1309
+ // ref_stack has no strings and lists!
1310
+ _eval.mark_used(strings, lists);
1311
+
1312
+ // Take into account tags
1313
+ for (size_t i = 0; i < _tags.size(); ++i) {
1314
+ strings.mark_used(_tags[i]);
1315
+ }
1316
+ // Take into account choice text
1317
+ for (size_t i = 0; i < _choices.size(); i++) {
1318
+ strings.mark_used(_choices[i]._text);
1319
+ }
1320
+ }
1321
+
1322
+ void runner_impl::save()
1323
+ {
1324
+ inkAssert(! _saved, "Runner state already saved");
1325
+
1326
+ _saved = true;
1327
+ _output.save();
1328
+ _stack.save();
1329
+ _ref_stack.save();
1330
+ _backup = _ptr;
1331
+ _container.save();
1332
+ _globals->save();
1333
+ _eval.save();
1334
+ _threads.save();
1335
+ _choices.save();
1336
+ _tags.save();
1337
+ _saved_evaluation_mode = _evaluation_mode;
1338
+
1339
+ // Not doing this anymore. There can be lingering stack entries from function returns
1340
+ // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty");
1341
+ }
1342
+
1343
+ void runner_impl::restore()
1344
+ {
1345
+ inkAssert(_saved, "Can't restore. No runner state saved.");
1346
+ // the output can be restored without the rest
1347
+ if (_output.saved()) {
1348
+ _output.restore();
1349
+ }
1350
+ _stack.restore();
1351
+ _ref_stack.restore();
1352
+ _ptr = _backup;
1353
+ _container.restore();
1354
+ _globals->restore();
1355
+ _eval.restore();
1356
+ _threads.restore();
1357
+ _choices.restore();
1358
+ _tags.restore();
1359
+ _evaluation_mode = _saved_evaluation_mode;
1360
+
1361
+ // Not doing this anymore. There can be lingering stack entries from function returns
1362
+ // inkAssert(_eval.is_empty(), "Can not save interpreter state while eval stack is not empty");
1363
+
1364
+ _saved = false;
1365
+ }
1366
+
1367
+ void runner_impl::forget()
1368
+ {
1369
+ // Do nothing if we haven't saved
1370
+ if (! _saved) {
1371
+ return;
1372
+ }
1373
+
1374
+ _output.forget();
1375
+ _stack.forget();
1376
+ _ref_stack.forget();
1377
+ _container.forget();
1378
+ _globals->forget();
1379
+ _eval.forget();
1380
+ _threads.forget();
1381
+ _choices.forgett();
1382
+ _tags.forgett();
1383
+
1384
+ // Nothing to do for eval stack. It should just stay as it is
1385
+
1386
+ _saved = false;
1387
+ }
1388
+
1389
+ #ifdef INK_ENABLE_STL
1390
+ std::ostream& operator<<(std::ostream& out, runner_impl& in)
1391
+ {
1392
+ in.getline(out);
1393
+ return out;
1394
+ }
1395
+ #endif
1396
+ } // namespace ink::runtime::internal