diff --git a/inkcpp/container_operations.cpp b/inkcpp/container_operations.cpp index 64575b1d..6b7aa6d5 100644 --- a/inkcpp/container_operations.cpp +++ b/inkcpp/container_operations.cpp @@ -13,40 +13,34 @@ #include -namespace ink::runtime::internal { +namespace ink::runtime::internal +{ - void operation::operator()( - basic_eval_stack& stack, value* vals) - { - container_t id; - bool success = _story.get_container_id( - _story.instructions() + vals[0].get(), - id); - inkAssert(success, "failed to find container to read visit count!"); - stack.push(value{}.set( - static_cast(_visit_counts.visits( id ) - ))); - } - - void operation::operator()( - basic_eval_stack& stack, value* vals) - { - container_t id; - bool success = _story.get_container_id( - _story.instructions() + vals[0].get(), - id); - inkAssert(success, "failed to find container to read turn count!"); - stack.push(value{}.set( - static_cast(_visit_counts.turns(id) - ))); - } +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + container_t id; + bool success = _story.find_container_id(vals[0].get(), id); + inkAssert(success, "failed to find container to read visit count!"); + stack.push(value{}.set(static_cast(_visit_counts.visits(id)))); +} - void operation::operator() - (basic_eval_stack& stack, value* vals) - { - stack.push(value{}.set(static_cast( - _runner.num_choices() - ))); - } +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + container_t id; + bool success = _story.find_container_id(vals[0].get(), id); + inkAssert(success, "failed to find container to read turn count!"); + stack.push(value{}.set(static_cast(_visit_counts.turns(id)))); +} +void operation::operator()( + basic_eval_stack& stack, value* vals +) +{ + stack.push(value{}.set(static_cast(_runner.num_choices()))); } + +} // namespace ink::runtime::internal diff --git a/inkcpp/globals_impl.cpp b/inkcpp/globals_impl.cpp index 6daed149..52a65787 100644 --- a/inkcpp/globals_impl.cpp +++ b/inkcpp/globals_impl.cpp @@ -20,7 +20,7 @@ globals_impl::globals_impl(const story_impl* story) , _visit_counts_backup() , _owner(story) , _runners_start(nullptr) - , _lists(story->list_meta(), story->get_header()) + , _lists(story->list_meta()) , _globals_initialized(false) { _visit_counts.resize(_num_containers); @@ -46,13 +46,10 @@ globals_impl::globals_impl(const story_impl* story) } } -void globals_impl::visit(uint32_t container_id, bool entering_at_start) +void globals_impl::visit(uint32_t container_id) { - if ((! (_owner->container_flag(container_id) & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) - || entering_at_start) { - _visit_counts[container_id].visits += 1; - _visit_counts[container_id].turns = 0; - } + _visit_counts[container_id].visits += 1; + _visit_counts[container_id].turns = 0; } uint32_t globals_impl::visits(uint32_t container_id) const diff --git a/inkcpp/globals_impl.h b/inkcpp/globals_impl.h index 06c787d6..8517ccd7 100644 --- a/inkcpp/globals_impl.h +++ b/inkcpp/globals_impl.h @@ -52,8 +52,7 @@ class globals_impl final public: // Records a visit to a container - /// @param start_cmd iff the visit was initiatet through a MARKER_START_CONTAINER - void visit(uint32_t container_id, bool entering_at_start); + void visit(uint32_t container_id); // Checks the number of visits to a container uint32_t visits(uint32_t container_id) const; diff --git a/inkcpp/header.cpp b/inkcpp/header.cpp index e99d08cc..e92ee2a1 100644 --- a/inkcpp/header.cpp +++ b/inkcpp/header.cpp @@ -7,38 +7,27 @@ #include "header.h" #include "version.h" -namespace ink::internal { +namespace ink::internal +{ - header header::parse_header(const char *data) - { - header res; - const char* ptr = data; - res.endien = *reinterpret_cast(ptr); - ptr += sizeof(header::endian_types); - - using v_t = decltype(header::ink_version_number); - using vcpp_t = decltype(header::ink_bin_version_number); - - if (res.endien == header::endian_types::same) { - res.ink_version_number = - *reinterpret_cast(ptr); - ptr += sizeof(v_t); - res.ink_bin_version_number = - *reinterpret_cast(ptr); +bool header::verify() const +{ + if (endian() == endian_types::none) { + inkFail("Header magic number was wrong!"); + return false; + } - } else if (res.endien == header::endian_types::differ) { - res.ink_version_number = - swap_bytes(*reinterpret_cast(ptr)); - ptr += sizeof(v_t); - res.ink_bin_version_number = - swap_bytes(*reinterpret_cast(ptr)); - } else { - inkFail("Failed to parse endian encoding!"); - } + if (endian() == endian_types::differ) { + inkFail("Can't load content with different endian-ness!"); + return false; + } - if (res.ink_bin_version_number != InkBinVersion) { - inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); - } - return res; + if (ink_bin_version_number != InkBinVersion) { + inkFail("InkCpp-version mismatch: file was compiled with different InkCpp-version!"); + return false; } + + return true; } + +} // namespace ink::internal diff --git a/inkcpp/list_table.cpp b/inkcpp/list_table.cpp index 85a756ea..ebaaa7dc 100644 --- a/inkcpp/list_table.cpp +++ b/inkcpp/list_table.cpp @@ -32,7 +32,7 @@ void list_table::copy_lists(const data_t* src, data_t* dst) } } -list_table::list_table(const char* data, const ink::internal::header& header) +list_table::list_table(const char* data) : _valid{false} { if (data == nullptr) { @@ -41,7 +41,7 @@ list_table::list_table(const char* data, const ink::internal::header& header) list_flag flag; const char* ptr = data; int start = 0; - while ((flag = header.read_list_flag(ptr)) != null_flag) { + while ((flag = read_list_flag(ptr)) != null_flag) { // start of new list if (_list_end.size() == flag.list_id) { start = _list_end.size() == 0 ? 0 : _list_end.back(); diff --git a/inkcpp/list_table.h b/inkcpp/list_table.h index 1740d9b8..860c5666 100644 --- a/inkcpp/list_table.h +++ b/inkcpp/list_table.h @@ -107,7 +107,7 @@ class list_table : public snapshot_interface list& add_inplace(list& lh, list_flag rh); // parse binary list meta data - list_table(const char* data, const ink::internal::header&); + list_table(const char* data); explicit list_table() : _entrySize{0} diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 05be3fd7..aa6e3f34 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -42,7 +42,7 @@ namespace ink::runtime::internal hash_t runner_impl::get_current_knot() const { - return _current_knot_id == ~0 ? 0 : _story->container_hash(_current_knot_id); + return _current_knot_id == ~0 ? 0 : _story->container_data(_current_knot_id)._hash; } template<> @@ -170,9 +170,6 @@ inline T runner_impl::read() // Read memory T val = *( const T* ) _ptr; - if (_story->get_header().endien == header::endian_types::differ) { - val = header::swap_bytes(val); - } // Advance ip _ptr += sizeof(T); @@ -300,103 +297,83 @@ void runner_impl::jump(ip_t dest, bool record_visits, bool track_knot_visit) // without entering any other containers // OR IF if target is same position do nothing // could happend if jumping to and of an unnamed container - if (dest == _ptr) { - _ptr = dest; + if (dest == _ptr) return; - } - const uint32_t* iter = nullptr; - container_t id; - ip_t offset = nullptr; - size_t comm_end; - bool reversed = _ptr > dest; - - if (reversed) { - comm_end = 0; - iter = nullptr; - const ContainerData* old_iter = nullptr; - const uint32_t* last_comm_iter = nullptr; - _container.rev_iter(old_iter); - - // find commen part of old and new stack - while (_story->iterate_containers(iter, id, offset)) { - if (old_iter == nullptr || offset >= dest) { - break; - } - if (old_iter != nullptr && id == old_iter->id) { - last_comm_iter = iter; - _container.rev_iter(old_iter); - ++comm_end; - } - } + // Discard old stack, preserving save region. + while (! _container.empty()) + _container.pop(); - // clear old part from stack - while (_container.size() > comm_end) { - _container.pop(); - } - iter = last_comm_iter; + // Record location and jump. + const uint32_t current_offset = _ptr - _story->instructions(); + _ptr = dest; - } else { - iter = nullptr; - comm_end = _container.size(); - // go to current possition in container list - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= _ptr) { - break; - } + // Find the container at or before dest, which will become the top of the post-jump stack. + const uint32_t dest_offset = dest - _story->instructions(); + const container_t dest_id = _story->find_container_for(dest_offset); + + // If there's no destination container, stop. + if (dest_id == ~0) + return; + + // Are we entering the new container at its start? + using container_data_t = ink::internal::container_data_t; + const container_data_t& dest_container = _story->container_data(dest_id); + if (dest_offset == dest_container._start_offset) { + // Record direct jump to non-knot if requested. (Knots handled below.) + if (record_visits && ! dest_container.knot()) { + _globals->visit(dest_id); } - _story->iterate_containers(iter, id, offset, true); + + // Consume instruction so we don't process it again during normal flow. (We need to do this here + // to know if it should be tracked or not.) + _ptr += 6; } - // move to destination and update container stack on the go - while (_story->iterate_containers(iter, id, offset)) { - if (offset >= dest) { - break; - } - if (_container.empty() || _container.top().id != id) { - _container.push({id, offset - _story->instructions()}); - } else { - _container.pop(); - if (_container.size() < comm_end) { - comm_end = _container.size(); + // If we're tracking knots, we only want the first one. + bool first_knot = track_knot_visit; + + // Assemble temp stack in reverse order by traversing container tree. + container_t stack[abs(config::limitContainerDepth)]; + uint32_t depth = 0; + for (container_t id = dest_id; id != ~0; /* advance in body */) { + // Append to stack. + inkAssert(depth < abs(config::limitContainerDepth)); + stack[depth++] = id; + + // Find container for this ID. + const container_data_t& container = _story->container_data(id); + + // Is this a new knot? + if (container.knot() && ! container.contains(current_offset)) { + // Named knots/stitches need special handling - their visit counts are updated wherever the + // story enters them, + // and we generally need to know which knot we're in, for tagging, unless we're jumping to a + // tunnel or similar + // which suppresses knot tracking. + // + // Ink has a rule about incrementing visit counts when you jump to the top of a knot, which + // seems to need to override inkcpp's knot_visit flag. + if (track_knot_visit || container._start_offset == dest_offset) { + _globals->visit(id); + } + + // If tracking, update with the first knot we encounter, which is the one closest to the top + // of the new stack. + if (first_knot) { + _current_knot_id = id; + _entered_knot = true; + first_knot = false; } } - } - _ptr = dest; - - // if we jump directly to a named container start, go inside, if its a ONLY_FIRST container - // it will get visited in the next step - if (offset == dest && static_cast(offset[0]) == Command::START_CONTAINER_MARKER) { - if (track_knot_visit - && static_cast(offset[1]) & CommandFlag::CONTAINER_MARKER_IS_KNOT) { - _current_knot_id = id; - _entered_knot = true; - } - _ptr += 6; - _container.push({id, offset - _story->instructions()}); - if (reversed && comm_end == _container.size() - 1) { - ++comm_end; - } + + // Next one. + id = container._parent; } - // iff all container (until now) are entered at first position - bool allEnteredAtStart = true; - ip_t child_position = dest; - if (record_visits) { - const ContainerData* iData = nullptr; - size_t level = _container.size(); - if (_container.iter(iData) - && (level > comm_end - || _story->container_flag(iData->offset + _story->instructions()) - & CommandFlag::CONTAINER_MARKER_ONLY_FIRST)) { - auto parrent_offset = _story->instructions() + iData->offset; - inkAssert(child_position >= parrent_offset, "Container stack order is broken"); - // 6 == len of START_CONTAINER_SIGNAL, if its 6 bytes behind the container it is a unnnamed - // subcontainers first child check if child_positino is the first child of current container - allEnteredAtStart = allEnteredAtStart && ((child_position - parrent_offset) <= 6); - child_position = parrent_offset; - _globals->visit(iData->id, allEnteredAtStart); - } + // Reverse order onto final stack. + for (uint32_t d = 0; d < depth; ++d) { + _container.push(stack[depth - 1 - d]); } } @@ -476,7 +453,7 @@ runner_impl::runner_impl(const story_impl* data, globals global) , _evaluation_mode{false} , _choices() , _tags_begin(0, ~0) - , _container(ContainerData{}) + , _container(~0) , _rng(time(NULL)) { @@ -512,13 +489,13 @@ runner_impl::line_type runner_impl::getline() // Advance interpreter one line and write to output advance_line(); -#ifdef INK_ENABLE_STL +# ifdef INK_ENABLE_STL line_type result{_output.get()}; -#elif defined(INK_ENABLE_UNREAL) +# elif defined(INK_ENABLE_UNREAL) line_type result{ANSI_TO_TCHAR(_output.get_alloc(_globals->strings(), _globals->lists()))}; -#else -# error unsupported constraints for getline -#endif +# else +# error unsupported constraints for getline +# endif // Fall through the fallback choice, if available if (! has_choices() && _fallback_choice) { @@ -531,11 +508,11 @@ runner_impl::line_type runner_impl::getline() runner_impl::line_type runner_impl::getall() { -#ifdef INK_ENABLE_STL +# ifdef INK_ENABLE_STL if (_debug_stream != nullptr) { _debug_stream->clear(); } -#endif +# endif line_type result{}; @@ -1335,7 +1312,7 @@ void runner_impl::step() if (flag & CommandFlag::CHOICE_IS_ONCE_ONLY) { // Need to convert offset to container index container_t destination = -1; - if (_story->get_container_id(_story->instructions() + path, destination)) { + if (_story->find_container_id(path, destination)) { // Ignore the choice if we've visited the destination before if (_globals->visits(destination) > 0) { break; @@ -1398,12 +1375,13 @@ void runner_impl::step() // Keep track of current container auto index = read(); // offset points to command, command has size 6 - _container.push({index, _ptr - _story->instructions() - 6}); + _container.push(index); // Increment visit count - if (flag & CommandFlag::CONTAINER_MARKER_TRACK_VISITS - || flag & CommandFlag::CONTAINER_MARKER_TRACK_TURNS) { - _globals->visit(_container.top().id, true); + if (uint8_t(flag) + & (uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS) + | uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_TURNS))) { + _globals->visit(index); } if (flag & CommandFlag::CONTAINER_MARKER_IS_KNOT) { _current_knot_id = index; @@ -1413,8 +1391,7 @@ void runner_impl::step() } break; case Command::END_CONTAINER_MARKER: { container_t index = read(); - - inkAssert(_container.top().id == index, "Leaving container we are not in!"); + inkAssert(_container.top() == index, "Leaving container we are not in!"); // Move up out of the current container _container.pop(); @@ -1449,7 +1426,7 @@ void runner_impl::step() // Push the visit count for the current container to the top // is 0-indexed for some reason. idk why but this is what ink expects _eval.push(value{}.set( - static_cast(_globals->visits(_container.top().id) - 1) + static_cast(_globals->visits(_container.top()) - 1) )); } break; case Command::TURN: { diff --git a/inkcpp/runner_impl.h b/inkcpp/runner_impl.h index 7cf61d32..9196f1f7 100644 --- a/inkcpp/runner_impl.h +++ b/inkcpp/runner_impl.h @@ -339,17 +339,8 @@ class runner_impl // TODO: Move to story? Both? functions _functions; - // Container set - struct ContainerData { - container_t id = ~0u; - ptrdiff_t offset = 0; - - bool operator==(const ContainerData& oth) const { return oth.id == id && oth.offset == offset; } - - bool operator!=(const ContainerData& oth) const { return ! (*this == oth); } - }; - - internal::managed_restorable_stack < ContainerData, + // Container stack + internal::managed_restorable_stack < container_t, config::limitContainerDepth<0, abs(config::limitContainerDepth)> _container; bool _is_falling = false; diff --git a/inkcpp/story_impl.cpp b/inkcpp/story_impl.cpp index f072d95a..0e8ec748 100644 --- a/inkcpp/story_impl.cpp +++ b/inkcpp/story_impl.cpp @@ -101,53 +101,62 @@ story_impl::~story_impl() const char* story_impl::string(uint32_t index) const { return _string_table + index; } -bool story_impl::iterate_containers( - const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse -) const +bool story_impl::find_container_id(uint32_t offset, container_t& container_id) const { - if (iterator == nullptr) { - // Empty check - if (_container_list_size == 0) { - return false; - } - - // Start - iterator = reverse ? _container_list + (_container_list_size - 1) * 2 : _container_list; - } else { - // Range check - inkAssert( - iterator >= _container_list && iterator <= _container_list + _container_list_size * 2, - "Container fail" - ); - - // Advance - iterator += reverse ? -2 : 2; - - // End? - if (iterator >= _container_list + _container_list_size * 2 || iterator < _container_list) { - iterator = nullptr; - index = 0; - offset = nullptr; - return false; - } + // Find inmost container. + container_id = find_container_for(offset); + + // Exact match? + return container_data(container_id)._start_offset == offset; +} + +// Search sorted looking for the target or the largest value smaller than target. +template +static const entry* upper_bound(const entry* sorted, uint32_t count, uint32_t key) +{ + if (count == 0) + return nullptr; + + uint32_t begin = 0; + uint32_t end = count; + + while (begin < end) { + const uint32_t mid = begin + (end - begin + 1) / 2; + const uint32_t mid_key = sorted[mid].key(); + + if (mid_key > key) + // Look below + end = mid - 1; + else + // Look above + begin = mid; } - // Get metadata - index = *(iterator + 1); - offset = *iterator + instructions(); - return true; + return sorted + begin; } -bool story_impl::get_container_id(ip_t offset, container_t& container_id) const +container_t story_impl::find_container_for(uint32_t offset) const { - const uint32_t* iter = nullptr; - ip_t iter_offset = nullptr; - while (iterate_containers(iter, container_id, iter_offset)) { - if (iter_offset == offset) - return true; + // Container map contains offsets in even slots, container ids in odd. + const container_map_t* entry = upper_bound(_container_map, _container_map_size, offset); + + // The last container command before the offset could be either the start of a container + // (in which case the offset is contained within) or the end of a container, in which case + // the offset is inside that container's parent. + + // If we're not inside the container, walk out to find the actual parent. Normally we'd + // know that the parent contained the child, but the containers are sparse so we might + // not have anything. + container_t id = entry ? entry->_id : ~0; + while (id != ~0) { + const container_data_t& data = container_data(id); + if (data._start_offset <= offset && data._end_offset >= offset) + return id; + + id = data._parent; } - return false; + return id; } CommandFlag story_impl::container_flag(ip_t offset) const @@ -160,61 +169,12 @@ CommandFlag story_impl::container_flag(ip_t offset) const return static_cast(offset[1]); } -CommandFlag story_impl::container_flag(container_t id) const -{ - const uint32_t* iter = nullptr; - ip_t offset; - container_t c_id; - while (iterate_containers(iter, c_id, offset)) { - if (c_id == id) { - inkAssert( - static_cast(offset[0]) == Command::START_CONTAINER_MARKER, - "Container list pointer is invalid!" - ); - return static_cast(offset[1]); - } - } - inkFail("Container not found -> can't fetch flag"); - return CommandFlag::NO_FLAGS; -} - -hash_t story_impl::container_hash(container_t id) const -{ - const uint32_t* iter = nullptr; - ip_t offset; - container_t c_id; - bool hit = false; - while (iterate_containers(iter, c_id, offset)) { - if (c_id == id) { - hit = true; - break; - } - } - inkAssert(hit, "Unable to find container for id!"); - hash_t* h_iter = _container_hash_start; - while (iter != _container_hash_end) { - if (instructions() + *( offset_t* ) (h_iter + 1) == offset) { - return *h_iter; - } - h_iter += 2; - } - inkAssert(false, "Did not find hash entry for container!"); - return 0; -} - ip_t story_impl::find_offset_for(hash_t path) const { - hash_t* iter = _container_hash_start; + // Hash map contains hashes in even slots, offsets in odd. + const container_hash_t* entry = upper_bound(_container_hash, _container_hash_size, path); - while (iter != _container_hash_end) { - if (*iter == path) { - return instructions() + *( offset_t* ) (iter + 1); - } - - iter += 2; - } - - return nullptr; + return entry && entry->_hash == path ? _instruction_data + entry->_offset : nullptr; } globals story_impl::new_globals() @@ -269,108 +229,49 @@ runner story_impl::new_runner_from_snapshot(const snapshot& data, globals store, void story_impl::setup_pointers() { - using header = ink::internal::header; - _header = header::parse_header(reinterpret_cast(_file)); - - // String table is after the header - _string_table = ( char* ) _file + header::Size; - - // Pass over strings - const char* ptr = _string_table; - if (*ptr == 0) // SPECIAL: No strings - { - ptr++; - } else - while (true) { - // Read until null terminator - while (*ptr != 0) - ptr++; - - // Check next character - ptr++; - - // Second null. Strings are done. - if (*ptr == 0) { - ptr++; - break; - } - } - - // check if lists are defined - _list_meta = ptr; - if (list_flag flag = _header.read_list_flag(ptr); flag != null_flag) { - // skip list definitions - auto list_id = flag.list_id; - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip list name - do { - if (flag.list_id != list_id) { - list_id = flag.list_id; - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip list name - } - while (*ptr != 0) { - ++ptr; - } - ++ptr; // skip flag name - } while ((flag = _header.read_list_flag(ptr)) != null_flag); - - _lists = reinterpret_cast(ptr); - // skip predefined lists - while (_header.read_list_flag(ptr) != null_flag) { - while (_header.read_list_flag(ptr) != null_flag) - ; - } - } else { - _list_meta = nullptr; - _lists = nullptr; + const ink::internal::header& header = *reinterpret_cast(_file); + if (! header.verify()) + return; + + // Locate sections + if (header._strings._bytes) + _string_table = reinterpret_cast(_file + header._strings._start); + + // Address list sections if they exist + if (header._list_meta._bytes) { + _list_meta = reinterpret_cast(_file + header._list_meta._start); + + // Lists require metadata + if (header._lists._bytes) + _lists = reinterpret_cast(_file + header._lists._start); } - inkAssert( - _header.ink_bin_version_number == ink::InkBinVersion, - "invalid InkBinVerison! currently: %i you used %i", ink::InkBinVersion, - _header.ink_bin_version_number - ); - inkAssert( - _header.endien == header::endian_types::same, "different endien support not yet implemented" - ); + // Address containers section if it exists + if (header._containers._bytes) { + _num_containers = header._containers._bytes / sizeof(container_data_t); + _container_data = reinterpret_cast(_file + header._containers._start); + } - _num_containers = *( uint32_t* ) (ptr); - ptr += sizeof(uint32_t); - - // Pass over the container data - _container_list_size = 0; - _container_list = ( uint32_t* ) (ptr); - while (true) { - uint32_t val = *( uint32_t* ) ptr; - if (val == ~0) { - ptr += sizeof(uint32_t); - break; - } else { - ptr += sizeof(uint32_t) * 2; - _container_list_size++; - } + // Address container map if it exists + if (header._container_map._bytes) { + _container_map_size = header._container_map._bytes / sizeof(container_map_t); + _container_map = reinterpret_cast(_file + header._container_map._start); } - // Next is the container hash map - _container_hash_start = ( hash_t* ) (ptr); - while (true) { - uint32_t val = *( uint32_t* ) ptr; - if (val == ~0) { - _container_hash_end = ( hash_t* ) (ptr); - ptr += sizeof(uint32_t); - break; - } - - ptr += sizeof(uint32_t) * 2; + // Address container hash if it exists + if (header._container_hash._bytes) { + _container_hash_size = header._container_hash._bytes / sizeof(container_hash_t); + _container_hash + = reinterpret_cast(_file + header._container_hash._start); } - // After strings comes instruction data - _instruction_data = ( ip_t ) ptr; + // Address instructions, which we hope exist! + if (header._instructions._bytes) + _instruction_data = _file + header._instructions._start; + + // Shrink file length to fit exact length of instructions section. + inkAssert(end() >= _instruction_data + header._instructions._bytes); + _length = _instruction_data + header._instructions._bytes - _file; // Debugging info /*{ diff --git a/inkcpp/story_impl.h b/inkcpp/story_impl.h index 02d0fca3..3bebebf0 100644 --- a/inkcpp/story_impl.h +++ b/inkcpp/story_impl.h @@ -40,14 +40,33 @@ class story_impl : public story const char* list_meta() const { return _list_meta; } - bool iterate_containers( - const uint32_t*& iterator, container_t& index, ip_t& offset, bool reverse = false - ) const; - bool get_container_id(ip_t offset, container_t& container_id) const; - /// Get container flag from container offset (either start or end) + // Find the innermost container containing offset. If offset is the start of a container, return + // that container. + container_t find_container_for(uint32_t offset) const; + + // Find the container which starts exactly at offset. Return false if this isn't the start of a + // container. + bool find_container_id(uint32_t offset, container_t& container_id) const; + + using container_data_t = ink::internal::container_data_t; + using container_hash_t = ink::internal::container_hash_t; + using container_map_t = ink::internal::container_map_t; + + // Look up the details of the given container + const container_data_t& container_data(container_t id) const + { + inkAssert(id < _num_containers); + return _container_data[id]; + } + + // Look up the instruction pointer for the start of the given container + ip_t container_offset(container_t id) const + { + return _instruction_data + container_data(id)._start_offset; + } + + // Get container flag from container offset (either start or end) CommandFlag container_flag(ip_t offset) const; - CommandFlag container_flag(container_t id) const; - hash_t container_hash(container_t id) const; ip_t find_offset_for(hash_t path) const; @@ -58,35 +77,34 @@ class story_impl : public story virtual runner new_runner_from_snapshot(const snapshot&, globals store = nullptr, unsigned idx = 0) override; - const ink::internal::header& get_header() const { return _header; } - private: void setup_pointers(); private: // file information - unsigned char* _file; - size_t _length; - - ink::internal::header _header; + uint8_t* _file; + size_t _length; // string table - const char* _string_table; + const char* _string_table = nullptr; + + const char* _list_meta = nullptr; + const list_flag* _lists = nullptr; - const char* _list_meta; - const list_flag* _lists; + // Information about containers. + const container_data_t* _container_data = nullptr; + uint32_t _num_containers = 0; - // container info - uint32_t* _container_list; - uint32_t _container_list_size; - uint32_t _num_containers; + // How to find containers from instruction offsets. + const container_map_t* _container_map = nullptr; + uint32_t _container_map_size = 0; - // container hashes - hash_t* _container_hash_start; - hash_t* _container_hash_end; + // How to find containers from string hashes. + const container_hash_t* _container_hash = nullptr; + uint32_t _container_hash_size = 0; // instruction info - ip_t _instruction_data; + ip_t _instruction_data = nullptr; // story block used to creat various weak pointers ref_block* _block; diff --git a/inkcpp/string_operations.h b/inkcpp/string_operations.h index 2617871a..a392dd13 100644 --- a/inkcpp/string_operations.h +++ b/inkcpp/string_operations.h @@ -10,7 +10,6 @@ namespace ink::runtime::internal { - namespace casting { // define valid castings diff --git a/inkcpp_compiler/binary_emitter.cpp b/inkcpp_compiler/binary_emitter.cpp index 4f281843..9405eaad 100644 --- a/inkcpp_compiler/binary_emitter.cpp +++ b/inkcpp_compiler/binary_emitter.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #ifndef _MSC_VER # include @@ -101,7 +102,7 @@ uint32_t binary_emitter::start_container(int index_in_parent, const std::string& container->parent = _current; // Set offset to the current position - container->offset = _containers.pos(); + container->offset = _instructions.pos(); // Add to parents lists if (_current != nullptr) { @@ -117,17 +118,17 @@ uint32_t binary_emitter::start_container(int index_in_parent, const std::string& _current = container; // Return current position - return _containers.pos(); + return _instructions.pos(); } uint32_t binary_emitter::end_container() { // Move up the chain - _current->end_offset = _containers.pos(); + _current->end_offset = _instructions.pos(); _current = _current->parent; // Return offset - return _containers.pos(); + return _instructions.pos(); } int binary_emitter::function_container_arguments(const std::string& name) @@ -141,11 +142,11 @@ int binary_emitter::function_container_arguments(const std::string& name) } size_t offset = fn->second->offset; - byte_t cmd = _containers.get(offset); + byte_t cmd = _instructions.get(offset); int arity = 0; while (static_cast(cmd) == Command::DEFINE_TEMP) { offset += 6; // command(1) + flag(1) + variable_name_hash(4) - cmd = _containers.get(offset); + cmd = _instructions.get(offset); ++arity; } return arity; @@ -155,10 +156,10 @@ void binary_emitter::write_raw( Command command, CommandFlag flag, const char* payload, ink::size_t payload_size ) { - _containers.write(command); - _containers.write(flag); + _instructions.write(command); + _instructions.write(flag); if (payload_size > 0) - _containers.write(( const byte_t* ) payload, payload_size); + _instructions.write(( const byte_t* ) payload, payload_size); } void binary_emitter::write_path( @@ -169,7 +170,7 @@ void binary_emitter::write_path( write(command, ( uint32_t ) 0, flag); // Note the position of this later so we can resolve the paths at the end - size_t param_position = _containers.pos() - sizeof(uint32_t); + size_t param_position = _instructions.pos() - sizeof(uint32_t); bool op = flag & CommandFlag::FALLBACK_FUNCTION; _paths.push_back(std::make_tuple(param_position, path, op, _current, useCountIndex)); } @@ -212,43 +213,93 @@ void binary_emitter::write_list( void binary_emitter::handle_nop(int index_in_parent) { - _current->noop_offsets.insert({index_in_parent, _containers.pos()}); + _current->noop_offsets.insert({index_in_parent, _instructions.pos()}); +} + +template +void binary_emitter::emit_section(std::ostream& stream, const std::vector& data) const +{ + stream.write(reinterpret_cast(data.data()), data.size() * sizeof(type)); + close_section(stream); +} + +void binary_emitter::emit_section(std::ostream& stream, const binary_stream& data) const +{ + inkAssert((stream.tellp() & (ink::internal::header::Alignment - 1)) == 0); + data.write_to(stream); + close_section(stream); +} + +void binary_emitter::close_section(std::ostream& stream) const +{ + // Write zeroes until aligned. + while (! stream.fail() && (stream.tellp() % ink::internal::header::Alignment)) + stream.put('\0'); } void binary_emitter::output(std::ostream& out) { - // Write the ink version - // TODO: define this order in header? - using header = ink::internal::header; - header::endian_types same = header::endian_types::same; - out.write(( const char* ) &same, sizeof(decltype(same))); - out.write(( const char* ) &_ink_version, sizeof(decltype(_ink_version))); - out.write(( const char* ) &ink::InkBinVersion, sizeof(decltype(ink::InkBinVersion))); + // Create container data + std::vector container_data; + container_data.resize(_max_container_index); + build_container_data(container_data, ~0, _root); + + // Create container hash (and write the hashes into the data as well) + std::vector container_hash; + container_hash.reserve(_max_container_index); + build_container_hash_map(container_hash, container_data, "", _root); + + // Sort map on ascending hash code. + std::sort(container_hash.begin(), container_hash.end()); + + // If there's list meta data... + if (_list_meta.pos() > 0) { + // If there are any lists, terminate the data correctly. Otherwise leave an empty section. + if (_lists.pos() > 0) + _lists.write(null_flag); + } else + // No meta data -> no lists. + _lists.reset(); + + // Fill in header + ink::internal::header header; + header.ink_version_number = _ink_version; + header.ink_bin_version_number = ink::InkBinVersion; + + // Fill in sections + uint32_t offset = sizeof(header); + header._strings.setup(offset, _strings.pos()); + header._list_meta.setup(offset, _list_meta.pos()); + header._lists.setup(offset, _lists.pos()); + header._containers.setup(offset, container_data.size() * sizeof(container_data_t)); + header._container_map.setup(offset, _container_map.size() * sizeof(container_map_t)); + header._container_hash.setup(offset, container_hash.size() * sizeof(container_hash_t)); + header._instructions.setup(offset, _instructions.pos()); + + // Write the header + out.write(reinterpret_cast(&header), sizeof(header)); + close_section(out); // Write the string table - _strings.write_to(out); + emit_section(out, _strings); - // Write a separator - out << ( char ) 0; + // Write lists meta data and defined lists + emit_section(out, _list_meta); // Write lists meta data and defined lists - _lists.write_to(out); - // Write a seperator - out.write(reinterpret_cast(&null_flag), sizeof(null_flag)); + emit_section(out, _lists); - // Write out container map - write_container_map(out, _container_map, _max_container_index); + // Write out container information + emit_section(out, container_data); - // Write a separator - uint32_t END_MARKER = ~0; - out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); + // Write out container map + emit_section(out, _container_map); // Write container hash list - write_container_hash_map(out); - out.write(( const char* ) &END_MARKER, sizeof(uint32_t)); + emit_section(out, container_hash); - // Write the container data - _containers.write_to(out); + // Write the container contents (instruction stream) + emit_section(out, _instructions); // Flush the file out.flush(); @@ -259,8 +310,9 @@ void binary_emitter::initialize() // Reset binary data stores _strings.reset(); _list_count = 0; + _list_meta.reset(); _lists.reset(); - _containers.reset(); + _instructions.reset(); // clear other data _paths.clear(); @@ -286,13 +338,13 @@ uint32_t binary_emitter::fallthrough_divert() write(Command::DIVERT, ( uint32_t ) 0, CommandFlag::DIVERT_IS_FALLTHROUGH); // Return the location of the divert offset - return _containers.pos() - sizeof(uint32_t); + return _instructions.pos() - sizeof(uint32_t); } void binary_emitter::patch_fallthroughs(uint32_t position) { // Patch - _containers.set(position, _containers.pos()); + _instructions.set(position, _instructions.pos()); } void binary_emitter::process_paths() @@ -363,62 +415,78 @@ void binary_emitter::process_paths() if (noop_offset != ~0) { inkAssert(! useCountIndex, "Can't count visits to a noop!"); - _containers.set(position, noop_offset); + _instructions.set(position, noop_offset); } else { // If we want the count index, write that out if (useCountIndex) { inkAssert(container->counter_index != ~0, "No count index available for this container!"); - _containers.set(position, container->counter_index); + _instructions.set(position, container->counter_index); } else { // Otherwise, write container address if (container == nullptr) { - _containers.set(position, 0); + _instructions.set(position, 0); inkAssert(optional, "Was not able to resolve a not optional path! '%hs'", path.c_str()); } else { - _containers.set(position, container->offset); + _instructions.set(position, container->offset); } } } } } -void binary_emitter::write_container_map( - std::ostream& out, const container_map& map, container_t num -) +void binary_emitter::build_container_data( + std::vector& data, container_t parent, const container_data* context +) const { - // Write out container count - out.write(reinterpret_cast(&num), sizeof(container_t)); - - // Write out entries - for (const auto& pair : map) { - out.write(( const char* ) &pair.first, sizeof(uint32_t)); - out.write(( const char* ) &pair.second, sizeof(uint32_t)); + // Build data for this container + if (context->counter_index != ~0) { + container_data_t& d = data[context->counter_index]; + d._parent = parent; + d._start_offset = context->offset; + d._end_offset = context->end_offset; + const uint8_t flags = _instructions.get(context->offset + 1); + inkAssert(flags < 16); + d._flags = flags; + + // Since we might be skipping tree levels, we need to be explicit about the parent. + parent = context->counter_index; } -} -void binary_emitter::write_container_hash_map(std::ostream& out) -{ - write_container_hash_map(out, "", _root); + // Recurse + for (auto child : context->children) + build_container_data(data, parent, child); } -void binary_emitter::write_container_hash_map( - std::ostream& out, const std::string& name, const container_data* context -) +void binary_emitter::build_container_hash_map( + std::vector& hash_map, std::vector& data, + const std::string& name, const container_data* context +) const { + // Search named children first. for (auto child : context->named_children) { // Get the child's name in the hierarchy std::string child_name = name.empty() ? child.first : (name + "." + child.first); - hash_t name_hash = hash_string(child_name.c_str()); - // Write out name hash and offset - out.write(( const char* ) &name_hash, sizeof(hash_t)); - out.write(( const char* ) &child.second->offset, sizeof(uint32_t)); + + // Hash name. We only do this at the named child level. In theory we could support indexed + // children as well. The root is anonymous so the fact that it's skipped is not an issue. + const hash_t child_name_hash = hash_string(child_name.c_str()); + + // Store hash in the data. + if (child.second->counter_index != ~0) { + data[child.second->counter_index]._hash = child_name_hash; + } + + // Append the name hash and offset + hash_map.push_back({child_name_hash, child.second->offset}); // Recurse - write_container_hash_map(out, child_name, child.second); + build_container_hash_map(hash_map, data, child_name, child.second); } + // Search indexed children (which duplicates named childen...) + // TODO: Merge duplicate child arrays, very error-prone. for (auto child : context->indexed_children) { - write_container_hash_map(out, name, child.second); + build_container_hash_map(hash_map, data, name, child.second); } } @@ -432,15 +500,15 @@ void binary_emitter::set_list_meta(const list_data& list_defs) auto list_names = list_defs.get_list_names().begin(); int list_id = -1; for (const auto& flag : flags) { - _lists.write(flag.flag); + _list_meta.write(flag.flag); if (flag.flag.list_id != list_id) { list_id = flag.flag.list_id; - _lists.write(reinterpret_cast(list_names->data()), list_names->size()); + _list_meta.write(reinterpret_cast(list_names->data()), list_names->size()); ++list_names; - _lists.write('\0'); + _list_meta.write('\0'); } - _lists.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); + _list_meta.write(reinterpret_cast(flag.name->c_str()), flag.name->size() + 1); } - _lists.write(null_flag); + _list_meta.write(null_flag); } } // namespace ink::compiler::internal diff --git a/inkcpp_compiler/binary_emitter.h b/inkcpp_compiler/binary_emitter.h index a03137af..36dd4fa1 100644 --- a/inkcpp_compiler/binary_emitter.h +++ b/inkcpp_compiler/binary_emitter.h @@ -8,63 +8,86 @@ #include "emitter.h" #include "binary_stream.h" +#include "header.h" namespace ink::compiler::internal { - struct container_data; - class list_data; +struct container_data; +class list_data; - // binary emitter - class binary_emitter : public emitter - { - public: - binary_emitter(); - virtual ~binary_emitter(); +// binary emitter +class binary_emitter : public emitter +{ +public: + binary_emitter(); + virtual ~binary_emitter(); + + // Begin emitter + virtual uint32_t start_container(int index_in_parent, const std::string& name) override; + virtual uint32_t end_container() override; + virtual int function_container_arguments(const std::string& name) override; + virtual void write_raw( + Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, + ink::size_t payload_size = 0 + ) override; + virtual void write_path( + Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false + ) override; + virtual void write_variable(Command command, CommandFlag flag, const std::string& name) override; + virtual void write_string(Command command, CommandFlag flag, const std::string& string) override; + virtual void handle_nop(int index_in_parent) override; + virtual uint32_t fallthrough_divert() override; + virtual void patch_fallthroughs(uint32_t position) override; + virtual void set_list_meta(const list_data& list_defs) override; + virtual void + write_list(Command command, CommandFlag flag, const std::vector& entries) override; + // End emitter + + // write out the emitters data + virtual void output(std::ostream&); + +protected: + virtual void initialize() override; + virtual void finalize() override; + virtual void setContainerIndex(container_t index) override; + +private: + void process_paths(); - // Begin emitter - virtual uint32_t start_container(int index_in_parent, const std::string& name) override; - virtual uint32_t end_container() override; - virtual int function_container_arguments(const std::string& name) override; - virtual void write_raw(Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, ink::size_t payload_size = 0) override; - virtual void write_path(Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false) override; - virtual void write_variable(Command command, CommandFlag flag, const std::string& name) override; - virtual void write_string(Command command, CommandFlag flag, const std::string& string) override; - virtual void handle_nop(int index_in_parent) override; - virtual uint32_t fallthrough_divert() override; - virtual void patch_fallthroughs(uint32_t position) override; - virtual void set_list_meta(const list_data& list_defs) override; - virtual void write_list(Command command, CommandFlag flag, const std::vector& entries) override; - // End emitter + template + void emit_section(std::ostream& out, const std::vector& data) const; + void emit_section(std::ostream& out, const binary_stream& stream) const; + void close_section(std::ostream& out) const; - // write out the emitters data - virtual void output(std::ostream&); + using container_data_t = ink::internal::container_data_t; + using container_map_t = ink::internal::container_map_t; + using container_hash_t = ink::internal::container_hash_t; - protected: - virtual void initialize() override; - virtual void finalize() override; - virtual void setContainerIndex(container_t index) override; + void build_container_data( + std::vector& data, container_t parent, const container_data* context + ) const; - private: - void process_paths(); - void write_container_map(std::ostream&, const container_map&, container_t); - void write_container_hash_map(std::ostream&); - void write_container_hash_map(std::ostream&, const std::string&, const container_data*); + void build_container_hash_map( + std::vector& hash, std::vector& data, const std::string&, + const container_data* context + ) const; - private: - container_data* _root; - container_data* _current; - compilation_results* _results; +private: + container_data* _root; + container_data* _current; + compilation_results* _results; - binary_stream _strings; - uint32_t _list_count = 0; - binary_stream _lists; - binary_stream _containers; + binary_stream _strings; + uint32_t _list_count = 0; + binary_stream _list_meta; + binary_stream _lists; + binary_stream _instructions; - // positon to write address - // path as string - // if path may not exists (used for function fallbackes) - // container data - // use count index? - std::vector> _paths; - }; -} + // positon to write address + // path as string + // if path may not exists (used for function fallbackes) + // container data + // use count index? + std::vector> _paths; +}; +} // namespace ink::compiler::internal diff --git a/inkcpp_compiler/emitter.cpp b/inkcpp_compiler/emitter.cpp index 6c7d99a0..39e27fd3 100644 --- a/inkcpp_compiler/emitter.cpp +++ b/inkcpp_compiler/emitter.cpp @@ -8,55 +8,51 @@ namespace ink::compiler::internal { - void emitter::start(int ink_version, compilation_results* results) - { - // store - _ink_version = ink_version; - set_results(results); - - // reset - _container_map.clear(); - _max_container_index = 0; - - // initialize - initialize(); - } +void emitter::start(int ink_version, compilation_results* results) +{ + // store + _ink_version = ink_version; + set_results(results); - void emitter::finish(container_t max_container_index) - { - // store max index - _max_container_index = max_container_index; + // reset + _container_map.clear(); + _max_container_index = 0; - // finalize - finalize(); - } + // initialize + initialize(); +} - void emitter::add_start_to_container_map(uint32_t offset, container_t index) - { - if (_container_map.rbegin() != _container_map.rend()) - { - if (_container_map.rbegin()->first > offset) - { - warn() << "Container map written out of order. Wrote container at offset " - << offset << " after container with offset " << _container_map.rbegin()->first << std::flush; - } - } +void emitter::finish(container_t max_container_index) +{ + // store max index + _max_container_index = max_container_index; - _container_map.push_back(std::make_pair(offset, index)); - setContainerIndex(index); - } + // finalize + finalize(); +} - void emitter::add_end_to_container_map(uint32_t offset, container_t index) - { - if (_container_map.rbegin() != _container_map.rend()) - { - if (_container_map.rbegin()->first > offset) - { - warn() << "Container map written out of order. Wrote container at offset " - << offset << " after container with offset " << _container_map.rbegin()->first << std::flush; - } +void emitter::add_start_to_container_map(uint32_t offset, container_t index) +{ + if (_container_map.rbegin() != _container_map.rend()) { + if (_container_map.rbegin()->_offset > offset) { + warn() << "Container map written out of order. Wrote container at offset " << offset + << " after container with offset " << _container_map.rbegin()->_offset << std::flush; } + } + + _container_map.push_back({offset, index}); + setContainerIndex(index); +} - _container_map.push_back(std::make_pair(offset, index)); +void emitter::add_end_to_container_map(uint32_t offset, container_t index) +{ + if (_container_map.rbegin() != _container_map.rend()) { + if (_container_map.rbegin()->_offset > offset) { + warn() << "Container map written out of order. Wrote container at offset " << offset + << " after container with offset " << _container_map.rbegin()->_offset << std::flush; + } } + + _container_map.push_back({offset, index}); } +} // namespace ink::compiler::internal diff --git a/inkcpp_compiler/emitter.h b/inkcpp_compiler/emitter.h index ae1458d4..fa3eec21 100644 --- a/inkcpp_compiler/emitter.h +++ b/inkcpp_compiler/emitter.h @@ -9,96 +9,104 @@ #include "command.h" #include "system.h" #include "reporter.h" +#include "header.h" #include #include namespace ink::compiler::internal { - class list_data; +class list_data; - // Abstract base class for emitters which write ink commands to a file - class emitter : public reporter - { - public: - virtual ~emitter() { } +// Abstract base class for emitters which write ink commands to a file +class emitter : public reporter +{ +public: + virtual ~emitter() {} - // starts up the emitter (and calls initialize) - void start(int ink_version, compilation_results* results = nullptr); + // starts up the emitter (and calls initialize) + void start(int ink_version, compilation_results* results = nullptr); - // tells the emitter compilation is done (and calls finalize) - void finish(container_t max_container_index); + // tells the emitter compilation is done (and calls finalize) + void finish(container_t max_container_index); - // start a container - virtual uint32_t start_container(int index_in_parent, const std::string& name) = 0; + // start a container + virtual uint32_t start_container(int index_in_parent, const std::string& name) = 0; - // ends a container - virtual uint32_t end_container() = 0; + // ends a container + virtual uint32_t end_container() = 0; - // checks if _root contains a container named name to check - // if name is in valid internal function name - // @return number of arguments functions takes (arity) - // @retval -1 if the function was not found - virtual int function_container_arguments(const std::string& name) = 0; + // checks if _root contains a container named name to check + // if name is in valid internal function name + // @return number of arguments functions takes (arity) + // @retval -1 if the function was not found + virtual int function_container_arguments(const std::string& name) = 0; - // Writes a command with an optional payload - virtual void write_raw(Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, ink::size_t payload_size = 0) = 0; + // Writes a command with an optional payload + virtual void write_raw( + Command command, CommandFlag flag = CommandFlag::NO_FLAGS, const char* payload = nullptr, + ink::size_t payload_size = 0 + ) = 0; - // Writes a command with a path as the payload - virtual void write_path(Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false) = 0; + // Writes a command with a path as the payload + virtual void write_path( + Command command, CommandFlag flag, const std::string& path, bool useCountIndex = false + ) = 0; - // Writes a command with a variable as the payload - virtual void write_variable(Command command, CommandFlag flag, const std::string& name) = 0; + // Writes a command with a variable as the payload + virtual void write_variable(Command command, CommandFlag flag, const std::string& name) = 0; - // Writes a command with a string payload - virtual void write_string(Command command, CommandFlag flag, const std::string& string) = 0; + // Writes a command with a string payload + virtual void write_string(Command command, CommandFlag flag, const std::string& string) = 0; - // write a command with a list payload - virtual void write_list(Command commmand, CommandFlag flag, const std::vector& list) = 0; + // write a command with a list payload + virtual void write_list(Command commmand, CommandFlag flag, const std::vector& list) + = 0; - // Callback for nop commands - virtual void handle_nop(int index_in_parent) = 0; + // Callback for nop commands + virtual void handle_nop(int index_in_parent) = 0; - // adds a fallthrough divert - virtual uint32_t fallthrough_divert() = 0; + // adds a fallthrough divert + virtual uint32_t fallthrough_divert() = 0; - // Patches a fallthrough divert at the given position to divert to the current position - virtual void patch_fallthroughs(uint32_t position) = 0; + // Patches a fallthrough divert at the given position to divert to the current position + virtual void patch_fallthroughs(uint32_t position) = 0; - // Adds a container start marker to the container map - void add_start_to_container_map(uint32_t offset, container_t index); + // Adds a container start marker to the container map + void add_start_to_container_map(uint32_t offset, container_t index); - // Adds a container end marker to the container map - void add_end_to_container_map(uint32_t offset, container_t index); + // Adds a container end marker to the container map + void add_end_to_container_map(uint32_t offset, container_t index); - // add list definitions - virtual void set_list_meta(const list_data& lists_defs) = 0; + // add list definitions + virtual void set_list_meta(const list_data& lists_defs) = 0; - // Helpers - template - void write(Command command, const T& param, CommandFlag flag = CommandFlag::NO_FLAGS) - { - static_assert(sizeof(T) == 4, "Parameters must be 4 bytes long"); - write_raw(command, flag, (const char*)(¶m), sizeof(T)); - } + // Helpers + template + void write(Command command, const T& param, CommandFlag flag = CommandFlag::NO_FLAGS) + { + static_assert(sizeof(T) == 4, "Parameters must be 4 bytes long"); + write_raw(command, flag, ( const char* ) (¶m), sizeof(T)); + } - protected: - // Initialize (clear state, get ready for a new file) - virtual void initialize() = 0; +protected: + // Initialize (clear state, get ready for a new file) + virtual void initialize() = 0; - // Finalize (do any post processing necessary) - virtual void finalize() = 0; + // Finalize (do any post processing necessary) + virtual void finalize() = 0; - // Set container index for visit tracking - virtual void setContainerIndex(container_t index) = 0; + // Set container index for visit tracking + virtual void setContainerIndex(container_t index) = 0; - protected: - typedef std::vector> container_map; +protected: + using container_map_t = ink::internal::container_map_t; + typedef std::vector container_map; - // container map - container_map _container_map; - container_t _max_container_index; + // container map + container_map _container_map; + container_t _max_container_index; - // ink version - int _ink_version; - }; -} + // ink version + int _ink_version; +}; +} // namespace ink::compiler::internal diff --git a/inkcpp_test/UTF8.cpp b/inkcpp_test/UTF8.cpp index 81b03b3e..e6cf34d3 100644 --- a/inkcpp_test/UTF8.cpp +++ b/inkcpp_test/UTF8.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -18,7 +19,7 @@ SCENARIO("a story supports UTF-8", "[utf-8]") auto ink = story::from_file("UTF8Story.bin"); runner thread = ink->new_runner(); - std::ifstream demoFile("ink/UTF-8-demo.txt"); + std::ifstream demoFile(INK_TEST_RESOURCE_DIR "UTF-8-demo.txt"); if (!demoFile.is_open()) { throw std::runtime_error("cannot open UTF-8 demo file"); } diff --git a/shared/private/header.h b/shared/private/header.h index aac8a541..43383f15 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -7,40 +7,113 @@ #pragma once #include "system.h" +#include "command.h" -namespace ink::internal { - - struct header { - static header parse_header(const char* data); - - template - static T swap_bytes(const T& value) { - char data[sizeof(T)]; - for (int i = 0; i < sizeof(T); ++i) { - data[i] = reinterpret_cast(&value)[sizeof(T)-1-i]; - } - return *reinterpret_cast(data); - } - list_flag read_list_flag(const char*& ptr) const { - list_flag result = *reinterpret_cast(ptr); - ptr += sizeof(list_flag); - if (endien == ink::internal::header::endian_types::differ) { - result.flag = swap_bytes(result.flag); - result.list_id = swap_bytes(result.list_id); - } - return result; - } - - enum class endian_types: uint16_t { - none = 0, - same = 0x0001, - differ = 0x0100 - } endien = endian_types::none; - uint32_t ink_version_number = 0; - uint32_t ink_bin_version_number = 0; - static constexpr size_t Size = ///< actual data size of Header, - /// because padding of struct may - /// differ between platforms - sizeof(uint16_t) + 2 * sizeof(uint32_t); - }; -} +namespace ink::internal +{ + +struct header { + + static constexpr uint32_t InkBinMagic = ('I' << 24) | ('N' << 16) | ('K' << 8) | 'B'; + static constexpr uint32_t InkBinMagic_Differ = ('B' << 24) | ('K' << 16) | ('N' << 8) | 'I'; + static constexpr uint32_t Alignment = 16; + + uint32_t ink_bin_magic = InkBinMagic; + uint16_t ink_version_number = 0; + uint16_t ink_bin_version_number = 0; + + enum class endian_types : uint8_t { + none, + same, + differ + }; + + constexpr endian_types endian() const + { + switch (ink_bin_magic) { + case InkBinMagic: return endian_types::same; + case InkBinMagic_Differ: return endian_types::differ; + default: return endian_types::none; + } + } + + bool verify() const; + + struct section_t { + uint32_t _start = 0; + uint32_t _bytes = 0; + + void setup(uint32_t& offset, uint32_t bytes) + { + _start = (offset + Alignment - 1) & ~(Alignment - 1); + _bytes = bytes; + offset = _start + _bytes; + } + }; + + // File section sizes. Each section is aligned to Alignment + section_t _strings; + section_t _list_meta; + section_t _lists; + section_t _containers; + section_t _container_map; + section_t _container_hash; + section_t _instructions; +}; + +// One entry in the container hash. Used to translate paths into story locations. +struct container_hash_t { + // Hash of the container's path string. + hash_t _hash; + + // Offset to the start of this container. + uint32_t _offset; + + uint32_t key() const { return _hash; } + + bool operator<(const container_hash_t& other) const { return _hash < other._hash; } +}; + +// One entry in the container map. Used to work out which container a story location is in. +struct container_map_t { + // Offset to the start of this container's instructions. + uint32_t _offset; + + // Container index. + container_t _id; + + uint32_t key() const { return _offset; } + + bool operator<(const container_map_t& other) const { return _offset < other._offset; } +}; + +// One entry in the container data. Describes containers. +struct container_data_t { + /// Parent container, or ~0 if this is the root. + // TODO: Pack into 28 with explicit invalid container_t, since we expect fewer containers than + // instructions. + container_t _parent; + + /// Container flags (saves looking up via instruction data) + uint32_t _flags : 4; + + /// Instruction offset to the start instruction (enter marker) of this container. + uint32_t _start_offset : 28; + + /// Instruction offset to the end instruction (leave marker) of this container + uint32_t _end_offset; + + /// Container hash. + uint32_t _hash; + + /// Check to see if the instruction offset is part of the instructions for this container. Note + /// that this is inclusive not exclusive. + bool contains(uint32_t offset) const { return offset >= _start_offset && offset <= _end_offset; } + + /// Check to see if this is a knot container. + bool knot() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_IS_KNOT); } + + /// Check to see if this is a container which tracks visits. + bool visit() const { return _flags & uint8_t(CommandFlag::CONTAINER_MARKER_TRACK_VISITS); } +}; +} // namespace ink::internal diff --git a/shared/public/system.h b/shared/public/system.h index 25040751..d8a417d9 100644 --- a/shared/public/system.h +++ b/shared/public/system.h @@ -115,6 +115,13 @@ struct list_flag { bool operator!=(const list_flag& o) const { return ! (*this == o); } }; +inline list_flag read_list_flag(const char*& ptr) +{ + list_flag result = *reinterpret_cast(ptr); + ptr += sizeof(list_flag); + return result; +} + /** value of an unset list_flag */ constexpr list_flag null_flag{-1, -1}; /** value representing an empty list */ diff --git a/shared/public/version.h b/shared/public/version.h index db81dc29..62ea2faa 100644 --- a/shared/public/version.h +++ b/shared/public/version.h @@ -9,6 +9,6 @@ #include "system.h" namespace ink { -constexpr uint32_t InkBinVersion = 1; ///< Supportet version of ink.bin files +constexpr uint32_t InkBinVersion = 2; ///< Supportet version of ink.bin files constexpr uint32_t InkVersion = 21; ///< Supported version of ink.json files };