diff --git a/inkcpp/choice.cpp b/inkcpp/choice.cpp index 735b0c3d..005bb166 100644 --- a/inkcpp/choice.cpp +++ b/inkcpp/choice.cpp @@ -1,45 +1,48 @@ #include "choice.h" + #include "output.h" -#include "string_utils.h" #include "string_table.h" +#include "string_utils.h" -namespace ink -{ - namespace runtime +namespace ink { +namespace runtime { + choice& choice::setup( internal::basic_stream& in, internal::string_table& strings, internal::list_table& lists, int index, uint32_t path, thread_t thread, const char* const* tags ) { - choice& choice::setup(internal::basic_stream& in, internal::string_table& strings, internal::list_table& lists, int index, uint32_t path, thread_t thread) + char* text = nullptr; + // if we only have one item in our output stream + if ( in.queued() == 2 ) { - char* text = nullptr; - // if we only have one item in our output stream - if (in.queued() == 2) - { - // If it's a string, just grab it. Otherwise, use allocation - const internal::value& data = in.peek(); - switch (data.type()) - { - case internal::value_type::string: - text = strings.duplicate(data.get()); - in.discard(2); - break; - default: - text = in.get_alloc(strings, lists); - } - } - else + // If it's a string, just grab it. Otherwise, use allocation + const internal::value& data = in.peek(); + switch ( data.type() ) { - // Non-string. Must allocate - text = in.get_alloc(strings, lists); + case internal::value_type::string: + text = strings.duplicate( data.get() ); + in.discard( 2 ); + break; + default: + text = in.get_alloc( strings, lists ); } - char* end = text; - while(*end) { ++end; } - end = ink::runtime::internal::clean_string(text, end); - *end = 0; - _text = text; - // Index/path - _index = index; - _path = path; - _thread = thread; - return *this; } + else + { + // Non-string. Must allocate + text = in.get_alloc( strings, lists ); + } + char* end = text; + while ( *end ) + { + ++end; + } + end = ink::runtime::internal::clean_string( text, end ); + *end = 0; + _text = text; + // Index/path + _index = index; + _path = path; + _thread = thread; + _tags = tags; + return *this; } } +} // namespace ink::runtime diff --git a/inkcpp/include/choice.h b/inkcpp/include/choice.h index 00ba5b59..3d9ccf83 100644 --- a/inkcpp/include/choice.h +++ b/inkcpp/include/choice.h @@ -45,13 +45,30 @@ namespace ink const char* text() const { return _text; } choice() : choice(0) {} - choice(int) : _text{nullptr}, _index{~0}, _path{~0u}, _thread{~0u} {} + choice(int) : _tags{nullptr}, _text{nullptr}, _index{~0}, _path{~0u}, _thread{~0u} {} + + bool has_tags() const { return _tags != nullptr; } + size_t num_tags() const + { + size_t i = 0; + if (has_tags()) while ( _tags[i] != nullptr ) + { + ++i; + }; + return i; + } + const char* get_tag(size_t index) const { + return _tags[index]; + } + private: friend class internal::runner_impl; uint32_t path() const { return _path; } - choice& setup(internal::basic_stream&, internal::string_table& strings, internal::list_table& lists, int index, uint32_t path, thread_t thread); + choice& setup( internal::basic_stream&, internal::string_table& strings, internal::list_table& lists, int index, uint32_t path, thread_t thread, const char* const* tags ); + private: + const char* const* _tags; const char* _text; int _index; uint32_t _path; diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 123c4d67..27d89ed8 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -6,6 +6,7 @@ #include "header.h" #include "string_utils.h" #include "snapshot_impl.h" +#include "value.h" namespace ink::runtime { @@ -164,6 +165,7 @@ namespace ink::runtime::internal void runner_impl::clear_tags() { _tags.clear(); + _choice_tags_begin = -1; } void runner_impl::jump(ip_t dest, bool record_visits) @@ -330,6 +332,7 @@ namespace ink::runtime::internal { _ptr = _story->instructions(); _evaluation_mode = false; + _choice_tags_begin = -1; // register with globals _globals->add_runner(this); @@ -526,7 +529,7 @@ namespace ink::runtime::internal size_t runner_impl::num_tags() const { - return _tags.size(); + return _choice_tags_begin < 0 ? _tags.size() : _choice_tags_begin; } const char* runner_impl::get_tag(size_t index) const @@ -549,6 +552,7 @@ namespace ink::runtime::internal ptr = snap_write(ptr, _done, should_write); ptr = snap_write(ptr, _rng.get_state(), should_write); ptr = snap_write(ptr, _evaluation_mode, should_write); + ptr = snap_write(ptr, _string_mode, should_write); ptr = snap_write(ptr, _saved_evaluation_mode, should_write); ptr = snap_write(ptr, _saved, should_write); ptr = snap_write(ptr, _is_falling, should_write); @@ -556,6 +560,7 @@ namespace ink::runtime::internal ptr += _stack.snap(data ? ptr : nullptr, snapper); ptr += _ref_stack.snap(data ? ptr : nullptr, snapper); ptr += _eval.snap(data ? ptr : nullptr, snapper); + ptr = snap_write(ptr, _choice_tags_begin, should_write); ptr = snap_write(ptr, _tags.size(), should_write); for (const auto& tag : _tags) { std::uintptr_t offset = tag - snapper.story_string_table; @@ -600,6 +605,9 @@ namespace ink::runtime::internal ptr = _stack.snap_load(ptr, loader); ptr = _ref_stack.snap_load(ptr, loader); ptr = _eval.snap_load(ptr, loader); + int choice_tags_begin; + ptr = snap_read(ptr, choice_tags_begin); + _choice_tags_begin = choice_tags_begin; size_t num_tags; ptr = snap_read(ptr, num_tags); for(size_t i = 0; i < num_tags; ++i) { @@ -1082,6 +1090,7 @@ namespace ink::runtime::internal case Command::START_STR: { inkAssert(_evaluation_mode, "Can not enter string mode while not in evaluation mode!"); + _string_mode = true; _evaluation_mode = false; _output << values::marker; } break; @@ -1089,6 +1098,7 @@ namespace ink::runtime::internal { // TODO: Assert we really had a marker on there? inkAssert(!_evaluation_mode, "Must be in evaluation mode"); + _string_mode = false; _evaluation_mode = true; // Load value from output stream @@ -1098,6 +1108,20 @@ namespace ink::runtime::internal _globals->lists()))); } break; + case Command::START_TAG: + { + _output << values::marker; + } break; + + case Command::END_TAG: + { + auto tag = _output.get_alloc(_globals->strings(), _globals->lists()); + if(_string_mode && _choice_tags_begin < 0) { + _choice_tags_begin = _tags.size(); + } + _tags.push() = tag; + } break; + // == Choice commands case Command::CHOICE: { @@ -1141,12 +1165,19 @@ namespace ink::runtime::internal } for(;sc;--sc) { _output << stack[sc-1]; } + // fetch relevant tags + const char* const* tags = nullptr; + if (_choice_tags_begin >= 0 && _tags[_tags.size()-1] != nullptr) { + for(tags = _tags.end() - 1; *(tags-1) != nullptr && (tags - _tags.begin()) > _choice_tags_begin; --tags); + _tags.push() = nullptr; + } + // Create choice and record it if (flag & CommandFlag::CHOICE_IS_INVISIBLE_DEFAULT) { _fallback_choice - = choice{}.setup(_output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread()); + = choice{}.setup(_output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread(), tags); } else { - add_choice().setup(_output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread()); + add_choice().setup(_output, _globals->strings(), _globals->lists(), _choices.size(), path, current_thread(), tags); } // save stack at last choice if(_saved) { forget(); } @@ -1314,6 +1345,10 @@ namespace ink::runtime::internal // ref_stack has no strings and lists! _eval.mark_used(strings, lists); + // Take into account tags + for (size_t i = 0; i < _tags.size(); ++i) { + strings.mark_used(_tags[i]); + } // Take into account choice text for (size_t i = 0; i < _choices.size(); i++) strings.mark_used(_choices[i]._text); diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index aa804f79..590d4945 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -237,6 +237,7 @@ namespace ink::runtime::internal // Evaluation stack bool _evaluation_mode = false; + bool _string_mode = false; internal::eval_stack _eval; bool _saved_evaluation_mode = false; @@ -250,6 +251,7 @@ namespace ink::runtime::internal // Tag list managed_array _tags; + int _choice_tags_begin; // TODO: Move to story? Both? functions _functions; diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index ff6d77bb..d493988b 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -175,7 +175,14 @@ int main(int argc, const char** argv) int index = 1; for (const ink::runtime::choice& c : *thread) { - std::cout << index++ << ": " << c.text() << std::endl; + std::cout << index++ << ": " << c.text(); + if(c.has_tags()) { + std::cout << "\n\t"; + for(size_t i = 0; i < c.num_tags(); ++i) { + std::cout << "# " << c.get_tag(i) << " "; + } + } + std::cout << std::endl; } int c = 0; diff --git a/inkcpp_compiler/command.cpp b/inkcpp_compiler/command.cpp index 496734da..47a2b114 100644 --- a/inkcpp_compiler/command.cpp +++ b/inkcpp_compiler/command.cpp @@ -14,7 +14,7 @@ namespace ink "\n", "<>", "void", - "#", + "inkcpp_TAG", "inkcpp_DIVERT", "inkcpp_DIVERT_TO_VARIABLE", "inkcpp_TUNNEL", @@ -40,6 +40,8 @@ namespace ink "str", "/str", + "#", + "/#", "inkcpp_CHOICE", "thread", diff --git a/inkcpp_compiler/json_compiler.cpp b/inkcpp_compiler/json_compiler.cpp index 77c4c080..5f28fced 100644 --- a/inkcpp_compiler/json_compiler.cpp +++ b/inkcpp_compiler/json_compiler.cpp @@ -1,6 +1,8 @@ #include "json_compiler.h" #include "list_data.h" +#include "system.h" +#include "version.h" #include #include @@ -19,7 +21,7 @@ namespace ink::compiler::internal void json_compiler::compile(const nlohmann::json& input, emitter* output, compilation_results* results) { // Get the runtime version - int inkVersion = input["inkVersion"]; + _ink_version = input["inkVersion"]; // TODO: Do something with version number // Start the output @@ -27,7 +29,7 @@ namespace ink::compiler::internal _emitter = output; // Initialize emitter - _emitter->start(inkVersion, results); + _emitter->start(_ink_version, results); if(auto itr = input.find("listDefs"); itr != input.end()) { compile_lists_definition(*itr); @@ -396,6 +398,9 @@ namespace ink::compiler::internal else if (get(command, "#", val)) { + if (_ink_version > 20) { + ink_exception("with inkVerison 21 the tag system chages, and the '#: ' is deprecated now"); + } _emitter->write_string(Command::TAG, CommandFlag::NO_FLAGS, val); } diff --git a/inkcpp_compiler/json_compiler.h b/inkcpp_compiler/json_compiler.h index 199a6965..5179ea40 100644 --- a/inkcpp_compiler/json_compiler.h +++ b/inkcpp_compiler/json_compiler.h @@ -51,5 +51,6 @@ namespace ink::compiler::internal container_t _next_container_index; list_data _list_meta; + int _ink_version; }; } diff --git a/inkcpp_test/Tags.cpp b/inkcpp_test/Tags.cpp index 166abe6f..ffbf222c 100644 --- a/inkcpp_test/Tags.cpp +++ b/inkcpp_test/Tags.cpp @@ -56,26 +56,34 @@ SCENARIO("run story with tags", "[tags]") { auto itr = thread->begin(); std::string choices[2] = { - (itr++)->text(), - (itr++)->text() + itr[0].text(), + itr[1].text() }; - THEN("choices won't print tags, tags are still the same") + THEN("choices won't print tags, tags are still the same, but they can contain tags") { REQUIRE(choices[0] == "a"); REQUIRE(choices[1] == "b"); - REQUIRE(thread->has_tags() == true); + REQUIRE(thread->has_tags()); REQUIRE(thread->num_tags() == 4); REQUIRE(std::string(thread->get_tag(0)) == "global_tag"); REQUIRE(std::string(thread->get_tag(1)) == "knot_tag_start"); REQUIRE(std::string(thread->get_tag(2)) == "second_knot_tag_start"); REQUIRE(std::string(thread->get_tag(3)) == "output_tag_h"); + + REQUIRE_FALSE(itr[0].has_tags()); + REQUIRE(itr[0].num_tags() == 0); + REQUIRE(itr[1].has_tags()); + + REQUIRE(itr[1].num_tags() == 2); + REQUIRE(std::string(itr[1].get_tag(0)) == "choice_tag_b"); + REQUIRE(std::string(itr[1].get_tag(1)) == "choice_tag_b_2"); } WHEN("choose divert") { thread->choose(1); THEN("choosing won't add tags!") { - REQUIRE(thread->has_tags() == false); + REQUIRE_FALSE(thread->has_tags()); REQUIRE(thread->num_tags() == 0); } WHEN("proceed") @@ -83,22 +91,41 @@ SCENARIO("run story with tags", "[tags]") std::string line = thread->getall(); THEN("new knot tag and now line tag, also choice tag. AND dont print tag in choice") { - REQUIRE(line == "bKnot2\n"); - REQUIRE(thread->has_tags() == true); - REQUIRE(thread->num_tags() == 3); - REQUIRE(std::string(thread->get_tag(0)) == "choice_tag_b"); - REQUIRE(std::string(thread->get_tag(1)) == "knot_tag_2"); - REQUIRE(std::string(thread->get_tag(2)) == "output_tag_k"); + REQUIRE(line == "Knot2\n"); + REQUIRE(thread->has_tags()); + REQUIRE(thread->num_tags() == 2); + REQUIRE(std::string(thread->get_tag(0)) == "knot_tag_2"); + REQUIRE(std::string(thread->get_tag(1)) == "output_tag_k"); + + auto itr = thread->begin(); + REQUIRE(std::string(itr[0].text()) == "e"); + REQUIRE(std::string(itr[1].text()) == "f with detail"); + REQUIRE(std::string(itr[2].text()) == "g"); + + REQUIRE_FALSE(itr[0].has_tags()); + REQUIRE(itr[0].num_tags() == 0); + REQUIRE(itr[1].has_tags()); + REQUIRE(itr[1].num_tags() == 4); + REQUIRE(std::string(itr[1].get_tag(0)) == "shared_tag"); + REQUIRE(std::string(itr[1].get_tag(1)) == "shared_tag_2"); + REQUIRE(std::string(itr[1].get_tag(2)) == "choice_tag"); + REQUIRE(std::string(itr[1].get_tag(3)) == "choice_tag_2"); + REQUIRE(itr[2].has_tags()); + REQUIRE(itr[2].num_tags() == 1); } - WHEN("choose choice without tag, and proceed to end") + WHEN("choose choice with tag, and proceed to end") { - thread->choose(0); - thread->getall(); - THEN("no tags, tags behind END are ignored") - { - REQUIRE(thread->has_tags() == false); - REQUIRE(thread->num_tags() == 0); - } + thread->choose(1); + auto line = thread->getall(); + + REQUIRE(line == "f and content\nout"); + REQUIRE(thread->has_tags()); + REQUIRE(thread->num_tags() == 5); + REQUIRE(std::string(thread->get_tag(0)) == "shared_tag"); + REQUIRE(std::string(thread->get_tag(1)) == "shared_tag_2"); + REQUIRE(std::string(thread->get_tag(2)) == "content_tag"); + REQUIRE(std::string(thread->get_tag(3)) == "content_tag_2"); + REQUIRE(std::string(thread->get_tag(4)) == "close_tag"); } } } diff --git a/inkcpp_test/ink/TagsStory.ink b/inkcpp_test/ink/TagsStory.ink index f15afc31..e4169eac 100644 --- a/inkcpp_test/ink/TagsStory.ink +++ b/inkcpp_test/ink/TagsStory.ink @@ -6,7 +6,7 @@ Hello # output_tag_h * a -* b->knot2 # choice_tag_b +* [b # choice_tag_b # choice_tag_b_2]->knot2 - World! # output_tag_w * c # choice_tag_c * d # choice_tag_d @@ -17,5 +17,6 @@ Hello # output_tag_h Knot2 # output_tag_k * e -* f # choice_tag_f -- out->END # close_tag +* f #shared_tag # shared_tag_2 [ with detail #choice_tag #choice_tag_2] and content # content_tag # content_tag_2 +* g # choice_tag_g +- out # close_tag->END diff --git a/shared/private/command.h b/shared/private/command.h index 15465377..ac266791 100644 --- a/shared/private/command.h +++ b/shared/private/command.h @@ -49,6 +49,8 @@ namespace ink // == String stack START_STR, END_STR, + START_TAG, + END_TAG, // == Choice commands CHOICE, diff --git a/shared/public/system.h b/shared/public/system.h index 32721230..84004ed5 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -14,6 +14,7 @@ #include #include #include +#include #endif namespace ink