inkcpp_rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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