/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "utilities/parser.h" #include "utilities/Globals.h" #include "utilities/Logs.h" #include "scene/scenenodegroups.h" /* MaSzyna EU07 locomotive simulator parser Copyright (C) 2003 TOLARIS */ ///////////////////////////////////////////////////////////////////////////////////////////////////// // cParser -- generic class for parsing text data. namespace { inline std::array makeBreakTable(const char *brk) { std::array arr{}; for (unsigned char c : std::string_view(brk ? brk : "")) { arr[c] = true; } return arr; } inline char toLowerChar(char c) { return static_cast(std::tolower(static_cast(c))); } inline bool startsWithBOM(const std::string &s) { return s.size() >= 3 && static_cast(s[0]) == 0xEF && static_cast(s[1]) == 0xBB && static_cast(s[2]) == 0xBF; } std::atomic g_parser_file_cache_depth { 0 }; bool g_parser_file_cache_enabled { false }; std::mutex g_parser_file_cache_mutex; std::unordered_map> g_parser_file_cache; struct parser_file_cache_metrics { std::uint64_t open_attempts { 0 }; std::uint64_t disk_reads { 0 }; std::uint64_t cache_hits { 0 }; std::uint64_t cache_misses { 0 }; std::uint64_t failed_opens { 0 }; std::uint64_t bytes_read { 0 }; std::uint64_t untracked_path_calls { 0 }; std::uint64_t zero_copy_opens { 0 }; std::chrono::steady_clock::duration open_read_time {}; std::unordered_map path_counts; void reset() { *this = {}; } }; parser_file_cache_metrics g_parser_file_cache_metrics; class parser_memory_streambuf final : public std::streambuf { public: explicit parser_memory_streambuf(std::shared_ptr data) : data_(std::move(data)) { } protected: int underflow() override { if (!data_ || pos_ >= data_->size()) { return traits_type::eof(); } return static_cast((*data_)[pos_]); } int uflow() override { if (!data_ || pos_ >= data_->size()) { return traits_type::eof(); } return static_cast((*data_)[pos_++]); } std::streampos seekoff(std::streamoff off, std::ios_base::seekdir dir, std::ios_base::openmode which) override { if (!data_ || ((which & std::ios_base::in) == 0)) { return std::streampos(std::streamoff(-1)); } auto const size = static_cast(data_->size()); std::streamoff newpos = 0; switch (dir) { case std::ios_base::beg: newpos = off; break; case std::ios_base::cur: newpos = static_cast(pos_) + off; break; case std::ios_base::end: newpos = size + off; break; default: return std::streampos(std::streamoff(-1)); } if (newpos < 0) { newpos = 0; } if (newpos > size) { newpos = size; } pos_ = static_cast(newpos); return std::streampos(newpos); } std::streampos seekpos(std::streampos pos, std::ios_base::openmode which) override { return seekoff(std::streamoff(pos), std::ios_base::beg, which); } private: std::shared_ptr data_; std::size_t pos_ { 0 }; }; class parser_memory_istream final : public std::istream { public: explicit parser_memory_istream(std::shared_ptr data) : std::istream(nullptr) , buffer_(std::move(data)) { rdbuf(&buffer_); } private: parser_memory_streambuf buffer_; }; std::shared_ptr ParserFileCacheMakeMemoryStream(std::shared_ptr const &Content) { return std::make_shared(Content); } std::string ParserFileCacheKey(std::string const &Filename) { if (Filename.empty()) { return Filename; } try { auto path = std::filesystem::path(Filename); if (path.is_relative()) { path = std::filesystem::absolute(path); } auto key = path.lexically_normal().generic_string(); #ifdef _WIN32 for (auto &character : key) { auto const value = static_cast(character); if (value < 128) { character = static_cast(std::tolower(value)); } } #endif return key; } catch (std::filesystem::filesystem_error const &) { auto key = Filename; std::replace(key.begin(), key.end(), '\\', '/'); return key; } } double ParserFileCacheDurationMilliseconds(std::chrono::steady_clock::duration const Duration) { return std::chrono::duration(Duration).count(); } std::string ParserFileCacheFormatMilliseconds(std::chrono::steady_clock::duration const Duration) { std::ostringstream output; output << std::fixed << std::setprecision(3) << ParserFileCacheDurationMilliseconds(Duration); return output.str(); } void ParserFileCacheTrackPath(std::string const &Key) { if (auto lookup = g_parser_file_cache_metrics.path_counts.find(Key); lookup != g_parser_file_cache_metrics.path_counts.end()) { ++lookup->second; } else if (g_parser_file_cache_metrics.path_counts.size() < 4096) { g_parser_file_cache_metrics.path_counts.emplace(Key, 1); } else { ++g_parser_file_cache_metrics.untracked_path_calls; } } std::vector ParserFileCacheReport() { std::vector report; report.emplace_back( std::string("Parser file scope: mode=") + (g_parser_file_cache_enabled ? "cached" : "direct") + ", opens=" + std::to_string(g_parser_file_cache_metrics.open_attempts) + ", disk_reads=" + std::to_string(g_parser_file_cache_metrics.disk_reads) + ", hits=" + std::to_string(g_parser_file_cache_metrics.cache_hits) + ", misses=" + std::to_string(g_parser_file_cache_metrics.cache_misses) + ", failed=" + std::to_string(g_parser_file_cache_metrics.failed_opens) + ", bytes_read=" + std::to_string(g_parser_file_cache_metrics.bytes_read) + ", open_read_ms=" + ParserFileCacheFormatMilliseconds(g_parser_file_cache_metrics.open_read_time) + ", zero_copy_opens=" + std::to_string(g_parser_file_cache_metrics.zero_copy_opens) + ", untracked_paths=" + std::to_string(g_parser_file_cache_metrics.untracked_path_calls) + ", cached_files=" + std::to_string(g_parser_file_cache.size())); std::vector> top_paths; top_paths.reserve(g_parser_file_cache_metrics.path_counts.size()); for (auto const &entry : g_parser_file_cache_metrics.path_counts) { if (entry.second > 1) { top_paths.emplace_back(entry); } } constexpr std::size_t top_path_limit = 5; auto const top_count = std::min(top_path_limit, top_paths.size()); std::partial_sort( top_paths.begin(), top_paths.begin() + top_count, top_paths.end(), [](auto const &Left, auto const &Right) { if (Left.second != Right.second) { return Left.second > Right.second; } return Left.first < Right.first; }); for (std::size_t index = 0; index < top_count; ++index) { report.emplace_back(" " + std::to_string(top_paths[index].second) + "x " + top_paths[index].first); } return report; } void ParserFileCacheLogReport(std::vector const &Report) { for (auto const &line : Report) { WriteLog(line); } } std::shared_ptr ParserFileCacheOpenDirect(std::string const &Path, std::string const &Key) { { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.open_attempts; ParserFileCacheTrackPath(Key); } auto const timestart = std::chrono::steady_clock::now(); auto stream = std::make_shared(Path, std::ios_base::binary); if (stream->fail()) { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.failed_opens; g_parser_file_cache_metrics.open_read_time += std::chrono::steady_clock::now() - timestart; return stream; } std::error_code error; auto const size = std::filesystem::file_size(Path, error); { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.disk_reads; if (!error) { g_parser_file_cache_metrics.bytes_read += size; } g_parser_file_cache_metrics.open_read_time += std::chrono::steady_clock::now() - timestart; } return stream; } std::shared_ptr ParserFileCacheOpenCached(std::string const &Path, std::string const &Key) { { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.open_attempts; ParserFileCacheTrackPath(Key); if (auto lookup = g_parser_file_cache.find(Key); lookup != g_parser_file_cache.end()) { ++g_parser_file_cache_metrics.cache_hits; ++g_parser_file_cache_metrics.zero_copy_opens; return ParserFileCacheMakeMemoryStream(lookup->second); } ++g_parser_file_cache_metrics.cache_misses; } auto const timestart = std::chrono::steady_clock::now(); auto file = std::ifstream(Path, std::ios_base::binary); if (file.fail()) { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.failed_opens; g_parser_file_cache_metrics.open_read_time += std::chrono::steady_clock::now() - timestart; return std::make_shared(std::move(file)); } file.seekg(0, std::ios_base::end); auto const size = file.tellg(); file.seekg(0, std::ios_base::beg); if (size < 0) { { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.failed_opens; g_parser_file_cache_metrics.open_read_time += std::chrono::steady_clock::now() - timestart; } WriteLog("Parser file cache: falling back to direct read for \"" + Path + "\""); return std::make_shared(Path, std::ios_base::binary); } auto content = std::make_shared(); if (size > 0) { content->resize(static_cast(size)); file.read(content->data(), static_cast(size)); } if (file.bad() || ((size > 0) && (file.gcount() != static_cast(size)))) { { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.failed_opens; g_parser_file_cache_metrics.open_read_time += std::chrono::steady_clock::now() - timestart; } WriteLog("Parser file cache: falling back to direct read for \"" + Path + "\""); return std::make_shared(Path, std::ios_base::binary); } { std::lock_guard lock(g_parser_file_cache_mutex); ++g_parser_file_cache_metrics.disk_reads; g_parser_file_cache_metrics.bytes_read += static_cast(content->size()); g_parser_file_cache_metrics.open_read_time += std::chrono::steady_clock::now() - timestart; g_parser_file_cache.emplace(Key, content); } return ParserFileCacheMakeMemoryStream(content); } std::shared_ptr ParserFileCacheOpen(std::string const &Path) { if (g_parser_file_cache_depth.load(std::memory_order_acquire) == 0) { return std::make_shared(Path, std::ios_base::binary); } bool cache_enabled = false; std::string key; { std::lock_guard lock(g_parser_file_cache_mutex); if (g_parser_file_cache_depth.load(std::memory_order_relaxed) == 0) { return std::make_shared(Path, std::ios_base::binary); } cache_enabled = g_parser_file_cache_enabled; key = cache_enabled ? ParserFileCacheKey(Path) : Path; } if (cache_enabled) { return ParserFileCacheOpenCached(Path, key); } return ParserFileCacheOpenDirect(Path, key); } std::atomic g_parser_metrics_depth { 0 }; struct parser_cpu_metrics { std::atomic read_token_calls { 0 }; std::atomic tokens_read { 0 }; std::atomic get_tokens_calls { 0 }; std::atomic includes_opened { 0 }; std::atomic parsers_file_buffer { 0 }; std::atomic parsers_text_buffer { 0 }; std::atomic tokenize_ns { 0 }; std::atomic read_token_ns { 0 }; std::atomic convert_ns { 0 }; std::atomic get_tokens_ns { 0 }; std::atomic include_ns { 0 }; std::atomic fp_parse_calls { 0 }; std::atomic skip_until_calls { 0 }; std::atomic fast_skip_tokens { 0 }; std::atomic skip_until_ns { 0 }; void reset() { read_token_calls.store(0, std::memory_order_relaxed); tokens_read.store(0, std::memory_order_relaxed); get_tokens_calls.store(0, std::memory_order_relaxed); includes_opened.store(0, std::memory_order_relaxed); parsers_file_buffer.store(0, std::memory_order_relaxed); parsers_text_buffer.store(0, std::memory_order_relaxed); tokenize_ns.store(0, std::memory_order_relaxed); read_token_ns.store(0, std::memory_order_relaxed); convert_ns.store(0, std::memory_order_relaxed); get_tokens_ns.store(0, std::memory_order_relaxed); include_ns.store(0, std::memory_order_relaxed); fp_parse_calls.store(0, std::memory_order_relaxed); skip_until_calls.store(0, std::memory_order_relaxed); fast_skip_tokens.store(0, std::memory_order_relaxed); skip_until_ns.store(0, std::memory_order_relaxed); } }; namespace { bool parse_token_as_float(std::string const &Token, float &Value) { if (Token.empty()) { return false; } char *end = nullptr; Value = std::strtof(Token.c_str(), &end); return end != Token.c_str(); } bool parse_token_as_double(std::string const &Token, double &Value) { if (Token.empty()) { return false; } char *end = nullptr; Value = std::strtod(Token.c_str(), &end); return end != Token.c_str(); } } // namespace parser_cpu_metrics g_parser_cpu_metrics; std::mutex g_parser_metrics_mutex; bool ParserMetricsActive() { return g_parser_metrics_depth.load(std::memory_order_acquire) > 0; } void ParserMetricsAddNs(std::atomic &Counter, std::chrono::steady_clock::duration const Duration) { Counter.fetch_add( static_cast(std::chrono::duration_cast(Duration).count()), std::memory_order_relaxed); } class ParserMetricsReadTokenTimer { public: explicit ParserMetricsReadTokenTimer(bool const Enabled) : m_enabled(Enabled) { if (m_enabled) { g_parser_cpu_metrics.read_token_calls.fetch_add(1, std::memory_order_relaxed); m_start = std::chrono::steady_clock::now(); } } ~ParserMetricsReadTokenTimer() { if (m_enabled) { ParserMetricsAddNs(g_parser_cpu_metrics.read_token_ns, std::chrono::steady_clock::now() - m_start); } } private: bool const m_enabled; std::chrono::steady_clock::time_point m_start {}; }; class ParserMetricsTokenizeTimer { public: explicit ParserMetricsTokenizeTimer(bool const Enabled) : m_enabled(Enabled) { if (m_enabled) { m_start = std::chrono::steady_clock::now(); } } ~ParserMetricsTokenizeTimer() { if (m_enabled) { ParserMetricsAddNs(g_parser_cpu_metrics.tokenize_ns, std::chrono::steady_clock::now() - m_start); } } private: bool const m_enabled; std::chrono::steady_clock::time_point m_start {}; }; class ParserMetricsGetTokensTimer { public: explicit ParserMetricsGetTokensTimer(bool const Enabled) : m_enabled(Enabled) { if (m_enabled) { g_parser_cpu_metrics.get_tokens_calls.fetch_add(1, std::memory_order_relaxed); m_start = std::chrono::steady_clock::now(); } } ~ParserMetricsGetTokensTimer() { if (m_enabled) { ParserMetricsAddNs(g_parser_cpu_metrics.get_tokens_ns, std::chrono::steady_clock::now() - m_start); } } private: bool const m_enabled; std::chrono::steady_clock::time_point m_start {}; }; class ParserMetricsIncludeTimer { public: explicit ParserMetricsIncludeTimer(bool const Enabled) : m_enabled(Enabled) { if (m_enabled) { m_start = std::chrono::steady_clock::now(); } } ~ParserMetricsIncludeTimer() { if (m_enabled) { ParserMetricsAddNs(g_parser_cpu_metrics.include_ns, std::chrono::steady_clock::now() - m_start); } } private: bool const m_enabled; std::chrono::steady_clock::time_point m_start {}; }; double ParserMetricsNanosecondsToMilliseconds(std::uint64_t const Nanoseconds) { return static_cast(Nanoseconds) / 1'000'000.0; } std::string ParserMetricsFormatMilliseconds(std::uint64_t const Nanoseconds) { std::ostringstream output; output << std::fixed << std::setprecision(3) << ParserMetricsNanosecondsToMilliseconds(Nanoseconds); return output.str(); } std::vector ParserMetricsReport() { auto const read_token_calls = g_parser_cpu_metrics.read_token_calls.load(std::memory_order_relaxed); auto const tokens_read = g_parser_cpu_metrics.tokens_read.load(std::memory_order_relaxed); auto const get_tokens_calls = g_parser_cpu_metrics.get_tokens_calls.load(std::memory_order_relaxed); auto const includes_opened = g_parser_cpu_metrics.includes_opened.load(std::memory_order_relaxed); auto const parsers_file_buffer = g_parser_cpu_metrics.parsers_file_buffer.load(std::memory_order_relaxed); auto const parsers_text_buffer = g_parser_cpu_metrics.parsers_text_buffer.load(std::memory_order_relaxed); auto const tokenize_ns = g_parser_cpu_metrics.tokenize_ns.load(std::memory_order_relaxed); auto const read_token_ns = g_parser_cpu_metrics.read_token_ns.load(std::memory_order_relaxed); auto const convert_ns = g_parser_cpu_metrics.convert_ns.load(std::memory_order_relaxed); auto const get_tokens_ns = g_parser_cpu_metrics.get_tokens_ns.load(std::memory_order_relaxed); auto const include_ns = g_parser_cpu_metrics.include_ns.load(std::memory_order_relaxed); auto const fp_parse_calls = g_parser_cpu_metrics.fp_parse_calls.load(std::memory_order_relaxed); auto const skip_until_calls = g_parser_cpu_metrics.skip_until_calls.load(std::memory_order_relaxed); auto const fast_skip_tokens = g_parser_cpu_metrics.fast_skip_tokens.load(std::memory_order_relaxed); auto const skip_until_ns = g_parser_cpu_metrics.skip_until_ns.load(std::memory_order_relaxed); auto const cpu_total_ns = tokenize_ns + convert_ns + include_ns; std::string tokens_per_sec = "n/a"; if (cpu_total_ns > 0 && tokens_read > 0) { auto const rate = static_cast(tokens_read) / (static_cast(cpu_total_ns) / 1'000'000'000.0); tokens_per_sec = to_string(rate, 0); } std::vector report; report.emplace_back( std::string("Parser CPU scope: read_token=") + std::to_string(read_token_calls) + ", tokens=" + std::to_string(tokens_read) + ", get_tokens=" + std::to_string(get_tokens_calls) + ", includes=" + std::to_string(includes_opened) + ", parsers_file=" + std::to_string(parsers_file_buffer) + ", parsers_text=" + std::to_string(parsers_text_buffer) + ", tokenize_ms=" + ParserMetricsFormatMilliseconds(tokenize_ns) + ", read_token_ms=" + ParserMetricsFormatMilliseconds(read_token_ns) + ", convert_ms=" + ParserMetricsFormatMilliseconds(convert_ns) + ", get_tokens_ms=" + ParserMetricsFormatMilliseconds(get_tokens_ns) + ", include_ms=" + ParserMetricsFormatMilliseconds(include_ns) + ", fp_parse=" + std::to_string(fp_parse_calls) + ", skip_until=" + std::to_string(skip_until_calls) + ", fast_skip_tokens=" + std::to_string(fast_skip_tokens) + ", skip_until_ms=" + ParserMetricsFormatMilliseconds(skip_until_ns) + ", tokens_per_sec=" + tokens_per_sec); return report; } void ParserMetricsLogReport(std::vector const &Report) { for (auto const &line : Report) { WriteLog(line); } } } // namespace namespace parser_metrics { bool active() { return ParserMetricsActive(); } convert_timer::convert_timer() { enabled = active(); if (enabled) { start = std::chrono::steady_clock::now(); } } convert_timer::~convert_timer() { if (enabled) { ParserMetricsAddNs(g_parser_cpu_metrics.convert_ns, std::chrono::steady_clock::now() - start); } } } // namespace parser_metrics // constructors cParser::cParser(std::string const &Stream, buffertype const Type, std::string Path, bool const Loadtraction, std::vector Parameters, bool allowRandom) : allowRandomIncludes(allowRandom), LoadTraction(Loadtraction), mPath(Path) { if (ParserMetricsActive()) { if (Type == buffertype::buffer_FILE) { g_parser_cpu_metrics.parsers_file_buffer.fetch_add(1, std::memory_order_relaxed); } else { g_parser_cpu_metrics.parsers_text_buffer.fetch_add(1, std::memory_order_relaxed); } } // store to calculate sub-sequent includes from relative path if (Type == buffertype::buffer_FILE) { mFile = Stream; } // reset pointers and attach proper type of buffer switch (Type) { case buffer_FILE: { Path.append(Stream); mStream = ParserFileCacheOpen(Path); // content of *.inc files is potentially grouped together if ((Stream.size() >= 4) && (ToLower(Stream.substr(Stream.size() - 4)) == ".inc")) { mIncFile = true; scene::Groups.create(); } break; } case buffer_TEXT: { mStream = std::make_shared(Stream); break; } default: { break; } } // calculate stream size if (mStream) { if (true == mStream->fail()) { ErrorLog("Failed to open file \"" + Path + "\""); } else { mSize = mStream->rdbuf()->pubseekoff(0, std::ios_base::end); mStream->rdbuf()->pubseekoff(0, std::ios_base::beg); mLine = 1; } } // set parameter set if one was provided if (false == Parameters.empty()) { parameters.swap(Parameters); } } // destructor cParser::~cParser() { if (true == mIncFile) { // wrap up the node group holding content of processed file scene::Groups.close(); } } ParserFileCacheScope::ParserFileCacheScope() { std::lock_guard lock(g_parser_file_cache_mutex); if (g_parser_file_cache_depth.load(std::memory_order_relaxed) == 0) { g_parser_file_cache.clear(); g_parser_file_cache_enabled = Global.ScenarioParserFileCache; g_parser_file_cache_metrics.reset(); } g_parser_file_cache_depth.fetch_add(1, std::memory_order_release); } ParserFileCacheScope::~ParserFileCacheScope() { end(); } void ParserFileCacheScope::end() { if (false == m_active) { return; } std::vector report; { std::lock_guard lock(g_parser_file_cache_mutex); auto const depth = g_parser_file_cache_depth.load(std::memory_order_relaxed); if (depth <= 1) { report = ParserFileCacheReport(); g_parser_file_cache_depth.store(0, std::memory_order_release); g_parser_file_cache.clear(); g_parser_file_cache_enabled = false; g_parser_file_cache_metrics.reset(); } else { g_parser_file_cache_depth.store(depth - 1, std::memory_order_release); } m_active = false; } ParserFileCacheLogReport(report); } ParserMetricsScope::ParserMetricsScope() { std::lock_guard lock(g_parser_metrics_mutex); if (g_parser_metrics_depth.load(std::memory_order_relaxed) == 0) { g_parser_cpu_metrics.reset(); } g_parser_metrics_depth.fetch_add(1, std::memory_order_release); } ParserMetricsScope::~ParserMetricsScope() { end(); } void ParserMetricsScope::end() { if (false == m_active) { return; } std::vector report; { std::lock_guard lock(g_parser_metrics_mutex); auto const depth = g_parser_metrics_depth.load(std::memory_order_relaxed); if (depth <= 1) { report = ParserMetricsReport(); g_parser_metrics_depth.store(0, std::memory_order_release); g_parser_cpu_metrics.reset(); } else { g_parser_metrics_depth.store(depth - 1, std::memory_order_release); } m_active = false; } ParserMetricsLogReport(report); } template <> glm::vec3 cParser::getToken(bool const ToLower, char const *Break) { // NOTE: this specialization ignores default arguments getTokens(3, false, "\n\r\t ,;[]"); glm::vec3 output; *this >> output.x >> output.y >> output.z; return output; }; template <> cParser &cParser::operator>>(std::string &Right) { if (true == this->tokens.empty()) { return *this; } parser_metrics::convert_timer timer; Right = this->tokens.front(); this->tokens.pop_front(); return *this; } template <> cParser &cParser::operator>>(bool &Right) { if (true == this->tokens.empty()) { return *this; } parser_metrics::convert_timer timer; Right = ((this->tokens.front() == "true") || (this->tokens.front() == "yes") || (this->tokens.front() == "1")); this->tokens.pop_front(); return *this; } template <> bool cParser::getToken(bool const ToLower, const char *Break) { auto const token = getToken(true, Break); return ((token == "true") || (token == "yes") || (token == "1")); } // methods cParser &cParser::autoclear(bool const Autoclear) { m_autoclear = Autoclear; if (mIncludeParser) { mIncludeParser->autoclear(Autoclear); } return *this; } bool cParser::getTokens(unsigned int Count, bool ToLower, const char *Break) { auto const metrics_active = ParserMetricsActive(); ParserMetricsGetTokensTimer const get_tokens_timer(metrics_active); if (true == m_autoclear) { // legacy parser behaviour tokens.clear(); } /* if (LoadTraction==true) trtest="niemaproblema"; //wczytywać else trtest="x"; //nie wczytywać */ /* int i; this->str(""); this->clear(); */ std::string token; for (unsigned int i = tokens.size(); i < Count; ++i) { readToken(token, ToLower, Break); if (token.empty()) { // no more tokens break; } tokens.emplace_back(std::move(token)); // collect parameters /* if (i == 0) this->str(token); else { std::string temp = this->str(); temp.append("\n"); temp.append(token); this->str(temp); } */ } if (tokens.size() < Count) return false; else return true; } bool cParser::readTokenFloat(float &Value, bool ToLower, const char *Break) { std::string token; readToken(token, ToLower, Break); if (false == parse_token_as_float(token, Value)) { return false; } if (ParserMetricsActive()) { g_parser_cpu_metrics.fp_parse_calls.fetch_add(1, std::memory_order_relaxed); } return true; } bool cParser::readTokenDouble(double &Value, bool ToLower, const char *Break) { std::string token; readToken(token, ToLower, Break); if (false == parse_token_as_double(token, Value)) { return false; } if (ParserMetricsActive()) { g_parser_cpu_metrics.fp_parse_calls.fetch_add(1, std::memory_order_relaxed); } return true; } void cParser::readNextToken(std::string &Token, bool ToLower, const char *Break) { readToken(Token, ToLower, Break); } std::string cParser::readTokenFromStream(bool ToLower, const char *Break) { auto const metrics_active = ParserMetricsActive(); ParserMetricsTokenizeTimer const tokenize_timer(metrics_active); std::string token; token.reserve(64); const auto breakTable = makeBreakTable(Break); char c = 0; while (token.empty() && mStream->peek() != EOF) { while (mStream->peek() != EOF) { // idk why but with mStream->get(c) not all cars are loaded c = static_cast(mStream->get()); if (c == '\n') { ++mLine; } const unsigned char uc = static_cast(c); if (breakTable[uc]) { // separator ends token (or continues skipping if token empty) if (!token.empty()) break; continue; } if (ToLower) c = toLowerChar(c); token.push_back(c); if (findQuotes(token)) { continue; // glue quoted content } if (skipComments && trimComments(token)) { break; // don't glue tokens separated by comment } } } return token; } std::string cParser::readTokenFromStreamFast(bool ToLower, const char *Break) { std::string token; token.reserve(32); const auto breakTable = makeBreakTable(Break); char c = 0; while (token.empty() && mStream->peek() != EOF) { while (mStream->peek() != EOF) { c = static_cast(mStream->get()); if (c == '\n') { ++mLine; } const unsigned char uc = static_cast(c); if (breakTable[uc]) { if (!token.empty()) { break; } continue; } if (ToLower) { c = toLowerChar(c); } token.push_back(c); if (token.find('\"') != std::string::npos && findQuotes(token)) { continue; } if (token.find('/') != std::string::npos && skipComments && trimComments(token)) { break; } } } return token; } void cParser::readTokenForSkip(std::string &out, bool ToLower, const char *Break) { if (mIncludeParser) { mIncludeParser->readTokenForSkip(out, ToLower, Break); if (out.empty()) { mIncludeParser = nullptr; readTokenForSkip(out, ToLower, Break); } return; } out = readTokenFromStreamFast(ToLower, Break); stripFirstTokenBOM(out, ToLower, Break); if (out.find('(') != std::string::npos) { substituteParameters(out, ToLower); } if (handleIncludeIfPresent(out, ToLower, Break)) { if (ParserMetricsActive()) { g_parser_cpu_metrics.fast_skip_tokens.fetch_add(1, std::memory_order_relaxed); } return; } if (ParserMetricsActive() && false == out.empty()) { g_parser_cpu_metrics.fast_skip_tokens.fetch_add(1, std::memory_order_relaxed); } } void cParser::skipUntilKeyword(std::string const &Keyword, bool ToLower, const char *Break) { auto const metrics_active = ParserMetricsActive(); auto const timestart = metrics_active ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point {}; if (metrics_active) { g_parser_cpu_metrics.skip_until_calls.fetch_add(1, std::memory_order_relaxed); } std::string token; std::string keyword_match = Keyword; if (ToLower) { keyword_match = ::ToLower(keyword_match); } for (;;) { readTokenForSkip(token, ToLower, Break); if (token.empty() || token == keyword_match) { break; } } if (metrics_active) { ParserMetricsAddNs(g_parser_cpu_metrics.skip_until_ns, std::chrono::steady_clock::now() - timestart); } } void cParser::stripFirstTokenBOM(std::string& token, bool ToLower, const char* Break) { if (!mFirstToken) return; mFirstToken = false; if (startsWithBOM(token)) { token.erase(0, 3); } // if first "token" was standalone BOM, read the next real token (avoid recursion) while (token.empty() && mStream->peek() != EOF) { readToken(token, ToLower, Break); // readToken will not re-enter BOM stripping because mFirstToken is now false break; } } void cParser::substituteParameters(std::string& token, bool ToLower) { if (parameters.empty()) return; // Replace occurrences of "(pN)" anywhere in token. // Keep behavior: if missing parameter -> "none". size_t pos = 0; while ((pos = token.find("(p", pos)) != std::string::npos) { const size_t close = token.find(')', pos); if (close == std::string::npos) break; // malformed -> stop like old behavior (it would substr weirdly) const std::string idxStr = token.substr(pos + 2, close - (pos + 2)); token.erase(pos, (close - pos) + 1); const size_t nr = static_cast(std::atoi(idxStr.c_str())); const std::string repl = (nr >= 1 && (nr - 1) < parameters.size()) ? parameters[nr - 1] : std::string("none"); const size_t insertPos = pos; token.insert(insertPos, repl); if (ToLower) { // Lowercase only what we inserted (same intent as original) for (size_t i = insertPos; i < insertPos + repl.size(); ++i) { token[i] = toLowerChar(token[i]); } } pos = insertPos + repl.size(); // continue after inserted text } } void cParser::skipIncludeBlock() { // mimic original: while token != "end" readToken(true) std::string t; do { readToken(t, true); } while (t != "end" && !t.empty()); } void cParser::startIncludeFromParser(cParser& srcParser, bool ToLower, std::string includefile) { auto const metrics_active = ParserMetricsActive(); ParserMetricsIncludeTimer const include_timer(metrics_active); replace_slashes(includefile); const bool allowTraction = (true == LoadTraction) || ((false == contains(includefile, "tr/")) && (false == contains(includefile, "tra/"))); if (!allowTraction) { // skip include block until "end" (original behavior in token-mode include) skipIncludeBlock(); return; } const bool isTerrain = contains(includefile, "_ter.scm"); if (isTerrain && true == Global.file_binary_terrain_state) { WriteLog("SBT found, ignoring: " + includefile); readParameters(srcParser); // preserve original side-effect: still consume parameters return; } if (Global.ParserLogIncludes) { if (isTerrain) WriteLog("including terrain: " + includefile); else { // WriteLog("including: " + includefile); } } if (metrics_active) { g_parser_cpu_metrics.includes_opened.fetch_add(1, std::memory_order_relaxed); } mIncludeParser = std::make_shared( includefile, /*buffer_FILE*/ static_cast(/*buffer_FILE*/ 0), mPath, LoadTraction, readParameters(srcParser) ); mIncludeParser->allowRandomIncludes = allowRandomIncludes; mIncludeParser->autoclear(m_autoclear); if (mIncludeParser->mSize <= 0) { ErrorLog("Bad include: can't open file \"" + includefile + "\""); } } bool cParser::handleIncludeIfPresent(std::string& token, bool ToLower, const char* Break) { // token-mode include: token == "include" if (expandIncludes && token == "include") { std::string includefile; if (allowRandomIncludes) includefile = deserialize_random_set(*this); else readToken(includefile, ToLower); startIncludeFromParser(*this, ToLower, std::move(includefile)); // after processing include, return next token from current parser readToken(token, ToLower, Break); return true; } // line-mode HACK: Break == "\n\r" and line begins with "include" if ((std::strcmp(Break, "\n\r") == 0) && token.compare(0, 7, "include") == 0) { cParser includeparser(token.substr(7)); std::string includefile; if (allowRandomIncludes) includefile = deserialize_random_set(includeparser); else includeparser.readToken(includefile, ToLower); startIncludeFromParser(includeparser, ToLower, std::move(includefile)); readToken(token, ToLower, Break); return true; } return false; } void cParser::readToken(std::string &out, bool ToLower, const char *Break) { auto const metrics_active = ParserMetricsActive(); ParserMetricsReadTokenTimer const read_token_timer(metrics_active); if (mIncludeParser) { mIncludeParser->readToken(out, ToLower, Break); if (out.empty()) { mIncludeParser = nullptr; out = readTokenFromStream(ToLower, Break); } } else { out = readTokenFromStream(ToLower, Break); } stripFirstTokenBOM(out, ToLower, Break); substituteParameters(out, ToLower); handleIncludeIfPresent(out, ToLower, Break); if (metrics_active && false == out.empty()) { g_parser_cpu_metrics.tokens_read.fetch_add(1, std::memory_order_relaxed); } } std::vector cParser::readParameters(cParser &Input) { std::vector includeparameters; std::string parameter; Input.readToken(parameter, false); // w parametrach nie zmniejszamy while ((parameter.empty() == false) && (parameter != "end")) { includeparameters.emplace_back(parameter); Input.readToken(parameter, false); } return includeparameters; } std::string cParser::readQuotes(char const Quote) { // read the stream until specified char or stream end std::string token; char c{0}; bool escaped = false; while (mStream->get(c)) { // get all chars until the quote mark if (escaped) { escaped = false; } else { if (c == '\\') { escaped = true; continue; } else if (c == Quote) break; } if (c == '\n') ++mLine; // update line counter token += c; } return token; } void cParser::skipComment(std::string const &Endmark) { // pobieranie znaków aż do znalezienia znacznika końca std::string input; char c{0}; auto const endmarksize = Endmark.size(); while (mStream->get(c)) { if (c == '\n') { // update line counter ++mLine; } input += c; if (input == Endmark) // szukanie znacznika końca break; if (input.size() >= endmarksize) { // keep the read text short, to avoid pointless string re-allocations on longer comments input = input.substr(1); } } return; } bool cParser::findQuotes(std::string &String) { if (String.back() == '\"') { String.pop_back(); String += readQuotes(); return true; } return false; } bool cParser::trimComments(std::string &String) { for (auto const &comment : mComments) { if (String.size() < comment.first.size()) { continue; } if (String.compare(String.size() - comment.first.size(), comment.first.size(), comment.first) == 0) { skipComment(comment.second); String.resize(String.rfind(comment.first)); return true; } } return false; } void cParser::injectString(const std::string &str) { if (mIncludeParser) { mIncludeParser->injectString(str); } else { mIncludeParser = std::make_shared(str, buffer_TEXT, "", LoadTraction, std::vector(), allowRandomIncludes); mIncludeParser->autoclear(m_autoclear); } } int cParser::getProgress() const { return static_cast(mStream->rdbuf()->pubseekoff(0, std::ios_base::cur) * 100 / mSize); } int cParser::getFullProgress() const { int progress = getProgress(); if (mIncludeParser) return progress + ((100 - progress) * (mIncludeParser->getProgress()) / 100); else return progress; } std::size_t cParser::countTokens(std::string const &Stream, std::string Path) { return cParser(Stream, buffer_FILE, Path).count(); } std::size_t cParser::count() { std::string token; size_t count{0}; do { token.clear(); readToken(token, false); ++count; } while (false == token.empty()); return count - 1; } void cParser::addCommentStyle(std::string const &Commentstart, std::string const &Commentend) { mComments.insert(commentmap::value_type(Commentstart, Commentend)); } // returns name of currently open file, or empty string for text type stream std::string cParser::Name() const { if (mIncludeParser) { return mIncludeParser->Name(); } else { return mPath + mFile; } } // returns number of currently processed line std::size_t cParser::Line() const { if (mIncludeParser) { return mIncludeParser->Line(); } else { return mLine; } } int cParser::LineMain() const { return mIncludeParser ? -1 : mLine; }