diff --git a/src/ifcparse/FileReader.cpp b/src/ifcparse/FileReader.cpp index e16d26e641..8f9243dbf2 100644 --- a/src/ifcparse/FileReader.cpp +++ b/src/ifcparse/FileReader.cpp @@ -57,6 +57,7 @@ struct FullBufferImpl final : FileReader::Impl { if (pos >= buf_.size()) throw std::out_of_range("get out of range"); return buf_[pos]; } + const char* contiguous_data() const override { return buf_.data(); } }; struct PagedFileImpl final : FileReader::Impl { @@ -166,6 +167,8 @@ struct MMapImpl final : FileReader::Impl { if (pos >= size_) throw std::out_of_range("get out of range"); return map_.data()[pos]; } + + const char* contiguous_data() const override { return map_.data(); } }; #endif @@ -237,6 +240,7 @@ IfcParse::FileReader::FileReader(const std::string& fn) : cursor_(0) { impl_ = std::make_shared(fn); + init_contiguous_(); } IfcParse::FileReader::FileReader(const std::string& fn, const mmap_tag&) @@ -244,6 +248,7 @@ IfcParse::FileReader::FileReader(const std::string& fn, const mmap_tag&) { #ifdef USE_MMAP impl_ = std::make_shared(fn); + init_contiguous_(); #else (void)fn; throw std::runtime_error("IfcParse::FileReader: mmap_tag specified but library not compiled with USE_MMAP"); @@ -254,18 +259,31 @@ IfcParse::FileReader::FileReader(const caller_fed_tag&) : cursor_(0) { impl_ = std::make_shared(); + init_contiguous_(); } IfcParse::FileReader::FileReader(const std::string& content, const caller_fed_tag&) { impl_ = std::make_shared(); impl_->pushNextPage(content); + init_contiguous_(); } IfcParse::FileReader::FileReader(const std::string& fn, size_t page_size, size_t page_capacity) : cursor_(0) { impl_ = std::make_shared(fn, page_size, page_capacity); + init_contiguous_(); +} + +void IfcParse::FileReader::init_contiguous_() { + contiguous_ = impl_->contiguous_data(); + contiguous_size_ = contiguous_ != nullptr ? impl_->size() : 0; +} + +char IfcParse::FileReader::peek_paged_() const { + if (cursor_ >= impl_->size()) throw std::out_of_range("peek at EOF"); + return impl_->get(cursor_); } FileReader FileReader::clone() const { @@ -274,25 +292,6 @@ FileReader FileReader::clone() const { return c; } -void FileReader::seek(size_t pos) { - if (pos > impl_->size()) throw std::out_of_range("seek out of range"); - cursor_ = pos; -} - -size_t FileReader::tell() const { return cursor_; } - -size_t FileReader::size() const { return impl_->size(); } - -char FileReader::peek() const { - if (cursor_ >= impl_->size()) throw std::out_of_range("peek at EOF"); - return impl_->get(cursor_); -} - -void FileReader::increment(size_t n) { - if (cursor_ + n > impl_->size()) throw std::out_of_range("increment past EOF"); - cursor_ += n; -} - void IfcParse::FileReader::pushNextPage(const std::string& data) { impl_->pushNextPage(data); @@ -308,19 +307,3 @@ void IfcParse::FileReader::dropPages(size_t up_to_pos) impl_->dropPages(up_to_pos); } -bool IfcParse::FileReader::eof() const -{ - return cursor_ >= impl_->size(); -} - -char IfcParse::FileReader::read() -{ - auto c = peek(); - increment(1); - return c; -} - -char IfcParse::FileReader::get(size_t offset) const -{ - return impl_->get(offset); -} diff --git a/src/ifcparse/FileReader.h b/src/ifcparse/FileReader.h index 164c5826a6..b6000ff409 100644 --- a/src/ifcparse/FileReader.h +++ b/src/ifcparse/FileReader.h @@ -74,21 +74,33 @@ public: /// \brief Seek to an absolute byte position. /// \throws std::out_of_range if pos > size(). - void seek(size_t pos); + void seek(size_t pos) { + if (pos > size()) throw std::out_of_range("seek out of range"); + cursor_ = pos; + } /// \brief Return the current cursor position. - size_t tell() const; + size_t tell() const { return cursor_; } /// \brief Total file size in bytes. - size_t size() const; + size_t size() const { return contiguous_ ? contiguous_size_ : impl_->size(); } /// \brief Peek the byte at the current cursor. /// \throws std::out_of_range at EOF. - char peek() const; + char peek() const { + if (contiguous_) { + if (cursor_ >= contiguous_size_) throw std::out_of_range("peek at EOF"); + return contiguous_[cursor_]; + } + return peek_paged_(); + } /// \brief Advance the cursor by n bytes (default 1). /// \throws std::out_of_range if advancing crosses EOF. - void increment(size_t n = 1); + void increment(size_t n = 1) { + if (cursor_ + n > size()) throw std::out_of_range("increment past EOF"); + cursor_ += n; + } /// \brief Push the next sequential page (pushed backend only). /// \param data Contents of the page. @@ -102,18 +114,35 @@ public: /// \brief Returns true if the cursor is at or beyond the end of available data. /// For the pushed backend, EOF means all pushed bytes have been consumed. - bool eof() const; + bool eof() const { return cursor_ >= size(); } /// \brief Equivalent of peek() followed by increment(1) - char read(); + char read() { + auto c = peek(); + increment(1); + return c; + } /// \brief Equivalent of peek() followed by increment(1) - char get(size_t offset) const; + char get(size_t offset) const { + if (contiguous_) { + if (offset >= contiguous_size_) throw std::out_of_range("get out of range"); + return contiguous_[offset]; + } + return impl_->get(offset); + } + + /// \brief Entire backing buffer, indexed by absolute file offset, when the backend + /// is stable contiguous memory (full-buffer or mmap); nullptr otherwise. + const char* contiguous_buffer() const { return contiguous_; } struct Impl { virtual ~Impl() = default; virtual size_t size() const = 0; virtual char get(size_t pos) const = 0; + /// \brief Backends with a stable in-memory buffer return it here so that the + /// per-character accessors can bypass virtual dispatch; default nullptr. + virtual const char* contiguous_data() const { return nullptr; } /// \brief Backend may support pushing pages; default throws. virtual void pushNextPage(const std::string&) { throw std::logic_error("push_next_page: backend does not support pushed mode"); @@ -126,6 +155,11 @@ public: private: std::shared_ptr impl_; size_t cursor_ = 0; + const char* contiguous_ = nullptr; + size_t contiguous_size_ = 0; + + void init_contiguous_(); + char peek_paged_() const; }; } // namespace IfcParse diff --git a/src/ifcparse/IfcCharacterDecoder.cpp b/src/ifcparse/IfcCharacterDecoder.cpp index 019499a49e..453d8a2cf6 100644 --- a/src/ifcparse/IfcCharacterDecoder.cpp +++ b/src/ifcparse/IfcCharacterDecoder.cpp @@ -89,6 +89,35 @@ IfcCharacterDecoder::~IfcCharacterDecoder() { namespace { std::string read_string(IfcParse::FileReader& stream_, Logger& logger, IfcParse::IfcCharacterDecoder::ConversionMode mode, char substitution_character) { + // Fast path for strings of printable ASCII without escapes or doubled + // apostrophes, which decode identically in all conversion modes + if (const char* buf = stream_.contiguous_buffer()) { + const size_t begin = stream_.tell(); + const size_t n = stream_.size(); + size_t pos = begin; + bool simple = false; + while (pos < n) { + const auto character = static_cast(buf[pos]); + if (character == '\'') { + simple = true; + break; + } + if (character == '\\' || character < 0x20 || character >= 0x7f) { + break; + } + ++pos; + } + if (simple && pos + 1 < n && buf[pos + 1] != '\'') { + std::string result; + result.reserve(pos - begin + 2); + result.push_back('\''); + result.append(buf + begin, pos - begin); + result.push_back('\''); + stream_.seek(pos + 1); + return result; + } + } + std::u32string builder_; unsigned int parse_state = 0; diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index bc6df697af..66d5ae452f 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -190,6 +190,38 @@ Token IfcSpfLexer::Next() { if (character == '\'') { // If a string is encountered defer processing to the IfcCharacterDecoder str = *decoder_; + } else if (const char* buf = stream->contiguous_buffer()) { + // Bulk scan on contiguous storage to avoid per-character accessor calls + const size_t size = stream->size(); + size_t end = pos + 1; + bool has_whitespace = false; + while (end < size) { + character = buf[end]; + if (character == '(' || + character == ')' || + character == '=' || + character == ',' || + character == ';' || + character == '/') { + break; + } + if (character == ' ' || character == '\r' || character == '\n' || character == '\t') { + has_whitespace = true; + } + ++end; + } + if (!has_whitespace) { + str.assign(buf + pos, end - pos); + } else { + str.clear(); + for (size_t i = pos; i < end; ++i) { + character = buf[i]; + if (!(character == ' ' || character == '\r' || character == '\n' || character == '\t')) { + str.push_back(character); + } + } + } + stream->seek(end); } else { str.assign(&character, 1); @@ -219,6 +251,34 @@ Token IfcSpfLexer::Next() { // void IfcSpfLexer::TokenString(size_t offset, std::string& buffer) { buffer.clear(); + if (const char* buf = stream->contiguous_buffer()) { + const size_t size = stream->size(); + size_t pos = offset; + while (pos < size) { + char character = buf[pos]; + if (!buffer.empty() && (character == '(' || + character == ')' || + character == '=' || + character == ',' || + character == ';' || + character == '/')) { + break; + } + ++pos; + if (character == ' ' || + character == '\r' || + character == '\n' || + character == '\t') { + continue; + } + if (character == '\'') { + buffer = decoder_->get(pos); + break; + } + buffer.push_back(character); + } + return; + } auto local_stream = *this->stream; local_stream.seek(offset); while (!local_stream.eof()) {