From ddc8cc679727657db861192457cdbf27706350ef Mon Sep 17 00:00:00 2001 From: SirLynix Date: Fri, 17 Nov 2023 11:57:05 +0100 Subject: [PATCH] Core: Rework ParameterFile Improve parsing and usage --- include/Nazara/Core/ParameterFile.hpp | 86 ++++-- include/Nazara/Core/ParameterFile.inl | 271 +++++++++++------- src/Nazara/Core/ParameterFile.cpp | 248 +++++++++++----- .../Formats/PipelinePassListLoader.cpp | 42 ++- 4 files changed, 409 insertions(+), 238 deletions(-) diff --git a/include/Nazara/Core/ParameterFile.hpp b/include/Nazara/Core/ParameterFile.hpp index a20625f4e..80bc8787f 100644 --- a/include/Nazara/Core/ParameterFile.hpp +++ b/include/Nazara/Core/ParameterFile.hpp @@ -16,34 +16,41 @@ #include #include #include +#include namespace Nz { + class ParameterFileSection; class Stream; + // TOOD: Move to NazaraUtils + template + concept Functor = IsFunctor_v; + class NAZARA_CORE_API ParameterFile { + friend ParameterFileSection; + public: inline ParameterFile(Stream& stream); ParameterFile(const ParameterFile&) = delete; ParameterFile(ParameterFile&&) = delete; ~ParameterFile() = default; - template void Block(Args&&... args); - template void Handle(Args&&... args); + template void Parse(Args&&... args); ParameterFile& operator=(const ParameterFile&) = delete; ParameterFile& operator=(ParameterFile&&) = delete; - struct Keyword + struct Identifier { - std::string str; + std::string value; }; - struct Array_t {}; + struct List_t {}; struct OptionalBlock_t {}; - static constexpr Array_t Array{}; + static constexpr List_t List{}; static constexpr OptionalBlock_t OptionalBlock{}; private: @@ -55,28 +62,59 @@ namespace Nz ValueHandler handler; }; + struct ClosingCurlyBracket {}; + struct EndOfStream {}; + struct OpenCurlyBracket {}; + + struct String + { + std::string value; //< has to be a string because of text parsing + }; + + using Token = std::variant; + + Token Advance(); + void ConsumeChar(std::size_t count = 1); + template void HandleInner(Args&&... args); + Token& PeekToken(); + char PeekCharacter(std::size_t advance = 1); + template T& Peek(); + template T Read(); + template T ReadValue(); + + // Handlers + template ValueHandler BuildBlockHandler(FixedVector* keyValues, T* value, Rest&&... rest); + template ValueHandler BuildBlockHandler(FixedVector* keyValues, T&& handler, Rest&&... rest); + template ValueHandler BuildBlockHandler(FixedVector* keyValues, void(O::* method)(Args...), O* object, Rest&&... rest); + template ValueHandler BuildBlockHandler(FixedVector* keyValues, FunctionRef handler, Rest&&... rest); + template void BuildKeyValues(FixedVector* keyValues, K&& key, Rest&&... rest); + template ValueHandler GetSingleHandler(V&& value, Rest&&... rest); + template static std::string_view BuildBlockKey(T&& key); - template static ValueHandler BuildBlockHandler(ParameterFile& file, ValueHandler handler); - template static ValueHandler BuildBlockHandler(ParameterFile& file, T* value); - template static ValueHandler BuildBlockHandler(ParameterFile& file, T&& handler, std::enable_if_t>* = nullptr); - template static ValueHandler BuildBlockHandler(ParameterFile& file, FunctionRef handler); - template static void BuildKeyValues(ParameterFile& file, FixedVector& keyValues, K&& key, V&& value, Rest&&... rest); - template static ValueHandler GetSingleHandler(ParameterFile& file, V&& value, Rest&&... rest); + template static constexpr bool ShouldIgnoreImpl = std::is_same_v || std::is_same_v; + template static constexpr bool ShouldIgnore = ShouldIgnoreImpl>; - template void HandleInner(std::string_view listEnd, Args&&... args); - - template static T ReadValue(ParameterFile& file); - - template static constexpr bool ShouldIgnoreImpl = std::is_same_v || std::is_same_v; - template static constexpr bool ShouldIgnore = ShouldIgnoreImpl>>; - - bool EnsureLine(bool peek = false); - std::string ReadKeyword(bool peek = false); - std::string ReadString(); - - std::string m_currentLine; + std::size_t m_bufferOffset; + std::string m_buffer; Stream& m_stream; + Token m_nextToken; + unsigned int m_currentLine; + }; + + class ParameterFileSection + { + friend ParameterFile; + + public: + ParameterFileSection(const ParameterFileSection&) = default; + + template void Block(Args&&... args); + + private: + inline ParameterFileSection(ParameterFile& file); + + ParameterFile& m_file; }; } diff --git a/include/Nazara/Core/ParameterFile.inl b/include/Nazara/Core/ParameterFile.inl index 91040cb69..6ec9afd9e 100644 --- a/include/Nazara/Core/ParameterFile.inl +++ b/include/Nazara/Core/ParameterFile.inl @@ -3,7 +3,9 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include +#include #include +#include #include #include #include @@ -12,71 +14,172 @@ namespace Nz { inline ParameterFile::ParameterFile(Stream& stream) : - m_stream(stream) + m_bufferOffset(0), + m_stream(stream), + m_currentLine(1) { } template - void ParameterFile::Block(Args&&... args) + void ParameterFile::Parse(Args&&... args) { - using Types = TypeListTransform, std::remove_const>, std::decay>; + HandleInner(std::forward(args)...); + } + + template + void ParameterFile::HandleInner(Args&&... args) + { + using Types = TypeListTransform, std::decay>; + constexpr bool IsArray = TypeListHas; - constexpr bool IsOptionalBlock = TypeListHas; - if constexpr (IsOptionalBlock) + if constexpr (IsArray) { - std::string nextKeyword = ReadKeyword(true); - if (nextKeyword != "{") - return; + ValueHandler handler = GetSingleHandler(std::forward(args)...); + + for (;;) + { + if (std::holds_alternative(PeekToken())) + break; + + handler(*this); + } } - - std::string beginToken = ReadKeyword(); - if (beginToken != "{") - throw std::runtime_error(Format("expected \"{{\" token, got {}", beginToken)); - - HandleInner("}", std::forward(args)...); - - std::string endToken = ReadKeyword(); - if (endToken != "}") - throw std::runtime_error(Format("expected \"}}\" token, got {}", endToken)); - } - - template - void ParameterFile::Handle(Args&&... args) - { - HandleInner({}, std::forward(args)...); - } - - template - void ParameterFile::BuildKeyValues(ParameterFile& file, FixedVector& keyValues, K&& key, V&& value, Rest&&... rest) - { - if constexpr (ShouldIgnore) - return BuildKeyValues(file, keyValues, std::forward(value), std::forward(rest)...); else { - auto& keyValue = keyValues.emplace_back(); - keyValue.key = BuildBlockKey(std::forward(key)); - keyValue.handler = BuildBlockHandler(file, std::forward(value)); + FixedVector keys; + BuildKeyValues(&keys, std::forward(args)...); - if constexpr (sizeof...(Rest) > 0) - BuildKeyValues(file, keyValues, std::forward(rest)...); + for (;;) + { + if (std::holds_alternative(PeekToken())) + break; + + Identifier nextIdentifier = Read(); + + auto it = std::find_if(keys.begin(), keys.end(), [&](const KeyValue& keyValue) { return keyValue.key == nextIdentifier.value; }); + if (it == keys.end()) + throw std::runtime_error(Format("unexpected keyword \"{}\"", nextIdentifier.value)); + + const ValueHandler& handler = it->handler; + handler(*this); + } } } - template - auto ParameterFile::GetSingleHandler(ParameterFile& file, V&& value, Rest&&... rest) -> ValueHandler + template + auto ParameterFile::Peek() -> T& + { + Token& token = PeekToken(); + if (!std::holds_alternative(token)) + throw std::runtime_error(Format("expected {} on line {}", TypeName(), m_currentLine)); + + return std::get(token); + } + + template + auto ParameterFile::Read() -> T + { + Token token = std::move(m_nextToken); + if (!std::holds_alternative(token)) + throw std::runtime_error(Format("expected {} on line {}", TypeName(), m_currentLine)); + + Advance(); + return std::get(token); + } + + template + T ParameterFile::ReadValue() + { + if constexpr (std::is_same_v) + return Read().value; + else if constexpr (std::is_same_v) + return Read(); + else if constexpr (std::is_same_v) + return ParameterFileSection{ *this }; + else + static_assert(AlwaysFalse(), "unsupported type"); + } + + template + void ParameterFile::BuildKeyValues(FixedVector* keyValues, K&& key, Rest&&... rest) + { + if constexpr (ShouldIgnore) + return BuildKeyValues(keyValues, std::forward(rest)...); + else + { + assert(keyValues); + auto& keyValue = keyValues->emplace_back(); + keyValue.key = BuildBlockKey(std::forward(key)); + keyValue.handler = BuildBlockHandler(keyValues, std::forward(rest)...); + } + } + + template + auto ParameterFile::GetSingleHandler(V&& value, Rest&&... rest) -> ValueHandler { if constexpr (ShouldIgnore) { static_assert(sizeof...(Rest) > 0, "expected a handler"); - return GetSingleHandler(file, std::forward(rest)...); + return GetSingleHandler(std::forward(rest)...); } else { static_assert(sizeof...(Rest) == 0, "expected a single handler"); - return BuildBlockHandler(file, std::forward(value)); + return BuildBlockHandler(static_cast*>(nullptr), std::forward(value)); } } + template + auto ParameterFile::BuildBlockHandler(FixedVector* keyValues, T* value, Rest&&... rest) -> ValueHandler + { + ValueHandler valueHandler = [value](ParameterFile& file) + { + *value = file.ReadValue(); + }; + + if constexpr (sizeof...(Rest) > 0) + BuildKeyValues(keyValues, std::forward(rest)...); + + return valueHandler; + } + + template + auto ParameterFile::BuildBlockHandler(FixedVector* keyValues, T&& handler, Rest&&... rest) -> ValueHandler + { + using FunctionType = typename FunctionTraits::FuncType; + return BuildBlockHandler(keyValues, FunctionRef(handler), std::forward(rest)...); + } + + template + auto ParameterFile::BuildBlockHandler(FixedVector* keyValues, void(O::* method)(Args...), O* object, Rest&&... rest) -> ValueHandler + { + ValueHandler valueHandler = [object, method](ParameterFile& file) + { + std::tuple args{ object, file.ReadValue()... }; + std::apply(method, std::move(args)); + }; + + if constexpr (sizeof...(Rest) > 0) + BuildKeyValues(keyValues, std::forward(rest)...); + + return valueHandler; + } + + template + auto ParameterFile::BuildBlockHandler(FixedVector* keyValues, FunctionRef handler, Rest&&... rest) -> ValueHandler + { + ValueHandler valueHandler = [handler](ParameterFile& file) + { + std::tuple args{ file.ReadValue()... }; + std::apply(handler, std::move(args)); + }; + + if constexpr (sizeof...(Rest) > 0) + BuildKeyValues(keyValues, std::forward(rest)...); + + return valueHandler; + } + template std::string_view ParameterFile::BuildBlockKey(T&& key) { @@ -84,87 +187,35 @@ namespace Nz return std::string_view(std::forward(key)); } - template - auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, ValueHandler handler) -> ValueHandler + + inline ParameterFileSection::ParameterFileSection(ParameterFile& file) : + m_file(file) { - return handler; } - template - auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, T* value) -> ValueHandler + template + void ParameterFileSection::Block(Args&&... args) { - return [value](ParameterFile& file) + using Types = TypeListTransform, std::decay>; + + constexpr bool IsOptionalBlock = TypeListHas; + if constexpr (IsOptionalBlock) { - *value = ReadValue(file); - }; - } + if (!std::holds_alternative(m_file.PeekToken())) + return; - template - auto ParameterFile::BuildBlockHandler(ParameterFile& file, T&& handler, std::enable_if_t>*) -> ValueHandler - { - using FunctionType = typename FunctionTraits::FuncType; - return BuildBlockHandler(file, FunctionRef(handler)); - } - - template - auto ParameterFile::BuildBlockHandler(ParameterFile& /*file*/, FunctionRef handler) -> ValueHandler - { - return [handler](ParameterFile& file) - { - std::tuple args{ ReadValue(file)... }; - std::apply(handler, std::move(args)); - }; - } - - template - void ParameterFile::HandleInner(std::string_view listEnd, Args&&... args) - { - using Types = TypeListTransform, std::remove_const>, std::decay>; - constexpr bool IsArray = TypeListHas; - - if constexpr (IsArray) - { - ValueHandler handler = GetSingleHandler(*this, std::forward(args)...); - - for (;;) - { - std::string nextKeyword = ReadKeyword(true); - if (nextKeyword == listEnd) - break; - - handler(*this); - } + m_file.Advance(); } else { - FixedVector keys; - BuildKeyValues(*this, keys, std::forward(args)...); - - for (;;) - { - std::string nextKeyword = ReadKeyword(true); - if (nextKeyword == listEnd) - break; - - auto it = std::find_if(keys.begin(), keys.end(), [nextKeyword = ReadKeyword()](const KeyValue& keyValue) { return keyValue.key == nextKeyword; }); - if (it == keys.end()) - throw std::runtime_error(Format("unexpected keyword \"{}\"", nextKeyword)); - - const ValueHandler& handler = it->handler; - handler(*this); - } + if (!std::holds_alternative(m_file.Advance())) + throw std::runtime_error(Format("expected OpenCurlyBracket on line {}", m_file.m_currentLine)); } - } - template - T ParameterFile::ReadValue(ParameterFile& file) - { - if constexpr (std::is_same_v) - return file.ReadString(); - else if constexpr (std::is_same_v) - return Keyword{ file.ReadKeyword() }; - else - static_assert(AlwaysFalse(), "unsupported type"); + m_file.HandleInner(std::forward(args)...); + + if (!std::holds_alternative(m_file.Advance())) + throw std::runtime_error(Format("expected ClosingCurlyBracket on line {}", m_file.m_currentLine)); } } diff --git a/src/Nazara/Core/ParameterFile.cpp b/src/Nazara/Core/ParameterFile.cpp index 93b8681d5..df77b24fa 100644 --- a/src/Nazara/Core/ParameterFile.cpp +++ b/src/Nazara/Core/ParameterFile.cpp @@ -9,106 +9,196 @@ namespace Nz { - bool ParameterFile::EnsureLine(bool peek) + auto ParameterFile::Advance() -> Token { - while (m_currentLine.find_first_not_of(" \r\t\n") == m_currentLine.npos) + auto IsAlphaNum = [&](char c) { - m_currentLine.clear(); - m_stream.ReadLine(m_currentLine); - if (m_currentLine.empty()) - { - if (!peek) - throw std::runtime_error("unexpected end of file"); + return std::isalnum(c) || c == '_'; + }; - return false; + // Remove processed tokens from buffer + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + m_bufferOffset); + m_bufferOffset = 0; + + Token currentToken = std::move(m_nextToken); + + for (;;) + { + char c = PeekCharacter(0); + if (c == '\0') + { + m_nextToken = EndOfStream{}; + return currentToken; } - } - return true; - } - - std::string ParameterFile::ReadKeyword(bool peek) - { - std::size_t beginOffset; - do - { - if (!EnsureLine(peek)) - return {}; - - beginOffset = m_currentLine.find_first_not_of(" \r\t\n"); - } while (beginOffset == m_currentLine.npos); - - if (m_currentLine[beginOffset] == '"') - throw std::runtime_error("expected a keyword, got a string"); - - std::size_t endOffset = m_currentLine.find_first_of(" \r\t\n", beginOffset + 1); - if (endOffset == m_currentLine.npos) - endOffset = m_currentLine.size(); - - std::string currentToken = std::string(m_currentLine.substr(beginOffset, endOffset - beginOffset)); - if (!peek) - m_currentLine.erase(m_currentLine.begin(), m_currentLine.begin() + endOffset); - - return currentToken; - } - - std::string ParameterFile::ReadString() - { - std::size_t beginOffset; - do - { - EnsureLine(); - beginOffset = m_currentLine.find_first_not_of(" \r\t\n"); - } while (beginOffset == m_currentLine.npos); - - if (m_currentLine[beginOffset] != '"') - throw std::runtime_error("expected a string, got a keyword"); - - std::string str; - for (std::size_t i = beginOffset + 1; i < m_currentLine.size(); ++i) - { - switch (m_currentLine[i]) + switch (c) { - case '\0': - case '\n': + case ' ': + case '\t': case '\r': - throw std::runtime_error("expected a string, got a keyword"); + break; //< Ignore blank spaces - case '"': + case '\n': + m_currentLine++; + break; + + case '{': + ConsumeChar(); + m_nextToken = OpenCurlyBracket{}; + return currentToken; + + case '}': + ConsumeChar(); + m_nextToken = ClosingCurlyBracket{}; + return currentToken; + + case '/': { - m_currentLine.erase(m_currentLine.begin(), m_currentLine.begin() + beginOffset + i); - - return str; - } - - case '\\': - { - i++; - char character; - switch (m_currentLine[i]) + char next = PeekCharacter(); + if (next == '/') { - case 'n': character = '\n'; break; - case 'r': character = '\r'; break; - case 't': character = '\t'; break; - case '"': character = '"'; break; - case '\\': character = '\\'; break; - default: - throw std::runtime_error(Format("unrecognized character {}", character)); + // Line comment + do + { + ConsumeChar(); + next = PeekCharacter(); + } + while (next != '\0' && next != '\n'); + } + else if (next == '*') + { + // Block comment + for (;;) + { + ConsumeChar(); + next = PeekCharacter(); + + if (next == '*') + { + if (PeekCharacter(2) == '/') + { + ConsumeChar(2); + break; + } + } + else if (next == '\n') + m_currentLine++; + else if (next == '\0') + throw std::runtime_error(Format("unfinished block comment on line {}", m_currentLine)); + } } - str.push_back(character); break; } + case '"': + { + // string literal + ConsumeChar(); + + std::string literal; + + char cur; + while ((cur = PeekCharacter(0)) != '"') + { + char character; + switch (cur) + { + case '\0': + case '\n': + case '\r': + throw std::runtime_error(Format("unfinished string on line {}", m_currentLine)); + + case '\\': + { + ConsumeChar(); + char next = PeekCharacter(0); + switch (next) + { + case 'n': character = '\n'; break; + case 'r': character = '\r'; break; + case 't': character = '\t'; break; + case '"': character = '"'; break; + case '\\': character = '\\'; break; + default: + throw std::runtime_error(Format("unrecognized character on line {}", m_currentLine)); + } + break; + } + + default: + character = cur; + break; + } + + literal.push_back(character); + ConsumeChar(); + } + ConsumeChar(); + + m_nextToken = String{ std::move(literal) }; + return currentToken; + } + default: - str.push_back(m_currentLine[i]); + { + if (IsAlphaNum(c)) + { + // Identifier or keyword + std::size_t start = m_bufferOffset; + + while (IsAlphaNum(PeekCharacter())) + ConsumeChar(); + + m_nextToken = Identifier{ m_buffer.substr(start, m_bufferOffset - start) }; + return currentToken; + } + else + throw std::runtime_error(Format("unrecognized token on line {}", m_currentLine)); + } } + + ConsumeChar(); + } + } + + void ParameterFile::ConsumeChar(std::size_t count) + { + assert(m_bufferOffset < m_buffer.size()); + assert(m_bufferOffset + count <= m_buffer.size()); + m_bufferOffset += count; + } + + auto ParameterFile::PeekToken() -> Token& + { + if (std::holds_alternative(m_nextToken)) + Advance(); + + return m_nextToken; + } + + char ParameterFile::PeekCharacter(std::size_t advance) + { + constexpr std::size_t Capacity = 512; + + if NAZARA_UNLIKELY(m_bufferOffset + advance >= m_buffer.size()) + { + if NAZARA_UNLIKELY(m_stream.EndOfStream()) + return '\0'; + + std::size_t prevSize = m_buffer.size(); + m_buffer.resize(prevSize + Capacity); + std::size_t readSize = m_stream.Read(&m_buffer[prevSize], Capacity); + m_buffer.resize(prevSize + readSize); + + if (m_bufferOffset + advance >= m_buffer.size()) + return '\0'; } - throw std::runtime_error("unfinished string"); + return m_buffer[m_bufferOffset]; } // Required for MinGW (it tris to import those symbols, probably a bug) - constexpr ParameterFile::Array_t ParameterFile::Array; + constexpr ParameterFile::List_t ParameterFile::List; constexpr ParameterFile::OptionalBlock_t ParameterFile::OptionalBlock; } diff --git a/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp b/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp index 93a73a099..70fdbed04 100644 --- a/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp +++ b/src/Nazara/Graphics/Formats/PipelinePassListLoader.cpp @@ -26,24 +26,15 @@ namespace Nz::Loaders { std::string finalOutputAttachment; - m_paramFile.Handle( - "passlist", [&](std::string passListName) + m_paramFile.Parse( + "passlist", [&](ParameterFileSection section, std::string passListName) { m_current.emplace(); m_current->passList = std::make_shared(); - m_paramFile.Block( - "attachment", [this](std::string attachmentName) - { - HandleAttachment(std::move(attachmentName)); - }, - "attachmentproxy", [this](std::string proxyName, std::string targetName) - { - HandleAttachmentProxy(std::move(proxyName), std::move(targetName)); - }, - "pass", [this](std::string passName) - { - HandlePass(std::move(passName)); - }, + section.Block( + "attachment", &PassListLoader::HandleAttachment, this, + "attachmentproxy", &PassListLoader::HandleAttachmentProxy, this, + "pass", &PassListLoader::HandlePass, this, "output", &finalOutputAttachment ); } @@ -73,11 +64,11 @@ namespace Nz::Loaders } private: - void HandleAttachment(std::string attachmentName) + void HandleAttachment(ParameterFileSection section, std::string attachmentName) { std::string format; - m_paramFile.Block( + section.Block( "format", &format ); @@ -134,7 +125,7 @@ namespace Nz::Loaders m_current->attachmentsByName.emplace(std::move(proxyName), proxyId); } - void HandlePass(std::string passName) + void HandlePass(ParameterFileSection section, std::string passName) { struct InputOutput { @@ -150,17 +141,18 @@ namespace Nz::Loaders std::vector outputs; std::vector flags; - m_paramFile.Block( + section.Block( "depthstencilinput", &depthstencilInput, "depthstenciloutput", &depthstencilOutput, - "impl", [&](std::string passImpl) + "impl", [&](ParameterFileSection implSection, std::string passImpl) { impl = std::move(passImpl); - m_paramFile.Block(ParameterFile::OptionalBlock, - ParameterFile::Array, [&](ParameterFile::Keyword key, std::string value) - { - implConfig.SetParameter(std::move(key.str), std::move(value)); - }); + implSection.Block(ParameterFile::OptionalBlock, + ParameterFile::List, [&](ParameterFile::Identifier key, std::string value) + { + implConfig.SetParameter(std::move(key.value), std::move(value)); + } + ); }, "input", [&](std::string name, std::string attachment) {