From e866683c9a44136cbcfc5cb424a81fa4d591d1c0 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Thu, 15 Dec 2022 20:19:53 +0100 Subject: [PATCH] GenericDocument: added support for INI config format New script API: * GENERIC_DOCUMENT_OPTION_ALLOW_BRACED_KEYWORDS, //!< Allow INI-like '[keyword]' tokens. * GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_EQUALS, //!< Allow '=' as separator between tokens. * GENERIC_DOCUMENT_OPTION_ALLOW_HASH_COMMENTS //!< Allow comments starting with `#`. * string Terran::getTerrainFileName() * string Terrain::getTerrainFileResourceGroup() 'demo_script.as' extended to allow viewing TERRN2 and TOBJ files. --- .../Script2Game/GenericDocumentClass.h | 5 +- doc/angelscript/Script2Game/TerrainClass.h | 10 ++ resources/scripts/demo_script.as | 112 +++++++++++- .../bindings/GenericFileFormatAngelscript.cpp | 3 + .../scripting/bindings/TerrainAngelscript.cpp | 2 + source/main/terrain/Terrain.cpp | 9 + source/main/terrain/Terrain.h | 2 + source/main/utils/GenericFileFormat.cpp | 167 +++++++++++++++--- source/main/utils/GenericFileFormat.h | 3 + 9 files changed, 290 insertions(+), 23 deletions(-) diff --git a/doc/angelscript/Script2Game/GenericDocumentClass.h b/doc/angelscript/Script2Game/GenericDocumentClass.h index 6487030c3d..55713dfdc8 100644 --- a/doc/angelscript/Script2Game/GenericDocumentClass.h +++ b/doc/angelscript/Script2Game/GenericDocumentClass.h @@ -14,7 +14,10 @@ enum GenericDocumentOptions GENERIC_DOCUMENT_OPTION_ALLOW_SLASH_COMMENTS, //!< Allow comments starting with `//`. GENERIC_DOCUMENT_OPTION_FIRST_LINE_IS_TITLE, //!< First non-empty & non-comment line is a naked string with spaces. GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_COLON, //!< Allow ':' as separator between tokens. - GENERIC_DOCUMENT_OPTION_PARENTHESES_CAPTURE_SPACES //!< If non-empty NAKED string encounters '(', following spaces will be captured until matching ')' is found. + GENERIC_DOCUMENT_OPTION_PARENTHESES_CAPTURE_SPACES, //!< If non-empty NAKED string encounters '(', following spaces will be captured until matching ')' is found. + GENERIC_DOCUMENT_OPTION_ALLOW_BRACED_KEYWORDS, //!< Allow INI-like '[keyword]' tokens. + GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_EQUALS, //!< Allow '=' as separator between tokens. + GENERIC_DOCUMENT_OPTION_ALLOW_HASH_COMMENTS //!< Allow comments starting with `#`. }; /** diff --git a/doc/angelscript/Script2Game/TerrainClass.h b/doc/angelscript/Script2Game/TerrainClass.h index aead42a544..e8c4b166c9 100644 --- a/doc/angelscript/Script2Game/TerrainClass.h +++ b/doc/angelscript/Script2Game/TerrainClass.h @@ -21,6 +21,16 @@ class TerrainClass */ string getTerrainName(); + /** + * @return File name of the terrain definition (TERRN2 format). + */ + string getTerrainFileName(); + + /** + * @return OGRE resource group of the terrain bundle (ZIP/directory under 'mods/') where definition files live. + */ + string getTerrainFileResourceGroup(); + /** * @return GUID (global unique ID) of the terrain, or empty string if not specified. */ diff --git a/resources/scripts/demo_script.as b/resources/scripts/demo_script.as index 845e19810d..d76c8f7d03 100644 --- a/resources/scripts/demo_script.as +++ b/resources/scripts/demo_script.as @@ -40,6 +40,8 @@ CVarClass@ g_sim_state = console.cVarFind("sim_state"); // 0=off, 1=running, 2= CVarClass@ g_mp_state = console.cVarFind("mp_state"); // 0=disabled, 1=connecting, 2=connected, see MpState in Application.h CVarClass@ g_io_arcade_controls = console.cVarFind("io_arcade_controls"); // bool GenericDocumentClass@ g_displayed_document = null; +string g_displayed_doc_filename; +array g_terrain_tobj_files; /* --------------------------------------------------------------------------- @@ -72,6 +74,11 @@ void frameStep(float dt) ImGui::Text("Pro tip: Press '" + inputs.getEventCommandTrimmed(EV_COMMON_CONSOLE_TOGGLE) + "' to open console anytime."); + + // Reset simulation data + @g_displayed_document = null; + g_displayed_doc_filename = ""; + g_terrain_tobj_files.removeRange(0, g_terrain_tobj_files.length()); } else if (g_app_state.getInt() == 2) // simulation { @@ -95,6 +102,8 @@ void frameStep(float dt) ImGui::Text("(terrain edit)"); } + drawTerrainButtons(); + ImGui::TextDisabled("Camera controls:"); ImGui::Text("Change camera: " + inputs.getEventCommandTrimmed(EV_CAMERA_CHANGE)); ImGui::Text("Toggle free camera: " + inputs.getEventCommandTrimmed(EV_CAMERA_FREE_MODE)); @@ -103,6 +112,8 @@ void frameStep(float dt) BeamClass@ actor = game.getCurrentTruck(); if (@actor != null) { + // Actor name and "View document" button + ImGui::PushID("actor"); ImGui::AlignTextToFramePadding(); ImGui::Text("You are driving " + actor.getTruckName()); ImGui::SameLine(); @@ -119,6 +130,7 @@ void frameStep(float dt) if (doc.LoadFromResource(actor.getTruckFileName(), actor.getTruckFileResourceGroup(), flags)) { @g_displayed_document = @doc; + g_displayed_doc_filename = actor.getTruckFileName(); } } } @@ -127,8 +139,10 @@ void frameStep(float dt) if (ImGui::Button("Close document")) { @g_displayed_document = null; + g_displayed_doc_filename = ""; } } + ImGui::PopID(); //"actor" ImGui::TextDisabled("Vehicle controls:"); @@ -192,10 +206,106 @@ void frameStep(float dt) } } +void drawTerrainButtons() +{ + // Terrain name (with "view document" button) + ImGui::PushID("terrn"); + TerrainClass@ terrain = game.getTerrain(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Terrain: " + terrain.getTerrainName()); + ImGui::SameLine(); + if (@g_displayed_document == null) + { + if (ImGui::Button("View document")) + { + GenericDocumentClass@ doc = GenericDocumentClass(); + int flags = GENERIC_DOCUMENT_OPTION_ALLOW_NAKED_STRINGS + | GENERIC_DOCUMENT_OPTION_ALLOW_SLASH_COMMENTS + | GENERIC_DOCUMENT_OPTION_ALLOW_HASH_COMMENTS + | GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_EQUALS + | GENERIC_DOCUMENT_OPTION_ALLOW_BRACED_KEYWORDS; + if (doc.LoadFromResource(terrain.getTerrainFileName(), terrain.getTerrainFileResourceGroup(), flags)) + { + @g_displayed_document = @doc; + g_displayed_doc_filename = terrain.getTerrainFileName(); + + // Fetch TOBJ filenames + if (g_terrain_tobj_files.length() == 0) + { + GenericDocReaderClass@ reader = GenericDocReaderClass(doc); + bool in_section_objects = false; + while (!reader.EndOfFile()) + { + if (reader.GetTokType() == TOKEN_TYPE_KEYWORD && reader.GetTokKeyword().substr(0, 1) == "[") + { + in_section_objects = (reader.GetTokKeyword() == '[Objects]'); + } + else if (reader.GetTokType() == TOKEN_TYPE_STRING && in_section_objects) + { + // Note: in GenericDocument, a text on line start is always a KEYWORD token, + // but KEYWORDs must not contain special characters, + // so file names always decay to strings because of '.'. + g_terrain_tobj_files.insertLast(reader.GetTokString()); + } + reader.MoveNext(); + } + } + } + } + } + else + { + if (ImGui::Button("Close document")) + { + @g_displayed_document = null; + g_displayed_doc_filename = ""; + } + } + + // TOBJ files + ImGui::PushID("tobj"); + for (uint i = 0; i < g_terrain_tobj_files.length(); i++) + { + ImGui::PushID(i); + ImGui::AlignTextToFramePadding(); + ImGui::Bullet(); + ImGui::SameLine(); + ImGui::Text(g_terrain_tobj_files[i]); + ImGui::SameLine(); + if (@g_displayed_document == null) + { + if (ImGui::Button("View document")) + { + GenericDocumentClass@ doc = GenericDocumentClass(); + int flags = GENERIC_DOCUMENT_OPTION_ALLOW_NAKED_STRINGS + | GENERIC_DOCUMENT_OPTION_ALLOW_SLASH_COMMENTS; + if (doc.LoadFromResource(g_terrain_tobj_files[i], terrain.getTerrainFileResourceGroup(), flags)) + { + @g_displayed_document = @doc; + g_displayed_doc_filename = g_terrain_tobj_files[i]; + } + } + } + else + { + if (ImGui::Button("Close document")) + { + @g_displayed_document = null; + g_displayed_doc_filename = ""; + } + } + ImGui::PopID(); // i + } + ImGui::PopID(); //"tobj" + + ImGui::PopID(); //"terrn" +} + void drawDocumentWindow() { ImGui::PushID("document view"); - ImGui::Begin("Document view", /*open:*/true, /*flags:*/0); + string caption = "Document view (" + g_displayed_doc_filename + ")"; + ImGui::Begin(caption, /*open:*/true, /*flags:*/0); GenericDocReaderClass reader(g_displayed_document); while (!reader.EndOfFile()) diff --git a/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp b/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp index 766381a195..8f2249f7eb 100644 --- a/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp +++ b/source/main/scripting/bindings/GenericFileFormatAngelscript.cpp @@ -79,6 +79,9 @@ void RoR::RegisterGenericFileFormat(asIScriptEngine* engine) engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_FIRST_LINE_IS_TITLE", GenericDocument::OPTION_FIRST_LINE_IS_TITLE); engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_COLON", GenericDocument::OPTION_ALLOW_SEPARATOR_COLON); engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_PARENTHESES_CAPTURE_SPACES", GenericDocument::OPTION_PARENTHESES_CAPTURE_SPACES); + engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_ALLOW_BRACED_KEYWORDS", GenericDocument::OPTION_ALLOW_BRACED_KEYWORDS); + engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_EQUALS", GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS); + engine->RegisterEnumValue("GenericDocumentOptions", "GENERIC_DOCUMENT_OPTION_ALLOW_HASH_COMMENTS", GenericDocument::OPTION_ALLOW_HASH_COMMENTS); // class GenericDocument diff --git a/source/main/scripting/bindings/TerrainAngelscript.cpp b/source/main/scripting/bindings/TerrainAngelscript.cpp index 7912cdbac0..ca8ccf4440 100644 --- a/source/main/scripting/bindings/TerrainAngelscript.cpp +++ b/source/main/scripting/bindings/TerrainAngelscript.cpp @@ -36,6 +36,8 @@ void RoR::RegisterTerrain(asIScriptEngine* engine) int result = 0; result = engine->RegisterObjectMethod("TerrainClass", "string getTerrainName()", asMETHOD(RoR::Terrain,getTerrainName), asCALL_THISCALL); ROR_ASSERT(result>=0); + result = engine->RegisterObjectMethod("TerrainClass", "string getTerrainFileName()", asMETHOD(RoR::Terrain, getTerrainFileName), asCALL_THISCALL); ROR_ASSERT(result >= 0); + result = engine->RegisterObjectMethod("TerrainClass", "string getTerrainFileResourceGroup()", asMETHOD(RoR::Terrain, getTerrainFileResourceGroup), asCALL_THISCALL); ROR_ASSERT(result >= 0); result = engine->RegisterObjectMethod("TerrainClass", "string getGUID()", asMETHOD(RoR::Terrain,getGUID), asCALL_THISCALL); ROR_ASSERT(result>=0); result = engine->RegisterObjectMethod("TerrainClass", "int getVersion()", asMETHOD(RoR::Terrain,getVersion), asCALL_THISCALL); ROR_ASSERT(result>=0); result = engine->RegisterObjectMethod("TerrainClass", "bool isFlat()", asMETHOD(RoR::Terrain,isFlat), asCALL_THISCALL); ROR_ASSERT(result>=0); diff --git a/source/main/terrain/Terrain.cpp b/source/main/terrain/Terrain.cpp index 9a7ad955ca..d2718a38f3 100644 --- a/source/main/terrain/Terrain.cpp +++ b/source/main/terrain/Terrain.cpp @@ -550,3 +550,12 @@ ProceduralManagerPtr RoR::Terrain::getProceduralManager() return m_object_manager->getProceduralManager(); } +std::string RoR::Terrain::getTerrainFileName() +{ + return m_cache_entry->fname; +} + +std::string RoR::Terrain::getTerrainFileResourceGroup() +{ + return m_cache_entry->resource_group; +} diff --git a/source/main/terrain/Terrain.h b/source/main/terrain/Terrain.h index 820a164c59..90a767fa50 100644 --- a/source/main/terrain/Terrain.h +++ b/source/main/terrain/Terrain.h @@ -47,6 +47,8 @@ class Terrain : public ZeroedMemoryAllocator, public RefCountingObject /// @name Terrain info /// @{ std::string getTerrainName() const { return m_def.name; } + std::string getTerrainFileName(); + std::string getTerrainFileResourceGroup(); std::string getGUID() const { return m_def.guid; } int getCategoryID() const { return m_def.category_id; } int getVersion() const { return m_def.version; } diff --git a/source/main/utils/GenericFileFormat.cpp b/source/main/utils/GenericFileFormat.cpp index 5f9f53f92f..911da42c07 100644 --- a/source/main/utils/GenericFileFormat.cpp +++ b/source/main/utils/GenericFileFormat.cpp @@ -32,6 +32,7 @@ enum class PartialToken NONE, COMMENT_SEMICOLON, // Comment starting with ';' COMMENT_SLASH, // Comment starting with '//' + COMMENT_HASH, STRING_QUOTED, // String starting/ending with '"' STRING_NAKED, // String without '"' on either end STRING_NAKED_CAPTURING_SPACES, // Only for OPTION_PARENTHESES_CAPTURE_SPACES - A naked string seeking the closing ')'. @@ -42,6 +43,7 @@ enum class PartialToken NUMBER_SCIENTIFIC_STUB_MINUS, // Like SCIENTIFIC_STUB but with only '-' in exponent. NUMBER_SCIENTIFIC, // Valid decimal number in scientific notation. KEYWORD, // Unqoted string at the start of line. Accepted characters: alphanumeric and underscore + KEYWORD_BRACED, // Like KEYWORD but starting with '[' and ending with ']' BOOL_TRUE, // Partial 'true' BOOL_FALSE, // Partial 'false' GARBAGE, // Text not fitting any above category, will be discarded @@ -75,7 +77,9 @@ struct DocumentParser void DiscontinueBool(); void DiscontinueNumber(); + void DiscontinueKeyword(); void FlushStringishToken(RoR::TokenType type); + void FlushNumericToken(); }; void DocumentParser::BeginToken(const char c) @@ -107,6 +111,22 @@ void DocumentParser::BeginToken(const char c) } break; + case '=': + if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS) + { + line_pos++; + } + else + { + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) + partial_tok_type = PartialToken::STRING_NAKED; + else + partial_tok_type = PartialToken::GARBAGE; + tok.push_back(c); + line_pos++; + } + break; + case '\n': doc.tokens.push_back({ TokenType::LINEBREAK, 0.f }); line_num++; @@ -123,20 +143,49 @@ void DocumentParser::BeginToken(const char c) { partial_tok_type = PartialToken::COMMENT_SLASH; } - else if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS && - (doc.tokens.size() != 0 && doc.tokens.back().type != TokenType::LINEBREAK)) // not first on line? + else { + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) + partial_tok_type = PartialToken::STRING_NAKED; + else + partial_tok_type = PartialToken::GARBAGE; tok.push_back(c); - partial_tok_type = PartialToken::STRING_NAKED; + } + line_pos++; + break; + + case '#': + if (options & GenericDocument::OPTION_ALLOW_HASH_COMMENTS) + { + partial_tok_type = PartialToken::COMMENT_HASH; } else { - partial_tok_type = PartialToken::GARBAGE; + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) + partial_tok_type = PartialToken::STRING_NAKED; + else + partial_tok_type = PartialToken::GARBAGE; tok.push_back(c); } line_pos++; break; + case '[': + if (options & GenericDocument::OPTION_ALLOW_BRACED_KEYWORDS) + { + partial_tok_type = PartialToken::KEYWORD_BRACED; + } + else + { + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) + partial_tok_type = PartialToken::STRING_NAKED; + else + partial_tok_type = PartialToken::GARBAGE; + } + tok.push_back(c); + line_pos++; + break; + case '"': partial_tok_type = PartialToken::STRING_QUOTED; line_pos++; @@ -304,6 +353,19 @@ void DocumentParser::UpdateString(const char c) line_pos++; break; + case '=': + if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS + && (partial_tok_type == PartialToken::STRING_NAKED || partial_tok_type == PartialToken::STRING_NAKED_CAPTURING_SPACES)) + { + this->FlushStringishToken(TokenType::STRING); + } + else + { + tok.push_back(c); + } + line_pos++; + break; + case '"': if (partial_tok_type == PartialToken::STRING_QUOTED) { @@ -359,20 +421,12 @@ void DocumentParser::UpdateNumber(const char c) case ' ': case ',': case '\t': - // Flush number - tok.push_back('\0'); - doc.tokens.push_back({ TokenType::NUMBER, (float)Ogre::StringConverter::parseReal(tok.data()) }); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->FlushNumericToken(); line_pos++; break; case '\n': - // Flush number - tok.push_back('\0'); - doc.tokens.push_back({ TokenType::NUMBER, (float)Ogre::StringConverter::parseReal(tok.data()) }); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->FlushNumericToken(); // Break line doc.tokens.push_back({ TokenType::LINEBREAK, 0.f }); line_num++; @@ -382,11 +436,20 @@ void DocumentParser::UpdateNumber(const char c) case ':': if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_COLON) { - // Flush number - tok.push_back('\0'); - doc.tokens.push_back({ TokenType::NUMBER, (float)Ogre::StringConverter::parseReal(tok.data()) }); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->FlushNumericToken(); + } + else + { + this->DiscontinueNumber(); + tok.push_back(c); + } + line_pos++; + break; + + case '=': + if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS) + { + this->FlushNumericToken(); } else { @@ -521,6 +584,24 @@ void DocumentParser::UpdateBool(const char c) line_pos++; break; + case '=': + if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS) + { + // Discard token + tok.push_back('\0'); + App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING, + fmt::format("{}, line {}, pos {}: discarding incomplete boolean token '{}'", datastream->getName(), line_num, line_pos, tok.data())); + tok.clear(); + partial_tok_type = PartialToken::NONE; + } + else + { + partial_tok_type = PartialToken::GARBAGE; + tok.push_back(c); + } + line_pos++; + break; + case 'r': if (partial_tok_type != PartialToken::BOOL_TRUE || tok.size() != 1) { @@ -619,6 +700,14 @@ void DocumentParser::DiscontinueNumber() partial_tok_type = PartialToken::GARBAGE; } +void DocumentParser::DiscontinueKeyword() +{ + if (options & GenericDocument::OPTION_ALLOW_NAKED_STRINGS) + partial_tok_type = PartialToken::STRING_NAKED; + else + partial_tok_type = PartialToken::GARBAGE; +} + void DocumentParser::UpdateKeyword(const char c) { switch (c) @@ -648,7 +737,20 @@ void DocumentParser::UpdateKeyword(const char c) } else { - partial_tok_type = PartialToken::GARBAGE; + this->DiscontinueKeyword(); + tok.push_back(c); + } + line_pos++; + break; + + case '=': + if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS) + { + this->FlushStringishToken(TokenType::KEYWORD); + } + else + { + this->DiscontinueKeyword(); tok.push_back(c); } line_pos++; @@ -675,10 +777,23 @@ void DocumentParser::UpdateKeyword(const char c) line_pos++; break; + case ']': + if (partial_tok_type == PartialToken::KEYWORD_BRACED) + { + partial_tok_type == PartialToken::KEYWORD; // Do not allow any more ']'. + } + else + { + this->DiscontinueKeyword(); + } + tok.push_back(c); + line_pos++; + break; + default: if (!isalnum(c)) { - partial_tok_type = PartialToken::GARBAGE; + this->DiscontinueKeyword(); } tok.push_back(c); line_pos++; @@ -749,6 +864,14 @@ void DocumentParser::FlushStringishToken(RoR::TokenType type) partial_tok_type = PartialToken::NONE; } +void DocumentParser::FlushNumericToken() +{ + tok.push_back('\0'); + doc.tokens.push_back({ TokenType::NUMBER, (float)Ogre::StringConverter::parseReal(tok.data()) }); + tok.clear(); + partial_tok_type = PartialToken::NONE; +} + void GenericDocument::LoadFromDataStream(Ogre::DataStreamPtr datastream, const BitMask_t options) { // Reset the document @@ -776,6 +899,7 @@ void GenericDocument::LoadFromDataStream(Ogre::DataStreamPtr datastream, const B case PartialToken::COMMENT_SEMICOLON: case PartialToken::COMMENT_SLASH: + case PartialToken::COMMENT_HASH: parser.UpdateComment(c); break; @@ -799,6 +923,7 @@ void GenericDocument::LoadFromDataStream(Ogre::DataStreamPtr datastream, const B break; case PartialToken::KEYWORD: + case PartialToken::KEYWORD_BRACED: parser.UpdateKeyword(c); break; diff --git a/source/main/utils/GenericFileFormat.h b/source/main/utils/GenericFileFormat.h index b2b71c47cb..dd7848f3a9 100644 --- a/source/main/utils/GenericFileFormat.h +++ b/source/main/utils/GenericFileFormat.h @@ -64,6 +64,9 @@ struct GenericDocument: public RefCountingObject static const BitMask_t OPTION_FIRST_LINE_IS_TITLE = BITMASK(3); //!< First non-empty & non-comment line is a naked string with spaces. static const BitMask_t OPTION_ALLOW_SEPARATOR_COLON = BITMASK(4); //!< Allow ':' as separator between tokens. static const BitMask_t OPTION_PARENTHESES_CAPTURE_SPACES = BITMASK(5); //!< If non-empty NAKED string encounters '(', following spaces will be captured until matching ')' is found. + static const BitMask_t OPTION_ALLOW_BRACED_KEYWORDS = BITMASK(6); //!< Allow INI-like '[keyword]' tokens. + static const BitMask_t OPTION_ALLOW_SEPARATOR_EQUALS = BITMASK(7); //!< Allow '=' as separator between tokens. + static const BitMask_t OPTION_ALLOW_HASH_COMMENTS = BITMASK(8); //!< Allow comments starting with `#`. virtual ~GenericDocument() {};