From 234be8917ff0eb9f8816a2b046a476a8de80f62c Mon Sep 17 00:00:00 2001 From: tompzf <141007871+tompzf@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:40:02 +0100 Subject: [PATCH] update parser (#5) --- CMakeLists.txt | 3 +- LICENSE.txt | 202 ++ doxygen.config | 2 +- .../autoheadlight_app/autoheadlight_console.h | 3 +- .../door_app/include/console.h | 3 +- .../system_demo_example/example_app/control.h | 3 +- export/interfaces/core_idl.idl | 4 +- export/interfaces/ipc.idl | 2 +- export/interfaces/toml.idl | 239 +- export/support/component_impl.h | 3 +- export/support/interface_ptr.h | 3 +- export/support/toml.h | 13 + global/ascformat/ascreader.cpp | 16 +- global/ascformat/ascreader.h | 5 +- global/cmdlnparser/cmdlnparser.cpp | 3 +- global/cmdlnparser/cmdlnparser.h | 2 +- global/debug_log.h | 208 +- global/ipc_named_mutex.h | 8 +- global/localmemmgr.h | 6 +- global/path_match.h | 2 +- global/process_watchdog.h | 53 +- global/scheduler/scheduler.h | 5 +- global/trace.h | 16 +- global/tracefifo/trace_fifo.h | 17 +- sdv_executables/sdv_core/main.cpp | 3 +- .../sdv_idl_compiler/core_idl_backup.h | 4 +- .../entities/definition_entity.h | 2 +- .../generator/ps_class_generator_base.h | 6 +- sdv_executables/sdv_idl_compiler/source.h | 4 +- sdv_executables/sdv_iso/main.cpp | 10 +- sdv_executables/sdv_packager/CMakeLists.txt | 3 + sdv_executables/sdv_packager/environment.cpp | 2 +- sdv_executables/sdv_packager/environment.h | 3 + sdv_executables/sdv_trace_mon/main.cpp | 3 +- sdv_executables/sdv_vss_util/vss_helper.h | 2 +- sdv_services/core/CMakeLists.txt | 2 +- sdv_services/core/app_config.cpp | 42 +- sdv_services/core/app_control.cpp | 38 +- sdv_services/core/installation_manifest.cpp | 64 +- sdv_services/core/installation_manifest.h | 2 +- sdv_services/core/module.cpp | 28 +- sdv_services/core/module_control.cpp | 6 +- sdv_services/core/repository.cpp | 13 +- sdv_services/core/sdv_core.h | 2 +- .../toml_parser/character_reader_utf_8.cpp | 541 ++-- .../core/toml_parser/character_reader_utf_8.h | 215 +- sdv_services/core/toml_parser/exception.h | 31 +- sdv_services/core/toml_parser/lexer_toml.cpp | 2149 +++++++++------- sdv_services/core/toml_parser/lexer_toml.h | 553 ++-- .../core/toml_parser/lexer_toml_token.cpp | 552 ++++ .../core/toml_parser/lexer_toml_token.h | 421 +++ .../core/toml_parser/miscellaneous.cpp | 549 ++++ sdv_services/core/toml_parser/miscellaneous.h | 99 + .../core/toml_parser/parser_node_toml.cpp | 2292 ++++++++++++----- .../core/toml_parser/parser_node_toml.h | 2078 ++++++++++----- sdv_services/core/toml_parser/parser_toml.cpp | 847 +++--- sdv_services/core/toml_parser/parser_toml.h | 286 +- sdv_services/core/toml_parser_util.h | 2 +- sdv_services/ipc_com/com_channel.cpp | 31 +- sdv_services/ipc_com/com_channel.h | 17 +- sdv_services/ipc_com/marshall_object.cpp | 6 +- sdv_services/ipc_shared_mem/connection.cpp | 38 +- sdv_services/ipc_shared_mem/connection.h | 2 +- .../ipc_shared_mem/in_process_mem_buffer.h | 2 +- .../ipc_shared_mem/shared_mem_buffer_posix.h | 7 +- .../shared_mem_buffer_windows.h | 2 +- sdv_services/ipc_shared_mem/watchdog.h | 3 +- .../process_control/process_control.h | 46 +- tests/CMakeLists.txt | 2 +- tests/README.md | 14 +- tests/component_tests/config/config_tests.cpp | 9 +- .../data_dispatch_service_test.cpp | 8 +- .../data_dispatch_service/main.cpp | 29 +- .../transaction_test.cpp | 18 +- .../dbc_util/dbc_util_test.cpp | 160 +- tests/include/sdv_test_macro.h | 273 ++ tests/include/test_watchdog.h | 5 +- .../src/can_com_test_silkit.cpp | 3 +- .../unit_tests/asc_format/asc_writer_test.cpp | 12 +- tests/unit_tests/basic_types/serdes.cpp | 16 +- .../commandline_parser_test.h | 12 +- .../concurrency/concurrency_tests.cpp | 4 +- tests/unit_tests/dbc_parser/dbc_parser_test.h | 12 +- tests/unit_tests/idl_compiler/lexer_test.h | 12 +- tests/unit_tests/idl_compiler/parser_test.h | 12 +- .../composer_test_suite.cpp | 2 + .../composer_tests.cpp | 4 +- tests/unit_tests/ipc_com/main.cpp | 28 +- tests/unit_tests/logger_fifo/fifo_test.cpp | 7 +- tests/unit_tests/module_control/main.cpp | 2 + tests/unit_tests/module_control/mock.h | 13 +- .../named_mutex/named_mutex_test.cpp | 17 +- tests/unit_tests/repository/main.cpp | 2 + .../sdv_macro_test/sdv_macro_test.cpp | 122 +- tests/unit_tests/shared_mem/CMakeLists.txt | 20 - tests/unit_tests/shared_mem/app_connect.cpp | 66 +- .../in_process_mem_buffer_tests.cpp | 7 +- tests/unit_tests/shared_mem/main.cpp | 29 +- tests/unit_tests/shared_mem/pattern_gen.cpp | 1 + tests/unit_tests/shared_mem/pattern_gen.h | 5 +- .../shared_mem/shared_mem_buffer_tests.cpp | 138 +- .../shared_mem/shared_mem_connect.cpp | 12 +- .../shared_mem_large_data_tests.cpp | 41 +- .../src/can_com_test_socket.cpp | 5 +- tests/unit_tests/toml_parser/CMakeLists.txt | 5 +- .../toml_parser/character_reader_tests.cpp | 157 +- .../toml_parser/content_modifications.cpp | 449 ++++ .../toml_parser/generate_toml_tests.cpp | 1420 +++++----- tests/unit_tests/toml_parser/lexer_tests.cpp | 1613 ++++++++---- tests/unit_tests/toml_parser/main.cpp | 2 + .../toml_parser/miscellaneous_tests.cpp | 981 +++++++ tests/unit_tests/toml_parser/parser_tests.cpp | 396 ++- .../statement_boundary_detection.cpp | 1256 +++++++++ tests/unit_tests/trace_fifo/fifo_test.cpp | 13 +- tmp_project/CMakePresets.json | 175 ++ 115 files changed, 14038 insertions(+), 5380 deletions(-) create mode 100644 LICENSE.txt create mode 100644 sdv_services/core/toml_parser/lexer_toml_token.cpp create mode 100644 sdv_services/core/toml_parser/lexer_toml_token.h create mode 100644 sdv_services/core/toml_parser/miscellaneous.cpp create mode 100644 sdv_services/core/toml_parser/miscellaneous.h create mode 100644 tests/include/sdv_test_macro.h create mode 100644 tests/unit_tests/toml_parser/content_modifications.cpp create mode 100644 tests/unit_tests/toml_parser/miscellaneous_tests.cpp create mode 100644 tests/unit_tests/toml_parser/statement_boundary_detection.cpp create mode 100644 tmp_project/CMakePresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fad2c5..3e2f1f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,7 +84,8 @@ endif() # Default C++ settings if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") message("Use MSVC compiler...") - add_compile_options(/W4 /WX /wd4996 /wd4127 /permissive- /Zc:rvalueCast) + add_compile_options(/W4 /WX /wd4996 /wd4127 /permissive- /Zc:rvalueCast /Zi) + add_link_options(/DEBUG) if (CMAKE_ENABLE_PREFAST) message("Microsoft PREfast static code analysis enabled...") diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doxygen.config b/doxygen.config index 8d34e7d..d7d3f12 100644 --- a/doxygen.config +++ b/doxygen.config @@ -218,7 +218,7 @@ JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) +# requiring an explicit @brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO diff --git a/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h b/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h index 74f5ced..929b0b0 100644 --- a/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h +++ b/examples/auto_headlamp_example/autoheadlight_app/autoheadlight_console.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "signal_names.h" #include #include "vss_vehiclepositioncurrentlatitude_vd_rx.h" @@ -192,7 +193,7 @@ private: mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message std::thread m_threadReadTxSignals; ///< Simulation datalink thread. bool m_bThreadStarted = false; ///< Set when initialized. - bool m_bRunning = false; ///< When set, the application is running. + std::atomic_bool m_bRunning = false; ///< When set, the application is running. mutable std::mutex m_mPrintToConsole; ///< Mutex to print complete message sdv::core::CSignal m_signalCurrentLatitude; ///< Signal Current latitude diff --git a/examples/door_demo_example/door_app/include/console.h b/examples/door_demo_example/door_app/include/console.h index 90e8c13..4045a95 100644 --- a/examples/door_demo_example/door_app/include/console.h +++ b/examples/door_demo_example/door_app/include/console.h @@ -11,6 +11,7 @@ #include #include "signal_names.h" #include +#include #include "../interfaces/vss_vehiclechassisdooraxle01left_vd_rx.h" #include "../interfaces/vss_vehiclechassisdooraxle01left_bs_rx.h" #include "../interfaces/vss_vehiclechassisdooraxle01right_bs_rx.h" @@ -205,7 +206,7 @@ private: mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message bool m_bThreadStarted = false; ///< Set when initialized. - bool m_bRunning = false; ///< When set, the application is running. + std::atomic_bool m_bRunning = false; ///< When set, the application is running. bool m_isExternalApp = false; ///< True when we have an external application mutable std::mutex m_mPrintToConsole; ///< Mutex to print complete message diff --git a/examples/system_demo_example/example_app/control.h b/examples/system_demo_example/example_app/control.h index 71886bf..e6be83f 100644 --- a/examples/system_demo_example/example_app/control.h +++ b/examples/system_demo_example/example_app/control.h @@ -2,6 +2,7 @@ #define EXMAPLE_UTILITY_H #include +#include #include #include @@ -155,7 +156,7 @@ private: bool m_bInitialized = false; ///< Set when initialized. bool m_bCmdLnError = false; ///< Command line error occurred. bool m_bCmdLnHelp = false; ///< Command line help provided. - bool m_bRunning = false; ///< When set, the application is running. + std::atomic_bool m_bRunning = false; ///< When set, the application is running. ERunAs m_eRunAs = ERunAs::standalone; ///< Application operation. EAppControlReporting m_eReporting = EAppControlReporting::silent; ///< Application control reporting. uint32_t m_uiInstance = 1000u; ///< Server instance to connect to. diff --git a/export/interfaces/core_idl.idl b/export/interfaces/core_idl.idl index 7728f2f..8d28f3d 100644 --- a/export/interfaces/core_idl.idl +++ b/export/interfaces/core_idl.idl @@ -305,7 +305,7 @@ module sdv interface IEntityComments { /** - * \brief Comment mask + * @brief Comment mask */ enum ECommentMask : uint32 { @@ -342,7 +342,7 @@ module sdv interface IDefinitionEntity { /** - * \brief Does the entity have an unnamed definition. + * @brief Does the entity have an unnamed definition. * @return Returns 'true' when the entity has an unnamed definition; otherwise returns 'false'. */ boolean IsUnnamed() const; diff --git a/export/interfaces/ipc.idl b/export/interfaces/ipc.idl index 7dd8370..09f05c2 100644 --- a/export/interfaces/ipc.idl +++ b/export/interfaces/ipc.idl @@ -87,7 +87,7 @@ module sdv }; /** - * \brief Interface for managing IPC connection. + * @brief Interface for managing IPC connection. */ interface IConnect { diff --git a/export/interfaces/toml.idl b/export/interfaces/toml.idl index 51fc70e..8fe881d 100644 --- a/export/interfaces/toml.idl +++ b/export/interfaces/toml.idl @@ -34,28 +34,42 @@ module sdv /** * @brief Collection of possible data in parse tree node */ - enum ENodeType : uint8 + enum ENodeType : uint32 { - node_table, //!< Table - node_array, //!< Array - node_integer, //!< Integer - node_floating_point, //!< Floating point - node_boolean, //!< Boolean - node_string, //!< String - node_invalid //!< Invalid content + node_table, ///< Table + node_array, ///< Array + node_integer, ///< Integer + node_floating_point, ///< Floating point + node_boolean, ///< Boolean + node_string, ///< String + node_invalid ///< Invalid content }; + /** + * @brief The indicator of an invalid position used in various functions using indices. + */ + const uint32 npos = 0xFFFFFFFF; + /** * @brief Node information interface */ interface INodeInfo { /** - * @brief Get the node name. + * @brief Get the node name (no conversion to a literal or quoted key is made). * @return String containing the name of the node. - */ + */ u8string GetName() const; + /** + * @brief Get the node path following the key rules for bar, literal and quoted keys. + * @param[in] bResolveArrays When set, include array indices in the path. The path returned without array indices is + * identical to the code in the TOML file. The path returned with array indices is identical to the direct access of + * nodes within the parser. + * @return String containing the path of the node. + */ + u8string GetPath(in boolean bResolveArrays) const; + /** * @brief Get the node type. * @return Type of the node. @@ -70,10 +84,75 @@ module sdv any GetValue() const; /** - * @brief Return the TOML string belonging to this node including all potential child nodes. + * @brief Get the index of this node within the parent collection. + * @return The index of the node within the parent collection node or npos when no parent is available. + */ + uint32 GetIndex() const; + + /** + * @brief Get the parent collection node. + * @return Returns the parent collection node or NULL when there is no parent collection node. + */ + IInterfaceAccess GetParent() const; + + /** + * @brief Return the TOML string belonging to this node including all potential child nodes and comments. * @return The TOML string. */ u8string GetTOML() const; + + /** + * @brief Comment access flags. + * @details The comment access flags contain the comment code snippet index (0..15) as well as flags to receive the + * comments in interpreted or raw form. For comment code snippets are defined for all nodes, the comments before and + * after the node not related to the node and the comments before and after the node related to the node. Comment or + * whitespace in between the node tokens are part of the other 11 indices (each node interprets this differently). + */ + enum ECommentFlags + { + comment_before = 0, ///< The comment before the node. This will insert a newline and the comment + ///< text preceded by the '#' character. + comment_behind = 1, ///< The comment behind the node. This will align the comment to te next tab + ///< stop and precedes the comment with the '#' character. + out_of_scope_comment_before = 2, ///< An independent comment before the node. This comment is not part of the + ///< node and is separated by an extra newline. + out_of_scope_comment_behind = 3, ///< An independent comment behind the node. This comment is not part of the + ///< node and is separated by an extra newline. + comment_index_mask = 15, ///< Comment type mask to be used ot filter the comment type. + raw_comment = 8, ///< Store the comment exactly as provided (whitespace and comments). + replace_whitespace = 16, ///< When set, the comment will replace the current whitespace as well. + ///< Used with SetComment function. Automatically enabled for raw comments. + }; + + /** + * @brief Set or replace a comment for the node. + * @remarks This function can also be used to insert whitespace (with or without comments) when used in raw mode. + * Set the comment text for the node. If a comment is proided as text (normal behavior), the comment text will be + * formatted automatically when generating the TOML text. If the comment is provided as raw comment, the text should + * contain all whitespace and the comment '#' character before the comment text. + * Comments inserted before the enode will be inserted on the line before the node uness the comment is provided in raw + * format and is ended with a newline and optionally whitespace. Comment inserted behind the node will be inserted on + * the same line as the node. + * Comments provided as text is automatically wrapped to 80 characters if possible. Newlines in the text will cause a + * new comment line to start. + * @param[in] ssComment String containing the comment text or the raw comment string to set. + * @param[in] uiFlags One or more ECommentFlags flags influencing the behavior of the comment. + */ + void SetComment(in u8string ssComment, in uint32 uiFlags); + + /** + * Get the current comment for the node. + * @remarks To receive the whitespace formatting the node, use this function in raw mode. + * @param[in] uiFlags One or more ECommentFlags flags identifying the string format of the comment to return. + * @return String with the comment text or an empty string if no comment is available. + */ + u8string GetComment(in uint32 uiFlags); + + /** + * @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments + * will not be changed. + */ + void AutomaticFormat(); }; /** @@ -107,6 +186,144 @@ module sdv IInterfaceAccess GetNodeDirect(in u8string ssPath) const; }; + /** + * @brief Extend the collection node with values, arrays and tables. + */ + interface INodeCollectionInsert + { + /** + * @brief Insert a value into the collection at the location before the supplied index. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Value nodes cannot be inserted behind external + * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index + * is automatically corrected. + * @param[in] ssName Name of the node to insert. Will be ignored for an array collection. The name must adhere to the + * key names defined by the TOML specification. Defining the key multiple times is not allowed. Quotation of key names + * is done automatically; the parser decides itself whether the key is bare-key, a literal key or a quoted key. + * @param[in] anyValue The value of the node, being either an integer, floating point number, boolean value or a string. + * Conversion is automatically done to int64, double float, bool or u8string. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + IInterfaceAccess InsertValue(in uint32 uiIndex, in u8string ssName, in any anyValue); + + /** + * @brief Insert an array into the collection at the location before the supplied index. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Array nodes cannot be inserted behind external + * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index + * is automatically corrected. + * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. + * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not + * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a + * literal key or a quoted key. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + IInterfaceAccess InsertArray(in uint32 uiIndex, in u8string ssName); + + /** + * @brief Insert a table into the collection at the location before the supplied index. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Table nodes cannot be inserted before value nodes + * or arrays. If the index is referencing a position before a value node or an array, the index is automatically + * corrected. + * @param[in] ssKeyName Name of the table node to insert. Will be ignored if the current node is an array collection. + * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not + * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a + * literal key or a quoted key. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + IInterfaceAccess InsertTable(in uint32 uiIndex, in u8string ssKeyName); + + /** + * @brief Insert a table array into the collection at the location before the supplied index. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value + * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically + * corrected. + * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. + * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not + * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a + * literal key or a quoted key. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + IInterfaceAccess InsertTableArray(in uint32 uiIndex, in u8string ssName); + + /** + * @brief The result of the TOML string to insert. + */ + enum EInsertResult : uint32 + { + invalid_TOML, ///< The TOML string was invalid or didn't fit the collection node it was to be inserted. + insert_partly_success, ///< Part, but not all nodes could be inserted (duplicate nodes are not inserted). + insert_success, ///< All nodes could be inserted or the TOML didn't contain any nodes. + }; + + /** + * @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string + * should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML + * string should contain and inline table nodes without names. + * @param[in] ssTOML The TOML string to insert. + * @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted. + * @return The result of the insertion. + */ + EInsertResult InsertTOML(in u8string ssTOML, in boolean bRollbackOnPartly); + }; + + /** + * @brief Remove the current node. + * @remarks The root node cannot be deleted. + */ + interface INodeDelete + { + /** + * @brief Delete the current node. + * @attention A successful deletion will cause all interfaces to the current node to become inoperable. + * @return Returns whether the deletion was successful. + */ + boolean DeleteNode(); + }; + + /** + * @brief Update the current node. + * @remarks The root node cannot be updated. + */ + interface INodeUpdate + { + /** + * @brief Change the key name of the node (if the node is not a value node of an array). + * @param[in] ssNewName The name to assign to the node. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. + * @return Returns whether the name change was successful. + */ + boolean ChangeName(in u8string ssNewName); + + /** + * @brief Change the value of the node. + * @remarks Only valid for value nodes. Changing the value type is not supported. + * @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a + * string. Conversion is automatically done to int64, double float, bool or u8string. + * @return Returns whether the value change was successful. + */ + boolean ChangeValue(in any anyNewValue); + + /** + * @brief Move up the node in the collection. + * @remarks External tables or table arrays cannot be moved before value nodes. + * @remarks Moving if the node is the first node is not possible. + * @return Returns whether the move was successful. + */ + boolean MoveUp(); + + /** + * @brief Move down the node in the collection. + * @remarks Value nodes cannot be moved behind external tables or table arrays. + * @remarks Moving if the node is the last node is not possible. + * @return Returns whether the move was successful. + */ + boolean MoveDown(); + }; + /** * @brief TOML parser interface. */ diff --git a/export/support/component_impl.h b/export/support/component_impl.h index 65fc1f5..26e25a1 100644 --- a/export/support/component_impl.h +++ b/export/support/component_impl.h @@ -308,7 +308,6 @@ namespace sdv protected: /** * @brief Build the module manifest. - * @return Returns the pointer to a zero terminated string containing the module manifest or NULL when there is no string. */ void BuildManifest() { @@ -860,7 +859,7 @@ Version = )code" << SDVFrameworkInterfaceVersion /** * @brief Declare the object class type. To be placed in the SDV object class derived from CSdvObject. - * \param class_type The type of the object (EObjectType). + * @param class_type The type of the object (EObjectType). */ #define DECLARE_OBJECT_CLASS_TYPE(class_type) \ /** \ diff --git a/export/support/interface_ptr.h b/export/support/interface_ptr.h index d885236..df8cf93 100644 --- a/export/support/interface_ptr.h +++ b/export/support/interface_ptr.h @@ -58,7 +58,8 @@ namespace sdv } /** - * Select the section that is supported now. + * @brief Select the section that is supported now. + * @param[in] uiSection The section number that should be considered. */ void Select(int uiSection) { diff --git a/export/support/toml.h b/export/support/toml.h index 5492678..ba8ba88 100644 --- a/export/support/toml.h +++ b/export/support/toml.h @@ -49,6 +49,14 @@ namespace sdv::toml */ sdv::u8string GetName(); + /** + * @brief Retrurn the node qualified path including the parent path. + * @details The qualified path is a path composed through all parent nodes containing quoted names where needed. The path + * to the node can be used to directly access the node. + * @return String containing the qualified path to the node. + */ + sdv::u8string GetQualifiedPath(); + /** * @brief Get the node type. * @return The node type. @@ -247,6 +255,11 @@ namespace sdv::toml return m_pNodeInfo ? m_pNodeInfo->GetName() : sdv::u8string(); } + inline sdv::u8string CNode::GetQualifiedPath() + { + return m_pNodeInfo ? m_pNodeInfo->GetPath(true) : sdv::u8string(); + } + inline ENodeType CNode::GetType() { return m_pNodeInfo ? m_pNodeInfo->GetType() : ENodeType::node_invalid; diff --git a/global/ascformat/ascreader.cpp b/global/ascformat/ascreader.cpp index 11110d4..7d34e49 100644 --- a/global/ascformat/ascreader.cpp +++ b/global/ascformat/ascreader.cpp @@ -40,14 +40,24 @@ namespace asc enum class EState {header, body, footer} eState = EState::header; while (std::getline(fstream, ssLine)) { + // Compare two characters and two strings case insensitive + auto ichar_equals = [](char a, char b) + { + return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); + }; + auto iequals = [&](const std::string& a, const std::string& b) + { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), ichar_equals); + }; + switch (eState) { case EState::header: - if (ssLine.compare(0, 18, "Begin TriggerBlock") == 0) + if (iequals(ssLine.substr(0, 18), "Begin TriggerBlock")) eState = EState::body; break; case EState::body: - if (ssLine.compare(0, 16, "End TriggerBlock") == 0) + if (iequals(ssLine.substr(0, 16), "End TriggerBlock")) eState = EState::footer; else ProcessSample(ssLine); @@ -74,7 +84,7 @@ namespace asc uint32_t CAscReader::GetMessageCount() const { - return m_lstMessages.size(); + return static_cast(m_lstMessages.size()); } uint32_t CAscReader::GetLoopCount() const diff --git a/global/ascformat/ascreader.h b/global/ascformat/ascreader.h index 8365807..11a42c0 100644 --- a/global/ascformat/ascreader.h +++ b/global/ascformat/ascreader.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace asc { @@ -159,8 +160,8 @@ namespace asc std::list m_lstMessages; ///< Vector with messages std::list::iterator m_itCurrent; ///< Current iterator position std::thread m_threadPlayback; ///< Playback thread. - bool m_bPlaybackThread = false; ///< Set when running playback thread - bool m_bPlayback = false; ///< Set when running playback + std::atomic_bool m_bPlaybackThread = false; ///< Set when running playback thread + std::atomic_bool m_bPlayback = false; ///< Set when running playback std::atomic m_uiLoopCount{ 0 }; ///< Counter how often the data set was sent }; } diff --git a/global/cmdlnparser/cmdlnparser.cpp b/global/cmdlnparser/cmdlnparser.cpp index 87933a1..409fef8 100644 --- a/global/cmdlnparser/cmdlnparser.cpp +++ b/global/cmdlnparser/cmdlnparser.cpp @@ -58,7 +58,8 @@ bool CArgumentDefBase::CompareNameAndAssign(CArgumentIterator& rargit, const std while (nPos < rssArgument.size()) { char c = rssArgument[nPos]; - if (!std::isalnum(c) && c != '_' && c != '?') + // Protect against multi-byte characters (UTF-8). + if (static_cast(c) > 127u || (!std::isalnum(c) && c != '_' && c != '?')) break; ssArgNameCS += c; ssArgNameCI += static_cast(std::tolower(c)); diff --git a/global/cmdlnparser/cmdlnparser.h b/global/cmdlnparser/cmdlnparser.h index 0c0039a..07ed1ae 100644 --- a/global/cmdlnparser/cmdlnparser.h +++ b/global/cmdlnparser/cmdlnparser.h @@ -1389,7 +1389,7 @@ public: * @param[in] rtArgument Reference to the argument. * @param[in] rssValue Reference to the string containing the value to be assigned. */ - void Parse(TVar& /*rtArgument*/, const std::string& /*rssValue*/) {} + void Parse([[maybe_unused]] TVar& rtArgument, [[maybe_unused]] const std::string& rssValue) {} /** * @brief Get the markup string for the argument type. Overload in derived class. diff --git a/global/debug_log.h b/global/debug_log.h index a691996..20876fa 100644 --- a/global/debug_log.h +++ b/global/debug_log.h @@ -8,12 +8,28 @@ #include #include #include +#include +#include +#include +#include #include "exec_dir_helper.h" +#ifndef ENABLE_DEBUG_LOG /** - * @brief Enable debug log by defining it and assigning it the value 1. + * @brief Enable debug log by defining the ENABLE_DEBUG_LOG to a value other than zero. */ #define ENABLE_DEBUG_LOG 1 +#endif + +#ifndef DECOUPLED_DEBUG_LOG +/** + * @brief When DECOUPLED is set to a value not zero, the logging is decoupled from the writing. + * @attention Decoupling the logging from the writing might improve the accuracy of the logging, and reduce influencing the program + * to an absolute minimum. It does, however, cause messages to be written delayed, which might lead to missing the clue when a + * crash occurs. To log messages synchronously with the program code, set DECOUPLED_DEBUG_LOG to the value zero. + */ +#define DECOUPLED_DEBUG_LOG 1 +#endif /** * @brief Debug namespace @@ -33,14 +49,6 @@ namespace debug CLogger() { m_pathLogFile = GetExecDirectory() / GetExecFilename().replace_extension(".log"); - std::unique_lock lock(m_mtxLogger); - std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::trunc); - if (fstream.is_open()) - { - fstream << "Starting log of " << GetExecFilename().generic_u8string() << std::endl; - fstream.close(); - } - std::clog << "Starting log of " << GetExecFilename().generic_u8string() << std::flush << std::endl; } /** @@ -48,14 +56,18 @@ namespace debug */ ~CLogger() { - std::unique_lock lock(m_mtxLogger); - std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::app); - if (fstream.is_open()) +#if DECOUPLED_DEBUG_LOG != 0 + // Shut down the logger thread + if (m_threadLogger.joinable()) { - fstream << "End of log..." << std::endl; - fstream.close(); + m_bShutdown = true; + m_threadLogger.join(); } - std::clog << "End of log..." << std::flush << std::endl; + + // Prevent the logger mutex to be still in use. + std::unique_lock lock(m_mtxLogger); + lock.unlock(); +#endif } /** @@ -64,16 +76,28 @@ namespace debug */ void Log(const std::string& rss) { + // Create the message structure + SLogMsg sMsg{std::this_thread::get_id(), DepthPerThreadOperation(EDepthOperation::report_only), rss}; + +#if DECOUPLED_DEBUG_LOG != 0 std::unique_lock lock(m_mtxLogger); - std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::app); - std::string ssIndent(m_nDepth, '>'); - if (!ssIndent.empty()) ssIndent += ' '; - if (fstream.is_open()) + + // First log entry starts logging + if (!m_threadLogger.joinable()) { - fstream << ssIndent << rss << std::endl; - fstream.close(); + m_threadLogger = std::thread(&CLogger::LogToFileThreadFunc, this); + while (!m_threadLogger.joinable()) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - std::clog << ssIndent << rss << std::flush << std::endl; + std::cout << rss << std::endl; + + // Add message to the log queue + m_queueLogger.push(sMsg); +#else + std::ofstream fstream; + fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::app); + LogMsg(fstream, sMsg); +#endif } /** @@ -81,7 +105,7 @@ namespace debug */ void IncrDepth() { - m_nDepth++; + DepthPerThreadOperation(EDepthOperation::increase); } /** @@ -89,13 +113,141 @@ namespace debug */ void DecrDepth() { - if (m_nDepth) - m_nDepth--; + DepthPerThreadOperation(EDepthOperation::decrease); } private: - std::filesystem::path m_pathLogFile; ///< Path to the log file. - std::mutex m_mtxLogger; ///< Protect against multiple log entries at the same time. - size_t m_nDepth; ///< Depth level for indentation. + /** + * @brief Start the logging + */ + void StartLog() + { + std::ofstream fstream; + fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::trunc); + if (fstream.is_open()) + { + fstream << "Starting log of " << GetExecFilename().generic_u8string() << std::endl; + fstream.close(); + } + std::cout << "Starting log of " << GetExecFilename().generic_u8string() << std::endl << std::flush; + } + + /** + * @brief Finish the logging + */ + void FinishLog() + { + // Finish logging + std::ofstream fstream; + fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::app); + if (fstream.is_open()) + { + fstream << "End log of " << GetExecFilename().generic_u8string() << std::endl; + fstream.close(); + } + std::cout << "End log of " << GetExecFilename().generic_u8string() << std::endl << std::flush; + } + + /** + * @brief Log structure containing thread and log information. + */ + struct SLogMsg + { + std::thread::id id; ///< Thread ID + size_t nDepth; ///< Depth within the calls + std::string ssMsg; ///< Message to log + }; + + /** + * @brief Log a message. + * @param[in] rfstream Reference to the stream to log to. + * @param[in9 rsMsg Reference to the message to log. + */ + void LogMsg(std::ofstream& rfstream, const SLogMsg& rsMsg) + { + std::string ssIndent(rsMsg.nDepth, '>'); + if (!ssIndent.empty()) + ssIndent += ' '; + if (rfstream.is_open()) + { + rfstream << rsMsg.id << ": " << ssIndent << rsMsg.ssMsg << std::endl; + rfstream.close(); + } + + std::cout << rsMsg.id << ": " << ssIndent << rsMsg.ssMsg << std::endl << std::flush; + } + +#if DECOUPLED_DEBUG_LOG != 0 + /** + * @brief Log to file thread function. + */ + void LogToFileThreadFunc() + { + // Start logging + StartLog(); + + // Log until shutdown + while (!m_bShutdown) + { + std::unique_lock lock(m_mtxLogger); + + // Are there any message. If not, pause for 100 ms + if (m_queueLogger.empty()) + { + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; + } + std::queue queueLocal = std::move(m_queueLogger); + lock.unlock(); + + // Open the file and log all messages that are in the queue + std::ofstream fstream; + fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::app); + while (!queueLocal.empty()) + { + auto sMsg = std::move(queueLocal.front()); + queueLocal.pop(); + LogMsg(fstream, sMsg); + } + } + + // Finish logging + FinishLog(); + } +#endif + + /** + * @brief Depth operation selection. + */ + enum class EDepthOperation + { + increase, + decrease, + report_only, + }; + + /** + * @brief Do a per thread function depth operation (increase, decrease or report). + * @param[in] eOperation The operation to do. + * @return The current function call depth. + */ + size_t DepthPerThreadOperation(EDepthOperation eOperation) + { + thread_local static size_t nDepth = 0; ///< Depth level for indentation. + if (eOperation == EDepthOperation::increase) + nDepth++; + else if (eOperation == EDepthOperation::decrease && nDepth) + nDepth--; + return nDepth; + } + + std::filesystem::path m_pathLogFile; ///< Path to the log file. +#if DECOUPLED_DEBUG_LOG != 0 + std::mutex m_mtxLogger; ///< Protect against multiple log entries at the same time. + std::thread m_threadLogger; ///< Logger thread + std::atomic_bool m_bShutdown = false; ///< When set, terminate the logging thread + std::queue m_queueLogger; ///< Queue with messages to log. +#endif }; /** diff --git a/global/ipc_named_mutex.h b/global/ipc_named_mutex.h index 95105bd..7952861 100644 --- a/global/ipc_named_mutex.h +++ b/global/ipc_named_mutex.h @@ -125,7 +125,9 @@ namespace ipc else { std::srand(static_cast(std::time(nullptr))); // Use current time as seed for random generator - m_ssName = std::string("NAMED_MUTEX_") + std::to_string(std::rand()); + uint32_t uiRand = 0; + while (!uiRand) uiRand = std::rand(); + m_ssName = std::string("NAMED_MUTEX_") + std::to_string(uiRand); } m_handle = CreateMutexA(nullptr, FALSE, (std::string("Global\\") + m_ssName).c_str()); } @@ -191,7 +193,9 @@ namespace ipc else { std::srand(static_cast(std::time(nullptr))); // Use current time as seed for random generator - m_ssName = std::string("NAMED_MUTEX_") + std::to_string(std::rand()); + uint32_t uiRand = 0; + while (!uiRand) uiRand = std::rand(); + m_ssName = std::string("NAMED_MUTEX_") + std::to_string(uiRand); } m_handle = sem_open(m_ssName.c_str(), O_CREAT, 0777 /*O_RDWR*/, 1); } diff --git a/global/localmemmgr.h b/global/localmemmgr.h index 41dbbed..91cd1a5 100644 --- a/global/localmemmgr.h +++ b/global/localmemmgr.h @@ -11,7 +11,7 @@ #include /** - * \brief Local memory manager class allowing the use of the SDV support classes without having to start the framework. + * @brief Local memory manager class allowing the use of the SDV support classes without having to start the framework. * \attention Do not use the local memory manager together with the framework's memory manager. */ class CLocalMemMgr : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAccess, @@ -19,13 +19,13 @@ class CLocalMemMgr : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAcces { public: /** - * \brief Constructor assigning this class to the local services. + * @brief Constructor assigning this class to the local services. */ CLocalMemMgr() {} /** - * \brief Destructor removing this class from the local services. + * @brief Destructor removing this class from the local services. */ ~CLocalMemMgr() {} diff --git a/global/path_match.h b/global/path_match.h index 4d82309..cbb75ee 100644 --- a/global/path_match.h +++ b/global/path_match.h @@ -32,7 +32,7 @@ * extension ending with "n" match. * - "subdir?_*" - all files from a directory starting with the name "subdir" followed by a single digit or character, followed * with a "_" and zero or more characters. - * - "**\/file*.bin" - all files starting with the name "file" followed by zero or more characters and the extension ".bin" in this + * - "**\\/file*.bin" - all files starting with the name "file" followed by zero or more characters and the extension ".bin" in this * and any subdirectory. * @param[in] rpathRel Reference to the relative path to check for a match. * @param[in] rssPattern Reference to the string containing the pattern to match. diff --git a/global/process_watchdog.h b/global/process_watchdog.h index 5a2487b..4eb23ce 100644 --- a/global/process_watchdog.h +++ b/global/process_watchdog.h @@ -3,7 +3,11 @@ #include #include +#include #include +#include +#include +#include #ifdef _WIN32 // Prevent reassignment of "interface" #pragma push_macro("interface") @@ -65,11 +69,49 @@ public: } private: +#ifdef _WIN32 +#elif defined __linux__ + /** + * @brief Check whether the debugger is present and running the application. + * @return Returns true when the debugger is present or false otherwise. + */ + bool IsDebuggerPresent() + { + std::string ssFilename = "/proc/self/status"; // For the current process + std::ifstream fstreamStatus(ssFilename); + if (!fstreamStatus.is_open()) return false; // Cannot open file, assume not debugged + + std::string ssLine; + while (std::getline(fstreamStatus, ssLine)) + { + if (ssLine.substr(0, 9) == "TracerPid") + { + std::stringstream sstreamLine(ssLine); + std::string ssKey; + int iTracerPid = 0; + sstreamLine >> ssKey >> iTracerPid; + return iTracerPid != 0; + } + } + return false; // TracerPid not found + } +#else + /** + * @brief Check whether the debugger is present and running the application. + * @return Returns true when the debugger is present or false otherwise. + */ + bool IsDebuggerPresent() + { + // No implementation available to check for a debugger. + return false; + } +#endif + /** * @brief Watch thread function. - * @param[in] iWatchdogTimeS The duration the watchdog is monitoring before process termination in seconds (default 120s). + * @param[in] iWatchdogTimeS The duration the watchdog is monitoring before process termination in seconds. */ - void WatchdogThreadFunc(int64_t iWatchdogTimeS = 120ll) + void WatchdogThreadFunc(int64_t iWatchdogTimeS) { // Run for the most the set time; then terminate... auto tpStart = std::chrono::steady_clock::now(); @@ -79,6 +121,9 @@ private: auto tpNow = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(tpNow - tpStart).count() > iWatchdogTimeS) { + // Do not end the process when a debugger is present. + if (IsDebuggerPresent()) continue; + std::cerr << "WATCHDOG TERMINATION ENFORCED!!!" << std::endl; std::cerr.flush(); #ifdef _WIN32 @@ -97,8 +142,8 @@ private: } } - bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate. - std::thread m_threadWatchdog; ///< The watchdog thread. + std::atomic_bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate. + std::thread m_threadWatchdog; ///< The watchdog thread. }; #endif // !defined PROCESS_WATCHDOG_H diff --git a/global/scheduler/scheduler.h b/global/scheduler/scheduler.h index 2c2ca03..c094892 100644 --- a/global/scheduler/scheduler.h +++ b/global/scheduler/scheduler.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "../flags.h" /** @@ -112,8 +113,8 @@ private: void ThreadFunc(); std::thread m_thread; ///< The thread that executes the tasks. - bool m_bShutdown = false; ///< Set when the thread should terminate. - bool m_bStarted = false; ///< Set when the thread has started. + std::atomic_bool m_bShutdown = false; ///< Set when the thread should terminate. + std::atomic_bool m_bStarted = false; ///< Set when the thread has started. std::function m_fnTask; ///< The task to execute (will be updated with new tasks before execution). std::mutex m_mtxSyncStart; ///< The startup synchronization mutex. std::condition_variable m_cvStarted; ///< Triggered by the thread to indicate that it has started. diff --git a/global/trace.h b/global/trace.h index e7f19b6..63e3d62 100644 --- a/global/trace.h +++ b/global/trace.h @@ -5,6 +5,9 @@ #include #include #include +#ifdef _MSC_VER +#include +#endif #ifdef __GNUC__ #include #endif @@ -29,7 +32,15 @@ inline std::string GetTimestamp() const auto current_milliseconds {std::chrono::duration_cast (current_time_since_epoch).count() % 1000}; std::ostringstream stream; - stream << "PID#" << std::dec << getpid() << " " << std::put_time(¤t_localtime, "%H:%M:%S") << "." << std::setw(3) << std::setfill('0') << current_milliseconds << ": "; +#ifdef _MSC_VER + stream << "PID#" << std::dec << _getpid() << " " << std::put_time(¤t_localtime, "%H:%M:%S") << "." << std::setw(3) << + std::setfill('0') << current_milliseconds << ": "; +#elif defined __GNUC__ + stream << "PID#" << std::dec << getpid() << " " << std::put_time(¤t_localtime, "%H:%M:%S") << "." << std::setw(3) << + std::setfill('0') << current_milliseconds << ": "; +#else +#error The current OS is not supported! +#endif return stream.str(); } @@ -66,7 +77,8 @@ inline void Trace(TArgs... tArgs) } #else // ENABLE_TRACE == 0 -inline void Trace() +template +inline void Trace(TArgs...) {} #ifdef __GNUC__ diff --git a/global/tracefifo/trace_fifo.h b/global/tracefifo/trace_fifo.h index a6ab871..95784ae 100644 --- a/global/tracefifo/trace_fifo.h +++ b/global/tracefifo/trace_fifo.h @@ -6,6 +6,7 @@ #include #include #include +#include /** * @brief trace fifo open flags @@ -460,14 +461,14 @@ private: */ void DispatchThreadFunc(); - const size_t nReadIndex = 0; ///< The read index of the pipe descriptor. - const size_t nWriteIndex = 1; ///< The write index of the pipe descriptor. - bool m_bShutdown = false; ///< Shutdown flag for the pipe reader thread. - int m_rgPipeStdOut[2] = { -1, -1 }; ///< StdOut pipe with read and write descriptors. - int m_rgPipeStdErr[2] = { -1, -1 }; ///< StdErr pipe with read and write descriptors. - int m_iOldStdOut = -1; ///< Old descriptor for the StdOut - int m_iOldStdErr = -1; ///< Old descriptor for the StdErr - std::thread m_threadDispatch; ///< Dispatch thread + const size_t nReadIndex = 0; ///< The read index of the pipe descriptor. + const size_t nWriteIndex = 1; ///< The write index of the pipe descriptor. + std::atomic_bool m_bShutdown = false; ///< Shutdown flag for the pipe reader thread. + int m_rgPipeStdOut[2] = { -1, -1 }; ///< StdOut pipe with read and write descriptors. + int m_rgPipeStdErr[2] = { -1, -1 }; ///< StdErr pipe with read and write descriptors. + int m_iOldStdOut = -1; ///< Old descriptor for the StdOut + int m_iOldStdErr = -1; ///< Old descriptor for the StdErr + std::thread m_threadDispatch; ///< Dispatch thread }; #endif // !defined TRACE_FIFO_H \ No newline at end of file diff --git a/sdv_executables/sdv_core/main.cpp b/sdv_executables/sdv_core/main.cpp index f4f05e0..cb4cbdd 100644 --- a/sdv_executables/sdv_core/main.cpp +++ b/sdv_executables/sdv_core/main.cpp @@ -1,4 +1,5 @@ #include +#include #include "../../global/cmdlnparser/cmdlnparser.cpp" #include "../../global/exec_dir_helper.h" #include @@ -22,7 +23,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // See: https://stackoverflow.com/questions/51209268/using-stdthread-in-a-library-loaded-with-dlopen-leads-to-a-sigsev // NOTE EVE 27.05.2025: in release builds, starting and ending the thread right after each other causes incorrect behavior and // leads in some cases to create a deadlock in the join-function. The solution is to add delays in the thread processing. - bool bThreadStarted = false; + std::atomic_bool bThreadStarted = false; std::thread thread = std::thread([&]() { std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/sdv_executables/sdv_idl_compiler/core_idl_backup.h b/sdv_executables/sdv_idl_compiler/core_idl_backup.h index 6eeb5e4..abe3f5a 100644 --- a/sdv_executables/sdv_idl_compiler/core_idl_backup.h +++ b/sdv_executables/sdv_idl_compiler/core_idl_backup.h @@ -957,7 +957,7 @@ namespace sdv static constexpr ::sdv::interface_id _id = 0xEE1AD4FC2B9217BB; /** - * \brief Comment mask + * @brief Comment mask */ enum class ECommentMask : uint32_t { @@ -1024,7 +1024,7 @@ namespace sdv static constexpr ::sdv::interface_id _id = 0xC7BB02340D82D7AE; /** - * \brief Does the entity have an unnamed definition. + * @brief Does the entity have an unnamed definition. * @return Returns 'true' when the entity has an unnamed definition; otherwise returns 'false'. */ virtual bool IsUnnamed() const = 0; diff --git a/sdv_executables/sdv_idl_compiler/entities/definition_entity.h b/sdv_executables/sdv_idl_compiler/entities/definition_entity.h index 58d4b8b..f8b989f 100644 --- a/sdv_executables/sdv_idl_compiler/entities/definition_entity.h +++ b/sdv_executables/sdv_idl_compiler/entities/definition_entity.h @@ -110,7 +110,7 @@ public: void CreateInheritanceValueChildNodes(); /** - * \brief Does the entity have an Unnamed definition. Overload of IDefinitionEntity::Unnamed. + * @brief Does the entity have an Unnamed definition. Overload of IDefinitionEntity::Unnamed. * @return Returns 'true' when the definition supports unnamed definition; 'false' otherwise. */ virtual bool IsUnnamed() const override { return m_bAnonymousDefinition; }; diff --git a/sdv_executables/sdv_idl_compiler/generator/ps_class_generator_base.h b/sdv_executables/sdv_idl_compiler/generator/ps_class_generator_base.h index 64ce9ee..3b05f04 100644 --- a/sdv_executables/sdv_idl_compiler/generator/ps_class_generator_base.h +++ b/sdv_executables/sdv_idl_compiler/generator/ps_class_generator_base.h @@ -99,8 +99,10 @@ protected: std::string ssName; ///< Parameter name std::string ssDefaultValue; ///< Parameter default value (or empty for void return value) std::string ssSize; ///< Parameter size - enum class EDirection { in, out, inout, ret, ignored } eDirection = EDirection::ignored; ///< Parameter direction or return value - enum class EAllocType { direct, indirect, ifc} eAllocType = EAllocType::direct; ///< Parameter allocation type + enum class EDirection ///< Parameter direction or return value + { in, out, inout, ret, ignored } eDirection = EDirection::ignored; ///< Parameter direction or return value + enum class EAllocType ///< Parameter allocation type + { direct, indirect, ifc} eAllocType = EAllocType::direct; ///< Parameter allocation type }; /** diff --git a/sdv_executables/sdv_idl_compiler/source.h b/sdv_executables/sdv_idl_compiler/source.h index d4066bd..8a5705a 100644 --- a/sdv_executables/sdv_idl_compiler/source.h +++ b/sdv_executables/sdv_idl_compiler/source.h @@ -107,7 +107,7 @@ private: void PotentialSwapBuffer(TChar* szBuffer, size_t nSize, bool bIsSourceBigEndian); /** - * \brief Convert a UTF16 string to UTF-8. + * @brief Convert a UTF16 string to UTF-8. * @param[in] szBuffer The source string. * @param[in] nSize The length of the string (or zero terminating string when supplied as 0). * @return Returns the string as UTF8 std::string object. @@ -115,7 +115,7 @@ private: static std::string ConvertToUTF8(const char16_t* szBuffer, size_t nSize); /** - * \brief Convert a UTF32 string to UTF-8. + * @brief Convert a UTF32 string to UTF-8. * @param[in] szBuffer The source string. * @param[in] nSize The length of the string (or zero terminating string when supplied as 0). * @return Returns the string as UTF8 std::string object. diff --git a/sdv_executables/sdv_iso/main.cpp b/sdv_executables/sdv_iso/main.cpp index 7c3fb3a..6d7b9f7 100644 --- a/sdv_executables/sdv_iso/main.cpp +++ b/sdv_executables/sdv_iso/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "../../global/cmdlnparser/cmdlnparser.cpp" @@ -69,7 +70,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // See: https://stackoverflow.com/questions/51209268/using-stdthread-in-a-library-loaded-with-dlopen-leads-to-a-sigsev // NOTE EVE 27.05.2025: in release builds, starting and ending the thread right after each other causes incorrect behavior and // leads in some cases to create a deadlock in the join-function. The solution is to add delays in the thread processing. - bool bThreadStarted = false; + std::atomic_bool bThreadStarted = false; std::thread thread = std::thread([&]() { std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -223,12 +224,13 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) return APP_CONTROL_INVALID_ISOLATION_CONFIG; } SConnectEventCallbackWrapper sConnectEventWrapper; - pConnect->RegisterStatusEventCallback(&sConnectEventWrapper); + uint64_t uiCookie = pConnect->RegisterStatusEventCallback(&sConnectEventWrapper); // Connect to the core repository sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject("CommunicationControl"); if (!pConnectionControl) { + pConnect->UnregisterStatusEventCallback(uiCookie); if (!bSilent) std::cerr << "ERROR: " << COMMUNICATION_CONTROL_SERVICE_ACCESS_ERROR_MSG << std::endl; return COMMUNICATION_CONTROL_SERVICE_ACCESS_ERROR; @@ -237,6 +239,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) sdv::com::TConnectionID tConnection = pConnectionControl->AssignClientEndpoint(ptrChannelEndpoint, 3000, pCoreRepo); if (!tConnection.uiControl || !pCoreRepo) { + pConnect->UnregisterStatusEventCallback(uiCookie); if (!bSilent) std::cerr << "ERROR: " << CONNECT_SDV_SERVER_ERROR_MSG << std::endl; return CONNECT_SDV_SERVER_ERROR; @@ -246,6 +249,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) sdv::core::ILinkCoreRepository* pLinkCoreRepo = sdv::core::GetObject("RepositoryService"); if (!pLinkCoreRepo) { + pConnect->UnregisterStatusEventCallback(uiCookie); if (!bSilent) std::cerr << "ERROR: " << LINK_REPO_SERVICE_ERROR_MSG << std::endl; return LINK_REPO_SERVICE_ERROR; @@ -264,6 +268,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) sdv::core::IRepositoryUtilityCreate* pCreateUtility = sdv::core::GetObject("RepositoryService"); if (!pRepositoryInfo || !pRepositoryControl || !pObjectAccess || !pCreateUtility) { + pConnect->UnregisterStatusEventCallback(uiCookie); if (!bSilent) std::cerr << "ERROR: " << REPOSITORY_SERVICE_ACCESS_ERROR_MSG << std::endl; return REPOSITORY_SERVICE_ACCESS_ERROR; @@ -337,6 +342,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) } // Shutdwown + pConnect->UnregisterStatusEventCallback(uiCookie); parser.Clear(); pLinkCoreRepo->UnlinkCoreRepository(); appcontrol.Shutdown(); diff --git a/sdv_executables/sdv_packager/CMakeLists.txt b/sdv_executables/sdv_packager/CMakeLists.txt index 719c181..c0e8648 100644 --- a/sdv_executables/sdv_packager/CMakeLists.txt +++ b/sdv_executables/sdv_packager/CMakeLists.txt @@ -12,10 +12,13 @@ add_executable(sdv_packager "../../sdv_services/core/toml_parser/parser_toml.cpp" "../../sdv_services/core/toml_parser/lexer_toml.h" "../../sdv_services/core/toml_parser/lexer_toml.cpp" + "../../sdv_services/core/toml_parser/lexer_toml_token.cpp" "../../sdv_services/core/toml_parser/parser_node_toml.h" "../../sdv_services/core/toml_parser/parser_node_toml.cpp" "../../sdv_services/core/toml_parser/character_reader_utf_8.h" "../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" + "../../sdv_services/core/toml_parser/miscellaneous.h" + "../../sdv_services/core/toml_parser/miscellaneous.cpp" "../../sdv_services/core/toml_parser/exception.h" "environment.cpp" "environment.h" diff --git a/sdv_executables/sdv_packager/environment.cpp b/sdv_executables/sdv_packager/environment.cpp index b26a8be..197e299 100644 --- a/sdv_executables/sdv_packager/environment.cpp +++ b/sdv_executables/sdv_packager/environment.cpp @@ -929,7 +929,7 @@ bool CSdvPackagerEnvironment::ProcessCommandLine(const std::vector& } else { - // Check whether preceeded by at least one command + // Check whether preceded by at least one command if (!(m_uiShowFlags & 0x00ff)) { m_nError = CMDLN_MISSING_SHOW_COMMAND; diff --git a/sdv_executables/sdv_packager/environment.h b/sdv_executables/sdv_packager/environment.h index 7877c35..803ee67 100644 --- a/sdv_executables/sdv_packager/environment.h +++ b/sdv_executables/sdv_packager/environment.h @@ -33,6 +33,9 @@ inline bool iequals(const std::string& rssLeft, const std::string& rssRight) return std::equal(rssLeft.begin(), rssLeft.end(), rssRight.begin(), rssRight.end(), ichar_equals); } +/** + * @brief Environment access class. + */ class CSdvPackagerEnvironment { public: diff --git a/sdv_executables/sdv_trace_mon/main.cpp b/sdv_executables/sdv_trace_mon/main.cpp index 6ddbcfd..18acce2 100644 --- a/sdv_executables/sdv_trace_mon/main.cpp +++ b/sdv_executables/sdv_trace_mon/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "../../global/cmdlnparser/cmdlnparser.cpp" @@ -73,7 +74,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[]) // See: https://stackoverflow.com/questions/51209268/using-stdthread-in-a-library-loaded-with-dlopen-leads-to-a-sigsev // NOTE EVE 27.05.2025: in release builds, starting and ending the thread right after each other causes incorrect behavior and // leads in some cases to create a deadlock in the join-function. The solution is to add delays in the thread processing. - bool bThreadStarted = false; + std::atomic_bool bThreadStarted = false; std::thread thread = std::thread([&]() { std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/sdv_executables/sdv_vss_util/vss_helper.h b/sdv_executables/sdv_vss_util/vss_helper.h index 3b5fca4..cf89e34 100644 --- a/sdv_executables/sdv_vss_util/vss_helper.h +++ b/sdv_executables/sdv_vss_util/vss_helper.h @@ -344,7 +344,7 @@ protected: /** * @brief add to the type a cast, for example for 'std::string' it adds "const std::string&' - * @param[in]ssSignalType the string representing the type + * @param[in] ssSignalType the string representing the type * @return Returns either original string or 'const' and '&' added. */ std::string CastValueType(const std::string& ssSignalType) const; diff --git a/sdv_services/core/CMakeLists.txt b/sdv_services/core/CMakeLists.txt index 97c11bb..343cde9 100644 --- a/sdv_services/core/CMakeLists.txt +++ b/sdv_services/core/CMakeLists.txt @@ -42,7 +42,7 @@ add_library(core_services SHARED "installation_composer.h" "installation_composer.cpp" - ) + "toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp") # Link target if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") diff --git a/sdv_services/core/app_config.cpp b/sdv_services/core/app_config.cpp index 5d9b27c..2cc7055 100644 --- a/sdv_services/core/app_config.cpp +++ b/sdv_services/core/app_config.cpp @@ -61,10 +61,10 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st // Reset the current baseline ResetConfigBaseline(); - CParserTOML parser(ssContent); + toml_parser::CParser parser(ssContent); // Check for config file compatibility - auto ptrConfigVersion = parser.GetRoot().GetDirect("Configuration.Version"); + auto ptrConfigVersion = parser.Root().Direct("Configuration.Version"); if (!ptrConfigVersion) { SDV_LOG_ERROR("Configuration version must be present in the configuration file."); @@ -84,17 +84,17 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st !GetAppControl().IsMaintenanceApplication()) { // Load all modules in the component section - auto ptrComponents = parser.GetRoot().GetDirect("Component"); - if (ptrComponents && ptrComponents->GetArray()) + auto ptrComponents = parser.Root().Direct("Component"); + if (ptrComponents && ptrComponents->Cast()) { - for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++) + for (uint32_t uiIndex = 0; uiIndex < ptrComponents->Cast()->GetCount(); uiIndex++) { - auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex); + auto ptrComponent = ptrComponents->Cast()->Get(uiIndex); if (ptrComponent) { // Get the information from the component. std::filesystem::path pathModule; - auto ptrModule = ptrComponent->GetDirect("Path"); + auto ptrModule = ptrComponent->Direct("Path"); if (ptrModule) pathModule = static_cast(ptrModule->GetValue()); // If there is no path, this is allowed... skip this module @@ -118,17 +118,17 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st } // Load all modules from the module section. - auto ptrModules = parser.GetRoot().GetDirect("Module"); - if (ptrModules && ptrModules->GetArray()) + auto ptrModules = parser.Root().Direct("Module"); + if (ptrModules && ptrModules->Cast()) { - for (uint32_t uiIndex = 0; uiIndex < ptrModules->GetArray()->GetCount(); uiIndex++) + for (uint32_t uiIndex = 0; uiIndex < ptrModules->Cast()->GetCount(); uiIndex++) { - auto ptrModule = ptrModules->GetArray()->Get(uiIndex); + auto ptrModule = ptrModules->Cast()->Get(uiIndex); if (ptrModule) { // Get the information from the component. std::filesystem::path pathModule; - auto ptrModulePath = ptrModule->GetDirect("Path"); + auto ptrModulePath = ptrModule->Direct("Path"); if (ptrModulePath) pathModule = static_cast(ptrModulePath->GetValue()); if (pathModule.empty()) @@ -158,23 +158,23 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st // Start all components from the component section std::filesystem::path pathLastModule; - auto ptrComponents = parser.GetRoot().GetDirect("Component"); - if (ptrComponents && ptrComponents->GetArray()) + auto ptrComponents = parser.Root().Direct("Component"); + if (ptrComponents && ptrComponents->Cast()) { - for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++) + for (uint32_t uiIndex = 0; uiIndex < ptrComponents->Cast()->GetCount(); uiIndex++) { - auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex); + auto ptrComponent = ptrComponents->Cast()->Get(uiIndex); if (ptrComponent) { // Get the information from the component. std::filesystem::path pathModule; - auto ptrModule = ptrComponent->GetDirect("Path"); + auto ptrModule = ptrComponent->Direct("Path"); if (ptrModule) pathModule = static_cast(ptrModule->GetValue()); std::string ssClass; - auto ptrClass = ptrComponent->GetDirect("Class"); + auto ptrClass = ptrComponent->Direct("Class"); if (ptrClass) ssClass = static_cast(ptrClass->GetValue()); std::string ssName; - auto ptrName = ptrComponent->GetDirect("Name"); + auto ptrName = ptrComponent->Direct("Name"); if (ptrName) ssName = static_cast(ptrName->GetValue()); // If there is a path, store it. If there is none, take the last stored @@ -204,10 +204,10 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st { auto itModule = mapModules.find(pathModule); if (itModule == mapModules.end()) continue; // Module was not loaded before... - tObjectID = GetRepository().CreateObjectFromModule(itModule->second, ssClass, ssName, ptrComponent->CreateTOMLText()); + tObjectID = GetRepository().CreateObjectFromModule(itModule->second, ssClass, ssName, ptrComponent->GenerateTOML()); } else - tObjectID = GetRepository().CreateObject2(ssClass, ssName, ptrComponent->CreateTOMLText()); + tObjectID = GetRepository().CreateObject2(ssClass, ssName, ptrComponent->GenerateTOML()); if (!tObjectID) { diff --git a/sdv_services/core/app_control.cpp b/sdv_services/core/app_control.cpp index b706fa2..865011b 100644 --- a/sdv_services/core/app_control.cpp +++ b/sdv_services/core/app_control.cpp @@ -723,7 +723,7 @@ void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState) bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) { - CParserTOML parserConfig; + toml_parser::CParser parserConfig; std::string ssError; try { @@ -736,7 +736,7 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) } // Get the reporting settings (if this succeeded at all...) - auto ptrReport = parserConfig.GetRoot().GetDirect("Console.Report"); + auto ptrReport = parserConfig.Root().Direct("Console.Report"); if (ptrReport && ptrReport->GetValue() == "Silent") m_bSilent = true; if (ptrReport && ptrReport->GetValue() == "Verbose") m_bVerbose = true; @@ -751,8 +751,8 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) // Allow a custom logger to be defined std::filesystem::path pathLoggerModule; std::string ssLoggerClass; - std::shared_ptr ptrLogHandlerPath = parserConfig.GetRoot().GetDirect("LogHandler.Path"); - std::shared_ptr ptrLogHandlerClass = parserConfig.GetRoot().GetDirect("LogHandler.Class"); + std::shared_ptr ptrLogHandlerPath = parserConfig.Root().Direct("LogHandler.Path"); + std::shared_ptr ptrLogHandlerClass = parserConfig.Root().Direct("LogHandler.Class"); if (ptrLogHandlerPath && !ptrLogHandlerClass) { if (!m_bSilent) @@ -778,12 +778,12 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) } // Get an optional program tag for the logger - std::shared_ptr ptrLogPogramTag = parserConfig.GetRoot().GetDirect("LogHandler.Tag"); + std::shared_ptr ptrLogPogramTag = parserConfig.Root().Direct("LogHandler.Tag"); if (ptrLogPogramTag) m_ssProgramTag = static_cast(ptrLogPogramTag->GetValue()); // Get the application-mode std::string ssApplication = "Standalone"; - std::shared_ptr ptrApplication = parserConfig.GetRoot().GetDirect("Application.Mode"); + std::shared_ptr ptrApplication = parserConfig.Root().Direct("Application.Mode"); if (ptrApplication) ssApplication = static_cast(ptrApplication->GetValue()); if (ssApplication == "Standalone") m_eContextMode = sdv::app::EAppContext::standalone; else if (ssApplication == "External") m_eContextMode = sdv::app::EAppContext::external; @@ -820,21 +820,21 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) sdv::core::ELogSeverity eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::error; if (IsMainApplication() || IsIsolatedApplication()) eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::info; - std::shared_ptr ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.Filter"); + std::shared_ptr ptrLogSeverityFilter = parserConfig.Root().Direct("LogHandler.Filter"); m_eSeverityFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "", sdv::core::ELogSeverity::info); - ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.ViewFilter"); + ptrLogSeverityFilter = parserConfig.Root().Direct("LogHandler.ViewFilter"); m_eSeverityViewFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "", eLogDefaultViewSeverityFilter); // Get the optional instance ID. - std::shared_ptr ptrInstance = parserConfig.GetRoot().GetDirect("Application.Instance"); + std::shared_ptr ptrInstance = parserConfig.Root().Direct("Application.Instance"); if (ptrInstance) m_uiInstanceID = ptrInstance->GetValue(); else m_uiInstanceID = 1000u; // Number of attempts to establish a connection to a running instance. - std::shared_ptr ptrRetries = parserConfig.GetRoot().GetDirect("Application.Retries"); + std::shared_ptr ptrRetries = parserConfig.Root().Direct("Application.Retries"); if (ptrRetries) { m_uiRetries = ptrRetries->GetValue(); @@ -848,7 +848,7 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) if (IsMainApplication() || IsIsolatedApplication()) { // Get the optional installation directory. - std::shared_ptr ptrInstallDir = parserConfig.GetRoot().GetDirect("Application.InstallDir"); + std::shared_ptr ptrInstallDir = parserConfig.Root().Direct("Application.InstallDir"); if (ptrInstallDir) { m_pathRootDir = ptrInstallDir->GetValue(); @@ -877,7 +877,7 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) // not auto-updateable. if (!IsMaintenanceApplication() && !IsIsolatedApplication()) { - auto ptrConfigFile = parserConfig.GetRoot().GetDirect("Application.Config"); + auto ptrConfigFile = parserConfig.Root().Direct("Application.Config"); if (ptrConfigFile) m_pathAppConfig = ptrConfigFile->GetValue(); } @@ -905,10 +905,10 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) try { // Read the configuration - CParserTOML parserSettings(ssContent); + toml_parser::CParser parserSettings(ssContent); // Check for the version - auto ptrVersion = parserSettings.GetRoot().GetDirect("Settings.Version"); + auto ptrVersion = parserSettings.Root().Direct("Settings.Version"); if (!ptrVersion) { if (!m_bSilent) @@ -925,12 +925,12 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) } // Read the system configurations - auto ptrSystemConfigs = parserSettings.GetRoot().GetDirect("Settings.SystemConfig"); - if (ptrSystemConfigs && ptrSystemConfigs->GetArray()) + auto ptrSystemConfigs = parserSettings.Root().Direct("Settings.SystemConfig"); + if (ptrSystemConfigs && ptrSystemConfigs->Cast()) { - for (uint32_t uiIndex = 0; uiIndex < ptrSystemConfigs->GetArray()->GetCount(); uiIndex++) + for (uint32_t uiIndex = 0; uiIndex < ptrSystemConfigs->Cast()->GetCount(); uiIndex++) { - auto ptrSystemConfig = ptrSystemConfigs->GetArray()->Get(uiIndex); + auto ptrSystemConfig = ptrSystemConfigs->Cast()->Get(uiIndex); if (!ptrSystemConfig) continue; m_vecSysConfigs.push_back(static_cast(ptrSystemConfig->GetValue())); } @@ -939,7 +939,7 @@ bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig) // Get the application config - but only when not specified over the app-control-config. if (m_pathAppConfig.empty()) { - auto ptrAppConfig = parserSettings.GetRoot().GetDirect("Settings.AppConfig"); + auto ptrAppConfig = parserSettings.Root().Direct("Settings.AppConfig"); if (ptrAppConfig) { // Path available. Enable auto-update. diff --git a/sdv_services/core/installation_manifest.cpp b/sdv_services/core/installation_manifest.cpp index 34dc7b7..508e7bd 100644 --- a/sdv_services/core/installation_manifest.cpp +++ b/sdv_services/core/installation_manifest.cpp @@ -164,22 +164,22 @@ bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObj m_bBlockSystemObjects = bBlockSystemObjects; // Parse the manifest - CParserTOML parser(rssManifest); + toml_parser::CParser parser(rssManifest); // Get the installation version - must be identical to the interface version - auto ptrInstallVersionNode = parser.GetRoot().GetDirect("Installation.Version"); + auto ptrInstallVersionNode = parser.Root().Direct("Installation.Version"); if (!ptrInstallVersionNode || ptrInstallVersionNode->GetValue() != SDVFrameworkInterfaceVersion) return false; // Get the installation name - auto ptrInstallNameNode = parser.GetRoot().GetDirect("Installation.Name"); + auto ptrInstallNameNode = parser.Root().Direct("Installation.Name"); if (!ptrInstallNameNode) return false; m_ssInstallName = static_cast(ptrInstallNameNode->GetValue()); if (m_ssInstallName.empty()) return false; // Get installation properties. The properties are optional - auto ptrProperties = parser.GetRoot().GetDirect("Properties"); - std::shared_ptr ptrPropertyTable; - if (ptrProperties) ptrPropertyTable = ptrProperties->GetTable(); + auto ptrProperties = parser.Root().Direct("Properties"); + std::shared_ptr ptrPropertyTable; + if (ptrProperties) ptrPropertyTable = ptrProperties->Cast(); if (ptrPropertyTable) { for (uint32_t uiIndex = 0; uiIndex < ptrPropertyTable->GetCount(); uiIndex++) @@ -191,9 +191,9 @@ bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObj } // Build the module list - auto ptrModulesNode = parser.GetRoot().GetDirect("Module"); + auto ptrModulesNode = parser.Root().Direct("Module"); if (!ptrModulesNode) return true; // No modules in the manifest - auto ptrModuleArrayNode = ptrModulesNode->GetArray(); + auto ptrModuleArrayNode = ptrModulesNode->Cast(); if (!ptrModuleArrayNode) return false; // Must be array for (uint32_t uiModuleIndex = 0; uiModuleIndex < ptrModuleArrayNode->GetCount(); uiModuleIndex++) { @@ -202,19 +202,19 @@ bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObj if (!ptrModule) continue; // Get the module path - auto ptrModulePath = ptrModule->GetDirect("Path"); + auto ptrModulePath = ptrModule->Direct("Path"); if (!ptrModulePath) continue; std::filesystem::path pathModule = static_cast(ptrModulePath->GetValue()); std::string ssModuleManifest; // Get the component list (if available) - auto ptrModuleComponents = ptrModule->GetDirect("Component"); + auto ptrModuleComponents = ptrModule->Direct("Component"); if (ptrModuleComponents) { // The module manifest contains the TOML text of the component array - auto ptrModuleComponentArray = ptrModuleComponents->GetArray(); + auto ptrModuleComponentArray = ptrModuleComponents->Cast(); if (ptrModuleComponentArray) - ssModuleManifest = ptrModuleComponents->CreateTOMLText(); + ssModuleManifest = ptrModuleComponents->GenerateTOML(); } // Add the module @@ -256,10 +256,10 @@ std::string CInstallManifest::Write() const sstream << "[[Module]]" << std::endl << "Path=\"" << rsEntry.pathRelModule.generic_u8string() << "\"" << std::endl; // Read the module manifest - CParserTOML parser(rsEntry.ssManifest); + toml_parser::CParser parser(rsEntry.ssManifest); // Add the module manifest as part of the installation manifest. - sstream << parser.CreateTOMLText("Module") << std::endl; + sstream << parser.GenerateTOML("Module") << std::endl; } return sstream.str(); @@ -286,14 +286,14 @@ bool CInstallManifest::AddModule(const std::filesystem::path& rpathModulePath, if (!ssManifest.empty()) { // Check the interface version for compatibility - CParserTOML parser(ssManifest); - auto ptrInterfaceNode = parser.GetRoot().GetDirect("Interface.Version"); + toml_parser::CParser parser(ssManifest); + auto ptrInterfaceNode = parser.Root().Direct("Interface.Version"); if (!ptrInterfaceNode) return false; if (ptrInterfaceNode->GetValue() != SDVFrameworkInterfaceVersion) return false; - auto ptrComponentsNode = parser.GetRoot().GetDirect("Component"); + auto ptrComponentsNode = parser.Root().Direct("Component"); if (!ptrComponentsNode) return true; // No component available in the manifest - if (!ptrComponentsNode->GetArray()) return false; - ssComponentsManifest = ptrComponentsNode->CreateTOMLText(); + if (!ptrComponentsNode->Cast()) return false; + ssComponentsManifest = ptrComponentsNode->GenerateTOML(); } // Store path and component @@ -398,7 +398,7 @@ bool CInstallManifest::NeedQuotedName(const std::string& rssName) { for (char c : rssName) { - if (!std::isalnum(c) && c != '_' && c != '-') + if (static_cast(c) > 127u || (!std::isalnum(c) && c != '_' && c != '-')) return true; } return false; @@ -408,10 +408,10 @@ CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, bool bBlockSystemObjects) : pathRelModule(rpathRelModule), ssManifest(rssManifest) { // Parse the manifest and extract information from them... - CParserTOML parser(rssManifest); - auto ptrComponents = parser.GetRoot().GetDirect("Component"); + toml_parser::CParser parser(rssManifest); + auto ptrComponents = parser.Root().Direct("Component"); if (!ptrComponents) return; // No objects... - auto ptrComponentArray = ptrComponents->GetArray(); + auto ptrComponentArray = ptrComponents->Cast(); if (!ptrComponentArray) return; // No objects... for (uint32_t uiIndex = 0; uiIndex < ptrComponentArray->GetCount(); uiIndex++) { @@ -422,14 +422,14 @@ CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, SComponent sComponent{}; //sComponent.pathModule = rpathModule; sComponent.pathRelModule = rpathRelModule; - sComponent.ssManifest = ptrComponent->CreateTOMLText("Component"); - auto ptrClassName = ptrComponent->GetDirect("Class"); + sComponent.ssManifest = ptrComponent->GenerateTOML(toml_parser::CGenContext("Component")); + auto ptrClassName = ptrComponent->Direct("Class"); if (!ptrClassName) continue; sComponent.ssClassName = static_cast(ptrClassName->GetValue()); - auto ptrAliases = ptrComponent->GetDirect("Aliases"); + auto ptrAliases = ptrComponent->Direct("Aliases"); if (ptrAliases) { - auto ptrAliasesArray = ptrAliases->GetArray(); + auto ptrAliasesArray = ptrAliases->Cast(); for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++) { auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex); @@ -437,10 +437,10 @@ CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, sComponent.seqAliases.push_back(static_cast(ptrClassAlias->GetValue())); } } - auto ptrDefaultName = ptrComponent->GetDirect("DefaultName"); + auto ptrDefaultName = ptrComponent->Direct("DefaultName"); if (ptrDefaultName) sComponent.ssDefaultObjectName = static_cast(ptrDefaultName->GetValue()); else sComponent.ssDefaultObjectName = sComponent.ssClassName; - auto ptrType = ptrComponent->GetDirect("Type"); + auto ptrType = ptrComponent->Direct("Type"); if (!ptrType) continue; std::string ssType = static_cast(ptrType->GetValue()); if (ssType == "System") sComponent.eType = sdv::EObjectType::SystemObject; @@ -453,13 +453,13 @@ CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, else if (ssType == "Utility") sComponent.eType = sdv::EObjectType::Utility; else continue; if (bBlockSystemObjects && sComponent.eType == sdv::EObjectType::SystemObject) continue; - auto ptrSingleton = ptrComponent->GetDirect("Singleton"); + auto ptrSingleton = ptrComponent->Direct("Singleton"); if (ptrSingleton && static_cast(ptrSingleton->GetValue())) sComponent.uiFlags = static_cast(sdv::EObjectFlags::singleton); - auto ptrDependencies = ptrComponent->GetDirect("Dependencies"); + auto ptrDependencies = ptrComponent->Direct("Dependencies"); if (ptrDependencies) { - auto ptrDependencyArray = ptrDependencies->GetArray(); + auto ptrDependencyArray = ptrDependencies->Cast(); for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount(); uiDependencyIndex++) { diff --git a/sdv_services/core/installation_manifest.h b/sdv_services/core/installation_manifest.h index 223573f..e6f34c1 100644 --- a/sdv_services/core/installation_manifest.h +++ b/sdv_services/core/installation_manifest.h @@ -299,7 +299,7 @@ private: /** * @brief Interpret a version number as string. * @details A version string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored). - * @param rssVersion Reference to the version string. + * @param[in] rssVersion Reference to the version string. * @return The interpreted version. */ inline sdv::installation::SPackageVersion InterpretVersionString(const std::string& rssVersion) diff --git a/sdv_services/core/module.cpp b/sdv_services/core/module.cpp index f60f4cf..76cf822 100644 --- a/sdv_services/core/module.cpp +++ b/sdv_services/core/module.cpp @@ -206,10 +206,10 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept try { - CParserTOML parser(ssManifest); + toml_parser::CParser parser(ssManifest); // Check for the interface version - currently must be equal. - auto ptrVersion = parser.GetRoot().GetDirect("Interface.Version"); + auto ptrVersion = parser.Root().Direct("Interface.Version"); if (ptrVersion) m_uiIfcVersion = ptrVersion->GetValue(); if (!ptrVersion || m_uiIfcVersion != SDVFrameworkInterfaceVersion) { @@ -230,26 +230,26 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept } // Get available classes - auto ptrComponents = parser.GetRoot().GetDirect("Component"); - if (!ptrComponents || !ptrComponents->GetArray()) + auto ptrComponents = parser.Root().Direct("Component"); + if (!ptrComponents || !ptrComponents->Cast()) { SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no components available"); Unload(true); return false; } - for (std::uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++) + for (std::uint32_t uiIndex = 0; uiIndex < ptrComponents->Cast()->GetCount(); uiIndex++) { // Fill in the class info. sdv::SClassInfo sInfo{}; - auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex); + auto ptrComponent = ptrComponents->Cast()->Get(uiIndex); if (!ptrComponent) continue; - auto ptrClassName = ptrComponent->GetDirect("Class"); + auto ptrClassName = ptrComponent->Direct("Class"); if (!ptrClassName) continue; sInfo.ssClassName = static_cast(ptrClassName->GetValue()); - auto ptrAliases = ptrComponent->GetDirect("Aliases"); + auto ptrAliases = ptrComponent->Direct("Aliases"); if (ptrAliases) { - auto ptrAliasesArray = ptrAliases->GetArray(); + auto ptrAliasesArray = ptrAliases->Cast(); for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++) { auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex); @@ -257,10 +257,10 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept sInfo.seqClassAliases.push_back(static_cast(ptrClassAlias->GetValue())); } } - auto ptrDefaultName = ptrComponent->GetDirect("DefaultName"); + auto ptrDefaultName = ptrComponent->Direct("DefaultName"); if (ptrDefaultName) sInfo.ssDefaultObjectName = static_cast(ptrDefaultName->GetValue()); else sInfo.ssDefaultObjectName = sInfo.ssClassName; - auto ptrType = ptrComponent->GetDirect("Type"); + auto ptrType = ptrComponent->Direct("Type"); if (!ptrType) continue; std::string ssType = static_cast(ptrType->GetValue()); if (ssType == "System") sInfo.eType = sdv::EObjectType::SystemObject; @@ -272,13 +272,13 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept else if (ssType == "Stub") sInfo.eType = sdv::EObjectType::Stub; else if (ssType == "Utility") sInfo.eType = sdv::EObjectType::Utility; else continue; - auto ptrSingleton = ptrComponent->GetDirect("Singleton"); + auto ptrSingleton = ptrComponent->Direct("Singleton"); if (ptrSingleton && static_cast(ptrSingleton->GetValue())) sInfo.uiFlags = static_cast(sdv::EObjectFlags::singleton); - auto ptrDependencies = ptrComponent->GetDirect("Dependencies"); + auto ptrDependencies = ptrComponent->Direct("Dependencies"); if (ptrDependencies) { - auto ptrDependencyArray = ptrDependencies->GetArray(); + auto ptrDependencyArray = ptrDependencies->Cast(); for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount(); uiDependencyIndex++) { diff --git a/sdv_services/core/module_control.cpp b/sdv_services/core/module_control.cpp index f02708b..0552c18 100644 --- a/sdv_services/core/module_control.cpp +++ b/sdv_services/core/module_control.cpp @@ -312,11 +312,11 @@ sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rp { size_t nIndex = 0; size_t nUnsupportedObjectCount = 0; - CParserTOML parser(rssManifest); + toml_parser::CParser parser(rssManifest); do { - std::shared_ptr ptrComponentType = - parser.GetRoot().GetDirect(std::string("Component[") + std::to_string(nIndex++) + "].Type"); + std::shared_ptr ptrComponentType = + parser.Root().Direct(std::string("Component[") + std::to_string(nIndex++) + "].Type"); if (!ptrComponentType) break; std::string ssType = static_cast(ptrComponentType->GetValue()); if (ssType == "Device") continue; // Okay diff --git a/sdv_services/core/repository.cpp b/sdv_services/core/repository.cpp index 8d74108..afac423 100644 --- a/sdv_services/core/repository.cpp +++ b/sdv_services/core/repository.cpp @@ -983,11 +983,11 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs sstreamConfig << "Object = \"" << rssObjectName << "\"" << std::endl << std::endl; if (!rssObjectConfig.empty()) { - CParserTOML parserConfig(rssObjectConfig); - sstreamConfig << parserConfig.CreateTOMLText("Isolation.Config"); + toml_parser::CParser parserConfig(rssObjectConfig); + sstreamConfig << parserConfig.GenerateTOML("Isolation.Config"); } - CParserTOML parserConnection(ssConnectionString); - sstreamConfig << parserConnection.CreateTOMLText("Isolation.Connection"); + toml_parser::CParser parserConnection(ssConnectionString); + sstreamConfig << parserConnection.GenerateTOML("Isolation.Connection"); // Create command line arguments sdv::sequence seqArguments; @@ -1177,8 +1177,9 @@ sdv::core::TObjectID CRepository::CreateObjectID() static sdv::core::TObjectID tCurrent = 0; if (!tCurrent) { - srand(static_cast(time(0))); - tCurrent = rand(); + std::srand(static_cast(time(0))); + tCurrent = 0; + while (!tCurrent) tCurrent = std::rand(); } return ++tCurrent; } diff --git a/sdv_services/core/sdv_core.h b/sdv_services/core/sdv_core.h index 99b2cb7..c19c441 100644 --- a/sdv_services/core/sdv_core.h +++ b/sdv_services/core/sdv_core.h @@ -22,7 +22,7 @@ public: CSDVCore(); /** - * \brief Destructor + * @brief Destructor */ ~CSDVCore(); diff --git a/sdv_services/core/toml_parser/character_reader_utf_8.cpp b/sdv_services/core/toml_parser/character_reader_utf_8.cpp index de062b2..644e160 100644 --- a/sdv_services/core/toml_parser/character_reader_utf_8.cpp +++ b/sdv_services/core/toml_parser/character_reader_utf_8.cpp @@ -1,323 +1,302 @@ #include "character_reader_utf_8.h" #include "exception.h" -CCharacterReaderUTF8::CCharacterReaderUTF8() : m_nDataLength{0}, m_nCursor{0} -{} - -CCharacterReaderUTF8::CCharacterReaderUTF8(const std::string& rssString) : - m_ssString{rssString}, m_nDataLength{rssString.size()}, m_nCursor{0} +/// The TOML parser namespace +namespace toml_parser { - CheckForInvalidUTF8Bytes(); - CheckForInvalidUTF8Sequences(); -} - -void CCharacterReaderUTF8::Feed(const std::string& rssString) -{ - m_ssString = rssString; - m_nDataLength = rssString.size(); - m_nCursor = 0; - - CheckForInvalidUTF8Bytes(); - CheckForInvalidUTF8Sequences(); -} - -void CCharacterReaderUTF8::Reset() -{ - m_ssString.clear(); - m_nDataLength = 0; - m_nCursor = 0; -} - -std::string CCharacterReaderUTF8::Peek() -{ - return GetNextCharacter(); -} - -std::string CCharacterReaderUTF8::Peek(std::size_t n) -{ - if (n < 1) + CCharacterReaderUTF8::CCharacterReaderUTF8(const std::string& rssString) : m_ssString{rssString} { - return ""; + CheckForInvalidUTF8Bytes(); + CheckForInvalidUTF8Sequences(); } - size_t offset{0}; - for (std::size_t i = 0; i < n - 1; ++i) + + void CCharacterReaderUTF8::Feed(const std::string& rssString) { - offset += GetLengthOfNextCharacter(offset); - if (m_nDataLength <= m_nCursor + offset) + m_ssString = rssString; + m_nCursor = 0; + + CheckForInvalidUTF8Bytes(); + CheckForInvalidUTF8Sequences(); + } + + void CCharacterReaderUTF8::Reset() + { + m_ssString.clear(); + m_nCursor = 0; + } + + std::string CCharacterReaderUTF8::Peek(size_t nSkip /*= 0*/, size_t nAmount /*= 1*/) const + { + if (IsEOF()) + return {}; + size_t nOffset = 0; + for (size_t n = 0; n < nSkip; ++n) { - return ""; + nOffset += GetLengthOfNextCharacter(nOffset); + if (m_ssString.size() <= m_nCursor + nOffset) + return {}; } - } - return GetNextCharacter(offset); -} - -std::string CCharacterReaderUTF8::PeekUntil(const std::vector& lstCollection) -{ - size_t offset{0}; - bool found{false}; - std::string accumulation; - while (!found && m_nDataLength > m_nCursor + offset) - { - std::string character = GetNextCharacter(offset); - offset += GetLengthOfNextCharacter(offset); - for (const auto& delimiter : lstCollection) + std::string ssCharacters; + for (size_t n = 0; n < nAmount; n++) { - if (delimiter == character) + ssCharacters += GetNextCharacter(nOffset); + nOffset += GetLengthOfNextCharacter(nOffset); + } + return ssCharacters; + } + + std::string CCharacterReaderUTF8::PeekUntil(const std::vector& lstCollection) const + { + size_t nOffset = 0; + bool bFound = false; + std::string accumulation; + while (!bFound && m_ssString.size() > m_nCursor + nOffset) + { + std::string ssCharacter = GetNextCharacter(nOffset); + nOffset += GetLengthOfNextCharacter(nOffset); + for (const auto& delimiter : lstCollection) { - found = true; + if (delimiter == ssCharacter) + bFound = true; + } + if (!bFound) + accumulation += ssCharacter; + } + return accumulation; + } + + std::string CCharacterReaderUTF8::Consume(size_t nSkip /*= 0*/, size_t nAmount /*= 1*/) + { + if (IsEOF()) + return {}; + for (size_t n = 0; n < nSkip; ++n) + { + size_t nLength = GetLengthOfNextCharacter(); + m_nCursor += nLength; + if (!nLength || m_ssString.size() < m_nCursor) + return {}; + } + std::string ssCharacters; + for (size_t n = 0; n < nAmount; n++) + { + ssCharacters += GetNextCharacter(); + m_nCursor += GetLengthOfNextCharacter(); + } + return ssCharacters; + } + + std::string CCharacterReaderUTF8::ConsumeUntil(const std::vector& lstCollection) + { + size_t nOffset = 0; + bool bFound = false; + std::string accumulation; + while (!bFound && m_ssString.size() > m_nCursor + nOffset) + { + std::string ssCharacter = GetNextCharacter(nOffset); + nOffset += GetLengthOfNextCharacter(nOffset); + for (const auto& delimiter : lstCollection) + { + if (delimiter == ssCharacter) + { + bFound = true; + } + } + if (!bFound) + { + accumulation += ssCharacter; + } + else + { + nOffset -= ssCharacter.size(); } } - if (!found) - { - accumulation += character; - } + m_nCursor += nOffset; + return accumulation; } - return accumulation; -} -std::string CCharacterReaderUTF8::Consume() -{ - if (IsEOF()) + bool CCharacterReaderUTF8::IsEOF() const { - return ""; + return m_ssString.size() < m_nCursor + 1; } - std::string character{GetNextCharacter()}; - m_nCursor += GetLengthOfNextCharacter(); - return character; -} -std::string CCharacterReaderUTF8::Consume(std::size_t n) -{ - if (n < 1) + void CCharacterReaderUTF8::SetBookmark() { - return ""; + m_nBookmark = m_nCursor; } - size_t offset{0}; - for (uint32_t i = 0; i < n - 1; ++i) - { - offset += GetLengthOfNextCharacter(offset); - if (m_nDataLength < m_nCursor + offset) - { - return ""; - } - } - std::string character{GetNextCharacter(offset)}; - m_nCursor += offset + GetLengthOfNextCharacter(offset); - return character; -} -std::string CCharacterReaderUTF8::ConsumeUntil(const std::vector& lstCollection) -{ - std::size_t offset{0}; - bool found{false}; - std::string accumulation; - while (!found && m_nDataLength > m_nCursor + offset) + std::string CCharacterReaderUTF8::StringFromBookmark() const { - std::string character = GetNextCharacter(offset); - offset += GetLengthOfNextCharacter(offset); - for (const auto& delimiter : lstCollection) + return m_ssString.substr(m_nBookmark, (m_nCursor > m_nBookmark) ? m_nCursor - m_nBookmark : 0); + } + + void CCharacterReaderUTF8::CheckForInvalidUTF8Bytes() const + { + const unsigned char invalidByteC0{0xC0}; + const unsigned char invalidByteC1{0xC1}; + const unsigned char lowerBoundInvalidRegion{0xF5}; + for (size_t i = 0; i < m_ssString.size(); ++i) { - if (delimiter == character) + unsigned char uc = m_ssString[i]; + if (uc == invalidByteC0 || uc == invalidByteC1 || uc >= lowerBoundInvalidRegion) { - found = true; + std::stringstream message; + message << "Invalid byte " << std::hex << uc << std::dec << " at position " << i << "\n"; + throw XTOMLParseException(message.str()); } } - if (!found) - { - accumulation += character; - } - else - { - offset -= character.size(); - } } - m_nCursor += offset; - return accumulation; -} -bool CCharacterReaderUTF8::IsEOF() const -{ - return m_nDataLength < m_nCursor + 1; -} + void CCharacterReaderUTF8::CheckForInvalidUTF8Sequences() const + { + enum class EState + { + state_neutral, + state_two_byte, + state_three_byte, + state_four_byte, + state_error + }; + EState eCurrentState = EState::state_neutral; + uint8_t uiIndex = 0; -void CCharacterReaderUTF8::CheckForInvalidUTF8Bytes() const -{ - const unsigned char invalidByteC0{0xC0}; - const unsigned char invalidByteC1{0xC1}; - const unsigned char lowerBoundInvalidRegion{0xF5}; - for (std::size_t i = 0; i < m_ssString.size(); ++i) - { - unsigned char uc = m_ssString[i]; - if (uc == invalidByteC0 || uc == invalidByteC1 || uc >= lowerBoundInvalidRegion) + auto fnCheckByteInNeutralState = [&uiIndex, &eCurrentState](unsigned char uc) { - std::stringstream message; - message << "Invalid byte " << std::hex << uc << std::dec << " at position " << i << "\n"; - throw XTOMLParseException(message.str()); + if ((uc & m_uiOneByteCheckMask) == m_OneByteCheckValue) + { + eCurrentState = EState::state_neutral; + uiIndex = 0; + } + else if ((uc & m_uiFourByteCheckMask) == m_uiFourByteCheckValue) + { + eCurrentState = EState::state_four_byte; + uiIndex = 1; + } + else if ((uc & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue) + { + eCurrentState = EState::state_three_byte; + uiIndex = 1; + } + else if ((uc & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue) + { + eCurrentState = EState::state_two_byte; + uiIndex = 1; + } + else + { + eCurrentState = EState::state_error; + } + }; + auto fnCheckByteInTwoByteState = [&uiIndex, &eCurrentState](unsigned char uc) + { + if ((uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) + { + uiIndex = 0; + eCurrentState = EState::state_neutral; + } + else + { + eCurrentState = EState::state_error; + } + }; + auto fnCheckByteInThreeByteState = [&uiIndex, &eCurrentState](unsigned char uc) + { + if (uiIndex == 1 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) + { + uiIndex = 2; + } + else if (uiIndex == 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) + { + uiIndex = 0; + eCurrentState = EState::state_neutral; + } + else + { + eCurrentState = EState::state_error; + } + }; + auto fnCheckByteInFourByteState = [&uiIndex, &eCurrentState](unsigned char uc) + { + if (uiIndex <= 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) + { + ++uiIndex; + } + else if (uiIndex == 3 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) + { + uiIndex = 0; + eCurrentState = EState::state_neutral; + } + else + { + eCurrentState = EState::state_error; + } + }; + for (size_t i = 0; i < m_ssString.size(); ++i) + { + uint8_t uiCurrentByte = m_ssString[i]; + switch (eCurrentState) + { + case EState::state_neutral: + fnCheckByteInNeutralState(uiCurrentByte); + break; + case EState::state_two_byte: + fnCheckByteInTwoByteState(uiCurrentByte); + break; + case EState::state_three_byte: + fnCheckByteInThreeByteState(uiCurrentByte); + break; + case EState::state_four_byte: + fnCheckByteInFourByteState(uiCurrentByte); + break; + default: + std::stringstream sstreamMessage; + sstreamMessage << "Invalid character with byte " << std::hex << m_ssString[i - 1] << std::dec << "(" + << static_cast(m_ssString[i - 1]) << ") at index " << i - 1 << "\n"; + throw XTOMLParseException(sstreamMessage.str()); + } } - } -} - -void CCharacterReaderUTF8::CheckForInvalidUTF8Sequences() const -{ - enum class EState - { - state_neutral, - state_two_byte, - state_three_byte, - state_four_byte, - state_error - }; - EState eCurrentState{EState::state_neutral}; - uint8_t uiIndex{0}; - auto fnCheckByteInNeutralState = [&uiIndex, &eCurrentState](unsigned char uc) - { - if ((uc & m_uiOneByteCheckMask) == m_OneByteCheckValue) + if (eCurrentState != EState::state_neutral) { - eCurrentState = EState::state_neutral; - uiIndex = 0; - } - else if ((uc & m_uiFourByteCheckMask) == m_uiFourByteCheckValue) - { - eCurrentState = EState::state_four_byte; - uiIndex = 1; - } - else if ((uc & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue) - { - eCurrentState = EState::state_three_byte; - uiIndex = 1; - } - else if ((uc & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue) - { - eCurrentState = EState::state_two_byte; - uiIndex = 1; - } - else - { - eCurrentState = EState::state_error; - } - }; - auto fnCheckByteInTwoByteState = [&uiIndex, &eCurrentState](unsigned char uc) - { - if ((uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) - { - uiIndex = 0; - eCurrentState = EState::state_neutral; - } - else - { - eCurrentState = EState::state_error; - } - }; - auto fnCheckByteInThreeByteState = [&uiIndex, &eCurrentState](unsigned char uc) - { - if (uiIndex == 1 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) - { - uiIndex = 2; - } - else if (uiIndex == 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) - { - uiIndex = 0; - eCurrentState = EState::state_neutral; - } - else - { - eCurrentState = EState::state_error; - } - }; - auto fnCheckByteInFourByteState = [&uiIndex, &eCurrentState](unsigned char uc) - { - if (uiIndex <= 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) - { - ++uiIndex; - } - else if (uiIndex == 3 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue) - { - uiIndex = 0; - eCurrentState = EState::state_neutral; - } - else - { - eCurrentState = EState::state_error; - } - }; - for (std::size_t i = 0; i < m_ssString.size(); ++i) - { - uint8_t uiCurrentByte = m_ssString[i]; - switch (eCurrentState) - { - case EState::state_neutral: - fnCheckByteInNeutralState(uiCurrentByte); - break; - case EState::state_two_byte: - fnCheckByteInTwoByteState(uiCurrentByte); - break; - case EState::state_three_byte: - fnCheckByteInThreeByteState(uiCurrentByte); - break; - case EState::state_four_byte: - fnCheckByteInFourByteState(uiCurrentByte); - break; - default: std::stringstream sstreamMessage; - sstreamMessage << "Invalid character with byte " << std::hex << m_ssString[i - 1] << std::dec << "(" - << static_cast(m_ssString[i - 1]) << ") at index " << i - 1 << "\n"; + sstreamMessage << "Unfinished character at the end of file\n"; throw XTOMLParseException(sstreamMessage.str()); - break; } } - if (eCurrentState != EState::state_neutral) - { - std::stringstream sstreamMessage; - sstreamMessage << "Unfinished character at the end of file\n"; - throw XTOMLParseException(sstreamMessage.str()); - } -} -std::string CCharacterReaderUTF8::GetNextCharacter() -{ - if (IsEOF()) + std::string CCharacterReaderUTF8::GetNextCharacter(size_t nOffset /*= 0*/) const { - return ""; + if (IsEOF()) + return {}; + return {m_ssString, m_nCursor + nOffset, GetLengthOfNextCharacter(nOffset)}; } - return {m_ssString, m_nCursor, GetLengthOfNextCharacter()}; -} -std::string CCharacterReaderUTF8::GetNextCharacter(std::size_t offset) -{ - return {m_ssString, m_nCursor + offset, GetLengthOfNextCharacter(offset)}; -} - -size_t CCharacterReaderUTF8::GetLengthOfNextCharacter() const -{ - return GetLengthOfNextCharacter(0); -} - -size_t CCharacterReaderUTF8::GetLengthOfNextCharacter(std::size_t offset) const -{ - uint8_t ui = m_ssString[m_nCursor + offset]; - int32_t ret; - if ((ui & m_uiOneByteCheckMask) == m_OneByteCheckValue) + size_t CCharacterReaderUTF8::GetLengthOfNextCharacter(size_t nOffset) const { - ret = 1; + if (IsEOF()) + return 0; + uint8_t ui = static_cast(m_ssString[m_nCursor + nOffset]); + int32_t uiRet = 0; + if ((ui & m_uiOneByteCheckMask) == m_OneByteCheckValue) + { + uiRet = 1; + } + else if ((ui & m_uiFourByteCheckMask) == m_uiFourByteCheckValue) + { + uiRet = 4; + } + else if ((ui & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue) + { + uiRet = 3; + } + else if ((ui & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue) + { + uiRet = 2; + } + else + { + std::stringstream sstreamMessage; + sstreamMessage << "Invalid character sequence with byte " << std::hex << ui << std::dec << " as start byte\n"; + throw XTOMLParseException(sstreamMessage.str()); + } + return uiRet; } - else if ((ui & m_uiFourByteCheckMask) == m_uiFourByteCheckValue) - { - ret = 4; - } - else if ((ui & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue) - { - ret = 3; - } - else if ((ui & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue) - { - ret = 2; - } - else - { - std::stringstream sstreamMessage; - sstreamMessage << "Invalid character sequence with byte " << std::hex << ui << std::dec - << " as start byte\n"; - throw XTOMLParseException(sstreamMessage.str()); - } - return ret; -} +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/character_reader_utf_8.h b/sdv_services/core/toml_parser/character_reader_utf_8.h index 43590a2..ccd183b 100644 --- a/sdv_services/core/toml_parser/character_reader_utf_8.h +++ b/sdv_services/core/toml_parser/character_reader_utf_8.h @@ -6,122 +6,141 @@ #include #include -/** - * @brief Reads a given input string, interprets bytes as UTF-8 and returns UTF-8 characters or strings in order on - * demand - */ -class CCharacterReaderUTF8 +/// The TOML parser namespace +namespace toml_parser { -public: /** - * @brief Standard constructor for empty character reader + * @brief Reads a given input string, interprets bytes as UTF-8 and returns UTF-8 characters or strings in order of demand. */ - CCharacterReaderUTF8(); + class CCharacterReaderUTF8 + { + public: + /** + * @brief Standard constructor for empty character reader. + */ + CCharacterReaderUTF8() = default; - /** - * @brief Constructs a character reader from a given string - * @param[in] rssString UTF-8 input string. - * @throw InvalidCharacterException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters - * @throw InvalidByteException Throws an InvalidByteException if the input contains for UTF-8 invalid bytes - */ - CCharacterReaderUTF8(const std::string& rssString); + /** + * @brief Constructs a character reader from a given string. + * @param[in] rssString UTF-8 input string. + * @throw XTOMLParseException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters. + * @throw XTOMLParseException Throws an InvalidByteException if the input contains for UTF-8 invalid bytes. + */ + CCharacterReaderUTF8(const std::string& rssString); - /** - * @brief Feed the character reader from the given string. - * @param[in] rssString UTF-8 input string. - */ - void Feed(const std::string& rssString); + /** + * @brief Feed the character reader from the given string. + * @param[in] rssString UTF-8 input string. + */ + void Feed(const std::string& rssString); - /** - * @brief Reset the character reader content. - */ - void Reset(); + /** + * @brief Reset the character reader content. + */ + void Reset(); - /** - * @brief Gets the next UTF-8 character without advancing the cursor - * @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the - * end - */ - std::string Peek(); + /** + * @brief Get the next n-th UTF-8 character without advancing the cursor. + * @param[in] nSkip Amount of characters to skip. + * @param[in] nAmount The amount of characters to read. + * @return Returns one or more characters starting at the requested position after the current cursor or an empty string + * if the requested position is behind the last character. + */ + std::string Peek(size_t nSkip = 0, size_t nAmount = 1) const; - /** - * @brief Gets the next n-th UTF-8 character without advancing the cursor - * @param[in] n Step size - * @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read - * after the last character - */ - std::string Peek(std::size_t n); + /** + * @brief Get all upcoming UTF-8 characters until a terminating character without advancing the cursor. + * @param[in] lstCollection A collection of terminating characters. + * @return Returns a string of UTF-8 characters until (excluding) the first occurrence of one of the given characters. + */ + std::string PeekUntil(const std::vector& lstCollection) const; - /** - * @brief Gets all upcoming UTF-8 characters until a terminating character without advancing the cursor - * @param[in] lstCollection A collection of terminating characters - * @return Returns a string of UTF-8 characters until (excluding) the first occurrence of one of the given - * characters - */ - std::string PeekUntil(const std::vector& lstCollection); + /** + * @brief Get the next n-th UTF-8 character and advancing the cursor by n. + * @param[in] nSkip Amount of characters to skip. + * @param[in] nAmount The amount of characters to read. + * @return Returns one or more characters starting at the requested position after the current cursor or an empty string + * if the requested position is behind the last character. + */ + std::string Consume(size_t nSkip = 0, size_t nAmount = 1); - /** - * @brief Gets the next UTF-8 character and advancing the cursor by one - * @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the - * end - */ - std::string Consume(); + /** + * @brief Get all upcoming UTF-8 characters until a terminating character and advancing the cursor by the number of + * characters in the returned string. + * @param[in] lstCollection A collection of terminating characters. + * @return Returns a string of UTF-8 characters until (excluding) the first occurrence of one of the given characters. + */ + std::string ConsumeUntil(const std::vector& lstCollection); - /** - * @brief Gets the next n-th UTF-8 character and advancing the cursor by n - * @param[in] n Step size - * @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read - * after the last character - */ - std::string Consume(std::size_t n); + /** + * @brief Checks if the cursor is at the end of the data to read. + * @return Returns true if the cursor is at the end of the readable data, false otherwise. + */ + bool IsEOF() const; - /** - * @brief Gets all upcoming UTF-8 characters until a terminating character and advancing the cursor by the number - * of characters in the returned string - * @param[in] lstCollection A collection of terminating characters - * @return Returns a string of UTF-8 characters until excludingg) the first occurrence of one of the given - * characters - */ - std::string ConsumeUntil(const std::vector& lstCollection); + /** + * @brief Set the bookmark at the current sursor position. This can be used to read the raw string. + */ + void SetBookmark(); - /** - * @brief Checks if the cursor is at the end of the data to read - * @return Returns true if the cursor is at the end of the readable data, false otherwise - */ - bool IsEOF() const; + /** + * @brief Get string from bookmark. + * @return Returns the raw string from the last bookmark position (if set), or from the beginning of the string (if not + * set). + */ + std::string StringFromBookmark() const; -private: - void CheckForInvalidUTF8Bytes() const; + private: + /** + * @brief Check for an out-of-bound UTF8 byte. + * @throw XTOMLParseException Throws an InvalidByteException if the input contains for UTF-8 invalid bytes. + */ + void CheckForInvalidUTF8Bytes() const; - void CheckForInvalidUTF8Sequences() const; + /** + * @brief Check for an invalid UTF8 sequence. + * @throw XTOMLParseException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters. + */ + void CheckForInvalidUTF8Sequences() const; - std::string GetNextCharacter(); + /** + * @brief Get the next character using an nOffset of nOffset bytes. + * @attention This function doesn't protect for an invalid nOffset when reading multi-byte characters. + * @remarks In UTF8 one character could contain up to 4 bytes. + * @param[in] nOffset The nOffset in bytes to the next character to read. + * @return The character from the string or an empty string if there are no more characters in the string. + */ + std::string GetNextCharacter(size_t nOffset = 0) const; - std::string GetNextCharacter(std::size_t offset); + /** + * @brief Get the length of the next character in bytes. + * @remarks In UTF8 one character could contain up to 4 bytes. + * @param[in] nOffset The nOffset in bytes to the next character to read. + * @return the length of the character in bytes or zero when there are no more characters in the string. + * @throw XTOMLParseException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters. + */ + size_t GetLengthOfNextCharacter(size_t nOffset = 0) const; - size_t GetLengthOfNextCharacter() const; + static const uint8_t m_uiOneByteCheckMask{0b10000000}; ///< Checkmask for 1-Byte UTF-8 characters + static const uint8_t m_OneByteCheckValue{0b00000000}; ///< Value of a 1-Byte UTF-8 character + ///< after being or-ed with the checkmask + static const uint8_t m_uiFollowByteCheckMask{0b11000000}; ///< Checkmask for followbyte of a multi-Byte UTF-8 character + static const uint8_t m_uiFollowByteValue{0b10000000}; ///< Value of a followbyte of a multi-Byte UTF-8 character + ///< after being or-ed with the checkmask + static const uint8_t m_uiTwoByteCheckMask{0b11100000}; ///< Checkmask for startbyte of 2-Byte UTF-8 characters + static const uint8_t m_uiTwoByteCheckValue{0b11000000}; ///< Value of a startbyte of a 2-Byte UTF-8 character + ///< after being or-ed with the checkmask + static const uint8_t m_uiThreeByteCheckMask{0b11110000}; ///< Checkmask for startbyte of 3-Byte UTF-8 characters + static const uint8_t m_uiThreeByteCheckValue{0b11100000}; ///< Value of a startbyte of a 3-Byte UTF-8 character + ///< after being or-ed with the checkmask + static const uint8_t m_uiFourByteCheckMask{0b11111000}; ///< Checkmask for startbyte of 4-Byte UTF-8 characters + static const uint8_t m_uiFourByteCheckValue{0b11110000}; ///< Value of a startbyte of a 4-Byte UTF-8 character + ///< after being or-ed with the checkmask - size_t GetLengthOfNextCharacter(std::size_t offset) const; - - static const uint8_t m_uiOneByteCheckMask{0b10000000}; //!< Checkmask for 1-Byte UTF-8 characters - static const uint8_t m_OneByteCheckValue{0b00000000}; //!< Value of a 1-Byte UTF-8 character - //!< after being or-ed with the checkmask - static const uint8_t m_uiFollowByteCheckMask{0b11000000}; //!< Checkmask for followbyte of a multi-Byte UTF-8 character - static const uint8_t m_uiFollowByteValue{0b10000000}; //!< Value of a followbyte of a multi-Byte UTF-8 character - //!< after being or-ed with the checkmask - static const uint8_t m_uiTwoByteCheckMask{0b11100000}; //!< Checkmask for startbyte of 2-Byte UTF-8 characters - static const uint8_t m_uiTwoByteCheckValue{0b11000000}; //!< Value of a startbyte of a 2-Byte UTF-8 character - //!< after being or-ed with the checkmask - static const uint8_t m_uiThreeByteCheckMask{0b11110000}; //!< Checkmask for startbyte of 3-Byte UTF-8 characters - static const uint8_t m_uiThreeByteCheckValue{0b11100000}; //!< Value of a startbyte of a 3-Byte UTF-8 character - //!< after being or-ed with the checkmask - static const uint8_t m_uiFourByteCheckMask{0b11111000}; //!< Checkmask for startbyte of 4-Byte UTF-8 characters - static const uint8_t m_uiFourByteCheckValue{0b11110000}; //!< Value of a startbyte of a 4-Byte UTF-8 character - //!< after being or-ed with the checkmask - - std::string m_ssString; - std::size_t m_nDataLength; - std::size_t m_nCursor; -}; + std::string m_ssString; ///< String containing the characters to acquire. + size_t m_nCursor = 0; ///< Current position pointing to the next character. + size_t m_nBookmark = 0; ///< Bookmark cursor position to use to get raw string chunks. + }; +} // namespace toml_parser #endif // CHARACTER_READER_UTF_8_H diff --git a/sdv_services/core/toml_parser/exception.h b/sdv_services/core/toml_parser/exception.h index c8c3b30..aef78dc 100644 --- a/sdv_services/core/toml_parser/exception.h +++ b/sdv_services/core/toml_parser/exception.h @@ -3,18 +3,31 @@ #include -except XTOMLParseException : public sdv::toml::XTOMLParseException +/// The TOML parser namespace +namespace toml_parser { /** - * @brief Constructor + * @brief Extended exception for the TOML parser. */ - XTOMLParseException(const std::string& rss) { ssMessage = rss; }; + except XTOMLParseException : public sdv::toml::XTOMLParseException + { + /** + * @brief Constructor + */ + XTOMLParseException(const std::string& rss) + { + ssMessage = rss; + }; - /** - * @brief Return the explanatory string. - * @return The descriptive string. - */ - virtual const char* what() const noexcept override { return ssMessage.c_str(); } -}; + /** + * @brief Return the explanatory string. + * @return The descriptive string. + */ + virtual const char* what() const noexcept override + { + return ssMessage.c_str(); + } + }; +} // namespace toml_parser #endif // !defined CONFIG_EXCEPTION_H \ No newline at end of file diff --git a/sdv_services/core/toml_parser/lexer_toml.cpp b/sdv_services/core/toml_parser/lexer_toml.cpp index bf995bd..22fc0d6 100644 --- a/sdv_services/core/toml_parser/lexer_toml.cpp +++ b/sdv_services/core/toml_parser/lexer_toml.cpp @@ -1,1022 +1,1267 @@ #include "lexer_toml.h" #include #include "exception.h" +#include +#include "miscellaneous.h" -CLexerTOML::CLexerTOML(const std::string& rssString) +/// The TOML parser namespace +namespace toml_parser { - Feed(rssString); -} + CNodeTokenRange::CNodeTokenRange(const CToken& rInitialToken) : + m_refBeforeNodeBegin(rInitialToken), m_rangeExtendedNode(rInitialToken, rInitialToken), + m_rangeNodeMain(rInitialToken, rInitialToken), m_rangeNodeFinish(rInitialToken, rInitialToken), + m_refBehindNodeEnd(rInitialToken) + {} -void CLexerTOML::Feed(const std::string& rssString) -{ - Reset(); - try - { - m_reader.Feed(rssString); - GenerateTokens(); - } - catch (const sdv::toml::XTOMLParseException& rexcept) - { - SToken newToken{ETokenCategory::token_terminated}; - newToken.ssContentString = rexcept.what(); - m_vecTokens.push_back(newToken); - throw; - } -} + CNodeTokenRange::CNodeTokenRange(const CTokenRange& rrangeNodeMain) : + m_refBeforeNodeBegin(rrangeNodeMain.Begin()), m_rangeExtendedNode(rrangeNodeMain.Begin(), rrangeNodeMain.End()), + m_rangeNodeMain(rrangeNodeMain), m_rangeNodeFinish(rrangeNodeMain.End(), rrangeNodeMain.End()), + m_refBehindNodeEnd(rrangeNodeMain.End()) + {} -void CLexerTOML::Reset() -{ - m_reader.Reset(); - m_vecTokens.clear(); - m_nCursor = 0; -} - -CLexerTOML::SToken CLexerTOML::Peek() const -{ - if (m_vecTokens.empty()) + void CNodeTokenRange::LinesBeforeNode(const CToken& rTokenBegin) { - return SToken{ETokenCategory::token_eof}; + // Check if the begin is before or at the same location as the extended node begin. + m_refBeforeNodeBegin = (rTokenBegin.TokenIndex() > m_rangeExtendedNode.Begin().TokenIndex()) ? + m_rangeExtendedNode.Begin() : rTokenBegin; } - return m_vecTokens[m_nCursor]; -} - -CLexerTOML::SToken CLexerTOML::Consume() -{ - if (m_nCursor >= m_vecTokens.size() - 1) + + CTokenRange CNodeTokenRange::LinesBeforeNode() const { - m_nCursor = m_vecTokens.size() - 1; - if (m_vecTokens.empty()) - { - return CLexerTOML::SToken{ETokenCategory::token_eof}; - } - return m_vecTokens.back(); - } - return m_vecTokens[m_nCursor++]; -} - - -CLexerTOML::SToken CLexerTOML::Peek(int32_t n) -{ - if (n < 1) - { - return CLexerTOML::SToken{CLexerTOML::ETokenCategory::token_empty}; - } - if (m_nCursor + n >= m_vecTokens.size()) - { - if (m_vecTokens.empty()) - { - return CLexerTOML::SToken{ETokenCategory::token_eof}; - } - return m_vecTokens.back(); - } - return m_vecTokens[m_nCursor + n - 1]; -} - -CLexerTOML::SToken CLexerTOML::Consume(int32_t n) -{ - if (n < 1) - { - return CLexerTOML::SToken{CLexerTOML::ETokenCategory::token_empty}; - } - if (m_nCursor + n >= m_vecTokens.size()) - { - m_nCursor = m_vecTokens.size() - 1; - if (m_vecTokens.empty()) - { - return CLexerTOML::SToken{ETokenCategory::token_eof}; - } - return m_vecTokens.back(); + return CTokenRange(m_refBeforeNodeBegin.get(), m_rangeExtendedNode.Begin()); } - m_nCursor += n; - return m_vecTokens[m_nCursor - 1]; -} - -bool CLexerTOML::IsEnd() const -{ - return m_nCursor >= m_vecTokens.size() - 1; -} - -void CLexerTOML::GenerateTokens() -{ - m_stackExpectations.push(EExpectation::expect_key); - while (!m_reader.IsEOF()) + void CNodeTokenRange::ExtendedNode(const CTokenRange& rRange) { - if (IsBareKey()) - { - ReadBareKey(); - } - else if (IsBasicQuotedKey()) - { - ReadBasicQuotedKey(); - } - else if (IsLiteralQuotedKey()) - { - ReadLiteralQuotedKey(); - } - else if (IsBasicMultilineString()) - { - ReadBasicMultilineString(); - } - else if (IsBasicString()) - { - ReadBasicString(); - } - else if (IsLiteralMultilineString()) - { - ReadLiteralMultilineString(); - } - else if (IsLiteralString()) - { - ReadLiteralString(); - } - else if (IsInteger()) - { - ReadInteger(); - } - else if (IsFloat()) - { - ReadFloat(); - } - else if (IsBool()) - { - ReadBool(); - } - else if (IsWhitespace()) - { - ReadWhitespace(); - } - else if (IsSyntaxElement()) - { - ReadSyntaxElement(); - } - else if (IsComment()) - { - ReadComment(); - } - else - { - ReadUnknownSequence(); - } + // The begin is not allowed to start behind the end. + if (rRange.Begin().TokenIndex() > rRange.End().TokenIndex()) return; + + // The extended node begin should start at or before the main node begin. + const CToken& rTokenBegin = (rRange.Begin().TokenIndex() > m_rangeNodeMain.Begin().TokenIndex()) ? + m_rangeNodeMain.Begin() : rRange.Begin(); + + // The extended node end should start at or behind the main node end. + const CToken& rTokenEnd = (rRange.End().TokenIndex() < m_rangeNodeMain.End().TokenIndex()) ? + m_rangeNodeMain.End() : rRange.End(); + + // Set the extended node. + m_rangeExtendedNode = CTokenRange(rTokenBegin, rTokenEnd); + + // Check if the lines before the node start before or at the extended node. + if (m_refBeforeNodeBegin.get().TokenIndex() > m_rangeExtendedNode.Begin().TokenIndex()) + m_refBeforeNodeBegin = m_rangeExtendedNode.Begin(); + + // Check if the lines behind the node end behind or at the extended node. + if (m_refBehindNodeEnd.get().TokenIndex() < m_rangeExtendedNode.End().TokenIndex()) + m_refBehindNodeEnd = m_rangeExtendedNode.End(); } - m_vecTokens.emplace_back(ETokenCategory::token_eof); -} -bool CLexerTOML::IsBasicQuotedKey() -{ - return (m_stackExpectations.top() == EExpectation::expect_key && m_reader.Peek(1) == "\""); -} - -void CLexerTOML::ReadBasicQuotedKey() -{ - try + CTokenRange CNodeTokenRange::ExtendedNode() const { - bool endOfQuote = false; - std::string content = "\""; - m_reader.Consume(); - while (!endOfQuote) - { - if (m_reader.IsEOF()) - { - throw XTOMLParseException( - "Unexpected End of File reached while reading multiline string"); - } - std::string character = m_reader.Consume(); - if (character == "\\") - { - content += Unescape(); - } - else if (character == "\"") - { - endOfQuote = true; - } - else - { - content += character; - } - } - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_key); - content += "\""; - newToken.ssContentString = content; - m_vecTokens.push_back(newToken); + return m_rangeExtendedNode; } - catch (const sdv::toml::XTOMLParseException& e) + + CTokenRange CNodeTokenRange::NodeCommentsBefore() const { - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_error); - newToken.ssContentString = std::string(e.what()); - m_vecTokens.push_back(newToken); + return CTokenRange(m_rangeExtendedNode.Begin(), m_rangeNodeMain.Begin()); } -} -bool CLexerTOML::IsLiteralQuotedKey() -{ - return (m_stackExpectations.top() == EExpectation::expect_key && m_reader.Peek(1) == "\'"); -} + void CNodeTokenRange::NodeMain(const CTokenRange& rRange) + { + // The begin is not allowed to start behind the end. + if (rRange.Begin().TokenIndex() > rRange.End().TokenIndex()) return; -void CLexerTOML::ReadLiteralQuotedKey() -{ - bool endOfQuote = false; - std::string content = "'"; - m_reader.Consume(); // get the initial "'" - while (!endOfQuote) - { - std::string character = m_reader.Consume(); - if (character == "\'") - { - endOfQuote = true; - } - else - { - content += character; - } - } - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_key); - content += "'"; - newToken.ssContentString = content; - m_vecTokens.push_back(newToken); -} + // Set the main node range + m_rangeNodeMain = rRange; -bool CLexerTOML::IsBareKey() -{ - std::string character = m_reader.Peek(); - return (m_stackExpectations.top() == EExpectation::expect_key - && ((character[0] >= 'A' && character[0] <= 'Z') || (character[0] >= 'a' && character[0] <= 'z') - || (character[0] >= '0' && character[0] <= '9') || character[0] == '-' || character[0] == '_')); -} + // Update of main node finish required? + const CToken& rTokenBeginMainFinishRange = (m_rangeNodeFinish.Begin().TokenIndex() < m_rangeNodeMain.End().TokenIndex()) ? + m_rangeNodeMain.End() : m_rangeNodeFinish.Begin(); + const CToken& rTokenEndMainFinishRange = (m_rangeNodeFinish.End().TokenIndex() < rTokenBeginMainFinishRange.TokenIndex()) ? + rTokenBeginMainFinishRange : m_rangeNodeFinish.End(); + if (m_rangeNodeFinish.Begin() != rTokenBeginMainFinishRange || m_rangeNodeFinish.End() != rTokenEndMainFinishRange) + { + NodeMainFinish(CTokenRange(rTokenBeginMainFinishRange, rTokenEndMainFinishRange)); -void CLexerTOML::ReadBareKey() -{ - bool endOfKey = false; - std::string content; - bool error{false}; - while (!endOfKey) - { - std::string character = m_reader.Peek(); - if (!character.empty() && character[0] != '.' && character[0] != '=' && character[0] != ' ' - && character[0] != '\t' && character[0] != ']') - { - if ((character[0] < '0' || character[0] > '9') && (character[0] < 'A' || character[0] > 'Z') - && (character[0] < 'a' || character[0] > 'z') && (character[0] != '_') && (character[0] != '-')) - { - error = true; - } - m_reader.Consume(); - content += character; - } - else - { - endOfKey = true; - } - } - SToken newToken(ETokenCategory::token_key); - newToken.ssContentString = content; - if (error) - { - newToken.eCategory = ETokenCategory::token_error; - newToken.ssContentString = "Invalid bare key '" + content + "'"; - } - m_vecTokens.push_back(newToken); -} - -bool CLexerTOML::IsBasicString() -{ - return (m_stackExpectations.top() != EExpectation::expect_key && m_reader.Peek(1) == "\"" - && !(m_reader.Peek(2) == "\"" && m_reader.Peek(3) == "\"")); -} - -void CLexerTOML::ReadBasicString() -{ - try - { - bool endOfQuote = false; - std::string content; - m_reader.Consume(); - while (!endOfQuote) - { - if (m_reader.IsEOF()) - { - throw XTOMLParseException("Unexpected End of File reached while reading string"); - } - std::string character = m_reader.Consume(); - if (character == "\\") - { - content += Unescape(); - } - else if (character == "\"") - { - endOfQuote = true; - } - else - { - content += character; - } - } - SToken newToken(ETokenCategory::token_string); - newToken.ssContentString = content; - m_vecTokens.push_back(newToken); - } - catch (const sdv::toml::XTOMLParseException& e) - { - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_error); - newToken.ssContentString = std::string(e.what()); - m_vecTokens.push_back(newToken); - } - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} - -bool CLexerTOML::IsBasicMultilineString() -{ - return (m_stackExpectations.top() != EExpectation::expect_key && m_reader.Peek(1) == "\"" - && m_reader.Peek(2) == "\"" && m_reader.Peek(3) == "\""); -} - -void CLexerTOML::ReadBasicMultilineString() -{ - std::string content; - bool endOfQuote = false; - auto ignoreNewLineAtTheBeginning = [this]() - { - if (m_reader.Peek() == "\n") - { - m_reader.Consume(); - } - else if (m_reader.Peek() == "\r" && m_reader.Peek(2) == "\n") - { - m_reader.Consume(2); - } - }; - auto handleBackslashFunctionality = [this, &content]() - { - if (m_reader.Peek() == "\n" || (m_reader.Peek() == "\r" && m_reader.Peek(2) == "\n")) - { - std::string next = m_reader.Peek(); - while (next == "\n" || (next == "\r" && m_reader.Peek(2) == "\n") || next == " " || next == "\t") - { - m_reader.Consume(); - next = m_reader.Peek(); - } + // No need to check the extended range; this is done by the NodeMainFinish function. return; } - content += Unescape(); - }; - auto handleTrippleDoublequotes = [this, &content, &endOfQuote]() + + // Check extended range begin before and end behind rRange. + const CToken& rTokenBeginExtendedRange = (m_rangeExtendedNode.Begin().TokenIndex() > m_rangeNodeMain.Begin().TokenIndex()) ? + m_rangeNodeMain.Begin() : m_rangeExtendedNode.Begin(); + const CToken& rTokenEndExtendedRange = (m_rangeExtendedNode.End().TokenIndex() < m_rangeNodeFinish.End().TokenIndex()) ? + m_rangeNodeMain.End() : m_rangeExtendedNode.End(); + if (m_rangeExtendedNode.Begin() != rTokenBeginExtendedRange || m_rangeExtendedNode.End() != rTokenEndExtendedRange) + ExtendedNode(CTokenRange(rTokenBeginExtendedRange, rTokenEndExtendedRange)); + } + + CTokenRange CNodeTokenRange::NodeMain() const { - if (m_reader.Peek(3) == "\"") - { - content += '\"'; - return; - } - endOfQuote = true; - m_reader.Consume(2); - }; - try + return m_rangeNodeMain; + } + + void CNodeTokenRange::NodeMainFinish(const CTokenRange& rRange) { - m_reader.Consume(3); - ignoreNewLineAtTheBeginning(); - while (!endOfQuote) + // The begin is not allowed to start behind the end. + if (rRange.Begin().TokenIndex() > rRange.End().TokenIndex()) return; + + // Update of main node finish if required. + const CToken& rTokenBeginMainFinishRange = (rRange.Begin().TokenIndex() < m_rangeNodeMain.End().TokenIndex()) ? + m_rangeNodeMain.End() : rRange.Begin(); + const CToken& rTokenEndMainFinishRange = (rRange.End().TokenIndex() < rTokenBeginMainFinishRange.TokenIndex()) ? + rTokenBeginMainFinishRange : rRange.End(); + + // Set the main node finish range + m_rangeNodeFinish = CTokenRange(rTokenBeginMainFinishRange, rTokenEndMainFinishRange); + + // Check extended range begin before and end behind rRange. + const CToken& rTokenBeginExtendedRange = (m_rangeExtendedNode.Begin().TokenIndex() > m_rangeNodeMain.Begin().TokenIndex()) ? + m_rangeNodeMain.Begin() : m_rangeExtendedNode.Begin(); + const CToken& rTokenEndExtendedRange = (m_rangeExtendedNode.End().TokenIndex() < m_rangeNodeFinish.End().TokenIndex()) ? + m_rangeNodeMain.End() : m_rangeExtendedNode.End(); + if (m_rangeExtendedNode.Begin() != rTokenBeginExtendedRange || m_rangeExtendedNode.End() != rTokenEndExtendedRange) + ExtendedNode(CTokenRange(rTokenBeginExtendedRange, rTokenEndExtendedRange)); + } + + CTokenRange CNodeTokenRange::NodeMainFinish() const + { + return m_rangeNodeFinish; + } + + CTokenRange CNodeTokenRange::NodeCommentsBehind() const + { + return CTokenRange(m_rangeNodeFinish.End(), m_rangeExtendedNode.End()); + } + + void CNodeTokenRange::LinesBehindNode(const CToken& rTokenEnd) + { + // Check if the end is behind or at the same location as the extended node end. + m_refBehindNodeEnd = + (rTokenEnd.TokenIndex() < m_rangeExtendedNode.End().TokenIndex()) ? m_rangeExtendedNode.End() : rTokenEnd; + } + + CTokenRange CNodeTokenRange::LinesBehindNode() const + { + return CTokenRange(m_rangeExtendedNode.End(), m_refBehindNodeEnd.get()); + } + + CLexer::CLexer(const std::string& rssString, bool bValueOnly /*= false*/) + { + Feed(rssString, bValueOnly); + } + + void CLexer::Feed(const std::string& rssString, bool bValueOnly /*= false*/) + { + m_lstTokens.clear(); + m_itCursor = m_lstTokens.end(); + CCharacterReaderUTF8 reader; + try { - if (m_reader.IsEOF()) - { - throw XTOMLParseException( - "Unexpected End of File reached while reading multiline string"); - } - std::string character = m_reader.Consume(); - if (character == "\\") - { - handleBackslashFunctionality(); - } - else if (character == "\"" && m_reader.Peek() == "\"" && m_reader.Peek(2) == "\"") - { - handleTrippleDoublequotes(); - } + reader.Feed(rssString); + if (bValueOnly) + m_stackExpectations.push(EExpectation::expect_value); else - { - content += character; - } + m_stackExpectations.push(EExpectation::expect_key); + GenerateTokens(reader); + m_itCursor = m_lstTokens.begin(); } - SToken newToken(ETokenCategory::token_string); - newToken.ssContentString = content; - m_vecTokens.push_back(newToken); - } - catch (const sdv::toml::XTOMLParseException& e) - { - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_error); - newToken.ssContentString = std::string(e.what()); - m_vecTokens.push_back(newToken); - } - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} - -bool CLexerTOML::IsLiteralString() -{ - return (m_stackExpectations.top() != EExpectation::expect_key && m_reader.Peek(1) == "\'" - && !(m_reader.Peek(2) == "\'" && m_reader.Peek(3) == "\'")); -} - -void CLexerTOML::ReadLiteralString() -{ - try - { - bool endOfQuote{false}; - std::string content; - m_reader.Consume(); - while (!endOfQuote) + catch (const sdv::toml::XTOMLParseException& rexcept) { - if (m_reader.IsEOF()) - { - throw XTOMLParseException("Unexpected End of File reached while reading string"); - } - std::string character = m_reader.Consume(); - if (character == "'") - { - endOfQuote = true; - } - else - { - content += character; - } + CToken tokenTerminated(ETokenCategory::token_terminated, rexcept); + TTokenListIterator itLocation = m_lstTokens.insert(m_lstTokens.end(), tokenTerminated); + m_lstTokens.back().RawDataInfo(reader.StringFromBookmark(), m_lstTokens, itLocation); + m_itCursor = m_lstTokens.begin(); + throw; } - SToken newToken(ETokenCategory::token_string); - newToken.ssContentString = content; - m_vecTokens.push_back(newToken); } - catch (const sdv::toml::XTOMLParseException& e) - { - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_error); - newToken.ssContentString = std::string(e.what()); - m_vecTokens.push_back(newToken); - } - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} -bool CLexerTOML::IsLiteralMultilineString() -{ - return (m_stackExpectations.top() != EExpectation::expect_key && m_reader.Peek(1) == "\'" - && m_reader.Peek(2) == "\'" && m_reader.Peek(3) == "\'"); -} - -void CLexerTOML::ReadLiteralMultilineString() -{ - try + void CLexer::Reset() { - bool endOfQuote = false; - std::string content; - m_reader.Consume(3); - if (m_reader.Peek() == "\n") + m_itCursor = m_lstTokens.begin(); + } + + CLexer::ENavigationMode CLexer::NavigationMode() const + { + return m_eNavMode; + } + + void CLexer::NavigationMode(ENavigationMode eMode) + { + m_eNavMode = eMode; + } + + const CToken& CLexer::Peek(size_t nSkip) const + { + size_t nInternalSkip = nSkip; + size_t nOffset = 0; + TTokenListIterator itCursorTemp = m_itCursor; + TTokenListIterator itCurrent = m_itCursor; + + do { - m_reader.Consume(); - } - else if (m_reader.Peek() == "\r" && m_reader.Peek(2) == "\n") - { - m_reader.Consume(2); - } - bool bConsumeWhitespace = false; - while (!endOfQuote) - { - if (m_reader.IsEOF()) + // Current value is the one to return. + itCurrent = itCursorTemp; + + // If past the end of the token list, return EOF + if (itCursorTemp == m_lstTokens.end()) + break; + if (!*itCursorTemp) + break; + + // Skip whitespace and newlines. + switch (itCursorTemp->Category()) { - throw XTOMLParseException( - "Unexpected End of File reached while reading multiline string"); + case ETokenCategory::token_whitespace: + case ETokenCategory::token_comment: + if (m_eNavMode == ENavigationMode::skip_comments_and_whitespace) + nInternalSkip++; + break; + default: + break; } - std::string character = m_reader.Consume(); - if (character == "'" && m_reader.Peek() == "'" && m_reader.Peek(2) == "'") + + // Increase current position + ++itCursorTemp; + ++nOffset; + } while (nOffset <= nInternalSkip); + + if (itCurrent == m_lstTokens.end()) + return m_lstTokens.tokenEnd; + return *itCurrent; + } + + const CToken& CLexer::Consume(size_t nSkip) + { + size_t nOffset = 0; + size_t nInternalSkip = nSkip; + TTokenListIterator itCurrent = m_itCursor; + + do + { + // Current value is the one to return. + itCurrent = m_itCursor; + + // If past the end of the token list, return EOF + if (m_itCursor == m_lstTokens.end()) break; + if (!*m_itCursor) break; + + // Skip whitespace and newlines. + switch (m_itCursor->Category()) { - if (m_reader.Peek(3) == "'") + case ETokenCategory::token_whitespace: + case ETokenCategory::token_comment: + if (m_eNavMode == ENavigationMode::skip_comments_and_whitespace) + nInternalSkip++; + break; + default: + break; + } + + // Increase current position + ++m_itCursor; + ++nOffset; + } while (nOffset <= nInternalSkip); + + if (itCurrent == m_lstTokens.end()) + return m_lstTokens.tokenEnd; + return *itCurrent; + } + + bool CLexer::IsEnd() const + { + return m_itCursor == m_lstTokens.end(); + } + + void CLexer::SmartExtendNodeRange(CNodeTokenRange& rTokenRange) const + { + // Define an iterator range type. + using TRange = std::pair; + + // Get the first token in the line (the begin token in the list or the one following a newline) and one past the last token + // in the line (being the end token in the list or the first token of the next line). + // remarks No validity check is done for the supplied token iterator. + // param[in] rit Reference to the token list iterator marking the current position within the line to return. + // return A pair containing the begin and one past the end of the line. + auto fnGetLine = [this](const TTokenListIterator& rit) -> TRange + { + // When the end of the list is reached, there is no more line. + if (rit == m_lstTokens.end()) + return std::make_pair(m_lstTokens.end(), m_lstTokens.end()); + + // Search for the start of the line + TTokenListIterator itBegin = rit; + while (itBegin != m_lstTokens.begin()) + { + TTokenListIterator itOneBefore = itBegin; + --itOneBefore; + if (itOneBefore->Category() == ETokenCategory::token_syntax_new_line) + break; + itBegin = itOneBefore; + } + + // Search for the end of the line (and return one past the end). + TTokenListIterator itEnd = rit; + while (itEnd != m_lstTokens.end()) { + TTokenListIterator itTempEnd = itEnd; + ++itEnd; + if (itTempEnd->Category() == ETokenCategory::token_syntax_new_line) + break; + } + return std::make_pair(itBegin, itEnd); + }; + + // Get the previous line. + // remarks No validity check is done for the supplied token iterator. + // param[in, out] rprLine Reference to a pair with the start position and one past the end position of a line. Will be + // updated when there is a previous line. + // return Returns whether there is a previous line or not. + auto fnGetPrevLine = [this, fnGetLine](TRange& rprLine) -> bool + { + if (rprLine.first == m_lstTokens.begin()) + return false; + auto itPrev = rprLine.first; + --itPrev; + rprLine = fnGetLine(itPrev); + return true; + }; + + // Get the next line. + // remarks No validity check is done for the supplied token iterator. + // param[in, out] rprLine Reference to a pair with the start position and one past the end position of a line. Will be + // updated when there is a next line. + // return Returns whether there is a next line or not. + auto fnGetNextLine = [this, fnGetLine](TRange& rprLine) -> bool + { + if (rprLine.second == m_lstTokens.end()) + return false; + rprLine = fnGetLine(rprLine.second); + return true; + }; + + // Determine the length (in characters) of the whitespace based on spaces and tabs. + // remarks No validity check is done for the supplied token iterator. + // param[in] rprRange Pair with the start position and one past the end position of a range. + // return The length of the whitespace in characters (tabs can contain up to 4 spaces). + auto fnGetIndentation = [this](const TRange& rprRange) -> size_t + { + if (rprRange.first == m_lstTokens.end() || rprRange.first->Category() != ETokenCategory::token_whitespace) + return 0; + std::string ssWhitespace = rprRange.first->RawString(); + size_t nLen = 0; + for (char c : ssWhitespace) + { + switch (c) { - content += '\''; + case ' ': + nLen++; + break; + case '\t': + nLen += (nLen % 4) ? (4 - nLen % 4) : 4; + break; + default: + return nLen; + } + } + return nLen; + }; + + // Check a range for comments only (ignore whitespace and newlines). + // remarks No validity check is done for the supplied token iterator. + // param[in] rprRange Pair with the start position and one past the end position of a range. + // return Returns whether the line contains comments (and optionally whitespace and newlines) only. + auto fnCommentsOnly = [this](const TRange& rprRange) -> bool + { + auto itToken = rprRange.first; + bool bComments = false; + while (itToken != rprRange.second) + { + switch (itToken->Category()) + { + case ETokenCategory::token_comment: + bComments = true; + break; + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + break; + default: + return false; + } + ++itToken; + } + return bComments; + }; + + // Check a range for empty line(s) only (ignore whitespace and newlines). + // remarks No validity check is done for the supplied token iterator. + // param[in] rprRange Pair with the start position and one past the end position of a range. + // return Returns whether the line is (lines are) empty. + auto fnEmptyLine = [this](const TRange& rprRange) -> bool + { + auto itToken = rprRange.first; + while (itToken != rprRange.second) + { + switch (itToken->Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + break; + default: + return false; + } + ++itToken; + } + return true; + }; + + // Skip certain tokens. + // param[in] ritToken Reference to the token to be updated. + // param[in] eTokenCategory Category of the token to skip (if present). + auto fnSkipToken = [this](TTokenListIterator& ritToken, toml_parser::ETokenCategory eTokenCategory) -> bool + { + if (ritToken != m_lstTokens.end() && *ritToken && ritToken->Category() == eTokenCategory) + { + ritToken++; + return true; + } + return false; + }; + + // Get a token from the iterator. + // param[in] rit Reference to the iterator. + // return Reference to the token, either being an empty token when the iterator is pointer to the end of the list, or the + // token that is pointer to by the iterator. + auto fnToken = [this](const TTokenListIterator& rit) -> const CToken& + { + return rit == m_lstTokens.end() ? m_lstTokens.tokenEnd : *rit; + }; + + // Is a token range supplied? + if (!rTokenRange.NodeMain().Begin() || !rTokenRange.NodeMain().Begin().Location()) + throw XTOMLParseException("No token range was supplied to check for boundaries."); + + // Check whether the begin token (and if set, the end token automatically as well) are present in the token list of this + // lexer. + if (&(*rTokenRange.NodeMain().Begin().TokenList()).get() != &m_lstTokens) + throw XTOMLParseException("The tokens in the token range were not provided by this lexer."); + + // Get the initial iterator. + auto itTokenBegin = *rTokenRange.NodeMain().Begin().Location(); + + // Check whether there is a final iterator. + auto itTokenEnd = *rTokenRange.NodeMainFinish().End().Location(); + + // Following whitespace belongs to the extended range + fnSkipToken(itTokenEnd, toml_parser::ETokenCategory::token_whitespace); + + // If the following token is a comma, this belongs to the extended token (then the token is part of an array or table). + // If the following token is a dot, this belongs to the extended token (then the token is part of a table). The comments + // before belong to a child member and should not be used. Also table and table array syntax tokens do not belong to the + // statement. + fnSkipToken(itTokenEnd, toml_parser::ETokenCategory::token_syntax_comma); + bool bIsParent = fnSkipToken(itTokenEnd, toml_parser::ETokenCategory::token_syntax_dot); + + // The key could be part of a composed table name. Check for keys separated by dots prepending the key. Check for preceeding + // comments. + enum class EPrepending + { + expected_dot_or_comma, + expected_dot, + expected_key, + done + } ePrepending = EPrepending::expected_dot_or_comma; + auto itPrepending = itTokenBegin; + auto itPrependingComment = itTokenBegin; + bool bRelevantNewlineBefore = false; + bool bCommentFound = false; + while (itPrepending != m_lstTokens.begin() && ePrepending != EPrepending::done) + { + itPrepending--; + switch (itPrepending->Category()) + { + case toml_parser::ETokenCategory::token_syntax_dot: + // Dot okay; need a preceeding key + if (ePrepending == EPrepending::expected_dot || ePrepending == EPrepending::expected_dot_or_comma) + ePrepending = EPrepending::expected_key; + else + ePrepending = EPrepending::done; + break; + case toml_parser::ETokenCategory::token_key: // Key preceeding a dot; this belongs to the statement. + if (ePrepending == EPrepending::expected_key) + { + itTokenBegin = itPrepending; + ePrepending = EPrepending::expected_dot; + } + else + ePrepending = EPrepending::done; + break; + case toml_parser::ETokenCategory::token_whitespace: + if (bCommentFound) + { + bCommentFound = false; + ePrepending = EPrepending::done; + } else if (!bRelevantNewlineBefore) + itTokenBegin = itPrepending; + break; + case toml_parser::ETokenCategory::token_syntax_new_line: + if (bRelevantNewlineBefore || bIsParent) + ePrepending = EPrepending::done; + else + { + bRelevantNewlineBefore = true; + if (bCommentFound) + itTokenBegin = itPrependingComment; + } + bCommentFound = false; + break; + case toml_parser::ETokenCategory::token_comment: + itPrependingComment = itPrepending; + bRelevantNewlineBefore = false; + bCommentFound = true; + break; // Ignore + default: + ePrepending = EPrepending::done; + bCommentFound = false; + break; + } + } + if (bCommentFound) itTokenBegin = itPrependingComment; + + // The beginning and the end cannot be the same, except for an empty range. + if (itTokenBegin == itTokenEnd) return; + + // Determine line boundaries of the current range + auto itTokenBeginLine = itTokenBegin == m_lstTokens.begin() ? itTokenBegin : fnGetLine(itTokenBegin).first; + auto itTokenEndLine = itTokenEnd; + --itTokenEndLine; + itTokenEndLine = fnGetLine(itTokenEndLine).second; + + // Are the comments following the statement? + bool bCommentsSameLine = fnCommentsOnly(std::make_pair(itTokenEnd, itTokenEndLine)); + + // Is there only whitespace before or after the statement + bool bWhitespaceBefore = fnEmptyLine(std::make_pair(itTokenBeginLine, itTokenBegin)) && !bIsParent; + bool bWhitespaceAfter = fnEmptyLine(std::make_pair(itTokenEnd, itTokenEndLine)); + + // Get the indentation length of the line (whether there is other comments before or not). + size_t nIndentLen = fnGetIndentation(std::make_pair(itTokenBeginLine, itTokenEnd)); + + // Extend the range to include the beginning of the line and the end of the line. + TRange prExtended = std::make_pair(bWhitespaceBefore ? itTokenBeginLine : itTokenBegin, + (bCommentsSameLine || bWhitespaceAfter) ? itTokenEndLine : itTokenEnd); + + // Deal with whitespace and optionally comments before + TRange prLine = prExtended; + bool bEmptyLineBefore = false; + if (bWhitespaceBefore) + { + // Check for comments preceeding the range. If previous lines are having comments only (and optionally whitespace), and + // the indentation is identical or less than the indentation of the range, the comment belongs to the range. + while (fnGetPrevLine(prLine) && fnCommentsOnly(prLine) && fnGetIndentation(prLine) <= nIndentLen) + prExtended.first = prLine.first; + + // Check whether there is an empty line before the range or the range starts at the first token in the list. + bEmptyLineBefore = prLine.first != prExtended.first ? fnEmptyLine(prLine) : prLine.first == m_lstTokens.begin(); + } + + // Deal with whitespace and optionally comments following + if (bWhitespaceAfter) + { + // Check for comments following the range. But only when there are comments at the same line and the indentation of the + // next line is larger than the range indentation or there is an empty line following. + TRange prPotential = prExtended; + bool bUsePotential = false; + prLine = prExtended; + while (bCommentsSameLine && fnGetNextLine(prLine) && fnCommentsOnly(prLine)) + { + if (bUsePotential || fnGetIndentation(prLine) > nIndentLen) + { + bUsePotential = true; + prPotential.second = prLine.second; + } + else + prExtended.second = prLine.second; + } + + // Check whether there is an empty line following the range or the range ends at the end of the list. + bool bEmptyLineBeyond = prLine.second == m_lstTokens.end() ? true : fnEmptyLine(prLine); + + // If an empty line is following and a potential extension was detected, extend the range + if (bEmptyLineBeyond && bUsePotential) + prExtended.second = prPotential.second; + + // If there is an empty line before, include any empty lines until the next token or the end of the list. + while (bEmptyLineBefore && bEmptyLineBeyond && fnGetNextLine(prLine) && fnEmptyLine(prLine)) + prExtended.second = prLine.second; + } + + rTokenRange.ExtendedNode(CTokenRange(fnToken(prExtended.first), fnToken(prExtended.second))); + + // Determine if there are any more nodes before the end of the list. + auto itFinal = prExtended.second; + bool bNextNodeFound = false; + while (itFinal != m_lstTokens.end() && !bNextNodeFound) + { + switch (itFinal->Category()) + { + case ETokenCategory::token_comment: + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + break; + default: + bNextNodeFound = true; + break; + } + + // Next token + itFinal++; + } + if (!bNextNodeFound) // There is no following node. Include the code behind the node + rTokenRange.LinesBehindNode(fnToken(m_lstTokens.end())); + } + + void CLexer::GenerateTokens(CCharacterReaderUTF8& rReader) + { + while (!rReader.IsEOF()) + { + rReader.SetBookmark(); + CToken token = ReadBareKey(rReader); + if (!token) token = ReadBasicQuotedKey(rReader); + if (!token) token = ReadLiteralQuotedKey(rReader); + if (!token) token = ReadBasicMultilineString(rReader); + if (!token) token = ReadBasicString(rReader); + if (!token) token = ReadLiteralMultilineString(rReader); + if (!token) token = ReadLiteralString(rReader); + if (!token) token = ReadInteger(rReader); + if (!token) token = ReadFloat(rReader); + if (!token) token = ReadBool(rReader); + if (!token) token = ReadWhitespace(rReader); + if (!token) token = ReadSyntaxElement(rReader); + if (!token) token = ReadComment(rReader); + if (!token) + token = ReadUnknownSequence(rReader); + auto itLocation = m_lstTokens.insert(m_lstTokens.end(), token); + m_lstTokens.back().RawDataInfo(rReader.StringFromBookmark(), m_lstTokens, itLocation); + } + } + + CToken CLexer::ReadBasicQuotedKey(CCharacterReaderUTF8& rReader) const + { + if (m_stackExpectations.top() != EExpectation::expect_key || rReader.Peek() != "\"") return {}; + try + { + bool bEndOfQuote = false; + std::string ssContent; + rReader.Consume(); + while (!bEndOfQuote) + { + if (rReader.IsEOF()) + throw XTOMLParseException( + "Unexpected End of File reached while reading multiline string"); + std::string ssCharacter = rReader.Consume(); + if (ssCharacter == "\\") + ssContent += Unescape(rReader); + else if (ssCharacter == "\"") + bEndOfQuote = true; + else + ssContent += ssCharacter; + } + return CToken(ETokenCategory::token_key, ssContent, ETokenStringType::quoted_string); + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + return CToken(ETokenCategory::token_error, rexcept); + } + } + + CToken CLexer::ReadLiteralQuotedKey(CCharacterReaderUTF8& rReader) const + { + if (m_stackExpectations.top() != EExpectation::expect_key || rReader.Peek() != "\'") return {}; + bool bEndOfQuote = false; + std::string ssContent; + rReader.Consume(); // get the initial "'" + while (!bEndOfQuote) + { + std::string ssCharacter = rReader.Consume(); + if (ssCharacter == "\'") + bEndOfQuote = true; + else + ssContent += ssCharacter; + } + return CToken(ETokenCategory::token_key, ssContent, ETokenStringType::literal_string); + } + + CToken CLexer::ReadBareKey(CCharacterReaderUTF8& rReader) const + { + std::string ssCharacter = rReader.Peek(); + if (m_stackExpectations.top() != EExpectation::expect_key || ssCharacter.empty() || + ((ssCharacter[0] < 'A' || ssCharacter[0] > 'Z') && (ssCharacter[0] < 'a' || ssCharacter[0] > 'z') + && (ssCharacter[0] < '0' || ssCharacter[0] > '9') && ssCharacter[0] != '-' && ssCharacter[0] != '_')) + return {}; + + bool bEndOfKey = false; + std::string ssContent; + bool bError = false; + while (!bEndOfKey) + { + ssCharacter = rReader.Peek(); + if (!ssCharacter.empty() && ssCharacter[0] != '.' && ssCharacter[0] != '=' && ssCharacter[0] != ' ' + && ssCharacter[0] != '\t' && ssCharacter[0] != ']') + { + if ((ssCharacter[0] < '0' || ssCharacter[0] > '9') && (ssCharacter[0] < 'A' || ssCharacter[0] > 'Z') + && (ssCharacter[0] < 'a' || ssCharacter[0] > 'z') && (ssCharacter[0] != '_') && (ssCharacter[0] != '-')) + bError = true; + rReader.Consume(); + ssContent += ssCharacter; + } + else + bEndOfKey = true; + } + if (bError) + return CToken(ETokenCategory::token_error, "Invalid bare key '" + ssContent + "'"); + else + return CToken(ETokenCategory::token_key, ssContent); + } + + CToken CLexer::ReadBasicString(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key || rReader.Peek() != "\"" || + (rReader.Peek(1) == "\"" && rReader.Peek(2) == "\"")) + return {}; + + CToken token; + try + { + bool bEndOfQuote = false; + std::string ssContent; + rReader.Consume(); + while (!bEndOfQuote) + { + if (rReader.IsEOF()) + throw XTOMLParseException("Unexpected End of File reached while reading string"); + std::string ssCharacter = rReader.Consume(); + if (ssCharacter == "\\") + ssContent += Unescape(rReader); + else if (ssCharacter == "\"") + bEndOfQuote = true; + else + ssContent += ssCharacter; + } + token = CToken(ETokenCategory::token_string, ssContent, ETokenStringType::quoted_string); + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + token = CToken(ETokenCategory::token_error, rexcept); + } + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadBasicMultilineString(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key || rReader.Peek() != "\"" || rReader.Peek(1) != "\"" + || rReader.Peek(2) != "\"") + return {}; + + std::string ssContent; + bool bEndOfQuote = false; + + auto fnIgnoreNewLineAtTheBeginning = [&rReader]() + { + if (rReader.Peek() == "\n") + rReader.Consume(); + else if (rReader.Peek() == "\r" && rReader.Peek(1) == "\n") + rReader.Consume(1); + }; + auto fnHandleBackslashFunctionality = [this, &rReader, &ssContent]() + { + if (rReader.Peek() == "\n" || (rReader.Peek() == "\r" && rReader.Peek(1) == "\n")) + { + std::string next = rReader.Peek(); + while (next == "\n" || (next == "\r" && rReader.Peek(1) == "\n") || next == " " || next == "\t") + { + rReader.Consume(); + next = rReader.Peek(); + } + return; + } + ssContent += Unescape(rReader); + }; + auto fnHandleTrippleDoublequotes = [this, &rReader, &ssContent, &bEndOfQuote]() + { + if (rReader.Peek(2) == "\"") + { + ssContent += '\"'; + return; + } + bEndOfQuote = true; + rReader.Consume(1); + }; + + CToken token; + try + { + rReader.Consume(2); + fnIgnoreNewLineAtTheBeginning(); + while (!bEndOfQuote) + { + if (rReader.IsEOF()) + throw XTOMLParseException("Unexpected End of File reached while reading multiline string"); + std::string ssCharacter = rReader.Consume(); + if (ssCharacter == "\\") + fnHandleBackslashFunctionality(); + else if (ssCharacter == "\"" && rReader.Peek() == "\"" && rReader.Peek(1) == "\"") + fnHandleTrippleDoublequotes(); + else + ssContent += ssCharacter; + } + token = CToken(ETokenCategory::token_string, ssContent, ETokenStringType::multi_line_quoted); + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + token = CToken(ETokenCategory::token_error, rexcept); + } + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadLiteralString(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key || rReader.Peek() != "\'" + || (rReader.Peek(1) == "\'" && rReader.Peek(2) == "\'")) + return {}; + + CToken token; + try + { + bool bEndOfQuote{false}; + std::string ssContent; + rReader.Consume(); + while (!bEndOfQuote) + { + if (rReader.IsEOF()) + throw XTOMLParseException("Unexpected End of File reached while reading string"); + std::string ssCharacter = rReader.Consume(); + if (ssCharacter == "'") + bEndOfQuote = true; + else + ssContent += ssCharacter; + } + token = CToken(ETokenCategory::token_string, ssContent, ETokenStringType::literal_string); + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + token = CToken(ETokenCategory::token_error, rexcept); + } + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadLiteralMultilineString(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key || rReader.Peek() != "\'" || rReader.Peek(1) != "\'" + || rReader.Peek(2) != "\'") + return {}; + + CToken token; + try + { + bool bEndOfQuote = false; + std::string ssContent; + rReader.Consume(2); + if (rReader.Peek() == "\n") + rReader.Consume(); + else if (rReader.Peek() == "\r" && rReader.Peek(1) == "\n") + rReader.Consume(1); + bool bConsumeWhitespace = false; + while (!bEndOfQuote) + { + if (rReader.IsEOF()) + throw XTOMLParseException("Unexpected End of File reached while reading multiline string"); + std::string ssCharacter = rReader.Consume(); + if (ssCharacter == "'" && rReader.Peek() == "'" && rReader.Peek(1) == "'") + { + if (rReader.Peek(2) == "'") + ssContent += '\''; + else + { + bEndOfQuote = true; + rReader.Consume(1); + } + } + else if (ssCharacter == "\\" && rReader.Peek() == "\n") + { + bConsumeWhitespace = true; + rReader.Consume(); + } + else if (ssCharacter == "\\" && rReader.Peek() == "\r" && rReader.Peek(1) == "\n") + { + bConsumeWhitespace = true; + rReader.Consume(); } else { - endOfQuote = true; - m_reader.Consume(2); + if (!std::isspace(ssCharacter[0]) || !bConsumeWhitespace) + { + ssContent += ssCharacter; + bConsumeWhitespace = false; + } } } - else if (character == "\\" && m_reader.Peek() == "\n") + token = CToken(ETokenCategory::token_string, ssContent, ETokenStringType::multi_line_literal); + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + token = CToken(ETokenCategory::token_error, rexcept); + } + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadInteger(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key) + return {}; + std::string ssCharacter = rReader.PeekUntil(m_vecValueDelimiters); + if (ssCharacter.empty()) return {}; + if (ssCharacter[0] != '-' && ssCharacter[0] != '+' && !std::isdigit(ssCharacter[0])) + return {}; // Is not a number + if (ssCharacter.substr(0, 2) != "0x" && ssCharacter.find_first_of("eE.") != std::string::npos) + return {}; // Is float + if ((ssCharacter[0] == '-' || ssCharacter[0] == '+') && ssCharacter.size() > 1 && !std::isdigit(ssCharacter[1])) + return {}; // Likely float + + std::string ssIntegerString = rReader.ConsumeUntil(m_vecValueDelimiters); + + enum class EEncoding : int64_t + { + Decimal = 10, + Hexadecimal = 16, + Octal = 8, + Binary = 2 + }; + EEncoding eEncoding = EEncoding::Decimal; + + auto fnConvertToDecimal = [this, &eEncoding](char c) -> uint32_t + { + switch (eEncoding) { - bConsumeWhitespace = true; - m_reader.Consume(); + case EEncoding::Hexadecimal: + return HexadecimalToDecimal(std::string(1, c)); + case EEncoding::Decimal: + return DecimalToDecimal(std::string(1, c)); + case EEncoding::Octal: + return OctalToDecimal(std::string(1, c)); + case EEncoding::Binary: + return BinaryToDecimal(std::string(1, c)); + default: + return 0; } - else if (character == "\\" && m_reader.Peek() == "\r" && m_reader.Peek(2) == "\n") + }; + + CToken token; + try + { + std::size_t nIndex = 0; + int64_t iSign = 1; + int64_t iValue = 0; + if (ssIntegerString[0] == '-') { - bConsumeWhitespace = true; - m_reader.Consume(); + iSign = -1; + ++nIndex; + } + else if (ssIntegerString[0] == '+') + ++nIndex; + else if (ssIntegerString.substr(0, 2) == "0x") + { + eEncoding = EEncoding::Hexadecimal; + nIndex += 2; + } + else if (ssIntegerString.substr(0, 2) == "0o") + { + eEncoding = EEncoding::Octal; + nIndex += 2; + } + else if (ssIntegerString.substr(0, 2) == "0b") + { + eEncoding = EEncoding::Binary; + nIndex += 2; + } + + if (eEncoding == EEncoding::Decimal && ssIntegerString[nIndex] == '0' && ssIntegerString.size() > nIndex + 1) + throw XTOMLParseException("No leading zeros allowed!"); + + size_t nLastUnderscore = 999; + for (std::size_t n = nIndex; n < ssIntegerString.size(); ++n) + { + if (ssIntegerString[n] == '_') + { + if (n == (nLastUnderscore + 1) || n == nIndex || n == (ssIntegerString.size() - 1)) + throw XTOMLParseException("Underscore has to be enclosed in digits!"); + nLastUnderscore = n; + continue; + } + iValue = iValue * static_cast(eEncoding) + fnConvertToDecimal(ssIntegerString[n]); + } + token = CToken(ETokenCategory::token_integer, iSign * iValue); + } + catch (const sdv::toml::XTOMLParseException& rexcept) + { + token = CToken(ETokenCategory::token_error, rexcept); + } + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadFloat(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key) + return {}; + std::string ssFloatChars = rReader.PeekUntil(m_vecValueDelimiters); + if (ssFloatChars.empty()) + return {}; + + bool bIsFloat = ssFloatChars == "inf" || ssFloatChars == "+inf" || ssFloatChars == "-inf" || ssFloatChars == "nan" + || ssFloatChars == "+nan" || ssFloatChars == "-nan"; + for (char c : ssFloatChars) + { + if (bIsFloat) + break; + if ((c < '0' || c > '9') && c != '_' && c != '-' && c != '+' + && c != 'e' && c != 'E' && c != '.') + break; + if (c == 'e' || c == '.' || c == 'E') + bIsFloat = true; + } + if (!bIsFloat) return {}; + + rReader.ConsumeUntil(m_vecValueDelimiters); + std::string ssFloatingpointString; + + // Remove any underscores. Each underscore must be surrounded by one digit + for (size_t n = 0; n < ssFloatChars.size(); n++) + { + if (ssFloatChars[n] == '_') + { + if (n == 0 || n == (ssFloatChars.size() - 1) || !std::isdigit(ssFloatChars[n-1]) || !std::isdigit(ssFloatChars[n+1])) + { + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return CToken(ETokenCategory::token_error, "Underscore has to be enclosed in digits!"); + } } else - { - if (!std::isspace(character[0]) || !bConsumeWhitespace) - { - content += character; - bConsumeWhitespace = false; - } - } - } - SToken newToken(ETokenCategory::token_string); - newToken.ssContentString = content; - m_vecTokens.push_back(newToken); - } - catch (const sdv::toml::XTOMLParseException& e) - { - CLexerTOML::SToken newToken(CLexerTOML::ETokenCategory::token_error); - newToken.ssContentString = std::string(e.what()); - m_vecTokens.push_back(newToken); - } - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} - -bool CLexerTOML::IsInteger() -{ - if (m_stackExpectations.top() == EExpectation::expect_key) - { - return false; - } - - std::string character = m_reader.PeekUntil(m_vecValueDelimiters); - - if (character.empty()) - { - return false; - } - - if (character[0] == '0' && (character[1] == 'x' || character[1] == 'o' || character[1] == 'b')) - { - return true; - } - - return std::all_of(character.begin(), - character.end(), - [](char c) - { return ((c >= '0' && c <= '9') || c == '_' || c == '-' || c == '+'); }); - // for (const char& c : character) - // { - // if (!((c >= 0x30 && c <= 0x39) || c == '_' || c == '-' || c == '+')) - // { - // return false; - // } - // } - // return true; -} - -void CLexerTOML::ReadInteger() -{ - std::string integerString = m_reader.ConsumeUntil(m_vecValueDelimiters); - enum class EFormat : int64_t - { - Decimal = 10, - Hexadecimal = 16, - Octal = 8, - Binary = 2 - }; - EFormat encoding{EFormat::Decimal}; - SToken newToken{ETokenCategory::token_integer}; - - auto convertToDecimal = [this, &encoding](char character) -> uint32_t - { - switch (encoding) - { - case EFormat::Hexadecimal: - return HexToDecimal(character); - case EFormat::Decimal: - return DecimalToDecimal(character); - case EFormat::Octal: - return OctalToDecimal(character); - case EFormat::Binary: - return BinaryToDecimal(character); - default: - return 0; - } - }; - - try - { - std::size_t index{0}; - int64_t sign{1}; - int64_t value{0}; - if (integerString[0] == '-') - { - sign = -1; - ++index; - } - else if (integerString[0] == '+') - { - ++index; - } - - if (integerString[index] == '0' && integerString.size() != index + 1) - { - switch (integerString[index + 1]) - { - case 'x': - encoding = EFormat::Hexadecimal; - break; - case 'o': - encoding = EFormat::Octal; - break; - case 'b': - encoding = EFormat::Binary; - break; - default: - throw XTOMLParseException("No leading zeros allowed!"); - } - index += 2; - } - - for (std::size_t i = index; i < integerString.size(); ++i) - { - if (integerString[i] == '_') - { - if (i == index || i == integerString.size() - 1) - { - throw XTOMLParseException("Underscore has to be enclosed in digits!"); - } - continue; - } - value = value * static_cast(encoding) + convertToDecimal(integerString[i]); - } - newToken.iContentInteger = sign * value; - } - catch (const sdv::toml::XTOMLParseException& e) - { - newToken.eCategory = ETokenCategory::token_error; - newToken.ssContentString = e.what(); - } - m_vecTokens.push_back(newToken); - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} - -bool CLexerTOML::IsFloat() -{ - if (m_stackExpectations.top() == EExpectation::expect_key) - { - return false; - } - bool isFloat = false; - std::string floatChars = m_reader.PeekUntil(m_vecValueDelimiters); - - if (floatChars == "inf" || floatChars == "+inf" || floatChars == "-inf" || floatChars == "nan" - || floatChars == "+nan" || floatChars == "-nan") - { - return true; - } - - for (const char& character : floatChars) - { - if ((character < '0' || character > '9') && character != '_' && character != '-' && character != '+' - && character != 'e' && character != 'E' && character != '.') - // if (!((c >= 0x30 && c <= 0x39) || c == '_' || c == '-' || c == '+' || c == 'e' || c == 'E' || c == - // '.')) - { - return false; - } - if (character == 'e' || character == '.' || character == 'E') - { - isFloat = true; - } - } - - return isFloat; -} - -void CLexerTOML::ReadFloat() -{ - std::string floatingpointString = m_reader.ConsumeUntil(m_vecValueDelimiters); - SToken newToken{ETokenCategory::token_float}; - - floatingpointString.erase(std::remove(floatingpointString.begin(), floatingpointString.end(), '_'), - floatingpointString.end()); - - size_t dotPosition = floatingpointString.find('.'); - if (dotPosition == 0 || (dotPosition == floatingpointString.size() - 1) - || (dotPosition != std::string::npos - && ((floatingpointString[dotPosition - 1] < '0' || floatingpointString[dotPosition - 1] > '9') - || (floatingpointString[dotPosition + 1] < '0' || floatingpointString[dotPosition + 1] > '9')))) - { - newToken.eCategory = ETokenCategory::token_error; - newToken.ssContentString = "The '.' has to be between two digits"; - } - - try - { - newToken.dContentFloatingpoint = std::stod(floatingpointString); - } - catch ([[maybe_unused]] const std::exception& e) - { - newToken.eCategory = ETokenCategory::token_error; - newToken.ssContentString = "Unable to convert '" + floatingpointString + "' to floating point value"; - } - m_vecTokens.push_back(newToken); - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} - -bool CLexerTOML::IsBool() -{ - if (m_stackExpectations.top() == EExpectation::expect_key) - { - return false; - } - std::string boolChars = m_reader.PeekUntil(m_vecValueDelimiters); - return (boolChars == "true" || boolChars == "false"); -} - -void CLexerTOML::ReadBool() -{ - SToken newToken{ETokenCategory::token_boolean}; - std::string boolChars = m_reader.ConsumeUntil(m_vecValueDelimiters); - if (boolChars == "true") - { - newToken.bContentBoolean = true; - } - else if (boolChars == "false") - { - newToken.bContentBoolean = false; - } - else - { - newToken.eCategory = ETokenCategory::token_error; - } - m_vecTokens.push_back(newToken); - if (m_stackExpectations.top() == EExpectation::expect_value_once) - { - m_stackExpectations.pop(); - } -} - -bool CLexerTOML::IsWhitespace() -{ - return m_reader.Peek()[0] == ' ' || m_reader.Peek()[0] == '\t'; -} - -void CLexerTOML::ReadWhitespace() -{ - // Only read whitespace - m_reader.Consume(); -} - -bool CLexerTOML::IsSyntaxElement() -{ - switch (m_reader.Peek()[0]) - { - case '\n': - case '[': - case ']': - case '{': - case '}': - case ',': - case '.': - case '=': - return true; - case '\r': - return (m_reader.Peek(2)[0] == '\n'); - default: - return false; - } -} - -void CLexerTOML::ReadSyntaxElement() -{ - SToken newToken; - switch (m_reader.Consume()[0]) - { - case '\n': - newToken.eCategory = ETokenCategory::token_syntax_new_line; - break; - case '\r': - m_reader.Consume(); - newToken.eCategory = ETokenCategory::token_syntax_new_line; - break; - case '[': - if (m_stackExpectations.top() != EExpectation::expect_key) - { - newToken.eCategory = ETokenCategory::token_syntax_array_open; - m_stackExpectations.push(EExpectation::expect_value); + ssFloatingpointString += ssFloatChars[n]; } + + CToken token; + size_t nDotPosition = ssFloatingpointString.find('.'); + if (nDotPosition == 0 || (nDotPosition == ssFloatingpointString.size() - 1) + || (nDotPosition != std::string::npos + && ((ssFloatingpointString[nDotPosition - 1] < '0' || ssFloatingpointString[nDotPosition - 1] > '9') + || (ssFloatingpointString[nDotPosition + 1] < '0' || ssFloatingpointString[nDotPosition + 1] > '9')))) + token = CToken(ETokenCategory::token_error, "The '.' has to be between two digits"); else { - if (m_reader.Peek() == "[") + try { - newToken.eCategory = ETokenCategory::token_syntax_table_array_open; - m_reader.Consume(); + token = CToken(ETokenCategory::token_float, std::stod(ssFloatingpointString)); + } + catch ([[maybe_unused]] const std::exception&) + { + token = CToken(ETokenCategory::token_error, + std::string("Unable to convert '") + ssFloatingpointString + "' to floating point value"); + } + } + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadBool(CCharacterReaderUTF8& rReader) + { + if (m_stackExpectations.top() == EExpectation::expect_key) + return {}; + std::string ssBoolChars = rReader.PeekUntil(m_vecValueDelimiters); + if (ssBoolChars != "true" && ssBoolChars != "false") + return {}; + + CToken token; + ssBoolChars = rReader.ConsumeUntil(m_vecValueDelimiters); + if (ssBoolChars == "true") + token = CToken(ETokenCategory::token_boolean, true); + else if (ssBoolChars == "false") + token = CToken(ETokenCategory::token_boolean, false); + else + token = CToken(ETokenCategory::token_error, std::string("Unable to convert '") + ssBoolChars + "' to floating point value"); + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadWhitespace(CCharacterReaderUTF8& rReader) const + { + bool bWhitespace = false; + while (rReader.Peek()[0] == ' ' || rReader.Peek()[0] == '\t') + { + // Only read whitespace + rReader.Consume(); + bWhitespace = true; + } + if (!bWhitespace) + return {}; + + return CToken(ETokenCategory::token_whitespace); + } + + CToken CLexer::ReadSyntaxElement(CCharacterReaderUTF8& rReader) + { + std::string ssElement = rReader.Peek(); + if (ssElement.empty()) + return {}; + + CToken token; + switch (ssElement[0]) + { + case '\n': + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_new_line); + break; + case '\r': + rReader.Consume(); + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_new_line); + break; + case '[': + rReader.Consume(); + if (m_stackExpectations.top() != EExpectation::expect_key) + { + token = CToken(ETokenCategory::token_syntax_array_open); + m_stackExpectations.push(EExpectation::expect_value); } else { - newToken.eCategory = ETokenCategory::token_syntax_table_open; + if (rReader.Peek() == "[") + { + token = CToken(ETokenCategory::token_syntax_table_array_open); + rReader.Consume(); + } + else + token = CToken(ETokenCategory::token_syntax_table_open); } - } - break; - case ']': - if (m_stackExpectations.top() != EExpectation::expect_key) - { - newToken.eCategory = ETokenCategory::token_syntax_array_close; + break; + case ']': + rReader.Consume(); + if (m_stackExpectations.top() != EExpectation::expect_key) + { + token = CToken(ETokenCategory::token_syntax_array_close); + m_stackExpectations.pop(); + if (m_stackExpectations.top() == EExpectation::expect_value_once) + m_stackExpectations.pop(); + } + else + { + if (rReader.Peek() == "]") + { + token = CToken(ETokenCategory::token_syntax_table_array_close); + rReader.Consume(); + } + else + token = CToken(ETokenCategory::token_syntax_table_close); + } + break; + case '{': + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_inline_table_open); + m_stackExpectations.push(EExpectation::expect_key); + break; + case '}': + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_inline_table_close); m_stackExpectations.pop(); if (m_stackExpectations.top() == EExpectation::expect_value_once) - { m_stackExpectations.pop(); - } + break; + case ',': + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_comma); + break; + case '.': + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_dot); + break; + case '=': + rReader.Consume(); + token = CToken(ETokenCategory::token_syntax_assignment); + m_stackExpectations.push(EExpectation::expect_value_once); + break; + default: + return {}; } - else - { - if (m_reader.Peek() == "]") - { - newToken.eCategory = ETokenCategory::token_syntax_table_array_close; - m_reader.Consume(); - } - else - { - newToken.eCategory = ETokenCategory::token_syntax_table_close; - } - } - break; - case '{': - newToken.eCategory = ETokenCategory::token_syntax_inline_table_open; - m_stackExpectations.push(EExpectation::expect_key); - break; - case '}': - newToken.eCategory = ETokenCategory::token_syntax_inline_table_close; - m_stackExpectations.pop(); + return token; + } + + CToken CLexer::ReadComment(CCharacterReaderUTF8& rReader) + { + if (rReader.Peek()[0] != '#') return {}; + rReader.ConsumeUntil({"\n"}); + return CToken(ETokenCategory::token_comment); + } + + CToken CLexer::ReadUnknownSequence(CCharacterReaderUTF8& rReader) + { + std::string sequence = rReader.ConsumeUntil(m_vecValueDelimiters); + CToken token = CToken(ETokenCategory::token_error, "Invalid Sequence '" + sequence + "'"); if (m_stackExpectations.top() == EExpectation::expect_value_once) - { m_stackExpectations.pop(); + return token; + } + + std::string CLexer::Unescape(CCharacterReaderUTF8& rReader) + { + std::string ssEscapeChar = rReader.Consume(); + switch (ssEscapeChar[0]) + { + case 'b': + return "\b"; + case 't': + return "\t"; + case 'n': + return "\n"; + case 'f': + return "\f"; + case 'r': + return "\r"; + case '"': + return "\""; + case '\\': + return "\\"; + case 'u': + return EscapedUnicodeCharacterToUTF8(rReader, 4); + case 'U': + return EscapedUnicodeCharacterToUTF8(rReader, 8); + default: + throw XTOMLParseException(("Invalid escape sequence: \\" + ssEscapeChar).c_str()); } - break; - case ',': - newToken.eCategory = ETokenCategory::token_syntax_comma; - break; - case '.': - newToken.eCategory = ETokenCategory::token_syntax_dot; - break; - case '=': - newToken.eCategory = ETokenCategory::token_syntax_assignment; - m_stackExpectations.push(EExpectation::expect_value_once); - break; - default: - newToken.eCategory = ETokenCategory::token_error; } - m_vecTokens.push_back(newToken); -} -bool CLexerTOML::IsComment() -{ - return m_reader.Peek()[0] == '#'; -} - -void CLexerTOML::ReadComment() -{ - m_reader.ConsumeUntil({"\n"}); -} - -void CLexerTOML::ReadUnknownSequence() -{ - std::string sequence = m_reader.ConsumeUntil(m_vecValueDelimiters); - SToken newToken{ETokenCategory::token_error}; - newToken.ssContentString = "Invalid Sequence '" + sequence + "'"; - m_vecTokens.push_back(newToken); - if (m_stackExpectations.top() == EExpectation::expect_value_once) + std::string CLexer::EscapedUnicodeCharacterToUTF8(CCharacterReaderUTF8& rReader, size_t nDigits) { - m_stackExpectations.pop(); - } -} - -std::string CLexerTOML::Unescape() -{ - std::string escapeChar = m_reader.Consume(); - switch (escapeChar[0]) - { - case 'b': - return "\b"; - case 't': - return "\t"; - case 'n': - return "\n"; - case 'f': - return "\f"; - case 'r': - return "\r"; - case '"': - return "\""; - case '\\': - return "\\"; - case 'u': - return Unicode4DigitToUTF8(); - case 'U': - return Unicode8DigitToUTF8(); - default: - throw XTOMLParseException(("Invalid escape sequence: \\" + escapeChar).c_str()); - } -} - -std::string CLexerTOML::Unicode4DigitToUTF8() -{ - static constexpr uint8_t numDigits{4}; - return UnicodeToUTF8(numDigits); -} - -std::string CLexerTOML::Unicode8DigitToUTF8() -{ - static constexpr uint8_t numDigits{8}; - return UnicodeToUTF8(numDigits); -} - -std::string CLexerTOML::UnicodeToUTF8(uint8_t numCharacters) -{ - // Read the characters - uint32_t uiUTFVal = 0; - for (int i = 1; i <= numCharacters; ++i) - { - uiUTFVal = uiUTFVal * 16 + HexToDecimal(m_reader.Consume()[0]); + // Read the characters + std::string ssHexValue; + for (size_t n = 1; n <= nDigits; ++n) + { + std::string ssChar = rReader.Consume(); + if (ssChar.size() != 1 || !std::isxdigit(ssChar[0])) + throw XTOMLParseException("Invalid digit in UNICODE escape string."); + ssHexValue += ssChar; + } + return toml_parser::EscapedUnicodeCharacterToUTF8(ssHexValue); } - // One byte UTF-8 character - if (uiUTFVal < 0x80) - return std::string("") + static_cast(uiUTFVal); - - // Two byte UTF-8 character - if (uiUTFVal < 0x800) - return std::string("") + static_cast(uiUTFVal >> 6 | 0xc0) + static_cast((uiUTFVal & 0b111111) | 0x80); - - // Three byte UTF-8 character - if (uiUTFVal < 0x10000) - return std::string("") + static_cast(uiUTFVal >> 12 | 0xe0) + static_cast(((uiUTFVal >> 6) & 0b111111) | 0x80) + - static_cast((uiUTFVal & 0b111111) | 0x80); - - // Four byte UTF-8 character - if (uiUTFVal < 0x110000) - { - return std::string("") + static_cast(uiUTFVal >> 18 | 0xf0) + static_cast(((uiUTFVal >> 12) & 0b111111) | 0x80) + - static_cast(((uiUTFVal >> 6) & 0b111111) | 0x80) + static_cast((uiUTFVal & 0b111111) | 0x80); - } - std::stringstream message("Invalid 8 digit unicode: "); - message << std::hex << std::to_string(uiUTFVal) << std::dec; - throw XTOMLParseException(message.str()); -} - -uint32_t CLexerTOML::HexToDecimal(const char character) -{ - static constexpr int8_t valueOfA{10}; - if (character >= '0' && character <= '9') - { - return character - '0'; - } - if (character >= 'A' && character <= 'F') - { - return character - ('A' - valueOfA); - } - if (character >= 'a' && character <= 'f') - { - return character - ('a' - valueOfA); - } - throw XTOMLParseException((std::string("Invalid hex character: ") + character + "(" - + std::to_string(static_cast(character)) + ")") - .c_str()); -} - -uint32_t CLexerTOML::DecimalToDecimal(const char character) -{ - if (character >= '0' && character <= '9') - { - return character - '0'; - } - throw XTOMLParseException((std::string("Invalid decimal character: ") + character - + std::string("(") - + std::to_string(static_cast(character)) + ")") - .c_str()); -} - -uint32_t CLexerTOML::OctalToDecimal(const char character) -{ - if (character >= '0' && character <= '7') - { - return character - '0'; - } - throw XTOMLParseException((std::string("Invalid octal character: ") + character + "(" - + std::to_string(static_cast(character)) + ")") - .c_str()); -} - -uint32_t CLexerTOML::BinaryToDecimal(const char character) -{ - if (character == '0' || character == '1') - { - return character - '0'; - } - throw XTOMLParseException((std::string("Invalid binary character: ") + character + "(" - + std::to_string(static_cast(character)) + ")") - .c_str()); -} - +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/lexer_toml.h b/sdv_services/core/toml_parser/lexer_toml.h index d596be2..49afd39 100644 --- a/sdv_services/core/toml_parser/lexer_toml.h +++ b/sdv_services/core/toml_parser/lexer_toml.h @@ -3,224 +3,375 @@ #include #include +#include +#include #include "character_reader_utf_8.h" +#include "lexer_toml_token.h" -/** - * @brief Tokenizes the output of a character reader in regard of the TOML format and returns tokens in order on demand - */ -class CLexerTOML +/// The TOML parser namespace +namespace toml_parser { -public: /** - * @brief Enum for all possible token categories + * @brief Node token range used to regenerate the source from the node entries. + * @details A node can have several token ranges identifying code that belongs to the node or precedes or succeeds the node. The + * following ranges can be identified: + * - code before the node, not belonging to another node + * - code before the node with comments belonging to the node + * - the first part of the node + * - the in between part, which might contain other nodes (in case of an inline node collection) + * - the second part of the node finishing the node (in case of an inline node collection) + * - code behind the node with comments belonging to the node + * - code behind the node, not belonging to another node (this is the code until the end of the node list) */ - enum class ETokenCategory : uint8_t + class CNodeTokenRange { - token_none, ///< Default - token_syntax_assignment, ///< '=' - token_syntax_array_open, ///< '[' after '=' - token_syntax_array_close, ///< ']' after an array open - token_syntax_table_open, ///< '[' - token_syntax_table_close, ///< ']' - token_syntax_table_array_open, ///< '[[' - token_syntax_table_array_close, ///< ']]' - token_syntax_inline_table_open, ///< '{' - token_syntax_inline_table_close, ///< '}' - token_syntax_comma, ///< ',' - token_syntax_dot, ///< '.' - token_syntax_new_line, ///< Line break - token_key, ///< Key of a Key-Value-Pair - token_string, ///< A string for a Value of a Key-Value-Pair or Array - token_integer, ///< An integer for a Value of a Key-Value-Pair or Array - token_float, ///< A floating point number for a Value of a Key-Value-Pair or Array - token_boolean, ///< A bool for a Value of a Key-Value-Pair or Array - token_time_local, ///< Unused for now - token_date_time_offset, ///< Unused for now - token_date_time_local, ///< Unused for now - token_date_local, ///< Unused for now - token_eof, ///< End of File Token; may only be at the end of the token array - token_error, ///< Error token containing an error message; further lexing is not affected - token_empty, ///< Empty token for trying to read out of bounds - token_terminated, ///< Terminated token containing an error message; further lexing is terminated + public: + /** + * @brief Initialize the range with the first initial token. Any token before that belong to the token range of the previous + * node. + * @param[in] rInitialToken The initial token that defines/starts the range. + */ + CNodeTokenRange(const CToken& rInitialToken); + + /** + * @brief Construct a node range with the main node token range. + * @param[in] rrangeNodeMain Reference to the token range holding the main node tokens. + */ + CNodeTokenRange(const CTokenRange& rrangeNodeMain); + + /** + * @brief Set the token identifying the area before the extended node. Will start before or at and end at the extended node + * range. + * @param[in] rTokenBegin Reference to the token identifying the begin of the lines before the extended node range. + * @remarks Setting the begin will not change the extended node range. If starting within the extended node range, the begin + * of the extended node range determines the begin of the lines before. + */ + void LinesBeforeNode(const CToken& rTokenBegin); + + /** + * @brief Get the lines before range, being located before the extended node range. + * @return The token range with begin and end token of the lines before. If identical, there are no lines before. + */ + CTokenRange LinesBeforeNode() const; + + /** + * @brief Set the token range identifying the extended node range. Will start before or at and end behind or at the main + * node ranges (incl. the finish range). + * @param[in] rRange Token range holding the begin and end of the extended node range. + * @remarks If the range starts or ends within the main node range, the extended node range is adapted to the main node + * range. If the lines before or behind the node fall within the extended node range, the lines will be adapted to the + * extended node range. + */ + void ExtendedNode(const CTokenRange& rRange); + + /** + * @brief Get the extended node range. The range includes the main node ranges (including the node finish range). + * @return The token range with begin and end token of the extended node range. + */ + CTokenRange ExtendedNode() const; + + /** + * @brief Get the node comments part before the node definition, which is the difference between the beginning of the + * extended node and the main node. + * @return The token range with begin and end token of the node comments range. + */ + CTokenRange NodeCommentsBefore() const; + + /** + * @brief Set the main node range. + * @param[in] rRange Token range holding the begin and end of the main node range. + * @remarks If the main node finish range starts within or before the main node range, the main node finish range will be + * updated. If the extended node range does not include the main range completely (incl. finish node range), the extended + * node range will be updated. + */ + void NodeMain(const CTokenRange& rRange); + + /** + * @brief Get the main node range. + * @return The token range with begin and end token of the main node range. + */ + CTokenRange NodeMain() const; + + /** + * @brief Set the main node finish range (for inline tables and arrays). + * @param[in] rRange Token range holding the begin and end of the main node finish range. + * @remarks If the main node finish range starts within or before the main node range, the main node finish range will be + * updated. If the extended node range does not include the main range completely (incl. finish node range), the extended + * node range will be updated. + */ + void NodeMainFinish(const CTokenRange& rRange); + + /** + * @brief Get the main node finish range. + * @return The token range with begin and end token of the main node finish range. + */ + CTokenRange NodeMainFinish() const; + + /** + * @brief Get the node comments part behind the node definition, which is the difference between the end of the + * main node and the end of the extended node. + * @return The token range with begin and end token of the node comments range. + */ + CTokenRange NodeCommentsBehind() const; + + /** + * @brief Set the token identifying the area behind the extended node. Will start at and end behind or at the extended node + * range. + * @param[in] rTokenEnd Reference to the token identifying the end of the lines behind the extended node range. + * @remarks Setting the end will not change the extended node range. If ending within the extended node range, the end + * of the extended node range determines the end of the lines before. + */ + void LinesBehindNode(const CToken& rTokenEnd); + + /** + * @brief Get the lines behind range, being located behind the extended node range. + * @return The token range with begin and end token of the lines behind. If identical, there are no lines behind. + */ + CTokenRange LinesBehindNode() const; + + private: + std::reference_wrapper m_refBeforeNodeBegin; ///< The start of the lines before the extended node (not + ///< belonging to any node). + CTokenRange m_rangeExtendedNode; ///< The extended node including comments before and behind + ///< the node. + CTokenRange m_rangeNodeMain; ///< The node tokens or for inline tables and arrays the + ///< opening part of the node. + CTokenRange m_rangeNodeFinish; ///< For inline tables and arrays, the node tokens for closing the node. + std::reference_wrapper m_refBehindNodeEnd; ///< The end of the lines behind the extended node (not + ///< belonging to any node). }; /** - * @brief Contains lexed information for the parser + * @brief Tokenizes the output of a character reader using TOML v1.0: https://toml.io/en/v1.0.0 */ - struct SToken + class CLexer { + public: /** * @brief Default constructor */ - SToken() = default; + CLexer() = default; /** - * @brief Constructs a new Token object with a given category - * @param[in] category The initial token category value for the token to be constructed + * @brief Constructs a new LexerTOML object with given input data that will be lexed + * @param[in] rssString The UTF-8 encoded content of a TOML source + * @param[in] bValueOnly When set, the lexer should treat the string as a value assignment. */ - explicit SToken(ETokenCategory category) : eCategory(category) - {} + CLexer(const std::string& rssString, bool bValueOnly = false); - std::string ssContentString; ///< Token string content - int64_t iContentInteger{}; ///< Token integer content - double dContentFloatingpoint{}; ///< Token floatingpoint content - bool bContentBoolean{}; ///< Token boolean content - ETokenCategory eCategory{ETokenCategory::token_none}; ///< Token category + /** + * @brief Feed the lexer with the given string. This will replace a previous lexing result. + * @param[in] rssString UTF-8 input string. + * @param[in] bValueOnly When set, the lexer should treat the string as a value assignment. + */ + void Feed(const std::string& rssString, bool bValueOnly = false); + + /** + * @brief Reset the lexer cursor position. + */ + void Reset(); + + /** + * @brief Navigation modes supported by the lexer. + */ + enum class ENavigationMode + { + skip_comments_and_whitespace = 1, ///< Skip comments and whitespace during navigation (default) + do_not_skip_anything = 2, ///< Do not skip anything during navigation. + }; + + /** + * @brief Get the current navigation mode. + * @return The current naviation mode. + */ + ENavigationMode NavigationMode() const; + + /** + * @brief Set the navigation mode. + * @param[in] eMode The mode to be used for navigation. + */ + void NavigationMode(ENavigationMode eMode); + + /** + * @brief Gets the n-th token after the current cursor without advancing the cursor + * @remarks Whitespace and comments are skipped. + * @param[in] nSkip Skip the amount of tokens. + * @return Returns smart pointer to the token in the token list or an empty pointer. + */ + const CToken& Peek(size_t nSkip = 0) const; + + /** + * @brief Gets the n-th token after the current cursor and advancing the cursor by n + * @remarks Whitespace and comments are skipped. + * @param[in] nSkip Skip the amount of tokens. + * @return Returns smart pointer to the token in the token list or an empty pointer. + */ + const CToken& Consume(size_t nSkip = 0); + + /** + * @brief Checks if the end-token was consumed + * @return Returns true if the end-token was consumed by Consume() or Consume(n) or if there are no tokens; + * false otherwise + */ + bool IsEnd() const; + + /** + * @brief Check and extend the provided boundaries of a given token range to include white space and comments which + * obviously belong to the token range and check for additional tokens when reaching the end of the token list. + * @param[in, out] rTokenRange Reference to the node token range being updated with extended boundaries. + */ + void SmartExtendNodeRange(CNodeTokenRange& rTokenRange) const; + + private: + /** + * @brief Run through the string and generate tokens. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + */ + void GenerateTokens(CCharacterReaderUTF8& rReader); + + /** + * @brief Read a quoted key. A quoted key accepts basic strings as keys surrounded by double quotes. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a basic quoted key or an empty token if the token hasn't been found. + */ + CToken ReadBasicQuotedKey(CCharacterReaderUTF8& rReader) const; + + /** + * @brief Read a literal quoted key. A literal quoted key accepts basic strings as keys surrounded by single quotes. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a literal quoted key or an empty token if the token hasn't been found. + */ + CToken ReadLiteralQuotedKey(CCharacterReaderUTF8& rReader) const; + + /** + * @brief Read a bare key. A bare key may only contain ASCII letters, ASCII digits, underscores and dashes (A-Za-z0-9_-). + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a bare key or an empty token if the token hasn't been found. + */ + CToken ReadBareKey(CCharacterReaderUTF8& rReader) const; + + /** + * @brief Read a basic string. A basic string may contain any unicode character. Some characters need to be escaped. The + * basis string is surrounded by double quotes. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a basic string or an empty token if the token hasn't been found. + */ + CToken ReadBasicString(CCharacterReaderUTF8& rReader); + + /** + * @brief Read a multi-line basic string. A basic string may contain any unicode character. Some characters need to be + * escaped. The multi-line basis string is surrounded by three double quotes before and behind the string. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a multi-line basic string or an empty token if the token hasn't been found. + */ + CToken ReadBasicMultilineString(CCharacterReaderUTF8& rReader); + + /** + * @brief Read a literal string. A literal string may contain any unicode character, but does not support escaped + * characters. The literal string is surrounded by single quotes. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a literal string or an empty token if the token hasn't been found. + */ + CToken ReadLiteralString(CCharacterReaderUTF8& rReader); + + /** + * @brief Read a multi-line literal string. A literal string may contain any unicode character, but does not support escaped + * characters. The multi-line literal string is surrounded by three single quotes before and behind the string. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a multi-line literal string or an empty token if the token hasn't been found. + */ + CToken ReadLiteralMultilineString(CCharacterReaderUTF8& rReader); + + /** + * @brief Read the integer. An integer is number optionally preceded by a sign and possible defined as hexadecimal + * number (preceded by 0x), octal number (preceded by 0o) or binary number (preceeeded by 0b). Numbers can be split with + * underscores. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for an integer number or an empty token if the token hasn't been found. + */ + CToken ReadInteger(CCharacterReaderUTF8& rReader); + + /** + * @brief Read the floating point number. A floating point number follows the rules defined in IEEE 754 binary64 and consist + * of a integer part followed by a fractional part (separated by a dot). An sign can be preceded and an exponential part + * can be succeeded (separated from the fractional part with the 'e'). The number can be split with underscores. Infinite + * and not-a-number are also supported (as defined in the IEEE 754). + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a floating point number or an empty token if the token hasn't been found. + */ + CToken ReadFloat(CCharacterReaderUTF8& rReader); + + /** + * @brief Read the boolean. A boolean has the value 'true' or 'false'. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a boolean or an empty token if the token hasn't been found. + */ + CToken ReadBool(CCharacterReaderUTF8& rReader); + + /** + * @brief Get the whitespace. Whitespace is a tab or space. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for whitespace or an empty token if the token hasn't been found. + */ + CToken ReadWhitespace(CCharacterReaderUTF8& rReader) const; + + /** + * @brief Get the syntax element. A syntax element identifies tables, arrays, new-lines, separators and + * assignments: '[]{},.=\\r\\n'. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for a syntax element or an empty token if the token hasn't been found. + */ + CToken ReadSyntaxElement(CCharacterReaderUTF8& rReader); + + /** + * @brief Read the comment. A comment is identified by a hash '#' and includes the rest of the line. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for comment or an empty token if the token hasn't been found. + */ + static CToken ReadComment(CCharacterReaderUTF8& rReader); + + /** + * @brief Read the unknown sequence (not represented by defined tokens). + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return Returns the token for an unknown sequence or an empty token if the reader has reached EOF. + */ + CToken ReadUnknownSequence(CCharacterReaderUTF8& rReader); + + /** + * @brief In case of an escape, return the interpretation of the character following. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @return The unescaped character. + */ + static std::string Unescape(CCharacterReaderUTF8& rReader); + + /** + * @brief Generic implementation of escaped unicode character interpretation. + * @param[in] rReader Reference to the reader providing the UTF8 characters. + * @param[in] nDigits The amount of digits the unicode character consist of. + * @return The interpreted unicode character. + */ + static std::string EscapedUnicodeCharacterToUTF8(CCharacterReaderUTF8& rReader, size_t nDigits); + + ENavigationMode m_eNavMode = ENavigationMode::skip_comments_and_whitespace; ///< The current navigation mode. + TTokenList m_lstTokens; ///< List of tokens. + TTokenListIterator m_itCursor{m_lstTokens.end()}; ///< Current position within token list. + + /** + * @brief Enum for differentiating between keys and values that are potentially indifferent like '"value" = + * "value"' + */ + enum class EExpectation + { + expect_key, ///< A key is expected over a value + expect_value, ///< A value is expected over a key + expect_value_once, ///< A value is expected over a key once + }; + std::stack m_stackExpectations; ///< Tracking of key or value expectations in nested structures + + const std::vector + m_vecKeyDelimiters{"\n", "\t", "\r", " ", "", ".", "=", "]"}; ///< Characters that delimit a key + const std::vector + m_vecValueDelimiters{"\n", "\t", "\r", " ", ",", "", "]", "}", "#"}; ///< Characters that delimit a value }; - - /** - * @brief Default constructor - */ - CLexerTOML() = default; - - /** - * @brief Constructs a new LexerTOML object with given input data that will be lexed - * @param[in] rssString The UTF-8 encoded content of a TOML source - */ - CLexerTOML(const std::string& rssString); - - /** - * @brief Feed the lexer with the given string. - * @param[in] rssString UTF-8 input string. - */ - void Feed(const std::string& rssString); - - /** - * @brief Reset the lexer content. - */ - void Reset(); - - /** - * @brief Gets the next token after the current cursor position without advancing the cursor - * @return Returns the next token after the current cursor position or a End-of-File-Token if there are no - * tokens - */ - SToken Peek() const; - - /** - * @brief Gets the next token after the current cursor position and advancing the cursor by one - * @return Returns the next token after the current cursor position or a End-of-File-Token if there are no - * tokens - */ - SToken Consume(); - - /** - * @brief Gets the n-th token after the current cursor without advancing the cursor - * @param[in] n Step size - * @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token - * if a step of n would read a position after the end-token - */ - SToken Peek(int32_t n); - - /** - * @brief Gets the n-th token after the current cursor and advancing the cursor by n - * @param[in] n Step size - * @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token - * if a step of n would read a position after the end-token - */ - SToken Consume(int32_t n); - - /** - * @brief Checks if the end-token was consumed - * @return Returns true if the end-token was consumed by Consume() or Consume(n) or if there are no tokens; - * false otherwise - */ - bool IsEnd() const; - -private: - void GenerateTokens(); - - bool IsBasicQuotedKey(); - - void ReadBasicQuotedKey(); - - bool IsLiteralQuotedKey(); - - void ReadLiteralQuotedKey(); - - bool IsBareKey(); - - void ReadBareKey(); - - bool IsBasicString(); - - void ReadBasicString(); - - bool IsBasicMultilineString(); - - void ReadBasicMultilineString(); - - bool IsLiteralString(); - - void ReadLiteralString(); - - bool IsLiteralMultilineString(); - - void ReadLiteralMultilineString(); - - bool IsInteger(); - - void ReadInteger(); - - bool IsFloat(); - - void ReadFloat(); - - bool IsBool(); - - void ReadBool(); - - bool IsWhitespace(); - - void ReadWhitespace(); - - bool IsSyntaxElement(); - - void ReadSyntaxElement(); - - bool IsComment(); - - void ReadComment(); - - void ReadUnknownSequence(); - - std::string Unescape(); - - std::string Unicode4DigitToUTF8(); - - std::string Unicode8DigitToUTF8(); - - std::string UnicodeToUTF8(uint8_t numCharacters); - - static uint32_t HexToDecimal(const char character); - - static uint32_t DecimalToDecimal(const char character); - - static uint32_t OctalToDecimal(const char character); - - static uint32_t BinaryToDecimal(const char character); - - CCharacterReaderUTF8 m_reader; - std::vector m_vecTokens; - std::size_t m_nCursor{0}; - - /** - * @brief Enum for differentiating between keys and values that are potentially indifferent like '"value" = - * "value"' - */ - enum class EExpectation - { - expect_key, ///< A key is expected over a value - expect_value, ///< A value is expected over a key - expect_value_once, ///< A value is expected over a key once - }; - std::stack m_stackExpectations; ///< Tracking of key or value expectations in nested structures - // int32_t m_LineCount{0}; - - const std::vector m_vecKeyDelimiters{ - "\n", "\t", "\r", " ", "", ".", "=", "]"}; ///< Characters that delimit a key - const std::vector m_vecValueDelimiters{ - "\n", "\t", "\r", " ", ",", "", "]", "}"}; ///< Characters that delimit a value -}; - +} // namespace toml_parser #endif // LEXER_TOML_H diff --git a/sdv_services/core/toml_parser/lexer_toml_token.cpp b/sdv_services/core/toml_parser/lexer_toml_token.cpp new file mode 100644 index 0000000..48bdd0d --- /dev/null +++ b/sdv_services/core/toml_parser/lexer_toml_token.cpp @@ -0,0 +1,552 @@ +#include "lexer_toml_token.h" +#include + +/// The TOML parser namespace +namespace toml_parser +{ + CToken::CToken() + {} + + CToken::CToken(const TTokenList& rTokenList, EBoundary eBoundary) + { + switch (eBoundary) + { + case EBoundary::lower_boundary: + m_optTokenList = rTokenList; + // NOTE: No iterator assignment. + break; + case EBoundary::upper_boundary: + m_optTokenList = rTokenList; + m_optLocation = rTokenList.end(); + break; + case EBoundary::no_boundary: + default: + // NOTE: No tokenlist assignment. + break; + } + } + + CToken::CToken(ETokenCategory eCategory) : m_eCategory(eCategory) + { + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + case ETokenCategory::token_integer: + case ETokenCategory::token_float: + case ETokenCategory::token_boolean: + case ETokenCategory::token_time_local: + case ETokenCategory::token_date_time_offset: + case ETokenCategory::token_date_time_local: + case ETokenCategory::token_date_local: + { + std::stringstream sstreamMessage; + sstreamMessage << "Invalid token category for token without ssContent: " << static_cast(eCategory); + m_eCategory = ETokenCategory::token_error; + new (&m_ssContentString) std::string(sstreamMessage.str()); + } + default: + break; + } + } + + CToken::CToken(ETokenCategory eCategory, const std::string& rssContent, + ETokenStringType eStringType /*= ETokenStringType::not_specified*/) : + m_eCategory(eCategory), m_eStringType(eStringType) + { + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + new (&m_ssContentString) std::string(rssContent); + break; + default: + { + std::stringstream sstreamMessage; + sstreamMessage << "Invalid token category for string token: " << static_cast(eCategory); + m_eCategory = ETokenCategory::token_error; + new (&m_ssContentString) std::string(sstreamMessage.str()); + } + break; + } + } + + CToken::CToken(ETokenCategory eCategory, int64_t iContent) : m_eCategory(eCategory) + { + switch (m_eCategory) + { + case ETokenCategory::token_integer: + m_iContentInteger = iContent; + break; + default: + { + std::stringstream sstreamMessage; + sstreamMessage << "Invalid token category for integer token: " << static_cast(eCategory); + m_eCategory = ETokenCategory::token_error; + new (&m_ssContentString) std::string(sstreamMessage.str()); + } + break; + } + } + + CToken::CToken(ETokenCategory eCategory, double dContent) : m_eCategory(eCategory) + { + switch (m_eCategory) + { + case ETokenCategory::token_float: + m_dContentFloatingpoint = dContent; + break; + default: + { + std::stringstream sstreamMessage; + sstreamMessage << "Invalid token categoryfor floating point token: " << static_cast(eCategory); + m_eCategory = ETokenCategory::token_error; + new (&m_ssContentString) std::string(sstreamMessage.str()); + } + break; + } + } + + CToken::CToken(ETokenCategory eCategory, bool bContent) : m_eCategory(eCategory) + { + switch (m_eCategory) + { + case ETokenCategory::token_boolean: + m_bContentBoolean = bContent; + break; + default: + { + std::stringstream sstreamMessage; + sstreamMessage << "Invalid token category for boolean token: " << static_cast(eCategory); + m_eCategory = ETokenCategory::token_error; + new (&m_ssContentString) std::string(sstreamMessage.str()); + } + break; + } + } + + CToken::CToken(ETokenCategory eCategory, const sdv::toml::XTOMLParseException& rexcept) : + CToken(eCategory, std::string(rexcept.what())) + {} + + CToken::CToken(const CToken& rToken) : + m_ssRawString(rToken.m_ssRawString), m_optTokenList(rToken.m_optTokenList), m_optLocation(rToken.m_optLocation), + m_eCategory(rToken.m_eCategory), m_eStringType(rToken.m_eStringType) + { + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + new (&m_ssContentString) std::string(rToken.m_ssContentString); + break; + case ETokenCategory::token_integer: + m_iContentInteger = rToken.m_iContentInteger; + break; + case ETokenCategory::token_float: + m_dContentFloatingpoint = rToken.m_dContentFloatingpoint; + break; + case ETokenCategory::token_boolean: + m_bContentBoolean = rToken.m_bContentBoolean; + break; + default: + break; + } + } + + CToken::CToken(CToken&& rToken) : + m_ssRawString(std::move(rToken.m_ssRawString)), m_optTokenList(std::move(rToken.m_optTokenList)), + m_optLocation(rToken.m_optLocation), m_eCategory(rToken.m_eCategory), m_eStringType(rToken.m_eStringType) + { + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + new (&m_ssContentString) std::string(std::move(rToken.m_ssContentString)); + break; + case ETokenCategory::token_integer: + m_iContentInteger = rToken.m_iContentInteger; + break; + case ETokenCategory::token_float: + m_dContentFloatingpoint = rToken.m_dContentFloatingpoint; + break; + case ETokenCategory::token_boolean: + m_bContentBoolean = rToken.m_bContentBoolean; + break; + default: + break; + } + + // Clear the ssContent + rToken.~CToken(); + rToken.m_eCategory = ETokenCategory::token_none; + rToken.m_eStringType = ETokenStringType::not_specified; + } + + CToken::~CToken() + { + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + m_ssContentString.~basic_string(); + break; + default: + break; + } + } + + CToken& CToken::operator=(const CToken& rToken) + { + // Clear current ssContent. + this->~CToken(); + + // Copy new ssContent + m_ssRawString = rToken.m_ssRawString; + m_optTokenList = rToken.m_optTokenList; + m_optLocation = rToken.m_optLocation; + m_eCategory = rToken.m_eCategory; + m_eStringType = rToken.m_eStringType; + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + new (&m_ssContentString) std::string(rToken.m_ssContentString); + break; + case ETokenCategory::token_integer: + m_iContentInteger = rToken.m_iContentInteger; + break; + case ETokenCategory::token_float: + m_dContentFloatingpoint = rToken.m_dContentFloatingpoint; + break; + case ETokenCategory::token_boolean: + m_bContentBoolean = rToken.m_bContentBoolean; + break; + default: + break; + } + + return *this; + } + + CToken& CToken::operator=(CToken&& rToken) + { + // Clear current ssContent. + this->~CToken(); + + // Copy new ssContent + switch (rToken.m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + new (&m_ssContentString) std::string(std::move(rToken.m_ssContentString)); + break; + case ETokenCategory::token_integer: + m_iContentInteger = rToken.m_iContentInteger; + break; + case ETokenCategory::token_float: + m_dContentFloatingpoint = rToken.m_dContentFloatingpoint; + break; + case ETokenCategory::token_boolean: + m_bContentBoolean = rToken.m_bContentBoolean; + break; + default: + break; + } + m_ssRawString = std::move(rToken.m_ssRawString); + m_optTokenList = std::move(rToken.m_optTokenList); + m_optLocation = std::move(rToken.m_optLocation); + m_eCategory = rToken.m_eCategory; + m_eStringType = rToken.m_eStringType; + + // Clear origional ssContent + rToken.~CToken(); + rToken.m_eCategory = ETokenCategory::token_none; + rToken.m_eStringType = ETokenStringType::not_specified; + + return *this; + } + + bool CToken::operator==(const CToken& rToken) const + { + if (m_optLocation && rToken.m_optLocation) + return *m_optLocation == *rToken.m_optLocation; + + if (m_eCategory != rToken.m_eCategory) + return false; + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + return m_ssContentString == rToken.m_ssContentString; + case ETokenCategory::token_integer: + return m_iContentInteger == rToken.m_iContentInteger; + case ETokenCategory::token_float: + return m_dContentFloatingpoint == rToken.m_dContentFloatingpoint; + case ETokenCategory::token_boolean: + return m_bContentBoolean == rToken.m_bContentBoolean; + default: + return true; // No value to compare + } + } + + bool CToken::operator!=(const CToken& rToken) const + { + if (m_optLocation && rToken.m_optLocation) + return *m_optLocation != *rToken.m_optLocation; + + if (m_eCategory != rToken.m_eCategory) + return true; + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + return m_ssContentString != rToken.m_ssContentString; + case ETokenCategory::token_integer: + return m_iContentInteger != rToken.m_iContentInteger; + case ETokenCategory::token_float: + return m_dContentFloatingpoint != rToken.m_dContentFloatingpoint; + case ETokenCategory::token_boolean: + return m_bContentBoolean != rToken.m_bContentBoolean; + default: + return false; // No value to compare + } + } + + CToken::operator bool() const + { + return m_eCategory != ETokenCategory::token_none; + } + + const CToken& CToken::Next(size_t nSkip /*= 0*/) const + { + static CToken none; + if (!m_optLocation || !m_optTokenList) return none; + const TTokenListIterator& ritToken = *m_optLocation; + TTokenList::const_iterator ritToken2 = ritToken; + const TTokenList& rTokenList = *m_optTokenList; + if (ritToken2 == rTokenList.end()) return m_optTokenList->get().tokenEnd; + size_t nLocalSkip = nSkip + 1; + do + { + ++ritToken2; + if (ritToken2 == rTokenList.end()) return m_optTokenList->get().tokenEnd; + } while (--nLocalSkip); + return *ritToken2; + } + + const CToken& CToken::Prev(size_t nSkip /*= 0*/) const + { + static CToken none; + if (!m_optLocation || !m_optTokenList) return none; + const TTokenListIterator& ritToken = *m_optLocation; + TTokenList::const_iterator ritToken2 = ritToken; + const TTokenList& rTokenList = *m_optTokenList; + size_t nLocalSkip = nSkip + 1; + do + { + if (ritToken2 == rTokenList.begin()) return m_optTokenList->get().tokenReverseEnd; + --ritToken2; + } while (--nLocalSkip); + return *ritToken2; + } + + const CToken& CToken::JumpToBegin() const + { + static CToken none; + if (!m_optLocation || !m_optTokenList) return none; + return *(m_optTokenList->get().begin()); + } + + const CToken& CToken::JumpToEnd() const + { + static CToken none; + if (!m_optLocation || !m_optTokenList) return none; + return m_optTokenList->get().tokenEnd; + } + + ETokenCategory CToken::Category() const + { + return m_eCategory; + } + + ETokenStringType CToken::StringType() const + { + return m_eStringType; + } + + std::string CToken::StringValue() const + { + switch (m_eCategory) + { + case ETokenCategory::token_key: + case ETokenCategory::token_string: + case ETokenCategory::token_error: + case ETokenCategory::token_terminated: + return m_ssContentString; + default: + return {}; + } + } + + int64_t CToken::IntegerValue() const + { + switch (m_eCategory) + { + case ETokenCategory::token_integer: + return m_iContentInteger; + default: + return 0; + } + } + + double CToken::FloatValue() const + { + switch (m_eCategory) + { + case ETokenCategory::token_float: + return m_dContentFloatingpoint; + default: + return 0.0; + } + } + + bool CToken::BooleanValue() const + { + switch (m_eCategory) + { + case ETokenCategory::token_boolean: + return m_bContentBoolean; + default: + return false; + } + } + + const std::string& CToken::RawString() const + { + return m_ssRawString; + } + + uint32_t CToken::TokenIndex() const + { + if (m_eCategory == ETokenCategory::token_none) + return 0xffffffff; + return m_uiIndex; + } + + void CToken::RawDataInfo(const std::string& rssString, const TTokenList& rTokenList, const TTokenListIterator& ritLocation) + { + m_ssRawString = rssString; + m_optTokenList = rTokenList; + m_optLocation = std::cref(ritLocation); + } + + const std::optional& CToken::Location() const + { + return m_optLocation; + } + + const std::optional>& CToken::TokenList() const + { + return m_optTokenList; + } + + uint32_t CToken::CreateIndex() + { + static uint32_t uiIndexCounter = 0; + return uiIndexCounter++; + } + + CTokenList::CTokenList() : + tokenReverseEnd(*this, CToken::EBoundary::lower_boundary), tokenEnd(*this, CToken::EBoundary::upper_boundary) + {} + + CTokenRange::CTokenRange(const CToken& rTokenBegin) : m_rTokenBegin(rTokenBegin), m_rTokenEnd(rTokenBegin.JumpToEnd()) + { + if (!rTokenBegin.TokenList()) + throw XTOMLParseException("The begin token provided to the token range doesn't have a token list assigned."); + } + + CTokenRange::CTokenRange(const CToken& rTokenBegin, const CToken& rTokenEnd) : + m_rTokenBegin(rTokenBegin), m_rTokenEnd(rTokenEnd) + { + if (!rTokenBegin.TokenList()) + throw XTOMLParseException("The begin token provided to the token range doesn't have a token list assigned."); + if (!rTokenEnd.TokenList()) + throw XTOMLParseException("The end token provided to the token range doesn't have a token list assigned."); + if (&(*rTokenBegin.TokenList()).get() != &(*rTokenEnd.TokenList()).get()) + throw XTOMLParseException("The end token of the range is not from the same token list as the begin token."); + } + + void CTokenRange::ReassignBeginToken(const CToken& rTokenBegin) + { + if (!rTokenBegin.TokenList()) + throw XTOMLParseException("The begin token provided to the token range doesn't have a token list assigned."); + m_rTokenBegin = rTokenBegin; + m_rTokenEnd = rTokenBegin.JumpToEnd(); + } + + void CTokenRange::ReassignTokenRange(const CToken& rTokenBegin, const CToken& rTokenEnd) + { + if (!rTokenBegin.TokenList()) + throw XTOMLParseException("The begin token provided to the token range doesn't have a token list assigned."); + if (!rTokenEnd.TokenList()) + throw XTOMLParseException("The end token provided to the token range doesn't have a token list assigned."); + if (&(*rTokenBegin.TokenList()).get() != &(*rTokenEnd.TokenList()).get()) + throw XTOMLParseException("The end token of the range is not from the same token list as the begin token."); + m_rTokenBegin = rTokenBegin; + m_rTokenEnd = rTokenEnd; + } + + void CTokenRange::AssignEndToken(const CToken& rTokenEnd, bool bIncludeEnd /*= true*/) + { + // Check whether the end token corresponds to the same list of the begin token. + if (!rTokenEnd.TokenList()) + throw XTOMLParseException("The end token provided to the token range doesn't have a token list assigned."); + if (&(*m_rTokenBegin.get().TokenList()).get() != &(*rTokenEnd.TokenList()).get()) + throw XTOMLParseException("The end token of the range is not from the same token list as the begin token."); + const CToken& rTokenEndCorrected = bIncludeEnd ? rTokenEnd.Next() : rTokenEnd; + + m_rTokenEnd = rTokenEndCorrected; + } + + const CToken& CTokenRange::Begin() const + { + return m_rTokenBegin.get(); + } + + const CToken& CTokenRange::End() const + { + return m_rTokenEnd.get(); + } + + std::list CTokenRange::TokenListSLice() const + { + if (!m_rTokenBegin.get().Location() || !m_rTokenEnd.get().Location()) + return {}; + return std::list(*m_rTokenBegin.get().Location(), *m_rTokenEnd.get().Location()); + } + + CTokenRange::operator bool() const + { + return m_rTokenEnd.get() != m_rTokenBegin.get() && m_rTokenBegin.get().TokenIndex() < m_rTokenEnd.get().TokenIndex(); + } + +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/lexer_toml_token.h b/sdv_services/core/toml_parser/lexer_toml_token.h new file mode 100644 index 0000000..24a4495 --- /dev/null +++ b/sdv_services/core/toml_parser/lexer_toml_token.h @@ -0,0 +1,421 @@ +#ifndef LEXER_TOML_TOKEN_H +#define LEXER_TOML_TOKEN_H + +#include +#include +#include +#include +#include +#include "exception.h" + +/// The TOML parser namespace +namespace toml_parser +{ + /** + * @brief Enum for all possible token categories + */ + enum class ETokenCategory : uint32_t + { + token_none, ///< Default - not having read anything + token_syntax_assignment, ///< '=' + token_syntax_array_open, ///< '[' after '=' + token_syntax_array_close, ///< ']' after an array open + token_syntax_table_open, ///< '[' + token_syntax_table_close, ///< ']' + token_syntax_table_array_open, ///< '[[' + token_syntax_table_array_close, ///< ']]' + token_syntax_inline_table_open, ///< '{' + token_syntax_inline_table_close, ///< '}' + token_syntax_comma, ///< ',' + token_syntax_dot, ///< '.' + token_syntax_new_line, ///< Line break + token_key, ///< Key of a Key-Value-Pair + token_string, ///< A for a Value of a Key-Value-Pair or Array + token_integer, ///< An integer for a Value of a Key-Value-Pair or Array + token_float, ///< A floating point number for a Value of a Key-Value-Pair or Array + token_boolean, ///< A bool for a Value of a Key-Value-Pair or Array + token_time_local, ///< Unused for now + token_date_time_offset, ///< Unused for now + token_date_time_local, ///< Unused for now + token_date_local, ///< Unused for now + token_whitespace, ///< Whitespace token + token_comment, ///< Comment token + token_error, ///< Error token containing an error message; further lexing is not affected + token_empty, ///< Empty token for trying to read out of bounds + token_terminated, ///< Terminated token containing an error message; further lexing is terminated + }; + + /** + * @brief The string type for key and string tokens. + */ + enum ETokenStringType + { + not_specified, ///< Not specified or not applicable + quoted_string, ///< Quoted string + literal_string, ///< Literal string + multi_line_quoted, ///< Multiple lines as a quoted string (only for string tokens) + multi_line_literal, ///< Multiple lines as a literal string (only for string tokens) + }; + + // Forward declaration + class CLexer; + class CToken; + class CTokenList; + class CTokenRange; + + /// Token list type definition + using TTokenList = CTokenList; + + /// Token list iterator definition + using TTokenListIterator = std::list::const_iterator; + + /** + * @brief Contains lexed information for the parser + */ + class CToken + { + // Access to private functions is allowed for the following classes: + friend class CLexer; + friend class CTokenRange; + + public: + /** + * @brief Default constructor + */ + CToken(); + + /** + * @brief Boundary identification. + */ + enum EBoundary + { + no_boundary, ///< The token is not defined a boundary token. + lower_boundary, ///< One token before the first token in the token list. + upper_boundary, ///< One token behind the last token in the token list. + }; + + /** + * @brief Constructor to construct a boundary token (beginning or end of the token list). + * @param[in] rTokenList Reference to the token list this token resides in. + * @param[in] eBoundary The boundary type to define. + */ + explicit CToken(const TTokenList& rTokenList, EBoundary eBoundary); + + /** + * @brief Constructs a new Token object with a given category (can be anything, except key, string, integer, boolean, time + * and error). + * @param[in] eCategory The initial token category value for the token to be constructed. + */ + explicit CToken(ETokenCategory eCategory); + + /** + * @brief Constructs a new Token object with a given category (can only be a key, a string or an error). + * @param[in] eCategory The token category value for the token to be constructed. + * @param[in] rssContent Reference to the string value. + * @param[in] eStringType The type of string that should be used. + */ + CToken(ETokenCategory eCategory, const std::string& rssContent, + ETokenStringType eStringType = ETokenStringType::not_specified); + + /** + * @brief Constructs a new Token object with a given category (can only be an integer value). + * @param[in] eCategory The token category value for the token to be constructed. + * @param[in] iContent The integer value. + */ + explicit CToken(ETokenCategory eCategory, int64_t iContent); + + /** + * @brief Constructs a new Token object with a given category (can only be a floating point value). + * @param[in] eCategory The token category value for the token to be constructed. + * @param[in] dContent The double precision floating point value. + */ + explicit CToken(ETokenCategory eCategory, double dContent); + + /** + * @brief Constructs a new Token object with a given category (can only be a boolean value). + * @param[in] eCategory The token category value for the token to be constructed + * @param[in] bContent The boolean value. + */ + explicit CToken(ETokenCategory eCategory, bool bContent); + + /** + * @brief Error token. + * @param[in] eCategory The token category value for the token to be constructed (category error or terminated). + * @param[in] rexcept Reference to the exception that was triggered. + */ + CToken(ETokenCategory eCategory, const sdv::toml::XTOMLParseException& rexcept); + + /** + * @brief Copy constructor. + * @param[in] rToken Reference to the token to copy from. + */ + CToken(const CToken& rToken); + + /** + * @brief Move constructor. + * @param[in] rToken Reference to the token to move from. + */ + CToken(CToken&& rToken); + + /** + * @brief Destructor + */ + ~CToken(); + + /** + * @brief Copy assignment operator. + * @param[in] rToken Reference to the token to copy from. + * @return Reference to this token. + */ + CToken& operator=(const CToken& rToken); + + /** + * @brief Move constructor. + * @param[in] rToken Reference to the token to move from. + * @return Reference to this token. + */ + CToken& operator=(CToken&& rToken); + + /** + * @brief Check whether the supplied token is identical to this token. The tokens are identical if the token iterators of + * the token list are identical, or if they don't have a token iterator, have identical category and value. + * @param[in] rToken Reference to the token to use for the comparison. + * @return Returns whether the supplied token is identical. + */ + bool operator==(const CToken& rToken) const; + + /** + * @brief Check whether the supplied token is not identical to this token. The tokens are identical if the token iterators + * of the token list are identical, or if they don't have a token iterator, have identical category and value. + * @param[in] rToken Reference to the token to use for the comparison. + * @return Returns whether the supplied token is not identical. + */ + bool operator!=(const CToken& rToken) const; + + /** + * @brief Checks whether the token is initialized with any token other then ETokenCategory::token_none. + * @return Returns whether the token is initialized. + */ + operator bool() const; + + /** + * @brief Quick navigation through the token list. Get the next token in the token list. + * @param[in] nSkip The amount of tokens to skip while getting the next token. + * @return Returns the next token or an none-token when the end of the token list has been reached or no tokenlist + * assignment has been made for this token. + */ + const CToken& Next(size_t nSkip = 0) const; + + /** + * @brief Quick navigation through the token list. Get the previous token in the token list. + * @param[in] nSkip The amount of tokens to skip while getting the previous token. + * @return Returns the previous token or an none-token when the begin of the token list has been reached or no tokenlist + * assignment has been made for this token. + */ + const CToken& Prev(size_t nSkip = 0) const; + + /** + * @brief Quick navigation through the token list to the begin of the token list. + * @return Returns the first token of the token list. + */ + const CToken& JumpToBegin() const; + + /** + * @brief Quick navigation through the token list to the end of the token list. + * @remarks The last token of the token list will return "false" when tested. It can be used to iterate backwards through + * the token list. + * @return Returns the one beyond the last token of the token list. + */ + const CToken& JumpToEnd() const; + + /** + * @brief Return the current category. + * @return The token category. + */ + ETokenCategory Category() const; + + /** + * @brief Get the token string type. + * @return Returns the string type for the key or string token. Returns ETokenStringType::not_specified when there is no + * string type defined or the token isn't string based. + */ + ETokenStringType StringType() const; + + /** + * @brief Get the string value. Will be filled for keys, strings and errors. + * @return The string value or empty string when the category type is invalid. + */ + std::string StringValue() const; + + /** + * @brief Get the integer value. Will be filled for a integer number. + * @return The integer value or zero when the category type is invalid. + */ + int64_t IntegerValue() const; + + /** + * @brief Get the floating point number value. Will be filled for a floating point number. + * @return The floating point number value value or 0.0 when the category type is invalid. + */ + double FloatValue() const; + + /** + * @brief Get the boolean value. Will be filled for a boolean. + * @return The boolean value or false when the category type is invalid. + */ + bool BooleanValue() const; + + /** + * @brief Get the associated raw string. + * @return Reference to the raw string. + */ + const std::string& RawString() const; + + /** + * @brief The token index identifies the token order. + * @return The unique index of this token. + */ + uint32_t TokenIndex() const; + + private: + /** + * @brief Set the string chunk from the original TOML belonging to this token and the location within the lexer token list. + * @param[in] rssString Reference to the string containing the "raw" value of the token. + * @param[in] rTokenList Reference to the token list the iterator is referring to. + * @param[in] ritLocation The location in the lexer list. + */ + void RawDataInfo(const std::string& rssString, const TTokenList& rTokenList, const TTokenListIterator& ritLocation); + + /** + * @brief Return the location in the lexer token list. + * @return Reference to the iterator location option. + */ + const std::optional& Location() const; + + /** + * @brief Return a reference to the token list this token is located in. + * @return Reference to the token list reference option. + */ + const std::optional>& TokenList() const; + + /** + * @brief Create a new index. Indices are used to guarantee correct token order. + * @return The next index number. + */ + static uint32_t CreateIndex(); + + std::string m_ssRawString; ///< The raw string without interpretation. + std::optional> m_optTokenList; ///< The token list this token is referring to. + std::optional m_optLocation; ///< Location in the lexer token list. + + union + { + std::string m_ssContentString; ///< Token string content (used with keys, strings and errors). + int64_t m_iContentInteger; ///< Token integer content + double m_dContentFloatingpoint; ///< Token floatingpoint content + bool m_bContentBoolean; ///< Token boolean content + }; + ETokenCategory m_eCategory = ETokenCategory::token_none; ///< Token category + ETokenStringType m_eStringType = ETokenStringType::not_specified; ///< Token string type for key and string tokens. + uint32_t m_uiIndex = CreateIndex(); ///< Current token index. + }; + + /** + * @brief Token list with boundary tokens used in navigation. + */ + class CTokenList : public std::list + { + public: + /** + * @brief Default constructor. + */ + CTokenList(); + + CToken tokenReverseEnd; ///< One token before first token in token list. Used to detect backwards iteration boundary. + CToken tokenEnd; ///< One token behind last token in token list. Used to detect forwards iteration boundary. + }; + + /** + * @brief The token range is used to identify the initial and the final token belonging to a collection. All intermediate + * tokens are then part of the collection. + */ + class CTokenRange + { + public: + /** + * @brief The default constructor is deleted. + */ + CTokenRange() = delete; + + /** + * @brief Start a token range using the begin token. + * @remarks The end token is automatically set to one token beyond the last token in the list. This can be changed by + * assigning the end token. + * @param[in] rTokenBegin Reference to the first token in this range. + */ + CTokenRange(const CToken& rTokenBegin); + + /** + * @brief Directly assign a token range. + * @remarks If the begin token and the end token are identical, the range doesn't contain any tokens. + * @param[in] rTokenBegin Reference to the first token in this range. + * @param[in] rTokenEnd Reference to one token beyond the last token in this range. + */ + CTokenRange(const CToken& rTokenBegin, const CToken& rTokenEnd); + + /** + * @brief Reassign the first token in the range. This will set the end token to the one beyond the last token in the list. + * @param[in] rTokenBegin Reference to the first token in this range. + */ + void ReassignBeginToken(const CToken& rTokenBegin); + + /** + * @brief Reassign the token range. + * @remarks If the begin token and the end token are identical, the range doesn't contain any tokens. + * @param[in] rTokenBegin Reference to the first token in the range. + * @param[in] rTokenEnd Reference to one token beyond the last token in the range. + */ + void ReassignTokenRange(const CToken& rTokenBegin, const CToken& rTokenEnd); + + /** + * @brief Assign or overwrite an end token. + * @param[in] rTokenEnd Reference to the last token in this range. + * @param[in] bIncludeEnd When set, the token belongs to the range; if not, the token points past the last range token. + */ + void AssignEndToken(const CToken& rTokenEnd, bool bIncludeEnd = true); + + /** + * @brief Get the initial token in the range. + * @return Reference to the smart pointer containing the first token in this range. + */ + const CToken& Begin() const; + + /** + * @brief Get one token beyond the final token in the range. + * @remarks The end token can be one token beyond the last token in the list. This token will return 'false' when tested and + * can be used for navigation. + * @return Reference to the smart pointer containing one token beyond the final token in this range. + */ + const CToken& End() const; + + /** + * @brief Create a copy of the token list part identified by the start and end token (not including the end token). + * @return The token list containing the slice. + */ + std::list TokenListSLice() const; + + /** + * @brief Does the token range contain a valid range of at least one token. + * @return Returns whether the token range is valid. + */ + operator bool() const; + + private: + std::reference_wrapper m_rTokenBegin; ///< Smart pointer to the first token in the range. + std::reference_wrapper m_rTokenEnd; ///< Smart pointer to one token beyond the last token in the range. + }; + + +} // namespace toml_parser + +#endif // !defined LEXER_TOML_TOKEN_H diff --git a/sdv_services/core/toml_parser/miscellaneous.cpp b/sdv_services/core/toml_parser/miscellaneous.cpp new file mode 100644 index 0000000..3b81e96 --- /dev/null +++ b/sdv_services/core/toml_parser/miscellaneous.cpp @@ -0,0 +1,549 @@ +#include "miscellaneous.h" +#include "exception.h" +#include +#include + +namespace toml_parser +{ + std::string EscapedUnicodeCharacterToUTF8(const std::string& rss) + { + // Read the characters + uint32_t uiUTFVal = HexadecimalToDecimal(rss); + + // One byte UTF-8 character + if (uiUTFVal < 0x80) + return std::string() + static_cast(uiUTFVal); + + // Two byte UTF-8 character + if (uiUTFVal < 0x800) + return std::string() + static_cast(uiUTFVal >> 6 | 0xc0) + static_cast((uiUTFVal & 0b111111) | 0x80); + + // Three byte UTF-8 character + if (uiUTFVal < 0x10000) + return std::string() + static_cast(uiUTFVal >> 12 | 0xe0) + static_cast(((uiUTFVal >> 6) & 0b111111) | 0x80) + + static_cast((uiUTFVal & 0b111111) | 0x80); + + // Four byte UTF-8 character + if (uiUTFVal < 0x110000) + { + return std::string() + static_cast(uiUTFVal >> 18 | 0xf0) + + static_cast(((uiUTFVal >> 12) & 0b111111) | 0x80) + + static_cast(((uiUTFVal >> 6) & 0b111111) | 0x80) + static_cast((uiUTFVal & 0b111111) | 0x80); + } + + std::stringstream sstream; + sstream << "Invalid unicode conversion for hexadecimal number: " << rss; + throw XTOMLParseException(sstream.str()); + } + + uint32_t HexadecimalToDecimal(const std::string& rss) + { + if (rss.empty()) throw XTOMLParseException("Invalid hexadecimal number interpretation for string"); + uint32_t uiVal = 0; + bool bInitial = true; + for (char c : rss) + { + if (c >= '0' && c <= '9') + uiVal = uiVal * 16u + static_cast(c - '0'); + else if (c >= 'A' && c <= 'F') + uiVal = uiVal * 16u + static_cast(c - 'A' + 10u); + else if (c >= 'a' && c <= 'f') + uiVal = uiVal * 16u + static_cast(c - 'a' + 10u); + else + { + // Not a correct value... only an error if it is the first digit + if (bInitial) + throw XTOMLParseException(std::string("Invalid hexadecimal number interpretation for string: ") + rss); + break; + } + bInitial = false; + if (static_cast(uiVal) * 16u > std::numeric_limits().max()) break; + } + return uiVal; + } + + uint32_t DecimalToDecimal(const std::string& rss) + { + if (rss.empty()) throw XTOMLParseException("Invalid decimal number interpretation for string"); + uint32_t uiVal = 0; + bool bInitial = true; + for (char c : rss) + { + if (c >= '0' && c <= '9') + uiVal = uiVal * 10u + static_cast(c - '0'); + else + { + // Not a correct value... only an error if it is the first digit + if (bInitial) + throw XTOMLParseException(std::string("Invalid decimal number interpretation for string: ") + rss); + break; + } + bInitial = false; + if (static_cast(uiVal) * 10u > std::numeric_limits().max()) break; + } + return uiVal; + } + + uint32_t OctalToDecimal(const std::string& rss) + { + if (rss.empty()) throw XTOMLParseException("Invalid octal number interpretation for string"); + uint32_t uiVal = 0; + bool bInitial = true; + for (char c : rss) + { + if (c >= '0' && c <= '7') + uiVal = uiVal * 8u + static_cast(c - '0'); + else + { + // Not a correct value... only an error if it is the first digit + if (bInitial) + throw XTOMLParseException(std::string("Invalid octal number interpretation for string: ") + rss); + break; + } + bInitial = false; + if (static_cast(uiVal) * 8u > std::numeric_limits().max()) break; + } + return uiVal; + } + + uint32_t BinaryToDecimal(const std::string& rss) + { + if (rss.empty()) throw XTOMLParseException("Invalid binary number interpretation for string"); + uint32_t uiVal = 0; + bool bInitial = true; + for (char c : rss) + { + if (c == '0') + uiVal = uiVal * 2u; + else if (c == '1') + uiVal = uiVal * 2u + static_cast(c - '0'); + else + { + // Not a correct value... only an error if it is the first digit + if (bInitial) + throw XTOMLParseException(std::string("Invalid binary number interpretation for string: ") + rss); + break; + } + bInitial = false; + if (static_cast(uiVal) * 2u > std::numeric_limits().max()) break; + } + return uiVal; + } + + std::pair SplitNodeKey(const std::string& rssKeyPath) + { + if (rssKeyPath.empty()) + return {}; + enum class EType + { + normal, + expect_separator, + single_quoted_string, + double_quoted_string, + expect_index, + expect_index_or_end_bracket + } eType = EType::normal; + size_t nPos = 0; + std::string ssFirst; + while (nPos < rssKeyPath.size()) + { + char c = rssKeyPath[nPos]; + switch (c) + { + case ' ': + case '\t': + // When inside a string, add the whitespace to the key. + if (eType == EType::single_quoted_string || eType == EType::double_quoted_string) + { + ssFirst += c; + break; + } + if (eType == EType::normal && !ssFirst.empty()) // This is only allowed at the end of a key + eType = EType::expect_separator; + break; + case '\r': + case '\n': + if (eType == EType::normal && !ssFirst.empty()) // This is only allowed at the end of a key + eType = EType::expect_separator; + break; + case '\'': + if (eType == EType::normal) + { + if (!ssFirst.empty()) + return {}; // Cannot be placed in the middle of the key + eType = EType::single_quoted_string; + } + else if (eType == EType::single_quoted_string) + eType = EType::expect_separator; + else if (eType == EType::double_quoted_string) + ssFirst += c; + else + return {}; // Invalid usage + break; + case '\"': + if (eType == EType::normal) + { + if (!ssFirst.empty()) + return {}; // Cannot be placed in the middle of the key + eType = EType::double_quoted_string; + } + else if (eType == EType::double_quoted_string) + eType = EType::expect_separator; + else if (eType == EType::single_quoted_string) + ssFirst += c; + else + return {}; // Invalid usage + break; + case '\\': + if (eType == EType::single_quoted_string) + { + ssFirst += c; + break; + } + if (eType != EType::double_quoted_string) return {}; // Only allowed for double quoted strings. + nPos++; + if (nPos >= rssKeyPath.size()) return {}; // Ended with escape... + try + { + switch (rssKeyPath[nPos]) + { + case 'b': ssFirst += '\b'; break; + case 't': ssFirst += '\t'; break; + case 'n': ssFirst += '\n'; break; + case 'f': ssFirst += '\f'; break; + case 'r': ssFirst += '\r'; break; + case '"': ssFirst += '\"'; break; + case '\\': ssFirst += '\\'; break; + case 'u': ssFirst += EscapedUnicodeCharacterToUTF8(rssKeyPath.substr(nPos + 1, 4)); nPos += 4; break; + case 'U': ssFirst += EscapedUnicodeCharacterToUTF8(rssKeyPath.substr(nPos + 1, 8)); nPos += 8; break; + default: + return {}; // Invalid escape sequence + } + } + catch (const sdv::toml::XTOMLParseException&) + { + return {}; + } + break; + case '.': + if (eType == EType::single_quoted_string || eType == EType::double_quoted_string) + { + ssFirst += c; + break; + } + if (ssFirst.empty()) break; // Ignore an initial dot. + if (eType != EType::normal && eType != EType::expect_separator) + return {}; // Unexpected separator + return std::make_pair(ssFirst, rssKeyPath.substr(nPos + 1)); + case '[': + if (eType == EType::single_quoted_string || eType == EType::double_quoted_string) + { + ssFirst += c; + break; + } + if (eType != EType::normal && eType != EType::expect_separator) + return {}; // Unexpected separator + if (!ssFirst.empty()) // Belongs to second part? + return std::make_pair(ssFirst, rssKeyPath.substr(nPos)); + eType = EType::expect_index; + break; + case ']': + if (eType == EType::single_quoted_string || eType == EType::double_quoted_string) + { + ssFirst += c; + break; + } + if (eType != EType::expect_index_or_end_bracket) // Allowed? + return {}; + eType = EType::expect_separator; // Expected a separator to follow + break; + default: + if (eType == EType::single_quoted_string || eType == EType::double_quoted_string) + ssFirst += c; + else if ((eType == EType::expect_index || eType == EType::expect_index_or_end_bracket) && std::isdigit(c)) + { + ssFirst += c; + eType = EType::expect_index_or_end_bracket; + } + // Protect against multi-byte characters (UTF-8) + else if (eType == EType::normal && static_cast(c) < 127u && (std::isalnum(c) || c == '-' || c == '_')) + ssFirst += c; + else + return {}; // Invalid key character + break; + } + nPos++; + } + + // When within quotes, this is an error + if (eType == EType::single_quoted_string || eType == EType::double_quoted_string) + return {}; + + // When expecting index and/or end bracket, this is an error + if (eType == EType::expect_index || eType == EType::expect_index_or_end_bracket) + return {}; + + // When coming here, there is no more character in the string. + return std::make_pair(ssFirst, std::string()); + } + + std::pair, CTokenRange> SplitNodeKey(const CTokenRange& rrangeKeyPath) + { + std::reference_wrapper refToken = rrangeKeyPath.Begin(); + std::reference_wrapper refTokenFirst = refToken; + enum class EState + { + initial_key_or_index_marker, + dot_or_index_marker_or_end, + key, + index, + index_marker_close, + } eState = EState::initial_key_or_index_marker; + while (refToken.get() != rrangeKeyPath.End()) + { + switch (refToken.get().Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_comment: + case ETokenCategory::token_syntax_new_line: + break; + case ETokenCategory::token_key: + if (eState == EState::initial_key_or_index_marker) + refTokenFirst = refToken; + else if (eState == EState::key) + return std::make_pair(refTokenFirst, CTokenRange(refToken.get(), rrangeKeyPath.End())); + else + throw XTOMLParseException("Internal error: invalid token range, expecting key."); + eState = EState::dot_or_index_marker_or_end; + break; + case ETokenCategory::token_syntax_dot: + if (eState != EState::dot_or_index_marker_or_end) + throw XTOMLParseException("Internal error: invalid token range, expecting dot."); + eState = EState::key; + break; + case ETokenCategory::token_syntax_array_open: + if (eState == EState::initial_key_or_index_marker) + eState = EState::index; + else if (eState == EState::dot_or_index_marker_or_end) + return std::make_pair(refTokenFirst, CTokenRange(refToken, rrangeKeyPath.End())); + else + throw XTOMLParseException("Internal error: invalid token range, unexpected array open."); + break; + case ETokenCategory::token_integer: + if (eState != EState::index) + throw XTOMLParseException("Internal error: invalid token range, unexpected index."); + refTokenFirst = refToken; + eState = EState::index_marker_close; + break; + case ETokenCategory::token_syntax_array_close: + if (eState != EState::index_marker_close) + throw XTOMLParseException("Internal error: invalid token range, unexpected array close."); + eState = EState::dot_or_index_marker_or_end; + break; + default: + throw XTOMLParseException("Internal error: invalid token range, unexpected token."); + } + refToken = refToken.get().Next(); + } + + // Coming here would mean that there was one key, but nothing more + if (eState != EState::dot_or_index_marker_or_end) + throw XTOMLParseException("Internal error: invalid token range, unexpected end."); + + // Return a pair with the first token and an empty token range. + return std::make_pair(refTokenFirst, CTokenRange(refToken.get(), refToken.get())); + } + + std::string ExtractKeyName(const std::string& rssKeyPath) + { + // Split the key parh until there is no second part any more. + auto prSplittedKey = SplitNodeKey(rssKeyPath); + while (!prSplittedKey.second.empty()) + prSplittedKey = SplitNodeKey(prSplittedKey.second); + return prSplittedKey.first; + } + + std::string QuoteText(const std::string& rssText, EQuoteRequest eQuoteRequest /*= EQuoteRequest::smart_key*/) + { + EQuoteRequest eQuoteNeeded = eQuoteRequest; + if (rssText.empty() && eQuoteNeeded == EQuoteRequest::smart_key) + eQuoteNeeded = EQuoteRequest::quoted_text; + std::stringstream sstreamQuotedText; + sstreamQuotedText << "\""; + for (size_t nPos = 0; nPos < rssText.size(); nPos++) + { + uint8_t uiChar = static_cast(rssText[nPos]); + uint32_t uiUTFChar = 0; + switch (uiChar) + { + case '\'': + if (eQuoteRequest != EQuoteRequest::multi_line_quoted_text && + eQuoteRequest != EQuoteRequest::multi_line_literal_text) + eQuoteNeeded = EQuoteRequest::quoted_text; + sstreamQuotedText << '\''; + break; // Single quote character + case '\b': + if (eQuoteRequest == EQuoteRequest::multi_line_literal_text) + eQuoteNeeded = EQuoteRequest::multi_line_quoted_text; + else if (eQuoteRequest != EQuoteRequest::multi_line_quoted_text) + eQuoteNeeded = EQuoteRequest::quoted_text; + sstreamQuotedText << "\\b"; + break; // Escape backspace + case '\t': + if (eQuoteRequest == EQuoteRequest::multi_line_literal_text) + eQuoteNeeded = EQuoteRequest::multi_line_quoted_text; + else if (eQuoteRequest != EQuoteRequest::multi_line_quoted_text) + eQuoteNeeded = EQuoteRequest::quoted_text; + sstreamQuotedText << "\\t"; + break; // Escape tab + case '\n': + if (eQuoteRequest != EQuoteRequest::multi_line_quoted_text && + eQuoteRequest != EQuoteRequest::multi_line_literal_text) + { + eQuoteNeeded = EQuoteRequest::quoted_text; + sstreamQuotedText << "\\n"; + } + else + sstreamQuotedText << "\n"; + break; // Escape linefeed + case '\f': + if (eQuoteRequest == EQuoteRequest::multi_line_literal_text) + eQuoteNeeded = EQuoteRequest::multi_line_quoted_text; + else if (eQuoteRequest != EQuoteRequest::multi_line_quoted_text) + eQuoteNeeded = EQuoteRequest::quoted_text; + sstreamQuotedText << "\\f"; + break; // Escape form feed + case '\r': + if (eQuoteRequest != EQuoteRequest::multi_line_quoted_text && + eQuoteRequest != EQuoteRequest::multi_line_literal_text) + { + eQuoteNeeded = EQuoteRequest::quoted_text; + sstreamQuotedText << "\\r"; + } else + sstreamQuotedText << "\r"; + break; // Escape carriage return + case '\"': + if (eQuoteNeeded == EQuoteRequest::smart_key) + eQuoteNeeded = EQuoteRequest::quoted_text; + else if (eQuoteNeeded == EQuoteRequest::smart_text) + eQuoteNeeded = EQuoteRequest::literal_text; + sstreamQuotedText << "\\\""; + break; // Escape quote + case '\\': + if (eQuoteNeeded == EQuoteRequest::smart_key) + eQuoteNeeded = EQuoteRequest::quoted_text; + else if (eQuoteNeeded == EQuoteRequest::smart_text) + eQuoteNeeded = EQuoteRequest::literal_text; + sstreamQuotedText << "\\\\"; + break; // Escape backslash + default: + // Check for ASCII character + if (uiChar >= 0x20 && uiChar < 0x7f) + { + // Standard ASCII + sstreamQuotedText << static_cast(uiChar); + if (!std::isalnum(static_cast(uiChar)) && uiChar != '-' && uiChar != '_') + { + if (eQuoteNeeded == EQuoteRequest::smart_key || eQuoteNeeded == EQuoteRequest::smart_text) + eQuoteNeeded = EQuoteRequest::quoted_text; + } + break; + } + + // Not a standard ASCII character + if (eQuoteNeeded == EQuoteRequest::smart_key || eQuoteNeeded == EQuoteRequest::smart_text) + eQuoteNeeded = EQuoteRequest::quoted_text; + else if (eQuoteNeeded == EQuoteRequest::literal_text && (uiChar < 0x20 || uiChar == 0x7f)) + { + // If the character is one of the control characters (< 0x20 || == 0x7f) then must be quoted + eQuoteNeeded = EQuoteRequest::quoted_text; + } + else if (eQuoteNeeded == EQuoteRequest::multi_line_literal_text && (uiChar < 0x20 || uiChar == 0x7f)) + { + // If the character is one of the control characters (< 0x20 || == 0x7f) then must be quoted + eQuoteNeeded = EQuoteRequest::multi_line_quoted_text; + } + + // Use UNICODE escape character for the quoted text + if (uiChar <= 0x80) // One byte UTF-8 + uiUTFChar = static_cast(uiChar); + else if (uiChar <= 0xDF) // Two bytes UTF-8 + { + uiUTFChar = static_cast(uiChar & 0b00011111) << 6; + + // Expecting the next character to be between 0x80 and 0xBF + nPos++; + if (nPos >= rssText.size()) + break; + uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); + } + else if (uiChar <= 0xEF) // Three bytes UTF-8 + { + uiUTFChar = static_cast(uiChar & 0b00001111) << 6; + + // Expecting the next character to be between 0x80 and 0xBF + nPos++; + if (nPos >= rssText.size()) + break; + uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); + uiUTFChar <<= 6; + + // Expecting the next character to be between 0x80 and 0xBF + nPos++; + if (nPos >= rssText.size()) + break; + uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); + } + else if (uiChar <= 0xF7) // Four bytes UTF-8 + { + uiUTFChar = static_cast(uiChar & 0b00000111) << 6; + + // Expecting the next character to be between 0x80 and 0xBF + nPos++; + if (nPos >= rssText.size()) + break; + uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); + uiUTFChar <<= 6; + + // Expecting the next character to be between 0x80 and 0xBF + nPos++; + if (nPos >= rssText.size()) + break; + uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); + uiUTFChar <<= 6; + + // Expecting the next character to be between 0x80 and 0xBF + nPos++; + if (nPos >= rssText.size()) + break; + uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); + } + + // Stream the UTF character + if (uiUTFChar <= 0xFFFF) + sstreamQuotedText << "\\u" << std::uppercase << std::hex << std::setfill('0') << std::setw(4) << uiUTFChar; + else + sstreamQuotedText << "\\U" << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << uiUTFChar; + break; + } + } + sstreamQuotedText << "\""; + + // Return depedent of the needed quotation. + switch (eQuoteNeeded) + { + case EQuoteRequest::smart_key: + return rssText; // No need to change + case EQuoteRequest::literal_text: + return "'" + rssText + "'"; + case EQuoteRequest::multi_line_literal_text: + return "'''" + rssText + "'''"; + case EQuoteRequest::multi_line_quoted_text: + return "\"\"" + sstreamQuotedText.str() + "\"\""; + case EQuoteRequest::smart_text: + case EQuoteRequest::quoted_text: + default: + return sstreamQuotedText.str(); + } + } +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/miscellaneous.h b/sdv_services/core/toml_parser/miscellaneous.h new file mode 100644 index 0000000..4a77bcc --- /dev/null +++ b/sdv_services/core/toml_parser/miscellaneous.h @@ -0,0 +1,99 @@ +#ifndef MISCELLANEOUS_H +#define MISCELLANEOUS_H + +#include +#include +#include +#include +#include "lexer_toml_token.h" + +/// The TOML parser namespace +namespace toml_parser +{ + /** + * @brief Implementation of hexadecimal number to UTF8 unicode character. + * @param[in] rss Reference to the string providing the hexadecimal unicode number. + * @return The interpreted unicode character. + */ + std::string EscapedUnicodeCharacterToUTF8(const std::string& rss); + + /** + * @brief Calculate the interpretation value of a hexadecimal number in a string (characters 0-9a-fA-F). + * @param[in] rss Reference to the string containing the number. + * @return The calculated value. + */ + uint32_t HexadecimalToDecimal(const std::string& rss); + + /** + * @brief Calculate the interpretation value of a decimal number in a string (characters 0-9). + * @param[in] rss Reference to the string containing the number. + * @return The calculated value. + */ + uint32_t DecimalToDecimal(const std::string& rss); + + /** + * @brief Calculate the interpretation value of an octal number in a string (characters 0-7). + * @param[in] rss Reference to the string containing the number. + * @return The calculated value. + */ + uint32_t OctalToDecimal(const std::string& rss); + + /** + * @brief Calculate the interpretation value of a binary number in a string (characters 0-1). + * @param[in] rss Reference to the string containing the number. + * @return The calculated value. + */ + uint32_t BinaryToDecimal(const std::string& rss); + + /** + * @brief Split the node key in a first node key and the rest of the node key (if available). Separators are the dot, for table + * separation and [] index for array separation. + * @param[in] rssKeyPath Reference to the string containing the key path. + * @return Pair of string containing the first key (unquoted) and the rest of the key string (still quoted) if applicable. If + * no key is available, will be a reference to two empty strings. The dot and the indexing markers will not be part of the + * returned first string. + */ + std::pair SplitNodeKey(const std::string& rssKeyPath); + + /** + * @brief Split the node key contained in the token range in a first node key and the rest of the node key (if available). + * Separators are the dot, for table separation and [] index for array separation. + * @param[in] rrangeKeyPath Reference to the token range ccontaining the tokens representing the key path. + * @return Pair of string containing the first key (unquoted) and the rest of the key string (still quoted) if applicable. If + * no key is available, will be a reference to two empty strings. The dot and the indexing markers will not be part of the + * returned token. + */ + std::pair, CTokenRange> SplitNodeKey(const CTokenRange& rrangeKeyPath); + + /** + * @brief Extract the unquoted key name from a key path. The key name is the last node on the key path. + * @remarks Removes quotes, escape characters and index markers from the key name before returning. + * @param[in] rssKeyPath Reference to the string containing the key path. + * @return The unquoted key name. + */ + std::string ExtractKeyName(const std::string& rssKeyPath); + + /** + * @brief Quotation request enum for quoting text (or keys). + */ + enum class EQuoteRequest + { + smart_key, ///< Uses quotation markers and escape characters if needed. + smart_text, ///< Use literal markers if possible, use quotation markers if needed. + quoted_text, ///< Will always use quotation markers. + literal_text, ///< Will use literal markers if possible; otherwise uses quotation markers. + multi_line_quoted_text, ///< Multiple lines quoted text (carriage return and newline are not escaped). + multi_line_literal_text, ///< Multiple lines literal text (carriage return and newline are not escaped). + }; + + /** + * @brief If the provided key contains characters which are not allowed to be used in a bare-key, add quotes to the key and + * use escape characters if applicable. + * @param[in] rssText The text to (optionally) quote. + * @param[in] eQuoteRequest The quotation method requested. + * @return The quoted key (if applicable); otherwise the key without quotes. + */ + std::string QuoteText(const std::string& rssText, EQuoteRequest eQuoteRequest = EQuoteRequest::smart_text); +} // namespace toml_parser + +#endif // !defined MISCELLANEOUS_H \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_node_toml.cpp b/sdv_services/core/toml_parser/parser_node_toml.cpp index da37b52..7e7967e 100644 --- a/sdv_services/core/toml_parser/parser_node_toml.cpp +++ b/sdv_services/core/toml_parser/parser_node_toml.cpp @@ -2,759 +2,1651 @@ #include "parser_node_toml.h" #include "exception.h" #include +#include "parser_toml.h" -size_t FindFirst(const std::string& rss, const std::string& rssSeparator /*= "."*/) +/// The TOML parser namespace +namespace toml_parser { - enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal; - size_t nPos = 0; - while (nPos < rss.size()) + CGenContext::CGenContext(const std::string& rssPrefixKey /*= std::string()*/, uint32_t uiOptions /*= 0*/) : + m_ssPrefixKey(rssPrefixKey), m_uiOptions(uiOptions) + {} + + void CGenContext::InitTopMostNode(const std::shared_ptr& rptrNode) { - switch (rss[nPos]) + if (!m_ptrTopMostNode) + m_ptrTopMostNode = rptrNode; + } + + bool CGenContext::PartOfExcludedParents(const std::shared_ptr& rptrNode) const + { + if (!m_ptrTopMostNode) return false; + std::shared_ptr ptrParent = m_ptrTopMostNode->GetParentPtr(); + while (ptrParent) { - case '\'': - if (eType == EType::normal) - eType = EType::single_quoted_string; - else if (eType == EType::single_quoted_string) - eType = EType::normal; - break; - case '\"': - if (eType == EType::normal) - eType = EType::double_quoted_string; - else if (eType == EType::double_quoted_string) - eType = EType::normal; - break; - case '\\': - nPos++; - break; - default: - if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos) - return nPos; - break; + if (ptrParent == rptrNode) return true; + ptrParent = ptrParent->GetParentPtr(); } - nPos++; + return false; } - return nPos >= rss.size() ? std::string::npos : nPos; -} -size_t FindLast(const std::string& rss, const std::string& rssSeparator /*= "."*/) -{ - enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal; - size_t nPos = rss.size(); - while (nPos) + CGenContext CGenContext::CopyWithContext(const std::string& rssNewKeyContext) const { - nPos--; - bool bEscaped = nPos && rss[nPos - 1] == '\\'; - switch (rss[nPos]) + CGenContext context(m_ssPrefixKey, m_uiOptions); + context.m_ssKeyContext = rssNewKeyContext; + context.m_ptrTopMostNode = m_ptrTopMostNode; + context.m_bTopMost = false; + return context; + } + + const std::string& CGenContext::PrefixKey() const + { + return m_ssPrefixKey; + } + + const std::string& CGenContext::KeyContext() const + { + return m_ssKeyContext; + } + + bool CGenContext::TopMostNode() const + { + return m_bTopMost; + } + + void CGenContext::SetOption(EGenerateOptions eOption) + { + m_uiOptions |= static_cast(eOption); + } + + bool CGenContext::CheckOption(EGenerateOptions eOption) const + { + return (m_uiOptions & static_cast(eOption)) ? true : false; + } + + CNode::CNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName) : + m_ssName(rssName), m_ssRawName(rssRawName), m_rParser(rparser) + {} + + CNode::~CNode() + {} + + CParser& CNode::Parser() + { + return m_rParser; + } + + sdv::u8string CNode::GetName() const + { + return m_ssName; + } + + sdv::u8string CNode::GetPath(bool bResolveArrays) const + { + bool bRoot = dynamic_cast(this) ? true : false; + if (!m_bDeleted && !bRoot) // Do not include the root table in the path. { - case '\'': - if (bEscaped) - nPos--; - else if (eType == EType::normal) - eType = EType::single_quoted_string; - else if (eType == EType::single_quoted_string) - eType = EType::normal; - break; - case '\"': - if (bEscaped) - nPos--; - else if (eType == EType::normal) - eType = EType::double_quoted_string; - else if (eType == EType::double_quoted_string) - eType = EType::normal; - break; - default: - if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos) - return nPos; - break; - } - } - return std::string::npos; -} - -bool CompareEqual(const std::string& rss1, const std::string& rss2) -{ - size_t nStart1 = 0, nStop1 = rss1.size(); - if (rss1.size() && rss1.find_first_of("\"\'") == 0 && rss1.find_last_of("\"\'") == (rss1.size() - 1)) - { - nStart1++; - nStop1--; - } - size_t nStart2 = 0, nStop2 = rss2.size(); - if (rss2.size() && rss2.find_first_of("\"\'") == 0 && rss2.find_last_of("\"\'") == (rss2.size() - 1)) - { - nStart2++; - nStop2--; - } - - if (nStop1 - nStart1 != nStop2 - nStart2) return false; - for (size_t n = 0; n < (nStop1 - nStart1); n++) - { - if (rss1[nStart1 + n] != rss2[nStart2 + n]) - return false; - } - return true; -} - -std::string EscapeString(const std::string& rssString, const char cQuoteType /*= '\"'*/) -{ - // Iterate through the string - std::stringstream sstream; - size_t nPos = 0; - uint32_t uiUTFChar = 0; - while (nPos < rssString.size()) - { - uint8_t uiChar = static_cast(rssString[nPos]); - switch (uiChar) - { - case '\a': sstream << "\\a"; break; - case '\b': sstream << "\\b"; break; - case '\f': sstream << "\\f"; break; - case '\n': sstream << "\\n"; break; - case '\r': sstream << "\\r"; break; - case '\t': sstream << "\\t"; break; - case '\v': sstream << "\\v"; break; - case '\\': sstream << "\\\\"; break; - case '\'': if (static_cast(cQuoteType) == uiChar) sstream << "\\"; sstream << "\'"; break; - case '\"': if (static_cast(cQuoteType) == uiChar) sstream << "\\"; sstream << "\""; break; - default: - if (uiChar >= 0x20 && uiChar < 0x7f) + // Get the parent + std::string ssName; + auto ptrParent = m_ptrParent.lock(); + if (ptrParent && ptrParent->Cast()) { - // Standard ASCII - sstream << static_cast(uiChar); + if (bResolveArrays) + ssName = "[" + + std::to_string(ptrParent->Cast()->FindIndex(std::const_pointer_cast(shared_from_this()))) + + "]"; + } + else + ssName = QuoteText(m_ssName, EQuoteRequest::smart_key); + if (!ptrParent) return ssName; + + // Compose the path. Add a dot after a valid parent, except if the parent is an array. + std::string ssPath = ptrParent->GetPath(bResolveArrays); + if (!ssPath.empty() && !ptrParent->Cast()) + ssPath += "."; + ssPath += ssName; + return ssPath; + } + + return {}; + } + + std::list> CNode::GetRawPath(bool bResolveArrays) const + { + bool bRoot = dynamic_cast(this) ? true : false; + if (!m_bDeleted && !bRoot) // Do not include the root table in the path. + { + // Get the parent + std::string ssName, ssRawName; + auto ptrParent = m_ptrParent.lock(); + if (ptrParent && ptrParent->Cast()) + { + if (bResolveArrays) + ssName = + "[" + + std::to_string(ptrParent->Cast()->FindIndex(std::const_pointer_cast(shared_from_this()))) + + "]"; + } + else + { + ssName = m_ssName; + ssRawName = m_ssRawName.empty() ? QuoteText(m_ssName, EQuoteRequest::smart_key) : m_ssRawName; + } + if (!ptrParent) // Up-most leaf + return std::list>{std::make_pair(ssName, ssRawName)}; + + // Compose the path. Add a dot after a valid parent, except if the parent is an array. + std::list> lstKeyPath = ptrParent->GetRawPath(bResolveArrays); + lstKeyPath.push_back(std::make_pair(ssName, ssRawName)); + return lstKeyPath; + } + + return {}; + } + + sdv::any_t CNode::GetValue() const + { + return sdv::any_t(); + } + + uint32_t CNode::GetIndex() const + { + std::shared_ptr ptrParent = m_ptrParent.lock(); + if (!ptrParent) return sdv::toml::npos; + + return ptrParent->FindIndex(std::const_pointer_cast(shared_from_this())); + } + + sdv::IInterfaceAccess* CNode::GetParent() const + { + std::shared_ptr ptrParent = m_ptrParent.lock(); + if (!ptrParent) return nullptr; + return ptrParent.get(); + } + + sdv::u8string CNode::GetTOML() const + { + if (m_bDeleted) return {}; + + return GenerateTOML(); + } + + void CNode::SetComment(const sdv::u8string& ssComment, uint32_t uiFlags) + { + uint32_t uiIndex = uiFlags & static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_index_mask); + + // In case of raw comment code or when the replace flag is enabled, remove the current tokens from the list + if ((uiFlags & static_cast(sdv::toml::INodeInfo::ECommentFlags::raw_comment)) + || (uiFlags & static_cast(sdv::toml::INodeInfo::ECommentFlags::replace_whitespace))) + { + CodeSnippet(uiIndex).List().clear(); + CodeSnippet(uiIndex).Str().clear(); + } + + // Generate code from the string when not raw mode + std::string ssCommentCode; + TTokenListIterator itInsertLocation = CodeSnippet(uiIndex).List().end(); + if (!(uiFlags & static_cast(sdv::toml::INodeInfo::ECommentFlags::raw_comment))) + { + // Find the first comment and then the last comment. Delete everything in between. + TTokenListIterator it = CodeSnippet(uiIndex).List().begin(); + TTokenListIterator itStart = itInsertLocation; + TTokenListIterator itStop = itInsertLocation; + while (it != CodeSnippet(uiIndex).List().end()) + { + if (it->Category() == ETokenCategory::token_comment) + { + if (itStart == itInsertLocation) + itStart = it; + itStop = it; + } + } + + // Move the stop to after the last comment + if (itStop != CodeSnippet(uiIndex).List().end()) + itStop++; + + // Include the newline following the last comment + if (itStop != CodeSnippet(uiIndex).List().end() && itStop->Category() == ETokenCategory::token_syntax_new_line) + itStop++; + + // Determine insert location in text position (when on newline, no insert location, when following the code, determine the insert location based on the other topics. There is a max, then reduce). + // Determine max length in text position + // Build code string + // The whitespace location is dependable on whether this is an assignment and the parent is a normal + + + + + + // Remove the tokens if there are any. + if (itStart != CodeSnippet(uiIndex).List().end()) + CodeSnippet(uiIndex).List().erase(itStart, itStop); + + // The insert location is the stop iterator + itInsertLocation = itStop; + + + } + else + ssCommentCode = ssComment; + + + // Use the lexer to generate the tokens + CLexer lexerComments; + lexerComments.Feed(ssCommentCode); + lexerComments.NavigationMode(CLexer::ENavigationMode::do_not_skip_anything); + std::reference_wrapper refToken = lexerComments.Consume(); + while (refToken.get().Category() != ETokenCategory::token_none) + { + // Add whitespace and comments only... + switch (refToken.get().Category()) + { + case toml_parser::ETokenCategory::token_comment: + case toml_parser::ETokenCategory::token_whitespace: + case toml_parser::ETokenCategory::token_syntax_new_line: + CodeSnippet(uiIndex).List().insert(itInsertLocation, refToken.get()); + break; + default: break; } - else if (uiChar <= 0x80) // One byte UTF-8 - uiUTFChar = static_cast(uiChar); - else if (uiChar <= 0xDF) // Two bytes UTF-8 + } + + // Add a newline if the last token was a comment + if (!CodeSnippet(uiIndex).List().empty() && + CodeSnippet(uiIndex).List().back().Category() == ETokenCategory::token_comment) + CodeSnippet(uiIndex).List().push_back(CToken(ETokenCategory::token_syntax_new_line)); + } + + sdv::u8string CNode::GetComment(uint32_t uiFlags) + { + uint32_t uiIndex = uiFlags & static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_index_mask); + + // Iterate through the code tokens. + std::stringstream sstream; + for (const CToken& rToken : CodeSnippet(uiIndex).List()) + { + // Differentiate between interpreted comment or raw code comment + if (uiIndex & static_cast(sdv::toml::INodeInfo::ECommentFlags::raw_comment)) { - uiUTFChar = static_cast(uiChar & 0b00011111) << 6; - - // Expecting the next character to be between 0x80 and 0xBF - nPos++; - if (nPos >= rssString.size()) break; - uiUTFChar |= static_cast(rssString[nPos] & 0b00111111); + switch (rToken.Category()) + { + case toml_parser::ETokenCategory::token_whitespace: + case toml_parser::ETokenCategory::token_comment: + case toml_parser::ETokenCategory::token_syntax_new_line: + sstream << rToken.RawString(); + break; + default: + break; + } } - else if (uiChar <= 0xEF) // Three bytes UTF-8 - { - uiUTFChar = static_cast(uiChar & 0b00001111) << 6; - - // Expecting the next character to be between 0x80 and 0xBF - nPos++; - if (nPos >= rssString.size()) break; - uiUTFChar |= static_cast(rssString[nPos] & 0b00111111); - uiUTFChar <<= 6; - - // Expecting the next character to be between 0x80 and 0xBF - nPos++; - if (nPos >= rssString.size()) break; - uiUTFChar |= static_cast(rssString[nPos] & 0b00111111); - } - else if (uiChar <= 0xF7) // Four bytes UTF-8 - { - uiUTFChar = static_cast(uiChar & 0b00000111) << 6; - - // Expecting the next character to be between 0x80 and 0xBF - nPos++; - if (nPos >= rssString.size()) break; - uiUTFChar |= static_cast(rssString[nPos] & 0b00111111); - uiUTFChar <<= 6; - - // Expecting the next character to be between 0x80 and 0xBF - nPos++; - if (nPos >= rssString.size()) break; - uiUTFChar |= static_cast(rssString[nPos] & 0b00111111); - uiUTFChar <<= 6; - - // Expecting the next character to be between 0x80 and 0xBF - nPos++; - if (nPos >= rssString.size()) break; - uiUTFChar |= static_cast(rssString[nPos] & 0b00111111); - } - - // Stream the UTF character - if (uiUTFChar <= 0xFFFF) - sstream << "\\u" << std::uppercase << std::hex << std::setfill('0') << std::setw(4) << uiUTFChar; else - sstream << "\\U" << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << uiUTFChar; + { + if (rToken.Category() == toml_parser::ETokenCategory::token_comment && !rToken.RawString().empty() + && rToken.RawString()[0] == '#') + { + // The comment doesn't have a newline inside the comment text. This allows us to glue multiple comments to one + // large string. + // A new line will be inserted on the following conditions: + // - the line starts as comment, but only has whitespace. + // - there are at least some characters in the stream already. + // - the comment starts with a tab or multiple spaces + if (rToken.RawString().size() <= 1 || rToken.RawString().find_first_not_of(" \t", 1) == std::string::npos) + { + // Line has only whitespace + sstream << std::endl; + continue; + } + if (rToken.RawString()[1] == '\t') + { + // Line starts with a tab, are there any characters in the stream already. + if (!sstream.str().empty()) + sstream << std::endl; + sstream << rToken.RawString().substr(1); + continue; + } + size_t nStartComment = rToken.RawString()[1] == ' ' ? 2 : 1; + if (!sstream.str().empty()) + { + // The stream is not empty. Are there extra spaces (indicating a list or so) + if (rToken.RawString().substr(1, 2) == " ") + sstream << std::endl; + else // No newline, but space? + if (sstream.str().back() != ' ') + sstream << " "; + } + + // Add the content to the stream + sstream << rToken.RawString().substr(nStartComment); + } + + } + } + + // Return the comment string + return sstream.str(); + } + + void CNode::AutomaticFormat() + { + // TODO EVE + // Read the comment of each code snippet and remove the tokens. This will automatically format the code when recomposing. + for (auto& rmapSnippets : m_vecCodeSnippets) + { + for (auto& rvtSnippet : rmapSnippets) + { + std::cout << rvtSnippet.first; + } + } + } + + void CNode::UpdateNodeCode(const CNodeTokenRange& rNodeRange) + { + // Extract the node pre and post comments and whitespace (belonging and not belonging to the node). + CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_before)).List() = + rNodeRange.LinesBeforeNode().TokenListSLice(); + CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_behind)).List() = + rNodeRange.LinesBehindNode().TokenListSLice(); + CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_before)).List() = + rNodeRange.NodeCommentsBefore().TokenListSLice(); + CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_behind)).List() = + rNodeRange.NodeCommentsBehind().TokenListSLice(); + } + + bool CNode::DeleteNode() + { + bool bRet = false; + + // Remove the node from the parent. + std::shared_ptr ptrParent = m_ptrParent.lock(); + if (ptrParent) + { + // Remove the node from the parent --> this will put the node in the recycle bin. + bRet = ptrParent->RemoveNode(std::const_pointer_cast(shared_from_this())); + } + + // Remove the parent. + m_ptrParent.reset(); + + // Set the node to be deleted. + m_bDeleted = true; + + return bRet; + } + + bool CNode::IsDeleted() const + { + return m_bDeleted; + } + + bool CNode::ChangeName(const sdv::u8string& ssNewName) + { + // Deleted? + if (m_bDeleted) return false; + + // Has parent? + std::shared_ptr ptrParent = m_ptrParent.lock(); + if (!ptrParent) return false; + + // Node with identical name exists? + if (ptrParent->Direct(ssNewName)) return false; + + m_ssName = ssNewName; + + return true; + } + + bool CNode::ChangeValue(sdv::any_t /*anyNewValue*/) + { + // Default implementation doesn't do anything. + return false; + } + + bool CNode::MoveUp() + { + // TODO... + return false; + } + + bool CNode::MoveDown() + { + // TODO... + return false; + } + + std::shared_ptr CNode::GetParentPtr() const + { + return m_ptrParent.lock(); + } + + void CNode::SetParentPtr(const std::shared_ptr& rptrParent) + { + m_ptrParent = rptrParent; + } + + std::string CNode::GetParentPath() const + { + std::shared_ptr ptrParent = m_ptrParent.lock(); + if (!ptrParent) return {}; + return ptrParent->GetPath(false); + } + + void CNode::SetViewPtr(const std::shared_ptr& rptrView) + { + m_ptrView = rptrView; + } + + bool CNode::IsPartOfView(const CGenContext& rContext, const std::shared_ptr& rptrNode) const + { + std::shared_ptr ptrStoredView = m_ptrView.lock(); + if (ptrStoredView && !rContext.PartOfExcludedParents(ptrStoredView)) + return ptrStoredView == rptrNode; + return true; + } + + std::string CNode::GetCustomPath(const std::string& rssPrefixKey, const std::string& rssContext) const + { + // Example of path transformation with a table: + // + // [] (suppressed) "" "" + // [fruit] (suppressed) "" "" + // [fruit.apple] "" "" + // color = "red" "" "fruit.apple" + // [fruit.apple.taste] "" "" + // sweet = true "" "fruit.apple.taste" + // + // transfer with all + // [transfer] (suppressed) "transfer" "" + // [transfer.fruit] (suppressed) "transfer" "transfer" + // [transfer.fruit.apple] "transfer" "transfer" + // color = "red" "transfer" "transfer.fruit.apple" + // [transfer.fruit.apple.taste] "transfer" "transfer" + // sweet = true "transfer" "transfer.fruit.apple.taste" + // + // transfer with apple + // [transfer] (suppressed) "transfer" "fruit" + // [transfer.apple] "transfer" "transfer.fruit" + // color = "red" "transfer" "transfer.fruit.apple" + // [transfer.apple.taste] "transfer" "transfer.fruit" + // sweet = true "transfer" "transfer.fruit.apple.taste" + + // Insert the prefix before the key path. + auto lstKeyPath = GetRawPath(false); + auto prPrefixKey = SplitNodeKey(rssPrefixKey); + auto itPos = lstKeyPath.begin(); + while (!prPrefixKey.first.empty()) + { + lstKeyPath.insert(itPos, std::make_pair(prPrefixKey.first, prPrefixKey.first)); + prPrefixKey = SplitNodeKey(prPrefixKey.second); + } + + // Build relative node path by removing all the parent nodes that are equal to the parent nodes of the context. + std::string ssContextPath = rssContext; + while (!ssContextPath.empty() && !lstKeyPath.empty()) + { + // In case of a table array, the table doesn't have a name. Skip the table in the key composition. + if (lstKeyPath.begin()->second.empty()) + { + lstKeyPath.pop_front(); + continue; + } + + auto prContextPath = SplitNodeKey(ssContextPath); + if (lstKeyPath.begin()->first != prContextPath.first) + break; + lstKeyPath.pop_front(); + ssContextPath = prContextPath.second; + } + + // Build a custom key path by including all raw keys including their code snippet. Skip the initial pre-key code snippet, + // though. + std::string ssCustomKeyPath; + for (const auto& prKey : lstKeyPath) + { + // In case of a table array, the table doesn't have a name. Skip the table in the key composition. + if (prKey.second.empty()) + continue; + + // Add a dot if this is not the first key. + if (!ssCustomKeyPath.empty()) + ssCustomKeyPath += "."; + + // Add the pre-key cpde snippet. + ssCustomKeyPath += CodeSnippet(m_nPreKeyCode, prKey.first).Compose(CCodeSnippet::EComposeMode::compose_inline); + + // Add the raw key string + ssCustomKeyPath += prKey.second; + + // Add the pos-key cpde snippet. + ssCustomKeyPath += CodeSnippet(m_nPostKeyCode, prKey.first).Compose(CCodeSnippet::EComposeMode::compose_inline); + } + return ssCustomKeyPath; + } + + const CNode::CCodeSnippet& CNode::CodeSnippet(size_t nIndex, const std::string& rssKey /*= std::string()*/) const + { + static CCodeSnippet sEmptyCodeSnippet; + if (nIndex >= m_vecCodeSnippets.size()) return sEmptyCodeSnippet; + auto itKey = m_vecCodeSnippets[nIndex].find(rssKey); + if (itKey == m_vecCodeSnippets[nIndex].end()) return sEmptyCodeSnippet; + return itKey->second; + } + + CNode::CCodeSnippet& CNode::CodeSnippet(size_t nIndex, const std::string& rssKey /*= std::string()*/) + { + if (nIndex >= m_vecCodeSnippets.size()) m_vecCodeSnippets.resize(nIndex + 1); + return m_vecCodeSnippets[nIndex][rssKey]; + } + + bool CNode::ExplicitlyDefined() const + { + // Default implementation is explicitly. + return true; + } + + void CNode::MakeExplicit() + { + // Default implementaiton is explicitly. Therfore, nothing to do. + } + + std::list& CNode::CCodeSnippet::List() + { + return m_lstTokens; + } + + std::string& CNode::CCodeSnippet::Str() + { + return m_ssComment; + } + + std::string CNode::CCodeSnippet::Compose(EComposeMode eMode, size_t nAssignmentOffset /*= 0*/, size_t /*nCommentOffset = 0*/) const + { + // Build the stream until the first comment. + std::stringstream sstream; + TTokenListIterator it = m_lstTokens.begin(); + while (it != m_lstTokens.end() && it->Category() != ETokenCategory::token_comment) + { + if ((eMode != EComposeMode::compose_behind) || (it->Category() != ETokenCategory::token_syntax_comma)) + sstream << it->RawString(); + ++it; + } + + // Determine the last comment. + TTokenListIterator itFirstComment = it; + TTokenListIterator itLastComment = it; + while (it != m_lstTokens.end()) + { + if (it->Category() == ETokenCategory::token_comment) + itLastComment = it; + ++it; + } + + // Determine the code behind the last comment (and the obligatory newline). + TTokenListIterator itPostComment = itLastComment; + if (itPostComment != m_lstTokens.end()) + ++itPostComment; + if (itPostComment != m_lstTokens.end() && itPostComment->Category() == ETokenCategory::token_syntax_new_line) + ++itPostComment; + + // Stream the comment + if (m_ssComment.empty()) + { + for (it = itFirstComment; it != itPostComment; ++it) + { + if ((eMode != EComposeMode::compose_behind) || + (it->Category() != ETokenCategory::token_syntax_comma)) + sstream << it->RawString(); + } + } + else + { + switch (eMode) + { + case EComposeMode::compose_inline: + case EComposeMode::compose_behind: + if (itFirstComment != m_lstTokens.end()) + sstream << " "; + break; + default: + break; + } + // TODO: Align the comment.... + sstream << "# " << m_ssComment << std::endl; + + // TODO: Add spaces for next assignment + switch (eMode) + { + case EComposeMode::compose_inline: + if (itPostComment != m_lstTokens.end() && nAssignmentOffset) + sstream << std::string(nAssignmentOffset, ' '); + break; + default: + break; + } + } + + // Stream the rest of the tokens + for (it = itPostComment; it != m_lstTokens.end(); ++it) + { + if ((eMode != EComposeMode::compose_behind) || (it->Category() != ETokenCategory::token_syntax_comma)) + sstream << it->RawString(); + } + + // In some case a default new-line is needed + if (m_lstTokens.empty() && m_ssComment.empty()) + { + switch (eMode) + { + case EComposeMode::compose_behind: + sstream << std::endl; + break; + default: + break; + } + } + + return sstream.str(); + } + + CValueNode::CValueNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, + const std::string& rssRawValue) : + CNode(rparser, rssName, rssRawName), m_ssRawValue(rssRawValue) + {} + + bool CValueNode::Inline() const + { + // Default implementation is always inline. + return true; + } + + bool CValueNode::Inline(bool bInline) + { + return bInline; // When set to inline, okay; otherwise not. + } + + std::shared_ptr CValueNode::Direct(const std::string& /*rssPath*/) const + { + // The CNodeValue implementation doesn't have any children. Therefore there is nothing to get. + return std::shared_ptr(); + } + + std::string CValueNode::GenerateTOML(const CGenContext& rContext /*= CGenContext()*/) const + { + if (IsDeleted()) return {}; + + std::stringstream sstream; + + // Determine whether the statement should be embedded (same line separated by commas) and should have an assignment (not + // having a key name). + auto ptrParent = GetParentPtr(); + if (!ptrParent) return {}; + bool bEmbedded = ptrParent->Inline() && ptrParent->ExplicitlyDefined(); + bool bAssignment = !bEmbedded || !ptrParent->Cast(); // No assignment only with normal array + bool bLastNode = ptrParent->CheckLast(std::const_pointer_cast(shared_from_this())); + std::string ssContext = rContext.KeyContext().empty() ? + (rContext.TopMostNode() ? GetParentPath() : std::string()) : rContext.KeyContext(); + + // Add unconnected pre node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_before)). + Compose(CCodeSnippet::EComposeMode::compose_standalone); + + // Add pre node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_before)). + Compose(CCodeSnippet::EComposeMode::compose_before); + + // Add assignment + if (bAssignment) // Not an array entry + sstream << GetCustomPath(rContext.PrefixKey(), ssContext) << "="; + + // Stream the value + sstream << CodeSnippet(m_nPreValueCode).Compose(CCodeSnippet::EComposeMode::compose_inline) << RawValueText() << + CodeSnippet(m_nPostValueCode).Compose(CCodeSnippet::EComposeMode::compose_inline); + + // Add a comma if this is not the last node. + if (bEmbedded && !bLastNode) sstream << ","; + + // Add post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_behind)). + Compose(bLastNode ? CCodeSnippet::EComposeMode::compose_inline : CCodeSnippet::EComposeMode::compose_behind); + + // Add unconnected post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_behind)) + .Compose(CCodeSnippet::EComposeMode::compose_standalone); + + return sstream.str(); + } + + void CValueNode::UpdateNodeCode(const CNodeTokenRange& rNodeRange) + { + // Update the comments before and behind the code first (this is done in the CNode implementation of the UpdateNodeCode + // function). + CNode::UpdateNodeCode(rNodeRange); + + // Process the nodes + std::list lstWhitespace; + enum class EState + { + key_or_value, // Initial value, needing a key or a value + assignment_or_separator, // Assignment or key separator or whitespace before + key, // Key or whitespace before + value, // Value or whitespace before + post_value // Nothing more + } eState = EState::key_or_value; + std::string ssKeyName; + for (std::reference_wrapper refToken = rNodeRange.NodeMain().Begin(); + refToken.get() != rNodeRange.NodeMain().End(); refToken = refToken.get().Next()) + { + switch (refToken.get().Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + case ETokenCategory::token_comment: + if (eState != EState::assignment_or_separator && eState != EState::key && eState != EState::value && + eState != EState::post_value) + return; // Unexpected + lstWhitespace.push_back(refToken.get()); + break; + case ETokenCategory::token_key: + if (eState != EState::key_or_value && eState != EState::key) + return; // Unexpected + eState = EState::assignment_or_separator; + ssKeyName = refToken.get().StringValue(); + CodeSnippet(m_nPreKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_boolean: + case ETokenCategory::token_integer: + case ETokenCategory::token_float: + case ETokenCategory::token_string: + case ETokenCategory::token_date_local: + case ETokenCategory::token_time_local: + case ETokenCategory::token_date_time_local: + case ETokenCategory::token_date_time_offset: + if (eState != EState::key_or_value && eState != EState::value) + return; // Unexpected + eState = EState::post_value; + CodeSnippet(m_nPreValueCode).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_assignment: + if (eState != EState::assignment_or_separator) + return; // Unexpected + eState = EState::value; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_dot: + if (eState != EState::assignment_or_separator) + return; // Unexpected + eState = EState::key; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + default: + return; // Unexpected + } + } + if (eState != EState::post_value) + return; // Unexpected + + CodeSnippet(m_nPostValueCode).List() = std::move(lstWhitespace); + } + + std::string CValueNode::RawValueText() const + { + return m_ssRawValue.empty() ? ValueText() : m_ssRawValue; + } + + + CBooleanNode::CBooleanNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, bool bVal, + const std::string& rssRawValue) : + CValueNode(rparser, rssName, rssRawName, rssRawValue), m_bVal(bVal) + {} + + sdv::toml::ENodeType CBooleanNode::GetType() const + { + return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_boolean; + } + + sdv::any_t CBooleanNode::GetValue() const + { + return sdv::any_t(m_bVal); + } + + bool CBooleanNode::ChangeValue(sdv::any_t anyNewValue) + { + m_bVal = anyNewValue.get(); + return true; + } + + std::string CBooleanNode::ValueText() const + { + return m_bVal ? "true" : "false"; + } + + CIntegerNode::CIntegerNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, int64_t iVal, + const std::string& rssRawValue) : + CValueNode(rparser, rssName, rssRawName, rssRawValue), m_iVal(iVal) + {} + + sdv::toml::ENodeType CIntegerNode::GetType() const + { + return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_integer; + } + + sdv::any_t CIntegerNode::GetValue() const + { + return sdv::any_t(m_iVal); + } + + bool CIntegerNode::ChangeValue(sdv::any_t anyNewValue) + { + m_iVal = anyNewValue.get(); + return true; + } + + std::string CIntegerNode::ValueText() const + { + return std::to_string(m_iVal); + } + + CFloatingPointNode::CFloatingPointNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, double dVal, + const std::string& rssRawValue) : + CValueNode(rparser, rssName, rssRawName, rssRawValue), m_dVal(dVal) + {} + + sdv::toml::ENodeType CFloatingPointNode::GetType() const + { + return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_floating_point; + } + + sdv::any_t CFloatingPointNode::GetValue() const + { + return sdv::any_t(m_dVal); + } + + bool CFloatingPointNode::ChangeValue(sdv::any_t anyNewValue) + { + m_dVal = anyNewValue.get(); + return true; + } + + std::string CFloatingPointNode::ValueText() const + { + std::stringstream sstream; + sstream << std::setprecision(15) << std::defaultfloat << m_dVal; + return sstream.str(); + } + + CStringNode::CStringNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, const std::string& rssVal, + EQuotationType eQuotationType, const std::string& rssRawValue) : + CValueNode(rparser, rssName, rssRawName, rssRawValue), m_ssVal(rssVal), m_eQuotationType(eQuotationType) + {} + + sdv::toml::ENodeType CStringNode::GetType() const + { + return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_string; + } + + sdv::any_t CStringNode::GetValue() const + { + return sdv::any_t(m_ssVal); + } + + bool CStringNode::ChangeValue(sdv::any_t anyNewValue) + { + m_ssVal = anyNewValue.get(); + return true; + } + + std::string CStringNode::ValueText() const + { + switch (m_eQuotationType) + { + case EQuotationType::literal_string: + return QuoteText(m_ssVal, EQuoteRequest::literal_text); + break; + case EQuotationType::multi_line_quoted: + return QuoteText(m_ssVal, EQuoteRequest::multi_line_quoted_text); + break; + case EQuotationType::multi_line_literal: + return QuoteText(m_ssVal, EQuoteRequest::multi_line_literal_text); + break; + case EQuotationType::quoted_string: + default: + return QuoteText(m_ssVal, EQuoteRequest::quoted_text); break; } - nPos++; - } - return sstream.str(); -} - - -CNode::CNode(const std::string& rssName) : m_ssName(rssName) -{} - -CNode::~CNode() -{} - -sdv::u8string CNode::GetName() const -{ - return m_ssName; -} - -sdv::any_t CNode::GetValue() const -{ - return sdv::any_t(); -} - -sdv::u8string CNode::GetTOML() const -{ - std::string ssLastTable; - std::string ssParent; - return CreateTOMLText(ssParent, ssLastTable, true, false, true, true); -} - -std::shared_ptr CNode::GetArray() const -{ - if (!dynamic_cast(this)) return {}; - return std::static_pointer_cast(shared_from_this()); -} - -std::shared_ptr CNode::GetArray() -{ - if (!dynamic_cast(this)) return {}; - return std::static_pointer_cast(shared_from_this()); -} - -std::shared_ptr CNode::GetTable() const -{ - if (!dynamic_cast(this)) return {}; - return std::static_pointer_cast(shared_from_this()); -} - -std::shared_ptr CNode::GetTable() -{ - if (!dynamic_cast(this)) return {}; - return std::static_pointer_cast(shared_from_this()); -} - -std::weak_ptr CNode::GetParent() const -{ - return m_ptrParent; -} - -void CNode::SetParent(const std::shared_ptr& rptrParent) -{ - m_ptrParent = rptrParent; -} - -std::shared_ptr CNode::Find(const std::string& /*rssPath*/) const -{ - return std::shared_ptr(); -} - -std::shared_ptr CNode::GetDirect(const std::string& /*rssPath*/) const -{ - // The CNode implementation doesn't have any children. Therefore there is nothing to get. - return std::shared_ptr(); -} - -std::string CNode::CreateTOMLText(const std::string& rssParent /*= std::string()*/) const -{ - std::string ssLastTable; - return CreateTOMLText(rssParent, ssLastTable); -} - -void CNode::Add(const std::string& rssPath, const std::shared_ptr& /*rptrNode*/, bool /*bDefinedExplicitly = true*/) -{ - throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str()); -} - -CBooleanNode::CBooleanNode(const std::string& rssName, bool bVal) : CNode(rssName), m_bVal(bVal) -{} - -sdv::toml::ENodeType CBooleanNode::GetType() const -{ - return sdv::toml::ENodeType::node_boolean; -} - -sdv::any_t CBooleanNode::GetValue() const -{ - return sdv::any_t(m_bVal); -} - -std::string CBooleanNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool /*bRoot*/) const -{ - std::stringstream sstreamEntry; - - // Do we need to start a table? - if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable) - { - sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl; - rssLastPrintedTable = rssParent; } - if (bEmbedded && !bFirst) // 2nd or higher array entry - sstreamEntry << ", "; - if (!bEmbedded || bAssignment) // Not an array entry - sstreamEntry << GetName() << " = "; - sstreamEntry << (m_bVal ? "true" : "false"); - if (!bEmbedded) // Not an array entry - sstreamEntry << std::endl; + CNodeCollection::CNodeCollection(CParser& rparser, const std::string& rssName, const std::string& rssRawName) : + CNode(rparser, rssName, rssRawName) + {} - return sstreamEntry.str(); -} - -CIntegerNode::CIntegerNode(const std::string& rssName, int64_t iVal) : CNode(rssName), m_iVal(iVal) -{} - -sdv::toml::ENodeType CIntegerNode::GetType() const -{ - return sdv::toml::ENodeType::node_integer; -} - -sdv::any_t CIntegerNode::GetValue() const -{ - return sdv::any_t(m_iVal); -} - -std::string CIntegerNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool /*bRoot*/) const -{ - std::stringstream sstreamEntry; - - // Do we need to start a table? - if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable) + uint32_t CNodeCollection::GetCount() const { - sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl; - rssLastPrintedTable = rssParent; + return static_cast(m_vecNodeOrder.size()); } - if (bEmbedded && !bFirst) // 2nd or higher array entry - sstreamEntry << ", "; - if (!bEmbedded || bAssignment) // Not an array entry - sstreamEntry << GetName() << " = "; - sstreamEntry << m_iVal; - if (!bEmbedded) // Not an array entry - sstreamEntry << std::endl; - - return sstreamEntry.str(); -} - -CFloatingPointNode::CFloatingPointNode(const std::string& rssName, double dVal) : CNode(rssName), m_dVal(dVal) -{} - -sdv::toml::ENodeType CFloatingPointNode::GetType() const -{ - return sdv::toml::ENodeType::node_floating_point; -} - -sdv::any_t CFloatingPointNode::GetValue() const -{ - return sdv::any_t(m_dVal); -} - -std::string CFloatingPointNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, - bool bEmbedded, bool bAssignment, bool /*bRoot*/) const -{ - std::stringstream sstreamEntry; - - // Do we need to start a table? - if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable) + sdv::IInterfaceAccess* CNodeCollection::GetNode(/*in*/ uint32_t uiIndex) const { - sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl; - rssLastPrintedTable = rssParent; + auto ptrNode = Get(uiIndex); + return static_cast(ptrNode.get()); } - if (bEmbedded && !bFirst) // 2nd or higher array entry - sstreamEntry << ", "; - if (!bEmbedded || bAssignment) // Not an array entry - sstreamEntry << GetName() << " = "; - sstreamEntry << std::setprecision(15) << std::defaultfloat << m_dVal; - if (!bEmbedded) // Not an array entry - sstreamEntry << std::endl; - - return sstreamEntry.str(); -} - -CStringNode::CStringNode(const std::string& rssName, const std::string& rssVal) : CNode(rssName), m_ssVal(rssVal) -{} - -sdv::toml::ENodeType CStringNode::GetType() const -{ - return sdv::toml::ENodeType::node_string; -} - -sdv::any_t CStringNode::GetValue() const -{ - return sdv::any_t(m_ssVal); -} - -std::string CStringNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool /*bRoot*/) const -{ - std::stringstream sstreamEntry; - - // Do we need to start a table? - if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable) + std::shared_ptr CNodeCollection::Get(uint32_t uiIndex) const { - sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl; - rssLastPrintedTable = rssParent; + if (static_cast(uiIndex) >= m_vecNodeOrder.size()) + return nullptr; + + return m_vecNodeOrder[uiIndex]; } - if (bEmbedded && !bFirst) // 2nd or higher array entry - sstreamEntry << ", "; - if (!bEmbedded || bAssignment) // Not an array entry - sstreamEntry << GetName() << " = "; - sstreamEntry << "\"" << EscapeString(m_ssVal) << "\""; - if (!bEmbedded) // Not an array entry - sstreamEntry << std::endl; - - return sstreamEntry.str(); -} - -CNodeCollection::CNodeCollection(const std::string& rssName) : CNode(rssName) -{} - -uint32_t CNodeCollection::GetCount() const -{ - return static_cast(m_vecContent.size()); -} - -sdv::IInterfaceAccess* CNodeCollection::GetNode(/*in*/ uint32_t uiIndex) const -{ - auto ptrNode = Get(uiIndex); - return static_cast(ptrNode.get()); -} - -std::shared_ptr CNodeCollection::Get(uint32_t uiIndex) const -{ - if (static_cast(uiIndex) >= m_vecContent.size()) return nullptr; - - return m_vecContent[uiIndex]; -} - -sdv::IInterfaceAccess* CNodeCollection::GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const -{ - auto ptrNode = GetDirect(ssPath); - return static_cast(ptrNode.get()); -} - -bool CNodeCollection::AddElement(const std::shared_ptr& rptrNode, bool bUnique /*= false*/) -{ - if (!rptrNode) return false; - if (bUnique && std::find_if(m_vecContent.begin(), m_vecContent.end(), [&](const std::shared_ptr& rptrNodeEntry) - { - return CompareEqual(rptrNodeEntry->GetName(), rptrNode->GetName()); - }) != m_vecContent.end()) return false; - m_vecContent.push_back(rptrNode); - return true; -} - -CTable::CTable(const std::string& rssName) : CNodeCollection(rssName) -{} - -sdv::toml::ENodeType CTable::GetType() const -{ - return sdv::toml::ENodeType::node_table; -} - -std::shared_ptr CTable::GetDirect(const std::string& rssPath) const -{ - size_t nSeparator = FindFirst(rssPath, ".["); - std::string ssKey = rssPath.substr(0, nSeparator); - std::shared_ptr ptrNode; - for (uint32_t uiIndex = 0; !ptrNode && uiIndex < GetCount(); uiIndex++) - { - std::shared_ptr ptrNodeEntry = Get(uiIndex); - if (!ptrNodeEntry) continue; - if (CompareEqual(ptrNodeEntry->GetName(), ssKey)) ptrNode = ptrNodeEntry; - } - if (!ptrNode) return ptrNode; // Not found - - // Done? - if (nSeparator == std::string::npos) return ptrNode; - - // There is more... - if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot - return ptrNode->GetDirect(rssPath.substr(nSeparator)); -} - -std::string CTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const -{ - // Create the full name - std::string ssFullName = rssParent; - if (!GetName().empty() && !bRoot) - { - if (!ssFullName.empty()) ssFullName += "."; - ssFullName += GetName(); - } - - // Stream the table - std::stringstream sstreamEntry; - if (bEmbedded && !bFirst) // 2nd or higher array entry - sstreamEntry << ", "; - if (bEmbedded) // Embedded table in an array - sstreamEntry << "{"; - for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) - { - std::shared_ptr ptrNode = Get(uiIndex); - if (!ptrNode) continue; - sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable, uiIndex == 0, bEmbedded); - } - if (bEmbedded) // Embedded table in an array - sstreamEntry << "}"; - if (!bEmbedded && !bAssignment && !GetName().empty()) - sstreamEntry << std::endl; - - return sstreamEntry.str(); -} - -void CTable::Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly) -{ - if (!m_bOpenToAddChildren) - throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str()); - - size_t nDotPos = FindFirst(rssPath); - std::string ssFirst = rssPath.substr(0, nDotPos); - std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1); - auto ptrParent = Find(ssFirst); - - // Element does not already exist at given path - if (!ptrParent) - { - // Add the new element as a direct child - if (nDotPos == std::string::npos) - { - rptrNode->SetParent(shared_from_this()); - GetTable()->AddElement(rptrNode); - return; - } - // Add the new element as a descendant further down - auto ptrIntermediateTable = std::make_shared(ssFirst); - ptrIntermediateTable->SetParent(shared_from_this()); - ptrIntermediateTable->m_bDefinedExplicitly = bDefinedExplicitly; - GetTable()->AddElement(ptrIntermediateTable); - static_cast(ptrIntermediateTable.get())->Add(ssSecond, rptrNode, bDefinedExplicitly); - return; - } - if (dynamic_cast(ptrParent.get()) && nDotPos == std::string::npos) - { - ptrParent->Add(ssFirst, rptrNode, bDefinedExplicitly); - return; - } - // Element already exists but would be inserted as a direct child - if (nDotPos == std::string::npos) - { - // Make an already implicitly defined table explicitly defined - if (ptrParent->GetType() == rptrNode->GetType() && dynamic_cast(ptrParent.get()) && - !static_cast(ptrParent.get())->m_bDefinedExplicitly) - { - static_cast(ptrParent.get())->m_bDefinedExplicitly = true; - return; - } - throw XTOMLParseException(("Name '" + ssFirst + "' already exists").c_str()); - } - // Element already exists and new element would be added as descendant - ptrParent->Add(ssSecond, rptrNode, bDefinedExplicitly); -} - -std::shared_ptr CTable::Find(const std::string& rssPath) const -{ - size_t nDotPos = FindFirst(rssPath); - std::string ssFirst = rssPath.substr(0, nDotPos); - std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1); - - for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) + std::shared_ptr CNodeCollection::Direct(const std::string& rssPath) const { + auto prKey = SplitNodeKey(rssPath); std::shared_ptr ptrNode; - ptrNode = Get(uiIndex); - if (!ptrNode) continue; - if (CompareEqual(ptrNode->GetName(), ssFirst)) + for (const auto& rptrNode : m_lstNodes) { - if (nDotPos == std::string::npos) - return ptrNode; - return ptrNode->Find(ssSecond); + if (!rptrNode) continue; + if (rptrNode->GetName() == prKey.first) + { + ptrNode = rptrNode; + break; + } } + if (!ptrNode) return ptrNode; // Not found + + // Done? + if (prKey.second.empty()) return ptrNode; + + // There is more... + return ptrNode->Direct(prKey.second); } - // No node found... - return std::shared_ptr(); -} - -CArray::CArray(const std::string& rssName) : CNodeCollection(rssName) -{} - -sdv::toml::ENodeType CArray::GetType() const -{ - return sdv::toml::ENodeType::node_array; -} - -std::shared_ptr CArray::GetDirect(const std::string& rssPath) const -{ - size_t nIndexBegin = FindFirst(rssPath, "["); - if (nIndexBegin == std::string::npos) return std::shared_ptr(); // Unexpected - - size_t nIndexEnd = rssPath.find_first_not_of("0123456789", nIndexBegin + 1); - if (nIndexEnd == std::string::npos) return std::shared_ptr(); // Unexpected - if (rssPath[nIndexEnd] != ']') return std::shared_ptr(); // Unexpected - std::string ssIndex = rssPath.substr(nIndexBegin + 1, nIndexEnd - nIndexBegin - 1); - if (ssIndex.empty()) return std::shared_ptr(); // Unexpected - uint32_t uiIndex = std::atol(ssIndex.c_str()); - nIndexEnd++; - - // Get the node - if (uiIndex >= GetCount()) return std::shared_ptr(); // Not found - std::shared_ptr ptrNode = Get(uiIndex); - - // Done? - if (nIndexEnd == rssPath.size()) return ptrNode; - - // Expecting a dot? - size_t nSeparator = nIndexEnd; - if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot - return ptrNode->GetDirect(rssPath.substr(nSeparator)); -} - -std::string CArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool /*bRoot*/) const -{ - std::stringstream sstreamEntry; - - // Do we need to start a table? - if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable) + sdv::IInterfaceAccess* CNodeCollection::GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const { - sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl; - rssLastPrintedTable = rssParent; + auto ptrNode = Direct(ssPath); + return static_cast(ptrNode.get()); } - // Stream the array - if (bEmbedded && !bFirst) // 2nd or higher array entry - sstreamEntry << ", "; - if (!bEmbedded || bAssignment) // Not an array entry - sstreamEntry << GetName() << " = "; - sstreamEntry << "["; - for (uint32_t ui = 0; ui < GetCount(); ui++) - sstreamEntry << Get(ui)->CreateTOMLText(rssParent, rssLastPrintedTable, ui == 0, true, false); - sstreamEntry << "]"; - if (!bEmbedded) // Not an array entry - sstreamEntry << std::endl; - - return sstreamEntry.str(); -} - -void CArray::Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly) -{ - size_t nDotPos = FindFirst(rssPath); - std::string ssFirst = rssPath.substr(0, nDotPos); - std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1); - - // Add new element to array - if (nDotPos == std::string::npos) + sdv::IInterfaceAccess* CNodeCollection::InsertValue(uint32_t /*uiIndex*/, const sdv::u8string& /*ssName*/, sdv::any_t /*anyValue*/) { - GetArray()->AddElement(rptrNode); - return; + //std::shared_ptr ptrNode; + //switch (anyValue.eValType) + //{ + //case sdv::any_t::EValType::val_type_bool: + // ptrNode = std::make_shared(Parser(), ssName, anyValue.get()); + // break; + //case sdv::any_t::EValType::val_type_int8: + //case sdv::any_t::EValType::val_type_uint8: + //case sdv::any_t::EValType::val_type_int16: + //case sdv::any_t::EValType::val_type_uint16: + //case sdv::any_t::EValType::val_type_int32: + //case sdv::any_t::EValType::val_type_uint32: + //case sdv::any_t::EValType::val_type_int64: + //case sdv::any_t::EValType::val_type_uint64: + // ptrNode = std::make_shared(Parser(), ssName, anyValue.get()); + // break; + //case sdv::any_t::EValType::val_type_float: + //case sdv::any_t::EValType::val_type_double: + //case sdv::any_t::EValType::val_type_long_double: + //case sdv::any_t::EValType::val_type_fixed: + // ptrNode = std::make_shared(Parser(), ssName, anyValue.get()); + // break; + //case sdv::any_t::EValType::val_type_string: + //case sdv::any_t::EValType::val_type_u8string: + //case sdv::any_t::EValType::val_type_u16string: + //case sdv::any_t::EValType::val_type_u32string: + //case sdv::any_t::EValType::val_type_wstring: + // ptrNode = std::make_shared(Parser(), ssName, anyValue.get()); + // break; + //case sdv::any_t::EValType::val_type_empty: + //case sdv::any_t::EValType::val_type_char: + //case sdv::any_t::EValType::val_type_char16: + //case sdv::any_t::EValType::val_type_char32: + //case sdv::any_t::EValType::val_type_wchar: + //case sdv::any_t::EValType::val_type_interface: + //case sdv::any_t::EValType::val_type_interface_id: + //case sdv::any_t::EValType::val_type_exception_id: + //default: + // return nullptr; // Not supported + //} + + + + return {}; } - // Add new element to subelement of array - if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); })) + + sdv::IInterfaceAccess* CNodeCollection::InsertArray(uint32_t /*uiIndex*/, const sdv::u8string& /*ssName*/) { - throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str()); + return {}; } - uint32_t uiIndex = std::stoi(ssFirst); - if (uiIndex >= GetArray()->GetCount()) + + sdv::IInterfaceAccess* CNodeCollection::InsertTable(uint32_t /*uiIndex*/, const sdv::u8string& /*ssKeyName*/) { - // This indicates an array within an arrays. Add the intermediate array - auto ptrIntermediateArray = std::make_shared(ssFirst); - ptrIntermediateArray->SetParent(shared_from_this()); - ptrIntermediateArray->m_bDefinedExplicitly = bDefinedExplicitly; - GetArray()->AddElement(ptrIntermediateArray); - static_cast(ptrIntermediateArray.get())->Add(ssSecond, rptrNode, bDefinedExplicitly); - //throw XTOMLParseException(("Invalid array access index '" + ssFirst + "'; out of bounds").c_str()); + // TODO: Do not insert a table if the parent is a table array. Only allow the use of InsertTableArray to insert table array + // tables. + return {}; } - GetArray()->Get(uiIndex)->Add(ssSecond, rptrNode, bDefinedExplicitly); -} -std::shared_ptr CArray::Find(const std::string& rssPath) const -{ - size_t nDotPos = FindFirst(rssPath); - std::string ssFirst = rssPath.substr(0, nDotPos); - std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1); - - if (ssFirst.empty()) - throw XTOMLParseException("Missing array subscript"); - - if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); })) - throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str()); - uint32_t uiIndex = std::stoi(ssFirst); - if (GetArray()->GetCount() <= uiIndex) - throw XTOMLParseException( - ("Invalid array access index '" + ssFirst + "'; out of bounds").c_str()); - if (nDotPos == std::string::npos) - return GetArray()->Get(uiIndex); - return GetArray()->Get(uiIndex)->Find(ssSecond); -} - -std::string CTableArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/, bool /*bEmbedded*/, - bool /*bAssignment*/, bool bRoot) const -{ - // Create the full name - std::string ssFullName = rssParent; - if (!ssFullName.empty() && !bRoot) ssFullName += "."; - ssFullName += GetName(); - - // Stream the array - std::stringstream sstreamEntry; - for (uint32_t ui = 0; ui < GetCount(); ui++) + sdv::IInterfaceAccess* CNodeCollection::InsertTableArray(uint32_t /*uiIndex*/, const sdv::u8string& /*ssName*/) { - sstreamEntry << std::endl << "[[" << ssFullName << "]]" << std::endl; - rssLastPrintedTable = ssFullName; - sstreamEntry << Get(ui)->CreateTOMLText(ssFullName, rssLastPrintedTable, ui == 0, false, false); + return {}; } - //sstreamEntry << std::endl; - return sstreamEntry.str(); -} - -void CTableArray::Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly) -{ - //size_t nDotPos = FindFirst(rssPath); - //std::string ssFirst = rssPath.substr(0, nDotPos); - //std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1); - - uint32_t uiSize = GetArray()->GetCount(); - if (uiSize == 0) + sdv::toml::INodeCollectionInsert::EInsertResult CNodeCollection::InsertTOML(const sdv::u8string& /*ssTOML*/, bool /*bRollbackOnPartly*/) { - throw XTOMLParseException("Trying to access table in an empty array of tables"); + return {}; } - GetArray()->Get(uiSize - 1)->Add(rssPath, rptrNode, bDefinedExplicitly); -} -std::shared_ptr CTableArray::Find(const std::string& rssPath) const -{ - //size_t nDotPos = FindFirst(rssPath); - //std::string ssFirst = rssPath.substr(0, nDotPos); - //std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1); - - if (!GetArray()->GetCount()) - throw XTOMLParseException(("Trying to access empty table array; " + rssPath).c_str()); - return GetArray()->Get(GetArray()->GetCount() - 1)->Find(rssPath); -} - -std::string CRootTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/, - bool /*bEmbedded*/, bool /*bAssignment*/, bool /*bRoot*/) const -{ - // Create the full name - std::string ssFullName = rssParent; - - // Stream the table - std::stringstream sstreamEntry; - for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) + bool CNodeCollection::DeleteNode() { + // Delete the children first + auto lstCopy = m_lstNodes; + bool bRet = true; + for (auto& rptrNode : lstCopy) + { + if (rptrNode) + bRet |= rptrNode->DeleteNode(); + } + + // Delete this node + return bRet && CNode::DeleteNode(); + } + + bool CNodeCollection::RemoveNode(const std::shared_ptr& rptrNode) + { + // Remove from the view + RemoveFromView(rptrNode); + + // Find the element + auto itElement = std::find(m_lstNodes.begin(), m_lstNodes.end(), rptrNode); + if (itElement == m_lstNodes.end()) + return false; + + // Shift the node into the recycle bin. + m_lstRecycleBin.push_back(rptrNode); + m_lstNodes.erase(itElement); + return true; + } + + bool CNodeCollection::RemoveFromView(const std::shared_ptr& rptrNode) + { + // Find the element and remove it from the view + auto itElement = std::find(m_vecNodeOrder.begin(), m_vecNodeOrder.end(), rptrNode); + if (itElement != m_vecNodeOrder.end()) + { + m_vecNodeOrder.erase(itElement); + return true; + } + return false; + } + + bool CNodeCollection::CheckLast(const std::shared_ptr& rptrNode) + { + // NOTE: Check for an empty vector. This is actually an error and should not occur. + return m_vecNodeOrder.empty() || m_vecNodeOrder.back() == rptrNode; + } + + uint32_t CNodeCollection::FindIndex(const std::shared_ptr& rptrNode) + { + // Find the element + for (uint32_t uiIndex = 0; uiIndex < m_vecNodeOrder.size(); uiIndex++) + { + if (rptrNode == m_vecNodeOrder[uiIndex]) + return uiIndex; + } + return sdv::toml::npos; + } + + CTable::CTable(CParser& rparser, const std::string& rssName, const std::string& rssRawName, bool bDefaultInline, + bool bExplicit /*= true*/) : + CNodeCollection(rparser, rssName, rssRawName), m_bDefinedExplicitly(bExplicit), m_bInline(bDefaultInline) + {} + + sdv::toml::ENodeType CTable::GetType() const + { + return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_table; + } + + std::string CTable::GenerateTOML(const CGenContext& rContext /*= CGenContext()*/) const + { + if (IsDeleted()) return {}; + + // Create a writable copy of the context and provide this node as potential top node + CGenContext rContextCopy = rContext; + rContextCopy.InitTopMostNode(shared_from_this()); + + std::stringstream sstream; + + // Determine whether the statement should be embedded (same line separated by commas) and should have an assignment (not + // having a key name). The collection node is inline when it is defined as inline or is embedded into a parent node. + auto ptrParent = GetParentPtr(); + bool bEmbedded = ptrParent ? ptrParent->Inline() : false; + bool bAssignment = !bEmbedded || (ptrParent ? !ptrParent->Cast() : false); // No assignment only with normal array + bool bInline = bEmbedded || Inline(); + bool bLastNode = ptrParent ? ptrParent->CheckLast(std::const_pointer_cast(shared_from_this())) : false; + bool bRoot = dynamic_cast(this) ? true : false; + + std::string ssContext; + if (rContextCopy.KeyContext().empty()) + { + ssContext = rContextCopy.PrefixKey(); + std::string ssGenContext = rContextCopy.TopMostNode() ? + static_cast(GetPath(false)) : std::string(); + if (!ssContext.empty() && !ssGenContext.empty()) + ssContext += "."; + ssContext += ssGenContext; + } else + ssContext = rContextCopy.KeyContext(); + std::string ssKeyPath = rContextCopy.PrefixKey(); + std::string ssFullContext = ssContext.empty() ? rContextCopy.PrefixKey() : ssContext; + std::string ssRelKeyPath = + GetCustomPath(rContextCopy.PrefixKey(), ssContext.empty() ? rContextCopy.PrefixKey() : ssContext); + if (!ssKeyPath.empty() && !ssRelKeyPath.empty()) + ssKeyPath += "."; + if (!ssFullContext.empty() && !ssRelKeyPath.empty()) + ssFullContext += "."; + ssKeyPath += ssRelKeyPath; + ssFullContext += ssRelKeyPath; + + // Impossible option: suppressing the table name (due to root flag) and being embedded. + if (bEmbedded && bRoot) return {}; + + // Do not print explicit tables with only tables inside (except if they have no children at all or if they have one or more + // inline tables). + // Do not print table headers when this sis the top most table. + bool bDoNotPrint = (rContext.TopMostNode() || (!Inline() && GetCount())) ? true : false; + for (uint32_t uiIndex = 0; bDoNotPrint && uiIndex < GetCount(); uiIndex++) + { + std::shared_ptr ptrNode = Get(uiIndex); + if (!ptrNode) continue; + if (!ptrNode->Cast() || ptrNode->Inline()) + bDoNotPrint = false; + } + + // Special case, table as part of table array - but only if the parent is included in the generation. This can be identified + // by the top most flag of the context. + bool bTableArray = false; + if (rContextCopy.TopMostNode() == 0) + bTableArray = !bEmbedded && ptrParent && ptrParent->Cast() && ptrParent->Cast()->TableArray(); + + // Add unconnected pre node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_before)) + .Compose(CCodeSnippet::EComposeMode::compose_standalone); + + // Add pre node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_before)) + .Compose(CCodeSnippet::EComposeMode::compose_before); + + // Do we need to start a table? + if (!bDoNotPrint && !bInline && !ssKeyPath.empty()) + { + if (bTableArray) + sstream << "[[" << ssKeyPath << "]]"; + else + sstream << "[" << ssKeyPath << "]"; + + // Add post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_behind)). + Compose(bLastNode ? CCodeSnippet::EComposeMode::compose_inline : CCodeSnippet::EComposeMode::compose_behind); + } + + // Stream the table + if (bAssignment && bInline && !bDoNotPrint) // Not an array entry + sstream << ssRelKeyPath << "="; + if ((bEmbedded || bInline) && (!bDoNotPrint || ptrParent->Cast())) // Embedded table in an array + sstream << CodeSnippet(m_nPreValueCode).Compose(CCodeSnippet::EComposeMode::compose_inline) << "{"; + for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++) + { + std::shared_ptr ptrNode = Get(uiIndex); + if (!ptrNode) continue; + + // If the node has a view pointer, which is not identical to this pointer, do not print the node... it will be printed + // by a different node. + if (!ptrNode->IsPartOfView(rContextCopy, Cast())) continue; + + if (bInline || ptrNode->Inline()) + { + // Inline nodes are only presented with a relative path (in most cases this is no path) for each node. + // If the parent node is a table array, the do-not-print-flag is active (because this node is a table as part of + // the array, which is used for data management and not for printing the node key) - use the full context for + // printing. + sstream << + ptrNode->GenerateTOML(rContextCopy.CopyWithContext((bDoNotPrint && !bTableArray) ? ssContext : ssFullContext)); + } + else + { + // Explicit collection nodes (tables and table-arrays) are presented with the complete path for each node. + sstream << ptrNode->GenerateTOML(rContextCopy.CopyWithContext(ssContext)); + } + } + if ((!bDoNotPrint || (ptrParent && ptrParent->Cast())) && (bEmbedded || bInline)) // Embedded table in an array + { + sstream << CodeSnippet(m_nPostValuesArray).Compose(CCodeSnippet::EComposeMode::compose_inline) << "}"; + + // Add a comma if this is not the last node. + if (bEmbedded && !bLastNode) + sstream << ","; + + // Add post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_behind)). + Compose(bLastNode ? CCodeSnippet::EComposeMode::compose_inline : CCodeSnippet::EComposeMode::compose_behind); + + } + + // Add unconnected post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_behind)) + .Compose(CCodeSnippet::EComposeMode::compose_standalone); + + return sstream.str(); + } + + void CTable::UpdateNodeCode(const CNodeTokenRange& rNodeRange) + { + // Update the comments before and behind the code first (this is done in the CNode implementation of the UpdateNodeCode + // function). + CNode::UpdateNodeCode(rNodeRange); + + // Process the main node tokens until array open + std::list lstWhitespace; + enum class EState + { + key_or_value_or_table_def_open, // Needing a key or a value or a table definition open-character + table_def_close_or_separator, // Needing a separator or a table definition close-character or whitespace before + assignment_or_separator, // Assignment or key separator or whitespace before + key, // Key or whitespace before + table_open, // Table open or whitespace before + table_content_and_close, // Table content and table close + done // Nothing more + } eState = EState::key_or_value_or_table_def_open; + std::string ssKeyName; + bool bExplicit = false; + for (std::reference_wrapper refToken = rNodeRange.NodeMain().Begin(); + refToken.get() != rNodeRange.NodeMain().End(); + refToken = refToken.get().Next()) + { + switch (refToken.get().Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + case ETokenCategory::token_comment: + if (eState != EState::assignment_or_separator && eState != EState::key && eState != EState::table_open && + eState != EState::table_def_close_or_separator && eState != EState::key_or_value_or_table_def_open) + return; // Unexpected + lstWhitespace.push_back(refToken.get()); + break; + case ETokenCategory::token_key: + if (eState != EState::key_or_value_or_table_def_open && eState != EState::key) + return; // Unexpected + eState = bExplicit ? EState::table_def_close_or_separator : EState::assignment_or_separator; + ssKeyName = refToken.get().StringValue(); + CodeSnippet(m_nPreKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_table_open: + if (eState != EState::key_or_value_or_table_def_open) + return; // Unexpected + bExplicit = true; + eState = EState::key; + break; + case ETokenCategory::token_syntax_table_close: + if (eState != EState::table_def_close_or_separator) + return; // Unexpected + eState = EState::done; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_inline_table_open: + if (eState != EState::key_or_value_or_table_def_open && eState != EState::table_open) + return; // Unexpected + eState = EState::table_content_and_close; + CodeSnippet(m_nPreValueCode).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_assignment: + if (eState != EState::assignment_or_separator) + return; // Unexpected + eState = EState::table_open; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_dot: + if (eState != EState::assignment_or_separator && eState != EState::table_def_close_or_separator) + return; // Unexpected + eState = EState::key; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + default: + return; // Unexpected + } + } + + // Done if this is an explicit table + if (bExplicit) return; + + // Expecting table content + if (eState != EState::table_content_and_close) + return; // Unexpected + + // Process the main node tokens from array close + lstWhitespace.clear(); + for (std::reference_wrapper refToken = rNodeRange.NodeMainFinish().Begin(); + refToken.get() != rNodeRange.NodeMainFinish().End(); + refToken = refToken.get().Next()) + { + switch (refToken.get().Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + case ETokenCategory::token_comment: + if (eState != EState::table_content_and_close && eState != EState::done) + return; // Unexpected + lstWhitespace.push_back(refToken.get()); + break; + case ETokenCategory::token_syntax_inline_table_close: + if (eState != EState::table_content_and_close) + return; // Unexpected + eState = EState::done; + break; + default: + return; // Unexpected + } + } + + // Expecting the table to have been closed + if (eState != EState::done) + return; + + CodeSnippet(m_nPostValueCode).List() = std::move(lstWhitespace); + } + + bool CTable::Inline() const + { + auto ptrParent = GetParentPtr(); + if (!ptrParent) return m_bInline; + return m_bInline; + } + + bool CTable::Inline(bool bInline) + { + if (bInline == m_bInline) return true; + auto ptrParent = GetParentPtr(); + if (ptrParent && ptrParent->Inline()) + m_bInline = true; + else + m_bInline = bInline; + return m_bInline == bInline; + } + + bool CTable::ExplicitlyDefined() const + { + return m_bDefinedExplicitly; + } + + void CTable::MakeExplicit() + { + m_bDefinedExplicitly = true; + } + + CArray::CArray(CParser& rparser, const std::string& rssName, const std::string& rssRawName, + bool bExplicitTableArray /*= false*/) : + CNodeCollection(rparser, rssName, rssRawName), m_bDefinedExplicitly(bExplicitTableArray), m_bInline(!bExplicitTableArray) + {} + + sdv::toml::ENodeType CArray::GetType() const + { + return IsDeleted() ? sdv::toml::ENodeType::node_invalid : sdv::toml::ENodeType::node_array; + } + + std::shared_ptr CArray::Direct(const std::string& rssPath) const + { + // The key might be an empty string. + auto prKey = SplitNodeKey(rssPath); + uint32_t uiIndex = GetCount() - 1; + std::string ssSecond = prKey.second; + if (prKey.first.find_first_not_of("0123456789)") != std::string::npos) + { + // If there is no index, take the latest entry in the array. This is needed to be able to insert into table arrays, + // which are automatically indexed by occurance. + if (!GetCount()) return {}; // Unexpected + ssSecond = rssPath; + } else + uiIndex = std::stoi(prKey.first); + + // Get the node + if (uiIndex >= GetCount()) return {}; // Not found std::shared_ptr ptrNode = Get(uiIndex); - if (!ptrNode) continue; - sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable); + if (!ptrNode) return {}; + + // Done? + if (ssSecond.empty()) return ptrNode; + + return ptrNode->Direct(ssSecond); } - sstreamEntry << std::endl; + std::string CArray::GenerateTOML(const CGenContext& rContext /*= CGenContext()*/) const + { + if (IsDeleted()) return {}; - // Skip whitespace at the beginning - std::string ssRet = sstreamEntry.str(); - size_t nStart = ssRet.find_first_not_of(" \t\f\r\n\v"); - if (nStart == std::string::npos) return std::string(); - return ssRet.substr(nStart); -} + // Create a writable copy of the context and provide this node as potential top node + CGenContext rContextCopy = rContext; + rContextCopy.InitTopMostNode(shared_from_this()); + + std::stringstream sstream; + + // Determine whether the statement should be embedded (same line separated by commas) and should have an assignment (not + // having a key name). + auto ptrParent = GetParentPtr(); + if (!ptrParent) return {}; + bool bEmbedded = ptrParent->Inline(); + bool bAssignment = !bEmbedded || !ptrParent->Cast(); // No assignment only with normal array + bool bInline = bEmbedded || Inline(); + bool bLastNode = ptrParent->CheckLast(std::const_pointer_cast(shared_from_this())); + + std::string ssContext; + if (rContextCopy.KeyContext().empty()) + { + ssContext = rContextCopy.PrefixKey(); + std::string ssGenContext = rContextCopy.TopMostNode() ? + (TableArray() ? static_cast(ptrParent->GetPath(false)) : static_cast(GetPath(false))) : + std::string(); + if (!ssContext.empty() && !ssGenContext.empty()) + ssContext += "."; + ssContext += ssGenContext; + } + else + ssContext = rContextCopy.KeyContext(); + std::string ssKeyPath = rContextCopy.PrefixKey(); + std::string ssFullContext = ssContext.empty() ? rContextCopy.PrefixKey() : ssContext; + std::string ssRelKeyPath = + GetCustomPath(rContextCopy.PrefixKey(), ssContext.empty() ? rContextCopy.PrefixKey() : ssContext); + if (!ssKeyPath.empty() && !ssRelKeyPath.empty()) + ssKeyPath += "."; + if (!ssFullContext.empty() && !ssRelKeyPath.empty()) + ssFullContext += "."; + ssKeyPath += ssRelKeyPath; + ssFullContext += ssRelKeyPath; + + // Stream only for inline + if (bInline) + { + // Add unconnected pre node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_before)) + .Compose(CCodeSnippet::EComposeMode::compose_standalone); + + // Add pre node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_before)) + .Compose(CCodeSnippet::EComposeMode::compose_before); + + // Add assignment + if (bAssignment) // Not an array entry + sstream << ssRelKeyPath << "="; + + sstream << CodeSnippet(m_nPreValueCode).Compose(CCodeSnippet::EComposeMode::compose_inline) << "["; + } + + // Stream the array content + for (uint32_t ui = 0; ui < GetCount(); ui++) + { + std::shared_ptr ptrNode = Get(ui); + if (!ptrNode) continue; + + // If the node has a view pointer, which is not identical to this pointer, do not print the node... it will be printed + // by a different node. + if (!ptrNode->IsPartOfView(rContextCopy, Cast())) + continue; + + sstream << ptrNode->GenerateTOML(rContextCopy.CopyWithContext(bInline ? ssFullContext : ssContext)); + } + + // Stream only for inline + if (bInline) + { + sstream << CodeSnippet(m_nPostValuesArray).Compose(CCodeSnippet::EComposeMode::compose_inline) << "]"; + + // Add a comma if this is not the last node. + if (bEmbedded && !bLastNode) sstream << ","; + + // Add post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::comment_behind)). + Compose(bLastNode ? CCodeSnippet::EComposeMode::compose_inline : CCodeSnippet::EComposeMode::compose_behind); + + // Add unconnected post node comments + sstream << CodeSnippet(static_cast(sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_behind)) + .Compose(CCodeSnippet::EComposeMode::compose_standalone); + } + + return sstream.str(); + } + + void CArray::UpdateNodeCode(const CNodeTokenRange& rNodeRange) + { + // Update the comments before and behind the code first (this is done in the CNode implementation of the UpdateNodeCode + // function). + CNode::UpdateNodeCode(rNodeRange); + + // Process the main node tokens until array open + std::list lstWhitespace; + enum class EState + { + key_or_value, // Initial value, needing a key or a value + assignment_or_separator, // Assignment or key separator or whitespace before + key, // Key or whitespace before + array_open, // Array open or whitespace before + array_content_and_close, // Array content and whitespace following the content, not belonging to a value + post_value // Nothing more + } eState = EState::key_or_value; + std::string ssKeyName; + for (std::reference_wrapper refToken = rNodeRange.NodeMain().Begin(); + refToken.get() != rNodeRange.NodeMain().End(); refToken = refToken.get().Next()) + { + switch (refToken.get().Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + case ETokenCategory::token_comment: + if (eState != EState::assignment_or_separator && eState != EState::key && eState != EState::array_open) + return; // Unexpected + lstWhitespace.push_back(refToken.get()); + break; + case ETokenCategory::token_key: + if (eState != EState::key_or_value && eState != EState::key) + return; // Unexpected + eState = EState::assignment_or_separator; + ssKeyName = refToken.get().StringValue(); + CodeSnippet(m_nPreKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_array_open: + if (eState != EState::key_or_value && eState != EState::array_open) + return; // Unexpected + eState = EState::array_content_and_close; + CodeSnippet(m_nPreValueCode).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_assignment: + if (eState != EState::assignment_or_separator) + return; // Unexpected + eState = EState::array_open; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + case ETokenCategory::token_syntax_dot: + if (eState != EState::assignment_or_separator) + return; // Unexpected + eState = EState::key; + CodeSnippet(m_nPostKeyCode, ssKeyName).List() = std::move(lstWhitespace); + break; + default: + return; // Unexpected + } + } + + // Expecting array content + if (eState != EState::array_content_and_close) + return; // Unexpected + + // Process the main node tokens from array close + lstWhitespace.clear(); + for (std::reference_wrapper refToken = rNodeRange.NodeMainFinish().Begin(); + refToken.get() != rNodeRange.NodeMainFinish().End(); refToken = refToken.get().Next()) + { + switch (refToken.get().Category()) + { + case ETokenCategory::token_whitespace: + case ETokenCategory::token_syntax_new_line: + case ETokenCategory::token_comment: + if (eState != EState::array_content_and_close && eState != EState::post_value) + return; // Unexpected + lstWhitespace.push_back(refToken.get()); + break; + case ETokenCategory::token_syntax_comma: // A comma is allowed after the last element and belongs to the finishing. + if (eState != EState::array_content_and_close) + return; + break; + case ETokenCategory::token_syntax_array_close: + if (eState != EState::array_content_and_close) + return; // Unexpected + eState = EState::post_value; + CodeSnippet(m_nPostValuesArray).List() = std::move(lstWhitespace); + break; + default: + return; // Unexpected + } + } + + // Expecting the array to have been closed + if (eState != EState::post_value) + return; + + CodeSnippet(m_nPostValueCode).List() = std::move(lstWhitespace); + } + + bool CArray::TableArray() const + { + // At least one table is needed + if (!GetCount()) return false; + + // Iterate throught he child-nodes and if a non-table is found, return false. + for (uint32_t ui = 0; ui < GetCount(); ui++) + { + std::shared_ptr ptrNode = Get(ui); + if (!ptrNode) continue; + if (ptrNode->GetType() != sdv::toml::ENodeType::node_table) + return false; + } + + // Only tables in the array, return true. + return true; + } + + bool CArray::Inline() const + { + return m_bInline; + } + + bool CArray::Inline(bool bInline) + { + // The array is inline per default. Only in the case of a table array (an array with only tables, and at least one table) + // the array ould be explicit. + if (bInline && !TableArray()) return false; + m_bInline = bInline; + return true; + } + + bool CRootTable::DeleteNode() + { + // Cannot delete the root node. + return false; + } +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_node_toml.h b/sdv_services/core/toml_parser/parser_node_toml.h index 3646f79..06d5b57 100644 --- a/sdv_services/core/toml_parser/parser_node_toml.h +++ b/sdv_services/core/toml_parser/parser_node_toml.h @@ -6,713 +6,1505 @@ #include #include #include +#include +#include #include #include +#include "lexer_toml.h" +#include "miscellaneous.h" -// Forward declaration -class CArray; -class CTable; - -/** - * @brief Find the first separator character. Do not include string content (single/or double quoted) and escape characters. - * @param[in] rss Reference to the string. - * @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string! - * @return The position of the first separator character or std::string::npos when none has found. - */ -size_t FindFirst(const std::string& rss, const std::string& rssSeparator = "."); - -/** - * @brief Find the last separator character. Do not include string content (single/or double quoted) and escape characters. - * @param[in] rss Reference to the string. - * @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string! - * @return The position of the last separator character or std::string::npos when none has found. - */ -size_t FindLast(const std::string& rss, const std::string& rssSeparator = "."); - -/** - * @brief Compare both string ignoring the quotes at the first position and last position. - * @param[in] rss1 Reference to the first string. - * @param[in] rss2 Reference to the second string. - * @return The comparison result. - */ -bool CompareEqual(const std::string& rss1, const std::string& rss2); - -/** - * @brief Escape a string using escape characters and UTF values. - */ -std::string EscapeString(const std::string& rssString, const char cQuoteType = '\"'); - -/** - * @brief Node to build up the parse tree - */ -class CNode : public std::enable_shared_from_this, public sdv::IInterfaceAccess, public sdv::toml::INodeInfo +/// The TOML parser namespace +namespace toml_parser { -protected: + // Forward declaration + class CArray; + class CTable; + class CTableArray; + class CParser; + class CNode; + class CNodeCollection; + /** - * @brief Constructs a new node object representing a table or an array. - * @param[in] rssName Reference to the name of the node. + * @brief TOML generation options. */ - CNode(const std::string& rssName); + enum class EGenerateOptions : uint32_t + { + inline_when_possible = 0x01, ///< Try to generate as much as possible as inline nodes. + explicit_when_possible = 0x02, ///< Try to generate as much as possible as explicit nodes. + no_comments = 0x10, ///< Do not include comments + }; -public: /** - * @brief Deleted since Nodes should only be handled via smart-pointer - * @{ + * @brief Generation context information, allowing the generation to take place from the root node as well as from any node + * collection that contains sub nodes. */ - CNode(const CNode&) = delete; - CNode& operator=(const CNode&) = delete; - CNode(const CNode&&) = delete; - CNode& operator=(const CNode&&) = delete; + class CGenContext + { + public: + /** + * @brief Default constructor. + * @param[in] rssPrefixKey Reference to the prefix key that is used as a parent during the generation. + * @param[in] uiOptions The initial options to set. + */ + CGenContext(const std::string& rssPrefixKey = std::string(), uint32_t uiOptions = 0); + + /** + * @brief Called by the node that is generating the TOML. If not initialized before, initializes with the provided node. + * @param[in] rptrNode Reference to the node that could be used for initialization as top most node. + */ + void InitTopMostNode(const std::shared_ptr& rptrNode); + + /** + * @brief Check whether the provided node is a parent of the top most node. + * @param[in] rptrNode Reference to the node to use for the checking. + * @return Returns true if the node is a parent of the top most node, false otherwise. + */ + bool PartOfExcludedParents(const std::shared_ptr& rptrNode) const; + + /** + * @brief Create a copy of the context class with a new key context. + * @param[in] rssNewKeyContext Reference to the string containing the new key context. + * @return The copy of the contetx class. + */ + CGenContext CopyWithContext(const std::string& rssNewKeyContext) const; + + /** + * @brief Get the stored prefix key that should be used for the TOML code generation. + * @return Reference to the prefix key string. + */ + const std::string& PrefixKey() const; + + /** + * @brief Get the stored key context. + * @return Reference to the key context string. + */ + const std::string& KeyContext() const; + + /** + * @brief Is this the top most node? + * @return Returns when the node is the top most node. + */ + bool TopMostNode() const; + + /** + * @brief Set a generation option. + * @param[in] eOption Option to set. + */ + void SetOption(EGenerateOptions eOption); + + /** + * @brief Check whether a generation option has been set. + * @param[in] eOption Option to set. + * @return Returns whether the option was set. + */ + bool CheckOption(EGenerateOptions eOption) const; + + private: + std::shared_ptr m_ptrTopMostNode; ///< Top most node that is used for the generation. The parent nodes of + ///< the top most node will not be part of the node generation and if + ///< they contain child nodes in their view, the nodes are printed by + ///< their parent and not by their view. + std::string m_ssPrefixKey; ///< Prefix key to be used during the generation of the TOML code. + std::string m_ssKeyContext; ///< string containing the current context. The string must follow the + ///< key rules for separation with bare, literal and quoted keys. + uint32_t m_uiOptions = 0; ///< Zero or more options to take into account when creating the text to + ///< the TOML nodes. + bool m_bTopMost = true; ///< Set when this context is the top most context. + }; + /** - * @} + * @brief Node to build up the parse tree */ + class CNode : + public std::enable_shared_from_this, public sdv::IInterfaceAccess, public sdv::toml::INodeInfo, + public sdv::toml::INodeDelete, public sdv::toml::INodeUpdate + { + protected: + /** + * @brief Constructs a new generic node object. + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + */ + CNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName); + + public: + /** + * @brief Deleted since Nodes should only be handled via smart-pointer + * @{ + */ + CNode(const CNode&) = delete; + CNode& operator=(const CNode&) = delete; + CNode(const CNode&&) = delete; + CNode& operator=(const CNode&&) = delete; + /** + * @} + */ + + /** + * @brief Destroy the node object + */ + ~CNode(); + + // Interface map + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(sdv::toml::INodeInfo) + SDV_INTERFACE_ENTRY(sdv::toml::INodeDelete) + SDV_INTERFACE_ENTRY(sdv::toml::INodeUpdate) + END_SDV_INTERFACE_MAP() + + /** + * @brief Get a reference to the TOML parser that generated this node. + * @return Reference to the TOML parse. + */ + CParser& Parser(); + + /** + * @brief Get the node name (no conversion to a literal or quoted key is made). Overload of sdv::toml::INodeInfo::GetName. + * @return String containing the name of the node. + */ + virtual sdv::u8string GetName() const override; + + /** + * @brief Get the node path following the key rules for bar, literal and quoted keys. Overload of + * sdv::toml::INodeInfo::GetPath. + * @param[in] bResolveArrays When set, include array indices in the path. The path returned without array indices is + * identical to the code in the TOML file. The path returned with array indices is identical to the direct access of + * nodes within the parser. + * @return String containing the path of the node. + */ + virtual sdv::u8string GetPath(bool bResolveArrays) const override; + + /** + * @brief Get the raw node path using the original keys if available. + * @param[in] bResolveArrays When set, include array indices in the path. The path returned without array indices is + * identical to the code in the TOML file. The path returned with array indices is identical to the direct access of + * nodes within the parser. + * @return List containing the keys pairs for each parent until this node. Each key pair contain the name (first) and the + * raw name as it was defined (second). + */ + std::list> GetRawPath(bool bResolveArrays) const; + + /* + * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. + * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function + * returns empty. + */ + virtual sdv::any_t GetValue() const override; + + /** + * @brief Get the index of this node within the parent collection. Overload of sdv::toml::INodeInfo::GetIndex. + * @return The index of the node within the parent collection node or npos when no parent is available. + */ + virtual uint32_t GetIndex() const override; + + /** + * @brief Get the parent collection node. Overload of sdv::toml::INodeInfo::GetParent. + * @return Returns the parent collection node or NULL when there is no parent collection node. + */ + virtual sdv::IInterfaceAccess* GetParent() const override; + + /** + * @brief The node value. Overload of sdv::toml::INodeInfo::GetTOML. + * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function + * returns empty. + */ + virtual sdv::u8string GetTOML() const override; + + /** + * @brief Set or replace a comment for the node. Overload of sdv::toml::INodeInfo::SetComment. + * @remarks This function can also be used to insert whitespace (with or without comments) when used in raw mode. + * Set the comment text for the node. If a comment is proided as text (normal behavior), the comment text will be + * formatted automatically when generating the TOML text. If the comment is provided as raw comment, the text should + * contain all whitespace and the comment '#' character before the comment text. + * Comments inserted before the enode will be inserted on the line before the node uness the comment is provided in raw + * format and is ended with a newline and optionally whitespace. Comment inserted behind the node will be inserted on + * the same line as the node. + * Comments provided as text is automatically wrapped to 80 characters if possible. Newlines in the text will cause a + * new comment line to start. + * @param[in] ssComment String containing the comment text or the raw comment string to set. + * @param[in] uiFlags One or more ECommentFlags flags influencing the behavior of the comment. + */ + virtual void SetComment(const sdv::u8string& ssComment, uint32_t uiFlags) override; + + /** + * Get the current comment for the node. Overload of sdv::toml::INodeInfo::GetComment. + * @remarks To receive the whitespace formatting the node, use this function in raw mode. + * @param[in] uiFlags One or more ECommentFlags flags identifying the string format of the comment to return. + * @return String with the comment text or an empty string if no comment is available. + */ + virtual sdv::u8string GetComment(uint32_t uiFlags) override; + + /** + * @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments + * will not be changed. Overload of sdv::toml::INodeInfo::AutomaticFormat. + */ + virtual void AutomaticFormat() override; + + /** + * @brief Update the node with TOML code information. The default implementation takes the comment and whitespace around the + * node and stores this for node reconstruction. + * @param[in] rNodeRange Reference to the node range information containing the tokens for the code snippets. + */ + virtual void UpdateNodeCode(const CNodeTokenRange& rNodeRange); + + /** + * @brief Delete the current node. Overload of sdv::toml::INodeDelete::DeleteNode. + * @attention A successful deletion will cause all interfaces to the current node to become inoperable. + * @return Returns whether the deletion was successful. + */ + virtual bool DeleteNode() override; + + /** + * @brief Is this node marked as deleted? + * @return Returns whether this node has been deleted. + */ + bool IsDeleted() const; + + /** + * @brief Change the key name of the node (if the node is not a value node of an array). Overload of + * sdv::toml::INodeUpdate::ChangeName. + * @param[in] ssNewName The name to assign to the node. The name must adhere to the key names defined by the TOML + * specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the + * parser decides itself whether the key is bare-key, a literal key or a quoted key. + * @return Returns whether the name change was successful. + */ + virtual bool ChangeName(const sdv::u8string& ssNewName) override; + + /** + * @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue. + * @remarks Only valid for value nodes. Changing the value type is not supported. + * @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a + * string. Conversion is automatically done to int64, double float, bool or u8string. + * @return Returns whether the value change was successful. + */ + virtual bool ChangeValue(sdv::any_t anyNewValue) override; + + /** + * @brief Move up the node in the collection. Overload of sdv::toml::INodeUpdate::MoveUp. + * @remarks External tables or table arrays cannot be moved before value nodes. + * @remarks Moving if the node is the first node is not possible. + * @return Returns whether the move was successful. + */ + virtual bool MoveUp() override; + + /** + * @brief Move down the node in the collection. Overload of sdv::toml::INodeUpdate::MoveDown. + * @remarks Value nodes cannot be moved behind external tables or table arrays. + * @remarks Moving if the node is the last node is not possible. + * @return Returns whether the move was successful. + */ + virtual bool MoveDown() override; + + /** + * @brief Do a dynamic cast to one of the base types of the node. + * @return Casted shared pointer to the base type if the type is valid, or an empty pointer if not. + */ + template + std::shared_ptr Cast(); + + /** + * @brief Do a dynamic cast to one of the base types of the node. + * @return Casted shared pointer to the base type if the type is valid, or an empty pointer if not. + */ + template + std::shared_ptr Cast() const; + + /** + * @brief Gets the parent node pointer + * @return Returns the parent node pointer or an empty pointer when no parent was assigned or the stored weak pointer could + * not be locked. + */ + std::shared_ptr GetParentPtr() const; + + /** + * @brief Set the parent node. + * @param[in] rptrParent Reference to the node to assign to this node as a parent. + */ + void SetParentPtr(const std::shared_ptr& rptrParent); + + /** + * @brief Get the parent path of the node. + * @return Return the parent path if existining and not a root. + */ + std::string GetParentPath() const; + + /** + * @brief Set the view definition node. The view definition node is a parent or grand parent that presents the node when + * generating TOML code. When not set, the parent node is taking over this role. + * @param[in] rptrView Reference to the node to assign to this node as a parent or grand parent. + */ + void SetViewPtr(const std::shared_ptr& rptrView); + + /** + * @brief Checks whether the node is part of the view. + * @details The node is part of the view if the supplied pointer is identical to the view definition pointer, when the view + * definition pointer is not part of a parent of the topmost node. In all other cases, the node is part of the view. + * @param[in] rContext Reference to the context class to use during TOML code generation. + * @param[in] rptrNode Reference to the node to check whether it registered for a view. + * @return Returns whether this node is part of the view with the supplied pointer. + */ + bool IsPartOfView(const CGenContext& rContext, const std::shared_ptr& rptrNode) const; + + /** + * @brief Accesses a node by its key in the parse tree. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @attention For an array, when no indexing is supplied, the latest entry will be returned. + * @param[in] rssPath The path of the node to searched for. + * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + */ + virtual std::shared_ptr Direct(const std::string& rssPath) const = 0; + + /** + * @brief Create the TOML text based on the content using an optional prefix node. + * @param[in] rContext Reference to the context class to use during TOML code generation. + * @return TOML text string. + */ + virtual std::string GenerateTOML(const CGenContext& rContext = CGenContext()) const = 0; + + protected: + /** + * @brief Compose a custom path from the node key path using a key prefix and a context. + * @param[in] rssPrefixKey The prefix to insert at as a base to the key tree. + * @param[in] rssContext The context that is used to define the relative portion of the key. To determine the relative + * portion, the context string contains the same prefix as is supplied in rssPrefixKey. + * @return Returns the custom path composed of the prefix and the relative portion of the original path. + */ + std::string GetCustomPath(const std::string& rssPrefixKey, const std::string& rssContext) const; + + /** + * @brief Comment or code snippet structure. + * @details Each node has multiple code snippets used to reproduce the exact code. For example with an assignment: + * @code + * + * # This is out of scope comment before + * + * # This is comment before + * var_a = "abc" # comment behind + * # more comment behind + * + * # This is out of scope comment behind + * + * @endcode + * + * The code snippets are identified as follows: + * @code + * + * + * = + * + * @endcode + */ + class CCodeSnippet + { + public: + /** + * @brief Access the token list. + * @return Reference to the list with tokens. + */ + std::list& List(); + + /** + * @brief Access the comment text string. + * @return Reference to the comment string. + */ + std::string& Str(); + + /** + * @brief Mode the code snippet composer should run in. + */ + enum class EComposeMode + { + compose_inline, ///< Compose as inline whitespace and comment. If there is no token list and no comment + ///< string, compose as one space. If there is only a comment string, insert a space, add + ///< the comment followed by an obligatory newline, and insert spaces until the next + ///< provided position. If there are tokens with a comment token, replace the comment. If + ///< there are tokens without comment, add the comment, newline and spaces. + compose_before, ///< Compose as comment assigned to and located before the node. If there is no token list + ///< and no comment string, doesn't add anything. If there is only a comment string, adds + ///< the comment followed by the obligatory newline. If there are tokens with a comment + ///< token, replace the comment. If there are tokens without the comment, place the comment + ///< before the last newline or when not available, at the end of the tokens followed by a + ///< new newline. + compose_behind, ///< Compose as comment assigned to and located behind the node. If there is no token list + ///< and no comment string, add a newline. If there is a comment string and no tokens, + ///< add a space, the comment string followed by the obligatory newline. If there is a token + ///< list without comment, add a comment before the newline or at the end with an additional + ///< newline. + compose_standalone, ///< Compose as stand-alone comment. Replace any token list if a comment string is + ///< available. + }; + + /** + * @brief Compose a conde string from the stored tokens and/or string. + * @param[in] eMode The mode the composer should run in. + * @param[in] nAssignmentOffset The offset for a next assignent; only used for inline composition. + * @param[in] nCommentOffset The offset to insert a multi-line comment; only used for inline and behind composition. + * @return The composed code string. + */ + std::string Compose(EComposeMode eMode, size_t nAssignmentOffset = 0, size_t nCommentOffset = 0) const; + + private: + std::list m_lstTokens; ///< Token list for the code snippet in raw format. + std::string m_ssComment; ///< The comment text for the code snippet in text format. + }; + + // White space and comment preservation indices for code generation. + const size_t m_nPreNodeCode = 0; ///< Code snippet before the node. Corresponds to + ///< sdv::toml::INodeInfo::ECommentFlags::comment_before. + const size_t m_nPostNodeCode = 1; ///< Comment behind the node. Corresponds to + ///< sdv::toml::INodeInfo::ECommentFlags::comment_behind. + const size_t m_nOutOfScopeCodeBefore = 2; ///< Out of scope comment before the node. Corresponds to + ///< sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_before. + const size_t m_nOutOfScopeCodeBehind = 3; ///< Out of scope comment behind the node. Corresponds to + ///< sdv::toml::INodeInfo::ECommentFlags::out_of_scope_comment_behind. + const size_t m_nPreKeyCode = 4; ///< Code snippet before the key. Is stored in combination with the + ///< corresponding key. + const size_t m_nPostKeyCode = 5; ///< Code Snippet behind the key. Is stored in combination with the + ///< corresponding key. + const size_t m_nPreValueCode = 6; ///< Code snippet before the value. + const size_t m_nPostValueCode = 7; ///< Code snippet behind the value. + const size_t m_nPostValuesArray = 8; ///< Code snippet at the end of the array values, before the closing character. + + /** + * @brief Get the code snippet. + * @param[in] nIndex The comment type index to get the comment for. + * @param[in] rssKey Reference to the key to be used for code snippet identification. + * @return Reference to the comment structure of the comment. If the provided index is not available in the vector, + * returns an empty code snippet. + */ + const CCodeSnippet& CodeSnippet(size_t nIndex, const std::string& rssKey = std::string()) const; + + /** + * @brief Get the code snippet (write access). This allows moving the snippet from one node to the another node. + * @remarks Since the request to the code snippet could change the location of the vector allocation, access to the code + * snippet is valid until the next code snippet is requested. + * @param[in] nIndex The comment type index to get the comment for. + * @param[in] rssKey Reference to the key to be used for code snippet identification. + * @return Reference to the comment structure of the comment. + */ + CCodeSnippet& CodeSnippet(size_t nIndex, const std::string& rssKey = std::string()); + + private: + std::weak_ptr m_ptrParent; ///< Weak pointer to the parent node (if existing). + std::weak_ptr m_ptrView; ///< Weak pointer to the view node (if existing and explicitly set). + std::string m_ssName; ///< Name of the node. + std::string m_ssRawName; ///< Raw name of the node. + bool m_bDeleted = false; ///< Enabled when the node was marked for deletion. + CParser& m_rParser; ///< Reference to the TOML parser. + std::vector> m_vecCodeSnippets; ///< Vector with comments/code snippets. + + public: + /** + * @brief The derived class from the node collection can be inline or not. + * @return Returns whether the node is an inline node. + */ + virtual bool Inline() const = 0; + + /** + * @brief With some node collections it is possible to switch between inline and normal. + * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is + * always successful). When returning false, the switching might not be supported for this type. + */ + virtual bool Inline(bool bInline) = 0; + + /** + * @brief Checks whether the table was explicitly defined. + * @return Returns the explicit definition flag. + */ + virtual bool ExplicitlyDefined() const; + + /** + * @brief If the table was an implicit definition, make it explicit. + */ + virtual void MakeExplicit(); + }; /** - * @brief Destroy the node object + * @brief Class implementing the value node. + * @details A value always is inline. Two versions exist: the assignment version and the embedded in-an-array-version. + * Not embedded in an array (with assignment): + * @code + * KEY.KEY=VALUE + * ^ ^ ^ ^ ^ ^ + * pre-node post_key_ws pre_key_ws post_key_ws pre_value_ws post-node + * @endcode + * Embedded in an array (without assignment): + * @code + * VALUE + * ^ ^ + * pre-node post-node + * @endcode + * The pre- and post-node whitespace areas are covered by the UpdateNodeCode function of the CNode base class. + * Multiple optional pre- and post-key whitespace areas can be defined. */ - ~CNode(); + class CValueNode : public CNode + { + protected: + /** + * @brief Constructs a new node object. + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] rssRawValue Reference to the raw value string. + */ + CValueNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, const std::string& rssRawValue); - // Interface map - BEGIN_SDV_INTERFACE_MAP() - SDV_INTERFACE_ENTRY(sdv::toml::INodeInfo) - END_SDV_INTERFACE_MAP() + /** + * @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline. + * @return Returns whether the node is an inline node. + */ + virtual bool Inline() const override; + + /** + * @brief With some node collections it is possible to switch between inline and normal. Overload of CNode::Inline. + * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is + * always successful). When returning false, the switching might not be supported for this type. + */ + virtual bool Inline(bool bInline) override; + + /** + * @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @attention For an array, when no indexing is supplied, the latest entry will be returned. + * @param[in] rssPath The path of the node to searched for. + * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + */ + virtual std::shared_ptr Direct(const std::string& rssPath) const override; + + /** + * @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML. + * @param[in] rContext Reference to the context class to use during TOML code generation. + * @return The TOML text string. + */ + virtual std::string GenerateTOML(const CGenContext& rContext = CGenContext()) const override; + + /** + * @brief Get the value in text form. + * @return String with the value in text form. + */ + virtual std::string ValueText() const = 0; + + /** + * @brief Update the node with TOML code information. Overload of CNode::UpdateNodeCode. + * @param[in] rNodeRange Reference to the node range information containing the tokens for the code snippets. + */ + virtual void UpdateNodeCode(const CNodeTokenRange& rNodeRange) override; + + /** + * @brief Get the raw value text. + * @return The raw value text of the original value token. Or if not existing, the value text. + */ + std::string RawValueText() const; + + private: + std::string m_ssRawValue; ///< Raw value string. + }; /** - * @brief Get the node name. Overload of sdv::toml::INodeInfo::GetName. - * @return String containing the name of the node. + * @brief Boolean value node. */ - virtual sdv::u8string GetName() const override; + class CBooleanNode : public CValueNode + { + public: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the string containing the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] bVal The value to assign. + * @param[in] rssRawValue Reference to the raw value string. + */ + CBooleanNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, bool bVal, + const std::string& rssRawValue); + + /** + * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. + * @return Type of the node. + */ + virtual sdv::toml::ENodeType GetType() const override; + + /** + * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. + * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function + * returns empty. + */ + virtual sdv::any_t GetValue() const override; + + /** + * @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue. + * @remarks Only valid for value nodes. Changing the value type is not supported. + * @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a + * string. Conversion is automatically done to int64, double float, bool or u8string. + * @return Returns whether the value change was successful. + */ + virtual bool ChangeValue(sdv::any_t anyNewValue) override; + + /** + * @brief Get the value in text form. Overload of CNodeValue::ValueText. + * @return String with the value in text form. + */ + virtual std::string ValueText() const override; + + private: + bool m_bVal = false; ///< Value in case of boolean node. + }; /** - * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. - * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function - * returns empty. + * @brief Integer value node. */ - virtual sdv::any_t GetValue() const override; + class CIntegerNode : public CValueNode + { + public: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the string containing the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] iVal The value to assign. + * @param[in] rssRawValue Reference to the raw value string. + */ + CIntegerNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, int64_t iVal, + const std::string& rssRawValue); + + /** + * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. + * @return Type of the node. + */ + virtual sdv::toml::ENodeType GetType() const override; + + /** + * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. + * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function + * returns empty. + */ + virtual sdv::any_t GetValue() const override; + + /** + * @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue. + * @remarks Only valid for value nodes. Changing the value type is not supported. + * @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a + * string. Conversion is automatically done to int64, double float, bool or u8string. + * @return Returns whether the value change was successful. + */ + virtual bool ChangeValue(sdv::any_t anyNewValue) override; + + /** + * @brief Get the value in text form. Overload of CNodeValue::ValueText. + * @return String with the value in text form. + */ + virtual std::string ValueText() const override; + + private: + int64_t m_iVal = 0; ///< Value in case of integer node. + }; /** - * @brief The node value. Overload of sdv::toml::INodeInfo::GetTOML. - * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function - * returns empty. + * @brief Floating point value node. */ - virtual sdv::u8string GetTOML() const override; + class CFloatingPointNode : public CValueNode + { + public: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the string containing the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] dVal The value to assign. + * @param[in] rssRawValue Reference to the raw value string. + */ + CFloatingPointNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, double dVal, + const std::string& rssRawValue); + + /** + * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. + * @return Type of the node. + */ + virtual sdv::toml::ENodeType GetType() const override; + + /** + * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. + * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function + * returns empty. + */ + virtual sdv::any_t GetValue() const override; + + /** + * @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue. + * @remarks Only valid for value nodes. Changing the value type is not supported. + * @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a + * string. Conversion is automatically done to int64, double float, bool or u8string. + * @return Returns whether the value change was successful. + */ + virtual bool ChangeValue(sdv::any_t anyNewValue) override; + + /** + * @brief Get the value in text form. Overload of CNodeValue::ValueText. + * @return String with the value in text form. + */ + virtual std::string ValueText() const override; + + private: + double m_dVal = 0.0; ///< Value in case of floating point node. + }; /** - * @brief Gets the array value of a node - * @return Returns a shared pointer of the array value stored in the node if the stored type is array + * @brief String value node. */ - std::shared_ptr GetArray() const; + class CStringNode : public CValueNode + { + public: + /** + * @brief Quotation type used for the string. + */ + enum EQuotationType + { + quoted_string, ///< String was a quoted string (default) + literal_string, ///< String was a literal string. + multi_line_quoted, ///< Multiple line quoted string. + multi_line_literal, ///< Multiple line lteral string. + }; + + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the string containing the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] rssVal The value to assign. + * @param[in] eQuotationType Type of quotation used for the value. + * @param[in] rssRawValue Reference to the raw value string. + */ + CStringNode(CParser& rparser, const std::string& rssName, const std::string& rssRawName, const std::string& rssVal, + EQuotationType eQuotationType, const std::string& rssRawValue); + + /** + * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. + * @return Type of the node. + */ + virtual sdv::toml::ENodeType GetType() const override; + + /** + * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. + * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function + * returns empty. + */ + virtual sdv::any_t GetValue() const override; + + /** + * @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue. + * @remarks Only valid for value nodes. Changing the value type is not supported. + * @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a + * string. Conversion is automatically done to int64, double float, bool or u8string. + * @return Returns whether the value change was successful. + */ + virtual bool ChangeValue(sdv::any_t anyNewValue) override; + + /** + * @brief Get the value in text form. Overload of CNodeValue::ValueText. + * @return String with the value in text form. + */ + virtual std::string ValueText() const override; + + private: + std::string m_ssVal; ///< Value in case of string or illegal (error) node. + EQuotationType m_eQuotationType = EQuotationType::quoted_string; ///< Quotation type of the string. + }; /** - * @brief Gets the array value of a node - * @return Returns a shared pointer of the array value stored in the node if the stored type is array + * @brief Base structure for arrays and tables. */ - std::shared_ptr GetArray(); + class CNodeCollection : public CNode, public sdv::toml::INodeCollection, public sdv::toml::INodeCollectionInsert + { + protected: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + */ + CNodeCollection(CParser& rparser, const std::string& rssName, const std::string& rssRawName); + + public: + // Interface map + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(sdv::toml::INodeCollection) + SDV_INTERFACE_ENTRY(sdv::toml::INodeCollectionInsert) + SDV_INTERFACE_CHAIN_BASE(CNode) + END_SDV_INTERFACE_MAP() + + /** + * @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount. + * @return The amount of nodes. + */ + virtual uint32_t GetCount() const override; + + /** + * @brief Get the node. Overload of sdv::toml::INodeCollection::GetNode. + * @param[in] uiIndex Index of the node to get. + * @return Interface to the node object. + */ + virtual IInterfaceAccess* GetNode(/*in*/ uint32_t uiIndex) const override; + + /** + * @brief Get the node. + * @param[in] uiIndex Index of the node to get. + * @return Smart pointer to the node object. + */ + std::shared_ptr Get(uint32_t uiIndex) const; + + /** + * @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @attention For an array, when no indexing is supplied, the latest entry will be returned. + * @param[in] rssPath The path of the node to searched for. + * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + */ + virtual std::shared_ptr Direct(const std::string& rssPath) const override; + + /** + * @brief Searches a node by its key in the parse tree + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child + * name. E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be + * accessed and traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of + * the array 'array'. These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @param[in] ssPath The path of the node to searched for. + * @return Returns an interface the requested node if available. + */ + virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override; + + /** + * @brief Insert a value into the collection at the location before the supplied index. Overload of + * sdv::toml::INodeCollectionInsert::InsertValue. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Value nodes cannot be inserted behind external + * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index + * is automatically corrected. + * @param[in] ssName Name of the node to insert. Will be ignored for an array collection. The name must adhere to the + * key names defined by the TOML specification. Defining the key multiple times is not allowed. Quotation of key names + * is done automatically; the parser decides itself whether the key is bare-key, a literal key or a quoted key. + * @param[in] anyValue The value of the node, being either an integer, floating point number, boolean value or a string. + * Conversion is automatically done to int64, double float, bool or u8string. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + virtual sdv::IInterfaceAccess* InsertValue(uint32_t uiIndex, const sdv::u8string& ssName, sdv::any_t anyValue) override; + + /** + * @brief Insert an array into the collection at the location before the supplied index. Overload of + * sdv::toml::INodeCollectionInsert::InsertArray. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Array nodes cannot be inserted behind external + * tables and table arrays. If the index is referencing a position behind an external table or a table array, the index + * is automatically corrected. + * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. + * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not + * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a + * literal key or a quoted key. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + virtual sdv::IInterfaceAccess* InsertArray(uint32_t uiIndex, const sdv::u8string& ssName) override; + + /** + * @brief Insert a table into the collection at the location before the supplied index. Overload of + * sdv::toml::INodeCollectionInsert::InsertTable. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Table nodes cannot be inserted before value nodes + * or arrays. If the index is referencing a position before a value node or an array, the index is automatically + * corrected. + * @param[in] ssKeyName Name of the table node to insert. Will be ignored if the current node is an array collection. + * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not + * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a + * literal key or a quoted key. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + virtual sdv::IInterfaceAccess* InsertTable(uint32_t uiIndex, const sdv::u8string& ssKeyName) override; + + /** + * @brief Insert a table array into the collection at the location before the supplied index. Overload of + * sdv::toml::INodeCollectionInsert::InsertTableArray. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value + * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically + * corrected. + * @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection. + * The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not + * allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a + * literal key or a quoted key. + * @return On success the interface to the newly inserted node is returned or NULL otherwise. + */ + virtual sdv::IInterfaceAccess* InsertTableArray(uint32_t uiIndex, const sdv::u8string& ssName) override; + + /** + * @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string + * should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML + * string should contain and inline table nodes without names. Overload of sdv::toml::INodeCollectionInsert::InsertTOML. + * @param[in] ssTOML The TOML string to insert. + * @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted. + * @return The result of the insertion. + */ + virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(const sdv::u8string& ssTOML, + bool bRollbackOnPartly) override; + + /** + * @brief Delete the current node. Overload of sdv::toml::INodeDelete::DeleteNode. + * @attention A successful deletion will cause all interfaces to the current node to become inoperable. + * @return Returns whether the deletion was successful. + */ + virtual bool DeleteNode() override; + +//protected: + /** + * @brief Remove a node from the collection. + * @remarks The node will not be deleted, but placed in the recycle bin. Deletion will take place at collection destruction. + * @param[in] rptrNode Reference to the smart pointer pointing to the node to remove. + * @return Returns whether the removal was successful. + */ + bool RemoveNode(const std::shared_ptr& rptrNode); + + /** + * @brief Remove a node from a view. + * @param[in] rptrNode Reference to the smart pointer pointing to the node to remove. + * @return Returns whether the removal was successful. + */ + bool RemoveFromView(const std::shared_ptr& rptrNode); + + /** + * @brief Check whether this node is the last node in the collection. + * @param[in] rptrNode Reference to the smart pointer pointing to the node to check for. + * @return Returns whether the provided node is the last node in the collection. + */ + bool CheckLast(const std::shared_ptr& rptrNode); + + public: + /** + * @brief Find the index belonging to the provided node. + * @param[in] rptrNode Reference to the smart pointer holding the node to return the index for. + * @return Return the node index. Returns npos if the node could not be found. + */ + uint32_t FindIndex(const std::shared_ptr& rptrNode); + + /** + * @brief Generic inserting function for nodes. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @attention Array indexing starts with 0! + * @attention For an array, when no indexing is supplied, the latest entry will be returned. + * @remarks If the node to insert exists already, but is marked implicit, the node will be returned and made explicit. In + * all other cases the an error will occur that the node already exists. + * @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the + * collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value + * nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically + * corrected. + * @param[in] rrangeKeyPath Reference to the token range containing the path to the node to insert. + * @param[in] rtArgs Zero or more references to arguments passed to the constructor of the node classes being created by + * this function. + * @return Returns a shared pointer to the inserted node. This node is of the requested type, except with table arrays; + * there the returned node is a table within the table array. + */ + template + std::shared_ptr Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs); + + private: + /** + * @brief When set, the child nodes need grouping (values following each other, tables and table arrays at the end). + */ + virtual bool GroupChildNodes() const { return Inline(); } + + // A table could contain nodes that are child notes of (implicitly) generated tables. For example: + // [MyTable] + // normal_value1 = "normal1" + // child_table.my_value1 = "hello1" + // normal_value2 = "normal2" + // child_table.my_value2 = "hello2" + // normal_value2 = "normal3" + // + // The implicit table MyTable.child_table has two values: my_value1 and my_value2 + // + // Making an explicit table out of it would theoretically be possible, but the insertion of values according to the position + // will become extremely difficult. + // + // [MyTable.child_table] # This will cause an explicit table + // + // The result will be, that the table is explicitly defined, causing the original table from above to not print the implicit + // table entries any more: + // [MyTable] + // normal_value1 = "normal1" + // normal_value2 = "normal2" + // normal_value2 = "normal3" + // + // [MyTable.child_table] # This will cause an explicit table + // child_table.my_value1 = "hello1" + // child_table.my_value2 = "hello2" + // + // The node collection uses the vector to include the nodes by order. + // The node collection uses the node list to include child nodes. + // The child node uses the parent node pointer to indicate which node holds node. + // The child node uses the view node pointer to indicate which node displays the node content. + + std::vector> m_vecNodeOrder; ///< Vector holding the child nodes (could contain grand children as + ///< well). + std::list> m_lstNodes; ///< List holding the direct child nodes. + std::list> m_lstRecycleBin; ///< List holding the child elements that were deleted. This will + ///< prevent destruction of the node class, which would otherwise + ///< lead to unstable behavior when interfaces of the node are still + ///< being accessed. + }; /** - * @brief Gets the table value of a node - * @return Returns a shared pointer of the table value stored in the node if the stored type is table + * @brief A dynamic table structure that allows mixed data in form of key value pairs. + * @details A table can be explicit and inline. One version of the explicit table exists. Two versions of the inline + * table exist: the assignment version and the embedded in-an-array-version. + * Explicit table: + * @code + * [] + * ^ ^ ^ ^ + * pre-node pre_key_ws post_key_ws post_node + * @endcode + * Not embedded in an array, but inline: + * @code + * KEY.KEY={ZERO_OR_MORE_VALUES} + * ^ ^ ^ ^ ^ ^ + * pre-node post_key_ws pre_key_ws post_key_ws pre_value_ws post-node + * @endcode + * Embedded in an array: + * @code + * {ZERO_OR_MORE_VALUES} + * ^ ^ + * pre-node post-node + * @endcode + * The pre- and post-node whitespace areas are covered by the UpdateNodeCode function of the CNode base class. + * Multiple optional pre- and post-separator whitespace areas can be defined. */ - std::shared_ptr GetTable() const; + class CTable : public CNodeCollection + { + public: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] bDefaultInline The default value for the inline flag. + * @param[in] bExplicit When set, the table is defined explicit. Otherwise the table is an implicit table and allows + * additional elements to be added. + */ + CTable(CParser& rparser, const std::string& rssName, const std::string& rssRawName, bool bDefaultInline, + bool bExplicit = true); + + /** + * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. + * @return Type of the node. + */ + virtual sdv::toml::ENodeType GetType() const override; + + /** + * @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML. + * @param[in] rContext Reference to the context class to use during TOML code generation. + * @return The TOML text string. + */ + virtual std::string GenerateTOML(const CGenContext& rContext = CGenContext()) const override; + + /** + * @brief Update the node with TOML code information. Overload of CNode::UpdateNodeCode. + * @param[in] rNodeRange Reference to the node range information containing the tokens for the code snippets. + */ + virtual void UpdateNodeCode(const CNodeTokenRange& rNodeRange) override; + + /** + * @brief The derived class from the node collection can be inline or not. Overload of CNodeCollection::Inline. + * @return Returns whether the node is an inline node. + */ + virtual bool Inline() const override; + + /** + * @brief Switch between inline and explicit table definition. Overload of CNodeCollection::Inline. + * @attention It is not possible to switch to an explicit table definition if the table is part of an array, since the + * table doesn't have a name. + * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is + * always successful). When returning false, the switching might not be supported for this type. + */ + virtual bool Inline(bool bInline) override; + + /** + * @brief Checks whether the table was explicitly defined. Overload of CNodeCollection::ExplicitlyDefined. + * @return Returns the explicit definition flag. + */ + virtual bool ExplicitlyDefined() const override; + + /** + * @brief If the table was an implicit definition, make it explicit. Overload of CNodeCollection::MakeExplicit. + */ + virtual void MakeExplicit() override; + + + //bool m_bOpenToAddChildren = true; ///< If internal table, the table can be extended until the table is closed. + + private: + bool m_bDefinedExplicitly = true; ///< When set, the table is defined explicitly. + bool m_bInline = false; ///< Flag determining whether the table is inline or not. + }; /** - * @brief Gets the table value of a node - * @return Returns a shared pointer of the table value stored in the node if the stored type is table + * @brief A dynamic array structure that allows mixed data of multiple values to be assigned to one key. + * @details In most cases an array is inline and is defined similar to value assignments. The table array (an array consisting) + * only of tables can also be defined as an explicit array as well. Two versions of the inline array exist: the assignment + * version and the embedded in-an-array-version. + * Explicit table array: + * @code + * [[KEY]] + * ^ ^ ^ ^ + * pre-node pre_key_ws post_key_ws post_node + * @endcode + * Not embedded in an array, but inline: + * @code + * KEY.KEY=[ZERO_OR_MORE_VALUES,] + * ^ ^ ^ ^ ^ ^ ^ + * pre-node post_key_ws pre_key_ws post_key_ws pre_value_ws post_values post-node + * @endcode + * Embedded in an array: + * @code + * [ZERO_OR_MORE_VALUES,] + * ^ ^ ^ + * pre-node post_values post-node + * @endcode + * The pre- and post-node whitespace areas are covered by the UpdateNodeCode function of the CNode base class. + * Multiple optional pre- and post-separator whitespace areas can be defined. */ - std::shared_ptr GetTable(); + class CArray : public CNodeCollection + { + public: + /** + * @brief Constructor + * @remarks If the array is not defined as explicit table array, the array will be inline. + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + * @param[in] bExplicitTableArray When set, the array is defined as explicit table array. In this case, the array will not + * be inline and needs at least one child-table. + */ + CArray(CParser& rparser, const std::string& rssName, const std::string& rssRawName, bool bExplicitTableArray = false); + + /** + * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. + * @return Type of the node. + */ + virtual sdv::toml::ENodeType GetType() const override; + + /** + * @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct. + * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. + * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and + * traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. + * These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. + * @remarks The definition of an array in TOML differentiate from the syntax to access the elements. For example an array in + * TOML could be defined by: + * @code + * integers = [ 1, 2, 3 ] + * nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] + * [[products]] + * name = "Hammer" + * sku = 738594937 + * @endcode + * The first two examples define the complete array at once. The third example defines one element to be added to an array. + * Random access to previous definitions is not required. The access functions need random access to each element. The Direct + * function uses the syntax similar to C++: + * @code + * integers[1] --> gives: 2 + * nested_mixed_array[1][2] --> gives: "c" + * products[0].sku --> gives: 738594937 + * @endcode + * To find array elements, the path names are composed of elements separated by a dot. The Add and Find functions use the + * following syntax: + * @code + * integers.1 --> stores: 2 + * nested_mixed_array.1.2 --> stores: "c" + * products.0.sku --> stores: 738594937 + * @endcode + * @attention Array element access indices starts with 0! + * @attention For an array element inserting, when no indexing is supplied, the latest entry will be returned. + * @param[in] rssPath Reference to the path of the node to searched for. + * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. + */ + virtual std::shared_ptr Direct(const std::string& rssPath) const override; + + /** + * @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML. + * @param[in] rContext Reference to the context class to use during TOML code generation. + * @return Return the TOML text string. + */ + virtual std::string GenerateTOML(const CGenContext& rContext = CGenContext()) const override; + + /** + * @brief Update the node with TOML code information. Overload of CNode::UpdateNodeCode. + * @param[in] rNodeRange Reference to the node range information containing the tokens for the code snippets. + */ + virtual void UpdateNodeCode(const CNodeTokenRange& rNodeRange) override; + + /** + * @brief Returns whether the array is defined as a table array (array with only tables and at least one table). + * @remarks Table arrays can still be inline. + * @return Returns whether the array is a table array. + */ + bool TableArray() const; + + /** + * @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline. + * @return Returns whether the node is an inline node. + */ + virtual bool Inline() const override; + + /** + * @brief Allow switching between the inline array and a table array. Overload of CNode::Inline. + * @details Most array definitions are inline. An exception to the rule is a table array, which is allowed to be explicit + * as well. To switch from inline array to an explicit array, at least one member needs to be present and all members need + * to be tables. + * @attention It is not possible to switch to an explicit array when the array is part of another array, since the atray + * doesn't have a name that can be used to define the explicit table array. + * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is + * always successful). When returning false, the switching might not be supported for this type. + */ + virtual bool Inline(bool bInline) override; + + private: + bool m_bDefinedExplicitly = true; ///< When set, the array is defined explicitly. + bool m_bInline = false; ///< Flag determining whether the table is inline or not. + }; -protected: /** - * @brief Gets the Parent Node - * @return Returns the parent Node - * @attention Beware of expiring pointers + * @brief Array of tables */ - std::weak_ptr GetParent() const; + class CTableArray : public CArray + { + public: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + * @param[in] rssName Reference to the name of the node. + * @param[in] rssRawName Reference to the raw name of the node. + */ + CTableArray(CParser& rparser, const std::string& rssName, const std::string& rssRawName) : + CArray(rparser, rssName, rssRawName, true) + {} + }; -public: /** - * @brief Set the parent node. - * @param[in] rptrParent Reference to the node to assign to this node as a parent. + * @brief Root table */ - void SetParent(const std::shared_ptr& rptrParent); + class CRootTable : public CTable + { + public: + /** + * @brief Constructor + * @param[in] rparser Reference to the TOML parser. + */ + CRootTable(CParser& rparser) : CTable(rparser, "root", "", false) + {} - /** - * @brief Accesses a node by its key in the parse tree. - * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. - * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed - * by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access - * conventions can also be chained like 'table.array[2][1].subtable.integerElement'. - * @attention Array indexing starts with 0! - * @param[in] rssPath The path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. - */ - virtual std::shared_ptr GetDirect(const std::string& rssPath) const; + /** + * @brief Delete the current node. Overload of sdv::toml::INodeDelete::DeleteNode. + * @attention A successful deletion will cause all interfaces to the current node to become inoperable. + * @return Returns whether the deletion was successful. + */ + virtual bool DeleteNode() override; - /** - * @brief Create the TOML text based on the content using an optional parent node. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @return The string containing the TOML text. - */ - std::string CreateTOMLText(const std::string& rssParent = std::string()) const; + /** + * @brief The derived class from the node collection can be inline or not. Overload of CNodeCollection::Inline. + * @return Returns whether the node is an inline node. + */ + virtual bool Inline() const override + { + return false; + } - /** - * @brief Get the TOML text based on the content. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst = true, - bool bEmbedded = false, bool bAssignment = true, bool bRoot = false) const = 0; + /** + * @brief With some node collections it is possible to switch between inline and normal. Overload of + * CNodeCollection::Inline. + * @remarks Additional node composition information will be removed and the order within the parent node might be changed. + * @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal. + * @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is + * always successful). When returning false, the switching might not be supported for this type. + */ + virtual bool Inline(bool bInline) override + { + return bInline == false; + } + }; -private: - std::weak_ptr m_ptrParent; ///< Weak pointer to the parent node (if existing). - std::string m_ssName; ///< Name of the node. + template + std::shared_ptr CNode::Cast() + { + return std::dynamic_pointer_cast(shared_from_this()); + } -public: - /** - * @brief Searches the subtree of the node for a node at the given location using the provided path. - * @remarks The path elements of arrays and tables are separated by a dot. - * @param[in] rssPath Reference to the string containing the path of the node to find. - * @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not - */ - virtual std::shared_ptr Find(const std::string& rssPath) const; + template + std::shared_ptr CNode::Cast() const + { + return std::dynamic_pointer_cast(shared_from_this()); + } - /** - * @brief Adds a given node to a given path in the tree - * @remarks The path elements of arrays and tables are separated by a dot. - * @param[in] rssPath Reference to the string containing the path in the tree of the location to the new node to be inserted. - * @param[in] rptrNode Reference to the smart pointer containing the new node to be added. - * @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is defined explicitly. - * @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add children. - * @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to - * be added is already defined explicitly - */ - virtual void Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly = true); -}; + template + inline std::shared_ptr CNodeCollection::Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs) + { + // Get the first part of the node + auto prKey = SplitNodeKey(rrangeKeyPath); -/** - * @brief Boolean value node. - */ -class CBooleanNode : public CNode -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the string containing the name of the node. - * @param[in] bVal The value to assign. - */ - CBooleanNode(const std::string& rssName, bool bVal); + // Find the node if it exists. + auto itNode = std::find_if(m_lstNodes.begin(), + m_lstNodes.end(), + [&](const std::shared_ptr& rptrNode) { return rptrNode->GetName() == prKey.first.get().StringValue(); }); - /** - * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. - * @return Type of the node. - */ - virtual sdv::toml::ENodeType GetType() const override; + // Is this the target node? + std::shared_ptr ptrNode; + if (!prKey.second) + { + if (std::is_same_v) + { + // If this is a table array, create if not existing and then create a table. + std::shared_ptr ptrTableArray; + if (itNode == m_lstNodes.end()) + { + ptrTableArray = std::make_shared(Parser(), prKey.first.get().StringValue(), + prKey.first.get().RawString()); + ptrTableArray->SetParentPtr(Cast()); - /** - * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. - * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function - * returns empty. - */ - virtual sdv::any_t GetValue() const override; + // Add to the list + m_lstNodes.push_back(ptrTableArray); + } + else + ptrTableArray = (*itNode)->template Cast(); + if (!ptrTableArray) + throw XTOMLParseException("The node '" + prKey.first.get().StringValue() + + "' exists already, but is not a table array."); - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; + // Create the table. + ptrNode = ptrTableArray->Insert(sdv::toml::npos, CTokenRange(prKey.first, prKey.first.get().Next()), + false, true); + } else if (!Cast() && itNode != m_lstNodes.end()) + { + // If existing... this might be a duplicate if not explicitly defined before. + // But only when the parent is not an array. + ptrNode = *itNode; -private: - bool m_bVal; ///< Value in case of boolean node. -}; + // Explicitly defined nodes cannot be defined once more + if (ptrNode->ExplicitlyDefined()) + throw XTOMLParseException("The node '" + prKey.first.get().StringValue() + "' exists already."); -/** - * @brief Integer value node. - */ -class CIntegerNode : public CNode -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the string containing the name of the node. - * @param[in] iVal The value to assign. - */ - CIntegerNode(const std::string& rssName, int64_t iVal); + // Inline nodes cannot be made explicit + if (ptrNode->Inline()) + throw XTOMLParseException("The node '" + prKey.first.get().StringValue() + "' exists already as inline node."); - /** - * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. - * @return Type of the node. - */ - virtual sdv::toml::ENodeType GetType() const override; + // Make the note explicit. This will remove the view pointer if it was set before. + ptrNode->MakeExplicit(); + } else + { + // Create the target node. + ptrNode = std::make_shared(Parser(), prKey.first.get().StringValue(), prKey.first.get().RawString(), + rtArgs...); + ptrNode->SetParentPtr(Cast()); - /** - * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. - * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function - * returns empty. - */ - virtual sdv::any_t GetValue() const override; + // Add to the list + m_lstNodes.push_back(ptrNode); - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; + // If the current node is implicit, take over the inline flag (this determines whether a sub-table definition is + // allowed or not). + if (!ExplicitlyDefined()) + Inline(ptrNode->Inline()); + } + } + else // Intermediate node + { + // This must be a non-inline table node + if (itNode != m_lstNodes.end()) + { + ptrNode = *itNode; -private: - int64_t m_iVal; ///< Value in case of integer node. -}; + // Process the nodes dependable on the type + if (ptrNode->Cast()) + { + // In case the node is a table array, check for the next key part. If this is a number, then the current node + // can be an array, which will be indexed by the next key part. If this is not a number, the last table node + // is automatically assumed. + auto prNextKey = SplitNodeKey(prKey.second); + if (prNextKey.first.get().Category() != ETokenCategory::token_integer) + { + // Get the last table + if (!ptrNode->Cast()->GetCount()) + throw XTOMLParseException("The parent table array node '" + prKey.first.get().StringValue() + + "' doesn't have any tables assigned."); + ptrNode = ptrNode->Cast()->Get(ptrNode->Cast()->GetCount() - 1); + if (ptrNode->Inline() || !ptrNode->Cast()) + throw XTOMLParseException("The parent node '" + prKey.first.get().StringValue() + + "' is of unexpected type."); + } + } + else if ((!ptrNode->Cast() && !ptrNode->Cast()) || (ptrNode->ExplicitlyDefined() && ptrNode->Inline())) + { + // This is allowed: + // [fruit] + // apple.color = "red" + // apple.taste.sweet = true + // [fruit.apple.texture] # you can add sub-tables + // --> fuit is explicitly defined, but not inline. apple is not explicitly defined + // + // This not: + // type = { name = "Nail" } + // type.edible = false # INVALID + // --> type is explicitly defined + throw XTOMLParseException("The parent node '" + prKey.first.get().StringValue() + "' is of unexpected type."); + } + } + else + { + // Create an implicit table node. + ptrNode = std::make_shared(Parser(), prKey.first.get().StringValue(), prKey.first.get().RawString(), false, false); + ptrNode->SetParentPtr(Cast()); + m_lstNodes.push_back(ptrNode); + } -/** - * @brief Floating point value node. - */ -class CFloatingPointNode : public CNode -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the string containing the name of the node. - * @param[in] dVal The value to assign. - */ - CFloatingPointNode(const std::string& rssName, double dVal); + // Insert the node in the child node + std::shared_ptr ptrNodeCollection = ptrNode->Cast(); + if (!ptrNodeCollection) + throw XTOMLParseException("Parent node is not an array or table '" + ptrNode->GetPath(true) + "'."); + ptrNode = ptrNodeCollection->Insert(sdv::toml::npos, prKey.second, rtArgs...); + if (!ptrNode) + throw XTOMLParseException("Could not create the node '" + prKey.first.get().StringValue() + "'."); - /** - * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. - * @return Type of the node. - */ - virtual sdv::toml::ENodeType GetType() const override; + // This node is not the parent, but still presents of the created node. Add the view pointer that they are linked. + ptrNode->SetViewPtr(Cast()); + } - /** - * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. - * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function - * returns empty. - */ - virtual sdv::any_t GetValue() const override; + // Insert the node at the requested location if this node is inline or this is the view of the node. + auto itPos = (static_cast(uiIndex) >= m_vecNodeOrder.size()) ? m_vecNodeOrder.end() : + m_vecNodeOrder.begin() + static_cast(uiIndex); + m_vecNodeOrder.insert(itPos, ptrNode); - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; + // Return the result + return ptrNode; + } -private: - double m_dVal; ///< Value in case of floating point node. -}; -/** - * @brief String value node. - */ -class CStringNode : public CNode -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the string containing the name of the node. - * @param[in] rssVal The value to assign. - */ - CStringNode(const std::string& rssName, const std::string& rssVal); - - /** - * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. - * @return Type of the node. - */ - virtual sdv::toml::ENodeType GetType() const override; - - /** - * @brief The node value. Overload of sdv::toml::INodeInfo::GetValue. - * @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function - * returns empty. - */ - virtual sdv::any_t GetValue() const override; - - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; - -private: - std::string m_ssVal; ///< Value in case of string or illegal (error) node. -}; - -/** - * @brief Base structure for arrays and tables. - */ -class CNodeCollection : public CNode, public sdv::toml::INodeCollection -{ -protected: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CNodeCollection(const std::string& rssName); - -public: - // Interface map - BEGIN_SDV_INTERFACE_MAP() - SDV_INTERFACE_ENTRY(sdv::toml::INodeCollection) - SDV_INTERFACE_CHAIN_BASE(CNode) - END_SDV_INTERFACE_MAP() - - /** - * @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount. - * @return The amount of nodes. - */ - virtual uint32_t GetCount() const override; - - /** - * @brief Get the node. Overload of sdv::toml::INodeCollection::GetNode. - * @param[in] uiIndex Index of the node to get. - * @return Interface to the node object. - */ - virtual IInterfaceAccess* GetNode(/*in*/ uint32_t uiIndex) const override; - - /** - * @brief Get the node. - * @param[in] uiIndex Index of the node to get. - * @return Smart pointer to the node object. - */ - std::shared_ptr Get(uint32_t uiIndex) const; - - /** - * @brief Searches a node by its key in the parse tree - * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child - * name. E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be - * accessed and traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of - * the array 'array'. These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'. - * @attention Array indexing starts with 0! - * @param[in] ssPath The path of the node to searched for. - * @return Returns an interface the requested node if available. - */ - virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override; - - /** - * @brief Add an element to the collection. - * @param[in] rptrNode Reference to the node element smart pointer. - * @param[in] bUnique When set, check prevents adding an element with the same name. - * @return Returns whether the element addition was successful. - */ - bool AddElement(const std::shared_ptr& rptrNode, bool bUnique = false); - -private: - - std::vector> m_vecContent; ///< Vector holding the child elements - -public: - bool m_bDefinedExplicitly = true; ///< WHen set, the array/table is defined explicitly - ///< (not internal). -}; - -/** - * @brief A dynamic table structure that allows mixed data in form of key value pairs - */ -class CTable : public CNodeCollection -{ -protected: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CTable(const std::string& rssName); - -public: - /** - * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. - * @return Type of the node. - */ - virtual sdv::toml::ENodeType GetType() const override; - - /** - * @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect. - * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. - * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed - * by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access - * conventions can also be chained like 'table.array[2][1].subtable.integerElement'. - * @attention Array indexing starts with 0! - * @param[in] rssPath The path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. - */ - virtual std::shared_ptr GetDirect(const std::string& rssPath) const override; - - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; - - /** - * @brief Adds a given node to a given path in the tree. Overload of CNode::Add. - * @param[in] rssPath Reference to the path in the tree where the new node is to be added - * @param[in] rptrNode Reference to the smart pointer holding the node. - * @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is - * defined explicitly - * @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add - * children - * @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to - * be added is already defined explicitly - */ - virtual void Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly) override; - - /** - * @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find. - * @remarks The path elements of arrays and tables are separated by a dot. - * @param[in] rssPath Reference to the path in the tree where the new node is to be added - * @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not - */ - virtual std::shared_ptr Find(const std::string& rssPath) const override; - - bool m_bOpenToAddChildren = true; ///< If internal table, the table can be extended until the table - ///< is closed. - -}; - -/** - * @brief A dynamic array structure that allows mixed data - * @details The definition of an array in TOML differentiate massively from the syntax to access the elements. For example an array - * in TOML could be defined by: - * @code - * integers = [ 1, 2, 3 ] - * nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] - * [[products]] - * name = "Hammer" - * sku = 738594937 - * @endcode - * The first two examples define the complete array at once. The third example defines one element to be added to an array. Random - * access to previous definitions is not required. - * The access functions need random access to each element. The GetDirect function uses the syntax similar to C++: - * @code - * integers[1] --> gives: 2 - * nested_mixed_array[1][2] --> gives: "c" - * products[0].sku --> gives: 738594937 - * @endcode - * To find array elements, the path names are composed of elements separated by a dot. The Add and Find functions use the following - * syntax: - * @code - * integers.1 --> stores: 2 - * nested_mixed_array.1.2 --> stores: "c" - * products.0.sku --> stores: 738594937 - * @endcode - */ -class CArray : public CNodeCollection -{ -protected: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CArray(const std::string& rssName); - -public: - /** - * @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType. - * @return Type of the node. - */ - virtual sdv::toml::ENodeType GetType() const override; - - /** - * @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect. - * @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name. - * E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed - * by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access - * conventions can also be chained like 'table.array[2][1].subtable.integerElement'. - * @attention Array indexing starts with 0! - * @param[in] rssPath Reference to the path of the node to searched for. - * @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found. - */ - virtual std::shared_ptr GetDirect(const std::string& rssPath) const override; - - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; - - /** - * @brief Adds a given node to a given path in the tree. Overload of CNode::Add. - * @param[in] rssPath Reference to the path in the tree where the new node is to be added. - * @param[in] rptrNode Reference top the smart pointer holding the node. - * @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is - * defined explicitly - * @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add - * children - * @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to - * be added is already defined explicitly - */ - virtual void Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly) override; - - /** - * @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find. - * @remarks The path elements of arrays and tables are separated by a dot. - * @param[in] rssPath Reference to the path of the node to find. - * @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not - */ - virtual std::shared_ptr Find(const std::string& rssPath) const override; -}; - -/** - * @brief Normal table - */ -class CNormalTable : public CTable -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CNormalTable(const std::string& rssName) : CTable(rssName) {} -}; - -/** - * @brief Inline table - */ -class CInlineTable : public CTable -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CInlineTable(const std::string& rssName) : CTable(rssName) {} -}; - -/** - * @brief Normal array - */ -class CNormalArray : public CArray -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CNormalArray(const std::string& rssName) : CArray(rssName) {} -}; - -/** - * @brief Array of tables - */ -class CTableArray : public CArray -{ -public: - /** - * @brief Constructor - * @param[in] rssName Reference to the name of the node. - */ - CTableArray(const std::string& rssName) : CArray(rssName) {} - - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; - - /** - * @brief Adds a given node to a given path in the tree. Overload of CNode::Add. - * @param[in] rssPath Reference to the path in the tree where the new node is to be added. - * @param[in] rptrNode Reference top the smart pointer holding the node. - * @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is - * defined explicitly - * @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add - * children - * @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to - * be added is already defined explicitly - */ - virtual void Add(const std::string& rssPath, const std::shared_ptr& rptrNode, bool bDefinedExplicitly) override; - - /** - * @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find. - * @remarks The path elements of arrays and tables are separated by a dot. - * @param[in] rssPath Reference to the string containing the path of the node to find. - * @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not - */ - virtual std::shared_ptr Find(const std::string& rssPath) const override; -}; - -/** - * @brief Root table - */ -class CRootTable : public CNormalTable -{ -public: - /** - * @brief Constructor - */ - CRootTable() : CNormalTable("root") {} - - /** - * @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a - * different table was printed in between. - * @param[in] bFirst When set, this is the first entry in an array or table. - * @param[in] bEmbedded When set, this is an embedded definition in an array or table. - * @param[in] bAssignment When set, this is a table assignment. - * @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name). - * @return The string containing the TOML text. - */ - virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded, - bool bAssignment, bool bRoot) const override; -}; +} // namespace toml_parser #endif // !defined PARSER_NODE_TOML_H \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_toml.cpp b/sdv_services/core/toml_parser/parser_toml.cpp index 6ae2423..36e84c4 100644 --- a/sdv_services/core/toml_parser/parser_toml.cpp +++ b/sdv_services/core/toml_parser/parser_toml.cpp @@ -1,380 +1,507 @@ #include "parser_toml.h" #include +#include "miscellaneous.h" #include "exception.h" -CParserTOML::CParserTOML(const std::string& rssString) : m_lexer(rssString) +/// The TOML parser namespace +namespace toml_parser { - Process(rssString); -} - -void CParserTOML::Clear() -{ - m_ptrRoot = std::make_shared(); - m_ssCurrentTable.clear(); - m_lexer.Reset(); - while (!m_stackEnvironment.empty()) m_stackEnvironment.pop(); -} - -bool CParserTOML::Process(/*in*/ const sdv::u8string& ssContent) -{ - Clear(); - m_lexer.Feed(ssContent); - try + CParser::CParser(const std::string& rssString) { - // Run through all tokens of the lexer and process the tokens. - while (!m_lexer.IsEnd()) + Process(rssString); + } + + void CParser::Clear() + { + m_ptrRoot.reset(); + m_ptrCurrentCollection.reset(); + m_lexer.Reset(); + while (!m_stackEnvironment.empty()) + m_stackEnvironment.pop(); + } + + bool CParser::Process(/*in*/ const sdv::u8string& ssContent) + { + // Process the TOML string + Clear(); + m_lexer.Feed(ssContent); + + // Create the root node. + m_ptrRoot = std::make_shared(*this); + m_ptrCurrentCollection = m_ptrRoot; + + // Nodes available at all? + if (m_lexer.IsEnd()) return true; + + // The initial start position for the node token is the begin of the token list. + std::reference_wrapper refStartNodeToken = m_lexer.Peek().JumpToBegin(); + + CNodeTokenRange rangeRootTokens(refStartNodeToken); + + try { - CLexerTOML::SToken current = m_lexer.Peek(); - switch (current.eCategory) + // Run through all tokens of the lexer and process the tokens. + bool bEOF = false; // Explicit test, since the peek could return EOF, but the cursor might not be at the end yet. + while (!bEOF && !m_lexer.IsEnd()) { - case CLexerTOML::ETokenCategory::token_syntax_table_open: - ProcessTable(); + // The node has its own token range + CNodeTokenRange rangeNode(refStartNodeToken.get()); + + const CToken& rCurrent = m_lexer.Peek(); + if (!rCurrent) + { + bEOF = true; + break; + } + switch (rCurrent.Category()) + { + case ETokenCategory::token_syntax_table_open: + ProcessTable(rangeNode); + break; + case ETokenCategory::token_syntax_table_array_open: + ProcessTableArray(rangeNode); + break; + case ETokenCategory::token_key: + ProcessValueKey(rangeNode); + break; + case ETokenCategory::token_syntax_new_line: + m_lexer.Consume(); + break; + case ETokenCategory::token_terminated: + case ETokenCategory::token_error: + throw XTOMLParseException(rCurrent.StringValue()); + break; + default: + throw XTOMLParseException("Invalid Syntax; not a Key, Table or Tablearray"); + } + + // Update the start of the next value range + refStartNodeToken = rangeNode.LinesBehindNode().End(); + } + } + catch (const sdv::toml::XTOMLParseException& e) + { + std::cout << e.what() << '\n'; + throw; + } + + // In case there are no nodes any more, but still comments and whitespace, attach this to the root node. + if (refStartNodeToken.get() != m_lexer.Peek()) + { + rangeRootTokens.NodeMain(CTokenRange(refStartNodeToken, refStartNodeToken)); + rangeRootTokens.LinesBehindNode(m_lexer.Peek()); + m_ptrRoot->UpdateNodeCode(rangeRootTokens); + } + + return true; + } + + CLexer& CParser::Lexer() + { + return m_lexer; + } + + const CNodeCollection& CParser::Root() const + { + auto ptrCollection = m_ptrRoot->Cast(); + return *ptrCollection.get(); + } + + CNodeCollection& CParser::Root() + { + auto ptrCollection = m_ptrRoot->Cast(); + return *ptrCollection.get(); + } + + std::string CParser::GenerateTOML(const std::string& rssPrefixKey) const + { + return m_ptrRoot->GenerateTOML(rssPrefixKey); + } + + void CParser::ProcessTable(CNodeTokenRange& rNodeRange) + { + // Get the table path (table name preceded by parent tables separated with dots). + CTokenRange rangeMain(m_lexer.Consume()); + CTokenRange rangeKeyPath = ProcessKeyPath(); + const CToken& rToken = m_lexer.Consume(); + if (!rToken || rToken.Category() != ETokenCategory::token_syntax_table_close) + throw XTOMLParseException("Invalid Table construct"); + rangeMain.AssignEndToken(rToken); // NOTE: This includes only the name and the brackets, not the values. + + // Assign the main token range to the node token range and let the lexer determine the extended token range + rNodeRange.NodeMain(rangeMain); + m_lexer.SmartExtendNodeRange(rNodeRange); + + // Add the table to the root + auto ptrTable = m_ptrRoot->Insert(sdv::toml::npos, rangeKeyPath, false); + if (ptrTable) + { + m_ptrCurrentCollection = ptrTable->Cast(); + ptrTable->UpdateNodeCode(rNodeRange); + } + } + + void CParser::ProcessTableArray(CNodeTokenRange& rNodeRange) + { + CTokenRange rangeMain(m_lexer.Consume()); + CTokenRange rangeKeyPath = ProcessKeyPath(); + const CToken& rToken = m_lexer.Consume(); + if (!rToken || rToken.Category() != ETokenCategory::token_syntax_table_array_close) + throw XTOMLParseException("Invalid Table Array construct"); + rangeMain.AssignEndToken(rToken); // NOTE: This includes only the name and the brackets, not the values. + + // Assign the main token range to the node token range and let the lexer determine the extended token range + rNodeRange.NodeMain(rangeMain); + m_lexer.SmartExtendNodeRange(rNodeRange); + + // Add the table array to the root + auto ptrTableArray = m_ptrRoot->Insert(sdv::toml::npos, rangeKeyPath); + if (ptrTableArray) + { + m_ptrCurrentCollection = ptrTableArray->Cast(); + ptrTableArray->UpdateNodeCode(rNodeRange); + } + } + + void CParser::ProcessValueKey(CNodeTokenRange& rNodeRange) + { + // Initial part of the main token range containing the key and the assignment + CTokenRange rangeMain(m_lexer.Peek()); + + CTokenRange rangeKeyPath = ProcessKeyPath(); + + const CToken& rToken = m_lexer.Consume(); + if (rToken.Category() != ETokenCategory::token_syntax_assignment) + throw XTOMLParseException("Assignment expected"); + rangeMain.AssignEndToken(rToken); + + // Store this initial range + rNodeRange.NodeMain(rangeMain); + + // Process the value assignment + ProcessValue(rangeKeyPath, rNodeRange); + } + + void CParser::ProcessValue(const CTokenRange& rrangeKeyPath, CNodeTokenRange& rNodeRange) + { + // Extend the main range + CTokenRange rangeMain = rNodeRange.NodeMain(); + + // Skip newlines (other whitespace and comments are already skipped) + while (m_lexer.Peek().Category() == ETokenCategory::token_syntax_new_line) + m_lexer.Consume(); + + // Get the value + const CToken& rAssignmentValue = m_lexer.Consume(); + if (!rAssignmentValue) + throw XTOMLParseException("Missing value"); + + // Assign the end token for the main part + rangeMain.AssignEndToken(rAssignmentValue); + rNodeRange.NodeMain(rangeMain); + + // Process the value + std::shared_ptr ptrNode; + switch (rAssignmentValue.Category()) + { + case ETokenCategory::token_boolean: + ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.BooleanValue(), + rAssignmentValue.RawString()); + break; + case ETokenCategory::token_integer: + ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.IntegerValue(), + rAssignmentValue.RawString()); + break; + case ETokenCategory::token_float: + ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.FloatValue(), + rAssignmentValue.RawString()); + break; + case ETokenCategory::token_string: + switch (rAssignmentValue.StringType()) + { + case ETokenStringType::literal_string: + ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), + CStringNode::EQuotationType::literal_string, rAssignmentValue.RawString()); break; - case CLexerTOML::ETokenCategory::token_syntax_table_array_open: - ProcessTableArray(); + case ETokenStringType::multi_line_literal: + ptrNode = m_ptrCurrentCollection->Insert( + sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::multi_line_literal, + rAssignmentValue.RawString()); break; - case CLexerTOML::ETokenCategory::token_key: - ProcessValueKey(); + case ETokenStringType::multi_line_quoted: + ptrNode = m_ptrCurrentCollection->Insert( + sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::multi_line_quoted, + rAssignmentValue.RawString()); break; - case CLexerTOML::ETokenCategory::token_syntax_new_line: + case ETokenStringType::quoted_string: + default: + ptrNode = m_ptrCurrentCollection->Insert( + sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::quoted_string, + rAssignmentValue.RawString()); + break; + } + break; + case ETokenCategory::token_syntax_array_open: + { + auto ptrCurrentCollectionStored = m_ptrCurrentCollection; + ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath); + m_ptrCurrentCollection = ptrNode->Cast(); + m_stackEnvironment.push(EEnvironment::env_array); + ProcessArray(rNodeRange); + m_stackEnvironment.pop(); + m_ptrCurrentCollection = ptrCurrentCollectionStored; + } + break; + case ETokenCategory::token_syntax_inline_table_open: + { + auto ptrCurrentCollectionStored = m_ptrCurrentCollection; + ptrNode = m_ptrCurrentCollection->Insert(sdv::toml::npos, rrangeKeyPath, true); + m_ptrCurrentCollection = ptrNode->Cast(); + m_stackEnvironment.push(EEnvironment::env_inline_table); + ProcessInlineTable(rNodeRange); + m_stackEnvironment.pop(); + m_ptrCurrentCollection = ptrCurrentCollectionStored; + } + break; + default: + throw XTOMLParseException("Missing value"); + break; + } + + // let the lexer determine the extended token range and update the node + m_lexer.SmartExtendNodeRange(rNodeRange); + + // Deal with the next value if expecting + std::reference_wrapper refToken = m_lexer.Peek(); + if (!m_stackEnvironment.empty()) + { + // Skip newlines + while (refToken.get().Category() == ETokenCategory::token_syntax_new_line) + { m_lexer.Consume(); + refToken = m_lexer.Peek(); + } + + switch (m_stackEnvironment.top()) + { + case EEnvironment::env_array: + { + int32_t index = 1; + while (refToken.get() && refToken.get().Category() == ETokenCategory::token_syntax_new_line) + { + refToken = m_lexer.Peek(index++); + } + if (!refToken.get() + || (refToken.get().Category() != ETokenCategory::token_syntax_comma + && refToken.get().Category() != ETokenCategory::token_syntax_array_close)) + { + throw XTOMLParseException("Invalid Token after value assignment in array; ',' or ']' needed"); + } + } break; - case CLexerTOML::ETokenCategory::token_terminated: - case CLexerTOML::ETokenCategory::token_error: - throw XTOMLParseException(current.ssContentString); + case EEnvironment::env_inline_table: + if (!refToken.get() + || (refToken.get().Category() != ETokenCategory::token_syntax_comma + && refToken.get().Category() != ETokenCategory::token_syntax_inline_table_close)) + { + throw XTOMLParseException("Invalid Token after value assignment in inline table; ',' or '}' needed "); + } break; default: - throw XTOMLParseException("Invalid Syntax; not a Key, Table or Tablearray"); + break; } + rNodeRange.LinesBehindNode(refToken); } - } - catch (const sdv::toml::XTOMLParseException& e) - { - std::cout << e.what() << '\n'; - throw; - } - return true; -} - -const CNodeCollection& CParserTOML::GetRoot() const -{ - auto ptrCollection = m_ptrRoot->GetTable(); - return *ptrCollection.get(); -} - -CNodeCollection& CParserTOML::GetRoot() -{ - auto ptrCollection = m_ptrRoot->GetTable(); - return *ptrCollection.get(); -} - -std::string CParserTOML::CreateTOMLText(const std::string& rssParent) const -{ - std::string ssLastPrintedTable; - return m_ptrRoot->CreateTOMLText(rssParent, ssLastPrintedTable); -} - -bool CParserTOML::Add(const std::string& rssPath, bool bVal) -{ - size_t nOffset = FindLast(rssPath); - if (nOffset == std::string::npos) - nOffset = 0; - else - nOffset++; - std::string ssName = rssPath.substr(nOffset); - - m_ptrRoot->Add(rssPath, std::make_shared(ssName, bVal)); - return true; -} - -bool CParserTOML::Add(const std::string& rssPath, int64_t iVal) -{ - size_t nOffset = FindLast(rssPath); - if (nOffset == std::string::npos) - nOffset = 0; - else - nOffset++; - std::string ssName = rssPath.substr(nOffset); - - m_ptrRoot->Add(rssPath, std::make_shared(ssName, iVal)); - return true; -} - -bool CParserTOML::Add(const std::string& rssPath, double dVal) -{ - size_t nOffset = FindLast(rssPath); - if (nOffset == std::string::npos) - nOffset = 0; - else - nOffset++; - std::string ssName = rssPath.substr(nOffset); - - m_ptrRoot->Add(rssPath, std::make_shared(ssName, dVal)); - return true; -} - -bool CParserTOML::Add(const std::string& rssPath, const std::string& rssVal) -{ - size_t nOffset = FindLast(rssPath); - if (nOffset == std::string::npos) - nOffset = 0; - else - nOffset++; - std::string ssName = rssPath.substr(nOffset); - - m_ptrRoot->Add(rssPath, std::make_shared(ssName, rssVal)); - return true; -} - -void CParserTOML::ProcessTable() -{ - // Get the table path (table name preceded by parent tables separated with dots). - m_lexer.Consume(); - std::string ssPath = ComposePath(); - CLexerTOML::SToken sToken = m_lexer.Consume(); - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_close) - { - throw XTOMLParseException("invalid Table construct"); - } - - // Find the last dot - the name follows - size_t nOffset = FindLast(ssPath); - if (nOffset == std::string::npos) - nOffset = 0; // No dot found, the whole path is one table name - else - nOffset++; // Skip the dot - std::string ssName = ssPath.substr(nOffset); - - // Add the table to the root - m_ptrRoot->Add(ssPath, std::make_shared(ssName), false); - - m_ssCurrentTable = ssPath; -} - -void CParserTOML::ProcessTableArray() -{ - m_lexer.Consume(); - std::string rssKeyPath = ComposePath(); - auto ptrNode = m_ptrRoot->Find(rssKeyPath); - if (!ptrNode) - { - Add(rssKeyPath); - ptrNode = m_ptrRoot->Find(rssKeyPath); - } - if (!ptrNode) return; - if (dynamic_cast(ptrNode.get())) - ptrNode->GetArray()->AddElement(std::make_shared("")); - else - throw XTOMLParseException(("'" + rssKeyPath + "' already defined as a non-table-array").c_str()); - m_ssCurrentTable = rssKeyPath; - - CLexerTOML::SToken sToken = m_lexer.Consume(); - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_array_close) - { - throw XTOMLParseException("invalid Table Array construct"); - } -} - -void CParserTOML::ProcessValueKey() -{ - std::string rssKeyPath = (m_ssCurrentTable.empty() ? "" : (m_ssCurrentTable + ".")) + ComposePath(); - - CLexerTOML::SToken sToken = m_lexer.Consume(); - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_assignment) - { - throw XTOMLParseException("Assignment expected"); - } - - ProcessValue(rssKeyPath); -} - -void CParserTOML::ProcessValue(const std::string& rssKeyPath) -{ - CLexerTOML::SToken assignmentValue = m_lexer.Consume(); - switch (assignmentValue.eCategory) - { - case CLexerTOML::ETokenCategory::token_boolean: - Add(rssKeyPath, assignmentValue.bContentBoolean); - break; - case CLexerTOML::ETokenCategory::token_integer: - Add(rssKeyPath, assignmentValue.iContentInteger); - break; - case CLexerTOML::ETokenCategory::token_float: - Add(rssKeyPath, assignmentValue.dContentFloatingpoint); - break; - case CLexerTOML::ETokenCategory::token_string: - Add(rssKeyPath, assignmentValue.ssContentString); - break; - case CLexerTOML::ETokenCategory::token_syntax_array_open: - Add(rssKeyPath); - m_stackEnvironment.push(EEnvironment::env_array); - ProcessArray(rssKeyPath); - m_stackEnvironment.pop(); - break; - case CLexerTOML::ETokenCategory::token_syntax_inline_table_open: - Add(rssKeyPath); - m_stackEnvironment.push(EEnvironment::env_inline_table); - ProcessInlineTable(rssKeyPath); - m_stackEnvironment.pop(); - break; - default: - throw XTOMLParseException("Missing value"); - break; - } - - CLexerTOML::SToken sToken = m_lexer.Peek(); - if (!m_stackEnvironment.empty()) - { - switch (m_stackEnvironment.top()) - { - case EEnvironment::env_array: - { - int32_t index = 2; - while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_new_line) - { - sToken = m_lexer.Peek(index++); - } - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma - && sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close) - { - throw XTOMLParseException( - "Invalid Token after value assignment in array; ',' or ']' needed"); - } - } - break; - case EEnvironment::env_inline_table: - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma - && sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close) - { - throw XTOMLParseException( - "Invalid Token after value assignment in inline table; ',' or '}' needed "); - } - break; - default: - break; - } - } - else - { - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_new_line && sToken.eCategory != CLexerTOML::ETokenCategory::token_eof) - { - throw XTOMLParseException("Invalid Token after value assignment; newline needed"); - } - } -} - -void CParserTOML::ProcessArray(const std::string& rssKeyPath) -{ - /* - Arrays are defined as follow: array_name = [value, value, ...] - Arrays can have new-lines between their values. - And can end with a comma. - For example: - integers = [ 1, 2, 3 ] - colors = [ "red", "yellow", "green", ] - nested_arrays_of_ints = [ [ 1, 2 ], - [3, 4, 5] ] - nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] - string_array = [ "all", 'strings', """are the same""", '''type''' ] - */ - - CLexerTOML::SToken sToken = m_lexer.Peek(); - - size_t nIndex = 0; - enum class EExpect {value_comma_end, comma_end} eExpect = EExpect::value_comma_end; - while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close) - { - switch (sToken.eCategory) - { - //case CLexerTOML::ETokenCategory::token_syntax_array_open: // Embedded array - // if (eExpect == comma_end) throw XTOMLParseException("Expecting comma or table end."); - // m_lexer.Consume(); - // ProcessArray(rssKeyPath + "." + std::to_string(nIndex++)); - // eExpect = comma_end; - // break; - case CLexerTOML::ETokenCategory::token_syntax_new_line: - m_lexer.Consume(); - break; - case CLexerTOML::ETokenCategory::token_syntax_comma: - m_lexer.Consume(); - eExpect = EExpect::value_comma_end; - break; - default: - if (eExpect == EExpect::comma_end) - throw XTOMLParseException("Expecting comma or table end."); - ProcessValue(rssKeyPath + "." + std::to_string(nIndex++)); - eExpect = EExpect::comma_end; - break; - } - sToken = m_lexer.Peek(); - } - m_lexer.Consume(); -} - -void CParserTOML::ProcessInlineTable(const std::string& rssKeyPath) -{ - /* - Inline tables are defined as follow: table_name = {value, value, ...} - For example: - name = { first = "Tom", last = "Preston-Werner" } - point = { x = 1, y = 2 } - animal = { type.name = "pug" } - */ - - CLexerTOML::SToken sToken = m_lexer.Peek(); - - std::string ssCurrentTableTemp = m_ssCurrentTable; - m_ssCurrentTable = rssKeyPath; - enum class EExpect { value_comma_end, value, comma_end } eExpect = EExpect::value_comma_end; - while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close) - { - switch (sToken.eCategory) - { - case CLexerTOML::ETokenCategory::token_syntax_new_line: - throw XTOMLParseException("No newlines allowed in inline table"); - break; - case CLexerTOML::ETokenCategory::token_syntax_comma: - if (eExpect == EExpect::value) - throw XTOMLParseException("Unexpected comma."); - m_lexer.Consume(); - eExpect = EExpect::value; - break; - default: - if (eExpect == EExpect::comma_end) - throw XTOMLParseException("Expecting comma or table end."); - ProcessValueKey(); - eExpect = EExpect::comma_end; - break; - } - sToken = m_lexer.Peek(); - } - if (eExpect == EExpect::value) - throw XTOMLParseException("Expecting a value before inline table end."); - - m_ptrRoot->Find(rssKeyPath)->GetTable()->m_bOpenToAddChildren = false; - - m_lexer.Consume(); - m_ssCurrentTable = ssCurrentTableTemp; -} - -std::string CParserTOML::ComposePath() -{ - std::string ssPath; - CLexerTOML::SToken sToken = m_lexer.Peek(); - if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_dot - && sToken.eCategory != CLexerTOML::ETokenCategory::token_key) - throw XTOMLParseException("Invalid Token to assemble path from keys"); - - while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_dot - || sToken.eCategory == CLexerTOML::ETokenCategory::token_key) - { - m_lexer.Consume(); - if (sToken.eCategory == CLexerTOML::ETokenCategory::token_key) - ssPath += sToken.ssContentString; else - ssPath += "."; - sToken = m_lexer.Peek(); + { + if (refToken.get() && refToken.get().Category() != ETokenCategory::token_syntax_new_line) + { + throw XTOMLParseException("Invalid Token after value assignment; newline needed"); + } + } + if (ptrNode) ptrNode->UpdateNodeCode(rNodeRange); } - return ssPath; -} + void CParser::ProcessArray(CNodeTokenRange& rNodeRange) + { + /* + Arrays are defined as follow: array_name = [value, value, ...] + Arrays can have new-lines between their values. + And can end with a comma. + For example: + integers = [ 1, 2, 3 ] + colors = [ "red", "yellow", "green", ] + nested_arrays_of_ints = [ [ 1, 2 ], + [3, 4, 5] ] + nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] + string_array = [ "all", 'strings', """are the same""", '''type''' ] + */ + + // The initial start position for the value token is the end of the first main part. + std::reference_wrapper refStartValueToken = rNodeRange.LinesBehindNode().End(); + + // Iterator token + std::reference_wrapper refToken = m_lexer.Peek(); + + // State machine + enum class EExpect + { + value_or_end, + comma_or_end // Trailing comma is allowed + } eExpect = EExpect::value_or_end; + + // Iterate through the values + size_t nIndex = 0; + bool bAdditionalCommaBeforeEnd = false; + std::reference_wrapper refCommaToken = m_lexer.Peek(); + while (refToken.get() && refToken.get().Category() != ETokenCategory::token_syntax_array_close) + { + // The value has its own token range + CNodeTokenRange rangeNode(refStartValueToken.get()); + + switch (refToken.get().Category()) + { + case ETokenCategory::token_syntax_new_line: + m_lexer.Consume(); + break; + case ETokenCategory::token_syntax_comma: + if (eExpect == EExpect::value_or_end) throw XTOMLParseException("Expecting value or array end."); + m_lexer.Consume(); + eExpect = EExpect::value_or_end; + refCommaToken = refToken; + bAdditionalCommaBeforeEnd = true; + break; + default: + { + bAdditionalCommaBeforeEnd = false; + if (eExpect == EExpect::comma_or_end) throw XTOMLParseException("Expecting comma or array end."); + CLexer lexerLocal("[" + std::to_string(nIndex++) + "]", true); + CTokenRange rangeIndexKey(lexerLocal.Peek(), lexerLocal.Peek().JumpToEnd()); + rangeNode.NodeMain(CTokenRange(refToken, refToken.get().Next())); + ProcessValue(rangeIndexKey, rangeNode); + eExpect = EExpect::comma_or_end; + + // Update the start of the next value range + refStartValueToken = rangeNode.LinesBehindNode().End(); + + break; + } + } + + // Get the next value + refToken = m_lexer.Peek(); + } + + // This is the node token range finishing main part. + rNodeRange.NodeMainFinish( + CTokenRange(bAdditionalCommaBeforeEnd ? refStartValueToken.get() : refToken.get(), refToken.get().Next())); + + // Consume the token + m_lexer.Consume(); + } + + void CParser::ProcessInlineTable(CNodeTokenRange& rNodeRange) + { + /* + Inline tables are defined as follow: table_name = {value, value, ...} + For example: + name = { first = "Tom", last = "Preston-Werner" } + point = { x = 1, y = 2 } + animal = { type.name = "pug" } + */ + + // The initial start position for the value token is the end of the first main part. + std::reference_wrapper refStartValueToken = rNodeRange.LinesBehindNode().End(); + + // Iterator token + std::reference_wrapper refToken = m_lexer.Peek(); + + // State machine + enum class EExpect + { + value_or_end, + value, + comma_or_end + } eExpect = EExpect::value_or_end; + + // Iterate through the value + while (refToken.get() && refToken.get().Category() != ETokenCategory::token_syntax_inline_table_close) + { + // The value has its own token range + CNodeTokenRange rangeNode(refStartValueToken.get()); + + switch (refToken.get().Category()) + { + case ETokenCategory::token_syntax_new_line: + m_lexer.Consume(); + break; + case ETokenCategory::token_syntax_comma: + if (eExpect != EExpect::comma_or_end) throw XTOMLParseException("Expecting value or table end."); + m_lexer.Consume(); + eExpect = EExpect::value; + break; + default: + if (eExpect == EExpect::comma_or_end) throw XTOMLParseException("Expecting comma or table end."); + ProcessValueKey(rangeNode); + eExpect = EExpect::comma_or_end; + break; + } + + // Update the start of the next value range + refStartValueToken = rangeNode.LinesBehindNode().End(); + + // Get the next value + refToken = m_lexer.Peek(); + } + if (eExpect == EExpect::value) + throw XTOMLParseException("Expecting a value before inline table end."); + + // This is the node token range finishing main part. + rNodeRange.NodeMainFinish(CTokenRange(refToken.get(), refToken.get().Next())); + + // Consume the token + m_lexer.Consume(); + } + + CTokenRange CParser::ProcessKeyPath() + { + //std::string ssPath; + std::reference_wrapper refToken = m_lexer.Peek(); + std::reference_wrapper refKeyStart = refToken; + if (!refToken.get() + || (refToken.get().Category() != ETokenCategory::token_syntax_dot + && refToken.get().Category() != ETokenCategory::token_key)) + throw XTOMLParseException("Invalid Token to assemble path from keys"); + + while (refToken.get() + && (refToken.get().Category() == ETokenCategory::token_syntax_dot + || refToken.get().Category() == ETokenCategory::token_key)) + { + m_lexer.Consume(); + //if (refToken.get().Category() == ETokenCategory::token_key) + //{ + // EQuoteRequest eQuoteRequest = EQuoteRequest::smart_key; + // switch (refToken.get().StringType()) + // { + // case ETokenStringType::literal_string: + // eQuoteRequest = EQuoteRequest::literal_text; + // break; + // case ETokenStringType::quoted_string: + // eQuoteRequest = EQuoteRequest::quoted_text; + // break; + // case ETokenStringType::multi_line_literal: + // eQuoteRequest = EQuoteRequest::multi_line_literal_text; + // break; + // case ETokenStringType::multi_line_quoted: + // eQuoteRequest = EQuoteRequest::multi_line_quoted_text; + // break; + // default: + // break; + // } + //} + refToken = m_lexer.Peek(); + } + + return CTokenRange(refKeyStart.get(), refToken); + } +} // namespace toml_parser \ No newline at end of file diff --git a/sdv_services/core/toml_parser/parser_toml.h b/sdv_services/core/toml_parser/parser_toml.h index d15374a..34eeb9a 100644 --- a/sdv_services/core/toml_parser/parser_toml.h +++ b/sdv_services/core/toml_parser/parser_toml.h @@ -3,171 +3,137 @@ #include "lexer_toml.h" #include "parser_node_toml.h" +#include "miscellaneous.h" +#include +#include +#include -/** - * @brief Creates a tree structure from input of UTF-8 encoded TOML source data - */ -class CParserTOML : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser +/// The TOML parser namespace +namespace toml_parser { -public: + class CNode; + /** - * @brief Default constructor + * @brief Creates a tree structure from input of UTF-8 encoded TOML source data */ - CParserTOML() = default; - - /** - * @brief Construct a new Parser object - * @param[in] rssString UTF-8 encoded data of a TOML source - */ - CParserTOML(const std::string& rssString); - - // Interface map - BEGIN_SDV_INTERFACE_MAP() - SDV_INTERFACE_ENTRY(sdv::toml::ITOMLParser) - SDV_INTERFACE_CHAIN_MEMBER(m_ptrRoot) - END_SDV_INTERFACE_MAP() - - /** - * @brief Clears the current parse result. - * @attention This will render any pointer invalid! - */ - void Clear(); - - // Ignore cppcheck warning for not using dynamic binding when being called through the constructor. - // cppcheck-suppress virtualCallInConstructor - /** - * @brief Process the configuration from the supplied content string. Overload of sdv::toml::ITOMLParser. - * @param[in] ssContent Configuration string. - * @return Returns 'true' when the configuration could be read successfully, false when not. - */ - virtual bool Process(/*in*/ const sdv::u8string& ssContent) override; - - /** - * @{ - * @brief Return the root node. - * @return Reference to the root node collection. - */ - const CNodeCollection& GetRoot() const; - CNodeCollection& GetRoot(); - /** - * @} - */ - - /** - * @brief Get the TOML text based on the content. - * @param[in] rssParent When present, uses the parent node into the TOML text generation. - * @return The string containing the TOML text. - */ - std::string CreateTOMLText(const std::string& rssParent = std::string()) const; - -private: - /** - * @brief Add a collection node (table or array). - * @tparam TCollectionNode The collection node class to add (to create). - * @param[in] rssPath Reference to the node path. - * @return Returns whether the node could be added. - */ - template - bool Add(const std::string& rssPath); - - /** - * @brief Add a boolean value node. - * @param[in] rssPath Reference to the node path. - * @param[in] bVal The boolean value. - * @return Returns whether the node could be added. - */ - bool Add(const std::string& rssPath, bool bVal); - - /** - * @brief Add a integer value node. - * @param[in] rssPath Reference to the node path. - * @param[in] iVal The integer value. - * @return Returns whether the node could be added. - */ - bool Add(const std::string& rssPath, int64_t iVal); - - /** - * @brief Add a floating point value node. - * @param[in] rssPath Reference to the node path. - * @param[in] dVal The floating point value. - * @return Returns whether the node could be added. - */ - bool Add(const std::string& rssPath, double dVal); - - /** - * @brief Add a string value node. - * @param[in] rssPath Reference to the node path. - * @param[in] rssVal Reference to the string value. - * @return Returns whether the node could be added. - */ - bool Add(const std::string& rssPath, const std::string& rssVal); - - /** - * @brief Process a table declaration. - */ - void ProcessTable(); - - /** - * @brief Process a table array declaration. - */ - void ProcessTableArray(); - - /** - * @brief Process the value key. - */ - void ProcessValueKey(); - - /** - * @brief Process the value with the supplied key. - * @param[in] rssKeyPath Reference to the key path string. - */ - void ProcessValue(const std::string& rssKeyPath); - - /** - * @brief Process the array value with the supplied key. - * @param[in] rssKeyPath Reference to the key path string. - */ - void ProcessArray(const std::string& rssKeyPath); - - /** - * @brief Process the inline table value with the supplied key. - * @param[in] rssKeyPath Reference to the key path string. - */ - void ProcessInlineTable(const std::string& rssKeyPath); - - /** - * @brief Compose a path from lexer tokens. A path is composed of table and array elements separated with a dot. - * @return The composed path. - */ - std::string ComposePath(); - - /** - * @brief Enum for differentiating between an array environment and an inline table environment for syntax checks. - */ - enum class EEnvironment + class CParser : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser { - env_array, //!< Environment for an array - env_inline_table //!< Environment for a table + public: + /** + * @brief Default constructor + */ + CParser() = default; + + /** + * @brief Construct a new Parser object + * @param[in] rssString UTF-8 encoded data of a TOML source + */ + CParser(const std::string& rssString); + + // Interface map + BEGIN_SDV_INTERFACE_MAP() + SDV_INTERFACE_ENTRY(sdv::toml::ITOMLParser) + SDV_INTERFACE_CHAIN_MEMBER(m_ptrRoot) + END_SDV_INTERFACE_MAP() + + /** + * @brief Clears the current parse result. + * @attention This will render any pointer invalid! + */ + void Clear(); + + // Ignore cppcheck warning for not using dynamic binding when being called through the constructor. + // cppcheck-suppress virtualCallInConstructor + /** + * @brief Process the configuration from the supplied content string. Overload of sdv::toml::ITOMLParser. + * @param[in] ssContent Configuration string. + * @return Returns 'true' when the configuration could be read successfully, false when not. + */ + virtual bool Process(/*in*/ const sdv::u8string& ssContent) override; + + /** + * @brief Get the lexer containing the token list. + * @return A reference to the lexer containing the token list. + */ + CLexer& Lexer(); + + /** + * @{ + * @brief Return the root node. + * @return Reference to the root node collection. + */ + const CNodeCollection& Root() const; + CNodeCollection& Root(); + /** + * @} + */ + + /** + * @brief Get the TOML text based on the content. + * @param[in] rssPrefixKey When present, uses the prefix node into the TOML text generation. The string must follow the key + * rules for separation with bare, literal and quoted keys. + * @return The string containing the TOML text. + */ + std::string GenerateTOML(const std::string& rssPrefixKey = std::string()) const; + + private: + /** + * @brief Process a table declaration. + * @param[in, out] rNodeRange Reference to the extended token range of the node. + */ + void ProcessTable(CNodeTokenRange& rNodeRange); + + /** + * @brief Process a table array declaration. + * @param[in, out] rNodeRange Reference to the extended token range of the node. + */ + void ProcessTableArray(CNodeTokenRange& rNodeRange); + + /** + * @brief Process the value key. + * @param[in, out] rNodeRange Reference to the extended token range of the node. + */ + void ProcessValueKey(CNodeTokenRange& rNodeRange); + + /** + * @brief Process the value with the supplied key. + * @param[in] rrangeKeyPath Reference to the key path token range. + * @param[in, out] rNodeRange Reference to the extended token range of the node. + */ + void ProcessValue(const CTokenRange& rrangeKeyPath, CNodeTokenRange& rNodeRange); + + /** + * @brief Process the array value with the supplied key. + * @param[in, out] rNodeRange Reference to the extended token range of the node. The second main range will be added. + */ + void ProcessArray(CNodeTokenRange& rNodeRange); + + /** + * @brief Process the inline table value with the supplied key. + * @param[in, out] rNodeRange Reference to the extended token range of the node. The second main range will be added. + */ + void ProcessInlineTable(CNodeTokenRange& rNodeRange); + + /** + * @brief Compose a path from lexer tokens. A path is composed of table and array elements separated with a dot. + * @return The token range of the key path. + */ + CTokenRange ProcessKeyPath(); + + /** + * @brief Enum for differentiating between an array environment and an inline table environment for syntax checks. + */ + enum class EEnvironment + { + env_array, ///< Environment for an array + env_inline_table ///< Environment for a table + }; + + std::stack m_stackEnvironment; ///< Tracking of environments in nested structures. + std::shared_ptr m_ptrRoot; ///< The one root node. + std::shared_ptr m_ptrCurrentCollection; ///< The current collection node. + CLexer m_lexer; ///< Lexer. }; - std::stack m_stackEnvironment; ///< Tracking of environments in nested structures. - std::shared_ptr m_ptrRoot = std::make_shared(); ///< The one root node. - std::string m_ssCurrentTable; ///< Path to the current table. - CLexerTOML m_lexer; ///< Lexer. -}; - -template -inline bool CParserTOML::Add(const std::string& rssPath) -{ - size_t nOffset = rssPath.rfind('.'); - if (nOffset == std::string::npos) - nOffset = 0; - else - nOffset++; - std::string ssName = rssPath.substr(nOffset); - - m_ptrRoot->Add(rssPath, std::make_shared(ssName), true); - - return true; -} +} // namespace toml_parser #endif // PARSER_TOML_H diff --git a/sdv_services/core/toml_parser_util.h b/sdv_services/core/toml_parser_util.h index 69f646d..1434a6f 100644 --- a/sdv_services/core/toml_parser_util.h +++ b/sdv_services/core/toml_parser_util.h @@ -25,7 +25,7 @@ public: DECLARE_OBJECT_CLASS_NAME("TOMLParserUtility") private: - CParserTOML m_parser; ///< Configuration parser + toml_parser::CParser m_parser; ///< Configuration parser }; DEFINE_SDV_OBJECT_NO_EXPORT(CTOMLParserUtility) diff --git a/sdv_services/ipc_com/com_channel.cpp b/sdv_services/ipc_com/com_channel.cpp index 1b29017..ca03a5a 100644 --- a/sdv_services/ipc_com/com_channel.cpp +++ b/sdv_services/ipc_com/com_channel.cpp @@ -12,7 +12,8 @@ CChannelConnector::CChannelConnector(CCommunicationControl& rcontrol, uint32_t u m_pDataSend(m_ptrChannelEndpoint.GetInterface()) { m_tConnectionID.uiIdent = uiIndex; - m_tConnectionID.uiControl = static_cast(rand()); + while (!m_tConnectionID.uiControl) + m_tConnectionID.uiControl = static_cast(rand()); } CChannelConnector::~CChannelConnector() @@ -29,7 +30,7 @@ CChannelConnector::~CChannelConnector() lock.unlock(); // Cancel the processing - rsEntry.bCancel = true; + rsEntry.eState = SCallEntry::EState::canceled; rsEntry.cvWaitForResult.notify_all(); // Handle next call. @@ -44,6 +45,10 @@ CChannelConnector::~CChannelConnector() if (m_uiConnectionStatusCookie) pConnection->UnregisterStatusEventCallback(m_uiConnectionStatusCookie); pConnection->Disconnect(); } + + // There are several dependencies on this channel connector, which should be availble during the processing of the asynchronous + // disconnect function. Wait for a quarter second to allow the processing to complete. + std::this_thread::sleep_for(std::chrono::milliseconds(250)); } bool CChannelConnector::ServerConnect(sdv::IInterfaceAccess* pObject, bool bAllowReconnect) @@ -230,20 +235,25 @@ void CChannelConnector::DecoupledReceiveData(/*inout*/ sdv::sequenceSendData(seqResult); - } else + } + else { // Look for the call entry std::unique_lock lockCallMap(m_mtxCalls); auto itCall = m_mapCalls.find(sAddress.uiCallIndex); - if (itCall == m_mapCalls.end()) return; + if (itCall == m_mapCalls.end()) + return; SCallEntry& rsCallEntry = itCall->second; m_mapCalls.erase(itCall); lockCallMap.unlock(); + if (rsCallEntry.eState != SCallEntry::EState::processing) + return; // Update the result std::unique_lock lockCall(rsCallEntry.mtxWaitForResult); rsCallEntry.seqResult = seqData; lockCall.unlock(); + rsCallEntry.eState = SCallEntry::EState::processed; rsCallEntry.cvWaitForResult.notify_all(); } } @@ -271,12 +281,14 @@ sdv::sequence> CChannelConnector::MakeCall(sdv::ps::TMarsh std::unique_lock lock(m_mtxCalls); SCallEntry sResult; m_mapCalls.try_emplace(sAddress.uiCallIndex, sResult); + sResult.eState = SCallEntry::EState::processing; lock.unlock(); // Store the channel context (used to marshall interfaces over the same connector) m_rcontrol.SetConnectorContext(this); // Send the data + std::unique_lock lockResult(sResult.mtxWaitForResult); try { if (!m_pDataSend->SendData(rseqInputData)) throw sdv::ps::XMarshallExcept(); @@ -291,10 +303,13 @@ sdv::sequence> CChannelConnector::MakeCall(sdv::ps::TMarsh } // Wait for the result - if (sResult.bCancel) throw sdv::ps::XMarshallTimeout(); - std::unique_lock lockResult(sResult.mtxWaitForResult); - sResult.cvWaitForResult.wait(lockResult); - if (sResult.bCancel) throw sdv::ps::XMarshallTimeout(); + // NOTE: Sinde the conditional variable doesn't keep its state, it might happen, that the variable is set before the wait + // function has been entered (race condition). This would cause the function to wait forever. + while (sResult.eState == SCallEntry::EState::processing) + sResult.cvWaitForResult.wait_for(lockResult, std::chrono::milliseconds(1)); + + if (sResult.eState == SCallEntry::EState::canceled) + throw sdv::ps::XMarshallTimeout(); return sResult.seqResult; } diff --git a/sdv_services/ipc_com/com_channel.h b/sdv_services/ipc_com/com_channel.h index 18335d5..e963805 100644 --- a/sdv_services/ipc_com/com_channel.h +++ b/sdv_services/ipc_com/com_channel.h @@ -114,10 +114,19 @@ private: */ struct SCallEntry { - sdv::sequence> seqResult; ///< The result data. - std::mutex mtxWaitForResult; ///< Mutex to protect result processing. - std::condition_variable cvWaitForResult; ///< Condition variable to trigger result processing. - bool bCancel = false; ///< Cancel processing when set. + /** + * @brief Call entry state. + */ + enum EState + { + initialized, + processing, + processed, + canceled, + } eState = EState::initialized; ///< Data processing state. + sdv::sequence> seqResult; ///< The result data. + std::mutex mtxWaitForResult; ///< Mutex to protect result processing. + std::condition_variable cvWaitForResult; ///< Condition variable to trigger result processing. }; CCommunicationControl& m_rcontrol; ///< Reference to the communication control class. diff --git a/sdv_services/ipc_com/marshall_object.cpp b/sdv_services/ipc_com/marshall_object.cpp index f50878e..21e4a8d 100644 --- a/sdv_services/ipc_com/marshall_object.cpp +++ b/sdv_services/ipc_com/marshall_object.cpp @@ -32,7 +32,9 @@ sdv::interface_t CMarshallObject::InitializeAsProxy(uint32_t uiProxyIndex, sdv:: m_eType = EType::proxy; // Create marshall ID from index and a random number. - sdv::ps::TMarshallID tMarshallID = { 0, GetProcessID(), uiProxyIndex, static_cast(rand()) }; + sdv::ps::TMarshallID tMarshallID = { 0, GetProcessID(), uiProxyIndex, 0 }; + while (!tMarshallID.uiControl) + tMarshallID.uiControl = static_cast(rand()); // Get the stub creation interface from the repository sdv::core::IRepositoryMarshallCreate* pMarshallCreate = @@ -99,6 +101,8 @@ bool CMarshallObject::InitializeAsStub(uint32_t uiStubIndex, sdv::interface_t if // Create marshall ID from index and a random number. sdv::ps::TMarshallID tMarshallID = { 0, GetProcessID(), uiStubIndex, static_cast(rand()) }; + while (!tMarshallID.uiControl) + tMarshallID.uiControl = static_cast(rand()); // Get the stub creation interface from the repository sdv::core::IRepositoryMarshallCreate* pMarshallCreate = diff --git a/sdv_services/ipc_shared_mem/connection.cpp b/sdv_services/ipc_shared_mem/connection.cpp index e03190c..7bd9a2a 100644 --- a/sdv_services/ipc_shared_mem/connection.cpp +++ b/sdv_services/ipc_shared_mem/connection.cpp @@ -342,7 +342,8 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver) if (m_eStatus != sdv::ipc::EConnectStatus::uninitialized) { for (auto& rprEventCallback : m_lstEventCallbacks) - if (rprEventCallback.pCallback) rprEventCallback.pCallback->SetStatus(sdv::ipc::EConnectStatus::connection_error); + if (rprEventCallback.pCallback && rprEventCallback.uiCookie) + rprEventCallback.pCallback->SetStatus(sdv::ipc::EConnectStatus::connection_error); return false; } @@ -456,6 +457,8 @@ uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* sdv::ipc::IConnectEventCallback* pCallback = pEventCallback->GetInterface(); if (!pCallback) return 0; uint64_t uiCookie = rand(); + while (!uiCookie) + uiCookie = rand(); std::unique_lock lock(m_mtxEventCallbacks); m_lstEventCallbacks.emplace(m_lstEventCallbacks.begin(), std::move(SEventCallback{ uiCookie, pCallback })); return uiCookie; @@ -490,7 +493,10 @@ void CConnection::DestroyObject() // Clear all events callbacks (if not done so already) std::shared_lock lock(m_mtxEventCallbacks); for (auto& rprEventCallback : m_lstEventCallbacks) + { + rprEventCallback.uiCookie = 0; rprEventCallback.pCallback = nullptr; + } lock.unlock(); // Just in case... so no calls are made into the destructed class any more. @@ -519,7 +525,10 @@ void CConnection::SetStatus(sdv::ipc::EConnectStatus eStatus) m_eStatus = eStatus; std::shared_lock lock(m_mtxEventCallbacks); for (auto& rprEventCallback : m_lstEventCallbacks) - if (rprEventCallback.pCallback) rprEventCallback.pCallback->SetStatus(eStatus); + { + if (rprEventCallback.pCallback && rprEventCallback.uiCookie) + rprEventCallback.pCallback->SetStatus(eStatus); + } // If disconnected by force update the disconnect status. if (m_eStatus == sdv::ipc::EConnectStatus::disconnected_forced) @@ -554,12 +563,13 @@ void CConnection::ReceiveMessages() { SetStatus(sdv::ipc::EConnectStatus::communication_error); SDV_LOG_ERROR("No valid shared memory for receiving."); + lock.unlock(); m_cvStartConnect.notify_all(); return; } - m_cvStartConnect.notify_all(); lock.unlock(); + m_cvStartConnect.notify_all(); // Read processing auto tpStart = std::chrono::high_resolution_clock::time_point(); @@ -623,8 +633,24 @@ void CConnection::ReceiveMessages() case EMsgType::data_fragment: case EMsgType::data: break; + case EMsgType::sync_request: + TRACE("Receive sync-request message of of ", message.GetSize(), " bytes"); + break; + case EMsgType::sync_answer: + TRACE("Receive sync-answer message of of ", message.GetSize(), " bytes"); + break; + case EMsgType::connect_request: + TRACE("Receive connect-request message of of ", message.GetSize(), " bytes"); + break; + case EMsgType::connect_answer: + TRACE("Receive connect-answer message of of ", message.GetSize(), " bytes"); + break; + case EMsgType::connect_term: + TRACE("Receive connect-termination message of of ", message.GetSize(), " bytes"); + break; default: - TRACE("Receive raw data 0x", (void*) &message.GetMsgHdr(), " of ", message.GetSize(), " bytes"); + TRACE("Received unknown message of type ", (uint32_t) message.GetMsgHdr().eType, " of ", message.GetSize(), " bytes"); + break; } #endif @@ -1011,7 +1037,7 @@ void CConnection::ReceiveDataFragementMessage(CMessage& rMessage, SDataContext& if (!rMessage.GetFragmentedHdr().uiOffset) { #if ENABLE_REPORTING >= 1 - TRACE("Start receive fragmented data message of ", rsDataCtxt.uiTotalSize, " bytes"); + TRACE("Start receive fragmented data message of ", rsDataCtxt.uiTotalSize, " bytes (following the header)"); #endif // Read the data directory table... // Note: it is assumed that the table fits in the first message completely. @@ -1029,7 +1055,7 @@ void CConnection::ReceiveDataFragementMessage(CMessage& rMessage, SDataContext& if (!sstream.str().empty()) sstream << ", "; sstream << rptrData.size(); } - TRACE("Fragmented message has ", rsDataCtxt.seqDataChunks.size(), " of {", sstream.str(), "} bytes"); + TRACE("Fragmented message has ", rsDataCtxt.seqDataChunks.size(), " chunk of ", sstream.str(), " bytes"); #endif } diff --git a/sdv_services/ipc_shared_mem/connection.h b/sdv_services/ipc_shared_mem/connection.h index cb94ab9..913625c 100644 --- a/sdv_services/ipc_shared_mem/connection.h +++ b/sdv_services/ipc_shared_mem/connection.h @@ -22,7 +22,7 @@ /// When put to 1, decoupling of receive data is activated (default is not activated). #define ENABLE_DECOUPLING 0 -#if ENABLE_REPORTING > 0 +#if ENABLE_REPORTING > 0 && !defined(ENABLE_TRACE) /// Enable tracing #define ENABLE_TRACE 1 #endif diff --git a/sdv_services/ipc_shared_mem/in_process_mem_buffer.h b/sdv_services/ipc_shared_mem/in_process_mem_buffer.h index 2bcfcf1..3aabbc5 100644 --- a/sdv_services/ipc_shared_mem/in_process_mem_buffer.h +++ b/sdv_services/ipc_shared_mem/in_process_mem_buffer.h @@ -29,7 +29,7 @@ public: CInProcMemBuffer(const std::string& rssConnectionString); /** - * \brief Default destructor + * @brief Default destructor */ ~CInProcMemBuffer() = default; diff --git a/sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h b/sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h index 55f15cb..4013865 100644 --- a/sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h +++ b/sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h @@ -43,7 +43,7 @@ public: CSharedMemBuffer(CSharedMemBuffer&&) = delete; /** - * \brief Default destructor + * @brief Default destructor */ ~CSharedMemBuffer(); @@ -364,10 +364,15 @@ CSharedMemBuffer::~CSharedMemBuffer() munmap(m_pBuffer, m_uiSize); if (m_bServer && !m_ssName.empty()) shm_unlink((std::string("/") + m_ssName).c_str()); + if (m_iFileDescr >= 0) close(m_iFileDescr); if (m_bServer && m_pSemaphoreTx) sem_unlink(m_ssSyncTx.c_str()); + if (m_pSemaphoreTx) + sem_close(m_pSemaphoreTx); if (m_bServer && m_pSemaphoreRx) sem_unlink(m_ssSyncRx.c_str()); + if (m_pSemaphoreRx) + sem_close(m_pSemaphoreRx); } template diff --git a/sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h b/sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h index e2a0bff..41507d4 100644 --- a/sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h +++ b/sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h @@ -53,7 +53,7 @@ public: CSharedMemBuffer(CSharedMemBuffer&&) = delete; /** - * \brief Default destructor + * @brief Default destructor */ ~CSharedMemBuffer(); diff --git a/sdv_services/ipc_shared_mem/watchdog.h b/sdv_services/ipc_shared_mem/watchdog.h index e937bea..4eefdd0 100644 --- a/sdv_services/ipc_shared_mem/watchdog.h +++ b/sdv_services/ipc_shared_mem/watchdog.h @@ -28,6 +28,7 @@ #include #include #include +#include // Forward declaration class CConnection; @@ -114,7 +115,7 @@ private: ///< connection is scheduled for destruction. std::queue> m_queueScheduledConnectionDestructions; ///< Scheduled connection for destruction. std::thread m_threadScheduledConnectionDestructions; ///< Thread processing the scheduled destructions. - bool m_bShutdown = false; ///< Set when shutting down the watchdog + std::atomic_bool m_bShutdown = false; ///< Set when shutting down the watchdog }; #endif // !defined WATCH_DOG_H \ No newline at end of file diff --git a/sdv_services/process_control/process_control.h b/sdv_services/process_control/process_control.h index 31621b7..1a1cf21 100644 --- a/sdv_services/process_control/process_control.h +++ b/sdv_services/process_control/process_control.h @@ -17,6 +17,7 @@ #include #include #include +#include /** * @brief Process control service class @@ -135,41 +136,40 @@ public: void MonitorThread(); sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status. - - std::mutex m_mtxProcessThreadShutdown; ///< Synchronize access + std::mutex m_mtxProcessThreadShutdown; ///< Synchronize access #ifdef _WIN32 std::map> m_mapProcessThreadShutdown; ///< Map with process IDs and event handles #elif __unix__ - std::set m_setProcessThreadShutdown; ///< Set with process IDs + std::set m_setProcessThreadShutdown; ///< Set with process IDs #else #error OS is not supported! #endif - /** - * @brief Process helper structure - */ - struct SProcessHelper - { - sdv::process::TProcessID tProcessID = 0; ///< Process ID + /** + * @brief Process helper structure + */ + struct SProcessHelper + { + sdv::process::TProcessID tProcessID = 0; ///< Process ID #ifdef _WIN32 - HANDLE hProcess = 0; ///< process handle + HANDLE hProcess = 0; ///< process handle #elif defined __unix__ - bool bNotAChild = false; ///< When set, the process is not a child of the monitor process. + bool bNotAChild = false; ///< When set, the process is not a child of the monitor process. #else #error OS is not supported! #endif - bool bRunning = true; ///< Set when the process is running and not terminated yet. - int64_t iRetVal = 0; ///< Process return value. - std::map mapAssociatedMonitors; ///< Map with associated monitors. - std::mutex mtxProcess; ///< Mutex for process access. - std::condition_variable cvWaitForProcess; ///< Condition variable to wait for process termination. - }; - mutable std::mutex m_mtxProcesses; ///< Access control for monitor map. - std::map> m_mapProcesses; ///< Monitor map - uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie - std::map> m_mapMonitors; ///< Map with monitors. - bool m_bShutdown = false; ///< Set to shutdown the monitor thread. - std::thread m_threadMonitor; ///< Monitor thread. + std::atomic_bool bRunning = true; ///< Set when the process is running and not terminated yet. + int64_t iRetVal = 0; ///< Process return value. + std::map mapAssociatedMonitors; ///< Map with associated monitors. + std::mutex mtxProcess; ///< Mutex for process access. + std::condition_variable cvWaitForProcess; ///< Condition variable to wait for process termination. + }; + mutable std::mutex m_mtxProcesses; ///< Access control for monitor map. + std::map> m_mapProcesses; ///< Monitor map + uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie + std::map> m_mapMonitors; ///< Map with monitors. + std::atomic_bool m_bShutdown = false; ///< Set to shutdown the monitor thread. + std::thread m_threadMonitor; ///< Monitor thread. }; DEFINE_SDV_OBJECT(CProcessControl) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a626684..d471853 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,7 +58,7 @@ add_subdirectory(unit_tests/smart_ifc) add_subdirectory(unit_tests/toml_parser) add_subdirectory(unit_tests/module_control) add_subdirectory(unit_tests/repository) -# add_subdirectory(unit_tests/shared_mem) +add_subdirectory(unit_tests/shared_mem) add_subdirectory(unit_tests/memory_manager) add_subdirectory(unit_tests/core_loader) add_subdirectory(unit_tests/named_mutex) diff --git a/tests/README.md b/tests/README.md index 4fee7f7..3856cac 100644 --- a/tests/README.md +++ b/tests/README.md @@ -11,7 +11,7 @@ or # SDV TEST MACRO ## Overview -There is set of macros in "export/support/sdv_test_macro.h" which are designed to extend the Google Test (GTEST) framework by incorporating a warning level mechanism. These macros allow developers to specify a warning level for each test assertion, providing more control over test outcomes and enabling better handling of test result conditions based on their warning level. +There is set of macros in "tests/include/sdv_test_macro.h" which are designed to extend the Google Test (GTEST) framework by incorporating a warning level mechanism. These macros allow developers to specify a warning level for each test assertion, providing more control over test outcomes and enabling better handling of test result conditions based on their warning level. ## Purpose The primary purpose of these macros is to enhance the flexibility and robustness of test assertions in the GTEST framework. By integrating a warning level, developers can decide whether to report failed tests as warning or error and handle them accordingly. This is particularly useful in where different test failures may have varying impacts on the overall build system. @@ -62,19 +62,19 @@ The WarningLevel enum defines three levels of warnings: Additionally SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD is implemented to check if tests are running with CMake build or any other way. It returns true if they are running with CMake build. ## Usage -To use these macros, include the header file '#include " in your test files. When writing test cases, use the SDV macros instead of the standard GTEST macros, and specify the appropriate warning level for each assertion. +To use these macros, include the header file '#include "tests/include/sdv_test_macro.h"' in your test files. When writing test cases, use the SDV macros instead of the standard GTEST macros, and specify the appropriate warning level for each assertion. ```cpp -#include +#include "tests/include/sdv_test_macro.h" TEST(SDVTestMacros, TestExpectEq) { int val1 = 5; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_EQ(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_EQ(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_EQ(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_EQ(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectStreq) @@ -82,9 +82,9 @@ TEST(SDVTestMacros, TestExpectStreq) std::string str1 = "test"; std::string str2 = "test"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_STREQ(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_STREQ(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_STREQ(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_STREQ(str1, str2, sdv_test::WARNING_ENABLED); } ``` In this example, the SDV_EXPECT_EQ macro checks if val1 and val2 are equal, while the SDV_EXPECT_STREQ macro checks if str1 and str2 are equal. In both test cases, it is checked whether it is running diff --git a/tests/component_tests/config/config_tests.cpp b/tests/component_tests/config/config_tests.cpp index abaaf95..c580f6e 100644 --- a/tests/component_tests/config/config_tests.cpp +++ b/tests/component_tests/config/config_tests.cpp @@ -163,11 +163,11 @@ ViewFilter = "Fatal" auto table1 = config.GetDirect("newTableArray[0]"); EXPECT_EQ(table1.GetType(), sdv::toml::ENodeType::node_table); - EXPECT_TRUE(table1.GetName().empty()); + EXPECT_EQ(table1.GetName(), "newTableArray"); auto table2 = config.GetDirect("newTableArray[1]"); EXPECT_EQ(table2.GetType(), sdv::toml::ENodeType::node_table); - EXPECT_TRUE(table2.GetName().empty()); + EXPECT_EQ(table2.GetName(), "newTableArray"); config.Clear(); appcontrol.Shutdown(); @@ -770,6 +770,11 @@ ViewFilter = "Fatal" [j."Êž".'l'] )")); + EXPECT_FALSE(sdv::toml::CTOMLParser(u8R"( + [ j . "Êž" . 'l' ] + ["j".'Êž'."l"] + )")); + EXPECT_FALSE(sdv::toml::CTOMLParser(R"( [fruit] apple = "red" diff --git a/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp b/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp index 9c1435a..15a4f4c 100644 --- a/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp +++ b/tests/component_tests/data_dispatch_service/data_dispatch_service_test.cpp @@ -929,13 +929,13 @@ TEST(DataDispatchServiceTest, DirectRxTxSignalConcurrency) while (!bShutdown) { signalPubA.Write(std::rand()); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); signalPubB.Write(std::rand()); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); signalPubC.Write(std::rand()); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); signalPubD.Write(std::rand()); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); } } catch (...) diff --git a/tests/component_tests/data_dispatch_service/main.cpp b/tests/component_tests/data_dispatch_service/main.cpp index 6dfdefb..b916ff6 100644 --- a/tests/component_tests/data_dispatch_service/main.cpp +++ b/tests/component_tests/data_dispatch_service/main.cpp @@ -1,4 +1,5 @@ #include +#include #include "../../../global/process_watchdog.h" #if defined(_WIN32) && defined(_UNICODE) @@ -7,8 +8,30 @@ extern "C" int wmain(int argc, wchar_t* argv[]) extern "C" int main(int argc, char* argv[]) #endif { - CProcessWatchdog watchdog; + // Check for the --gtest_repeat option. + bool bRepeatEnabled = false; + for (int iIndex = 0; iIndex < argc; iIndex++) + { + if (!argv[iIndex]) + continue; +#if defined(_WIN32) && defined(_UNICODE) + bRepeatEnabled |= std::wcsncmp(argv[iIndex], L"--gtest_repeat", 14) == 0; +#else + bRepeatEnabled |= std::strncmp(argv[iIndex], "--gtest_repeat", 14) == 0; +#endif + } - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + // When repeat is enabled, do not enable the watchdog. + if (bRepeatEnabled) + { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } + else + { + CProcessWatchdog watchdog; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } } diff --git a/tests/component_tests/data_dispatch_service/transaction_test.cpp b/tests/component_tests/data_dispatch_service/transaction_test.cpp index 3a516aa..b3e64fb 100644 --- a/tests/component_tests/data_dispatch_service/transaction_test.cpp +++ b/tests/component_tests/data_dispatch_service/transaction_test.cpp @@ -1446,7 +1446,7 @@ TEST(DataDispatchServiceTest, TransactionalRxTxSignalConcurrency) std::atomic_uint64_t uiValueCnt = 1000; - bool bShutdown = false; + bool bShutdownPublisher = false, bShutdownConsumer = false; std::srand(static_cast(std::time(0))); // Thread sync @@ -1471,18 +1471,18 @@ TEST(DataDispatchServiceTest, TransactionalRxTxSignalConcurrency) uiInitCnt++; std::shared_lock lock(mtxStart); - while (!bShutdown) + while (!bShutdownPublisher) { sdv::core::CTransaction transaction = dispatch.CreateTransaction(); uint64_t uiValue = std::rand(); signalPubA.Write(uiValue, transaction); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); signalPubB.Write(uiValue + 10, transaction); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); signalPubC.Write(uiValue + 20, transaction); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); signalPubD.Write(uiValue + 30, transaction); - std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 10)); + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 10)); transaction.Finish(); } } @@ -1513,7 +1513,7 @@ TEST(DataDispatchServiceTest, TransactionalRxTxSignalConcurrency) uiInitCnt++; std::shared_lock lock(mtxStart); - while (!bShutdown) + while (!bShutdownConsumer) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -1616,9 +1616,11 @@ TEST(DataDispatchServiceTest, TransactionalRxTxSignalConcurrency) // Wait for all threads to finalize appcontrol.SetConfigMode(); - bShutdown = true; + bShutdownPublisher = true; for (std::thread& rThread : rgPublishThreads) if (rThread.joinable()) rThread.join(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + bShutdownConsumer = true; for (std::thread& rThread : rgConsumeThreads) if (rThread.joinable()) rThread.join(); diff --git a/tests/component_tests/dbc_util/dbc_util_test.cpp b/tests/component_tests/dbc_util/dbc_util_test.cpp index 5bf6b72..5475140 100644 --- a/tests/component_tests/dbc_util/dbc_util_test.cpp +++ b/tests/component_tests/dbc_util/dbc_util_test.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include "../../include/sdv_test_macro.h" #include "../../../global/process_watchdog.h" #include "../global/ascformat/ascreader.cpp" #include "../global/ascformat/ascwriter.cpp" @@ -2713,38 +2713,38 @@ TEST(DbcUtilCanDLTest, CyclicTransmit) if (iCnt < 4) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED); } for (n = 0; n < 5; n++) { if (n != 0) { - if (vecStat[n] > 6) // In the unlucky case, 6 triggers might have occurred (during startup, there might be many more...). + if (vecStat[n] > 6u) // In the unlucky case, 6 triggers might have occurred (during startup, there might be many more...). { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(vecStat[n], 6, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(vecStat[n], 6, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_ENABLED); } double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0; if (dPeriod > 0.051) // Max 51ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED); } if (n == 4) { if (dPeriod < 0.019) // Min 19ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED); } } else @@ -2752,18 +2752,18 @@ TEST(DbcUtilCanDLTest, CyclicTransmit) if (dPeriod < 0.039) // Min 39ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED); } } } if (vecStat[n] < 4u) // At least 4 triggers. { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(vecStat[n], 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(vecStat[n], 4u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(vecStat[n], 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(vecStat[n], 4u, sdv_test::WARNING_ENABLED); } } } @@ -2857,9 +2857,9 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveTransmit) if (iCnt != 0 && iCnt != 2) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(iCnt, 0, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(iCnt, 0, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_ENABLED); } bInit = true; @@ -2874,9 +2874,9 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveTransmit) if (iCnt < 4) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED); } for (n = 0; n < 5; n++) @@ -2885,40 +2885,40 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveTransmit) { if (n == 2) { - if (vecStat[n] != 1) // One trigger should have occurred due to default value + if (vecStat[n] != 1u) // One trigger should have occurred due to default value { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(vecStat[n], 1, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(vecStat[n], 1, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_ENABLED); } } else { - if (vecStat[n] > 6) // In the unlucky case, 6 triggers might have occurred (during startup, there might be many more...). + if (vecStat[n] > 6u) // In the unlucky case, 6 triggers might have occurred (during startup, there might be many more...). { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(vecStat[n], 6, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(vecStat[n], 6, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_ENABLED); } } double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0; if (dPeriod > 0.051) // Max 51ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED); } if (n == 4) { if (dPeriod < 0.019) // Min 19ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED); } } else @@ -2926,9 +2926,9 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveTransmit) if (dPeriod < 0.039) // Min 39ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED); } } } @@ -3028,38 +3028,38 @@ TEST(DbcUtilCanDLTest, CyclicAndSpontaneousTransmit) if (iCnt < 4) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED); } for (n = 0; n < 5; n++) { if (n != 0) { - if (vecStat[n] != 2) // One trigger and one cycle. + if (vecStat[n] != 2u) // One trigger and one cycle. { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(vecStat[n], 2, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(vecStat[n], 2, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_ENABLED); } double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0; if (dPeriod > 0.051) // Max 51ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED); } if (n == 4) { if (dPeriod < 0.019) // Min 19ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED); } } else @@ -3067,9 +3067,9 @@ TEST(DbcUtilCanDLTest, CyclicAndSpontaneousTransmit) if (dPeriod < 0.039) // Min 39ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED); } } } @@ -3172,43 +3172,43 @@ TEST(DbcUtilCanDLTest, SpontaneousDelayTransmit) if (iCnt < 4) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED); } for (n = 0; n < 5; n++) { if (n != 0) { - if (vecStat[n] > 4) // Max 4 times + if (vecStat[n] > 4u) // Max 4 times { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(vecStat[n], 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(vecStat[n], 4u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(vecStat[n], 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(vecStat[n], 4u, sdv_test::WARNING_ENABLED); } - if (vecStat[n] < 3) // Min 3 times + if (vecStat[n] < 3u) // Min 3 times { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(vecStat[n], 3, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(vecStat[n], 3u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(vecStat[n], 3, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(vecStat[n], 3u, sdv_test::WARNING_ENABLED); } double dPeriod = std::round((vecLastTime[n] - vecFirstTime[n]) * 1000.0) / 1000.0; if (dPeriod > 0.061) // Max 61ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dPeriod, 0.061, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dPeriod, 0.061, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dPeriod, 0.061, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dPeriod, 0.061, sdv_test::WARNING_ENABLED); } if (dPeriod < 0.039) // Min 39ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED); } } } @@ -3310,38 +3310,38 @@ TEST(DbcUtilCanDLTest, CyclicAndSpontaneousDelayTransmit) if (iCnt < 4) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED); } for (n = 0; n < 5; n++) { if (n != 0) { - if (vecStat[n] != 2) // Two trigger and/or cycle. + if (vecStat[n] != 2u) // Two trigger and/or cycle. { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(vecStat[n], 2, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(vecStat[n], 2, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_ENABLED); } double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0; if (dPeriod > 0.051) // Max 51ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED); } if (n == 4) { if (dPeriod < 0.019) // Min 19ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED); } } else @@ -3349,9 +3349,9 @@ TEST(DbcUtilCanDLTest, CyclicAndSpontaneousDelayTransmit) if (dPeriod < 0.049) // Min 49ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.049, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.049, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.049, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.049, sdv_test::WARNING_ENABLED); } } } @@ -3446,9 +3446,9 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveAndSpontaneousTransmit) if (iCnt != 0 && iCnt != 2) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(iCnt, 0, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(iCnt, 0, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_ENABLED); } bInit = true; @@ -3463,9 +3463,9 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveAndSpontaneousTransmit) if (iCnt < 4) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(iCnt, 4, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED); } for (n = 0; n < 5; n++) { @@ -3473,40 +3473,40 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveAndSpontaneousTransmit) { if (n == 2) { - if (vecStat[n] != 1) // One trigger no cycle. + if (vecStat[n] != 1u) // One trigger no cycle. { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(vecStat[n], 1, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(vecStat[n], 1, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_ENABLED); } } else { - if (vecStat[n] != 2) // One trigger and one cycle. + if (vecStat[n] != 2u) // One trigger and one cycle. { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(vecStat[n], 2, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(vecStat[n], 2, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_ENABLED); } } double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0; if (dPeriod > 0.051) // Max 51ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dPeriod, 0.051, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED); } if (n == 4) { if (dPeriod < 0.019) // Min 19ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.019, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED); } } else @@ -3514,9 +3514,9 @@ TEST(DbcUtilCanDLTest, CyclicIfActiveAndSpontaneousTransmit) if (dPeriod < 0.039) // Min 39ms { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dPeriod, 0.039, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED); } } } diff --git a/tests/include/sdv_test_macro.h b/tests/include/sdv_test_macro.h new file mode 100644 index 0000000..9c2ce54 --- /dev/null +++ b/tests/include/sdv_test_macro.h @@ -0,0 +1,273 @@ +#ifndef SDV_TEST_MACRO_H +#define SDV_TEST_MACRO_H + +#include +#include +#include + +/** + * @brief Namespace sdv_test + */ +namespace sdv_test +{ + /** + * @brief Enum for warning levels. + */ + enum EWarningLevel + { + WARNING_ENABLED, //) + +#define SDV_ASSERT_GT(val1, val2, level) \ + HANDLE_WARNING_LEVEL(level, ASSERT_GT(val1, val2), val1, val2, >) + +#define SDV_EXPECT_GE(val1, val2, level) \ + HANDLE_WARNING_LEVEL(level, EXPECT_GE(val1, val2), val1, val2, >=) + +#define SDV_ASSERT_GE(val1, val2, level) \ + HANDLE_WARNING_LEVEL(level, ASSERT_GE(val1, val2), val1, val2, >=) + +#define SDV_EXPECT_STREQ(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, EXPECT_STREQ((str1).c_str(), (str2).c_str()), str1, str2, ==) + +#define SDV_ASSERT_STREQ(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, ASSERT_STREQ((str1).c_str(), (str2).c_str()), str1, str2, ==) + +#define SDV_EXPECT_STRNE(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, EXPECT_STRNE(str1.c_str(), str2.c_str()), str1, str2, !=) + +#define SDV_ASSERT_STRNE(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, ASSERT_STRNE(str1.c_str(), str2.c_str()), str1, str2, !=) + +#define SDV_EXPECT_STRCASEEQ(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, EXPECT_STRCASEEQ((str1).c_str(), (str2).c_str()), str1, str2, ==) + +#define SDV_ASSERT_STRCASEEQ(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, ASSERT_STRCASEEQ((str1).c_str(), (str2).c_str()), str1, str2, ==) + +#define SDV_EXPECT_STRCASENE(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, EXPECT_STRCASENE((str1).c_str(), (str2).c_str()), str1, str2, !=) + +#define SDV_ASSERT_STRCASENE(str1, str2, level) \ + HANDLE_WARNING_LEVEL(level, ASSERT_STRCASENE((str1).c_str(), (str2).c_str()), str1, str2, !=) + +/** + * @brief Macro for equality check (==) with warning reporting for time critical tests. + * @param[in] val1 The first value. + * @param[in] val2 The second value. + * @param[in] warningLevel The level of warning to display. + */ +#define SDV_EXPECT_EQ_WARN(val1, val2, warningLevel) \ + do { \ + auto expr1 = val1; \ + auto expr2 = val2; \ + sdv_test::ReportWarning((expr1) == (expr2), warningLevel, \ + "Expected " #val1 " == " #val2 " (" #val1 "=" + std::to_string(expr1) + ", " #val2 "=" + std::to_string(expr2) + ")", \ + __FILE__, __LINE__); \ + } while (0) + +/** + * @brief Macro for inequality check (!=) with warning reporting for time critical tests. + * @param[in] val1 The first value. + * @param[in] val2 The second value. + * @param[in] warningLevel The level of warning to display. + */ +#define SDV_EXPECT_NE_WARN(val1, val2, warningLevel) \ + do { \ + auto expr1 = val1; \ + auto expr2 = val2; \ + sdv_test::ReportWarning((expr1) != (expr2), warningLevel, \ + "Expected " #val1 " != " #val2 " (" #val1 "=" + std::to_string(expr1) + ", " #val2 "=" + std::to_string(expr2) + ")", \ + __FILE__, __LINE__); \ + } while (0) + +/** + * @brief Macro for greater-than check (>) with warning reporting for time critical tests. + * @param[in] val1 The first value. + * @param[in] val2 The second value. + * @param[in] warningLevel The level of warning to display. + */ +#define SDV_EXPECT_GT_WARN(val1, val2, warningLevel) \ + do { \ + auto expr1 = val1; \ + auto expr2 = val2; \ + sdv_test::ReportWarning((expr1) > (expr2), warningLevel, \ + "Expected " #val1 " > " #val2 " (" #val1 "=" + std::to_string(expr1) + ", " #val2 "=" + std::to_string(expr2) + ")", \ + __FILE__, __LINE__); \ + } while (0) + +/** + * @brief Macro for less-than check (<) with warning reporting for time critical tests. + * @param[in] val1 The first value. + * @param[in] val2 The second value. + * @param[in] warningLevel The level of warning to display. + */ +#define SDV_EXPECT_LT_WARN(val1, val2, warningLevel) \ + do { \ + auto expr1 = val1; \ + auto expr2 = val2; \ + sdv_test::ReportWarning((expr1) < (expr2), warningLevel, \ + "Expected " #val1 " < " #val2 " (" #val1 "=" + std::to_string(expr1) + ", " #val2 "=" + std::to_string(expr2) + ")", \ + __FILE__, __LINE__); \ + } while (0) + +/** + * @brief Macro for greater-than-or-equal-to check (>=) with warning reporting for time critical tests. + * @param[in] val1 The first value. + * @param[in] val2 The second value. + * @param[in] warningLevel The level of warning to display. + */ +#define SDV_EXPECT_GE_WARN(val1, val2, warningLevel) \ + do { \ + auto expr1 = val1; \ + auto expr2 = val2; \ + sdv_test::ReportWarning((expr1) >= (expr2), warningLevel, \ + "Expected " #val1 " >= " #val2 " (" #val1 "=" + std::to_string(expr1) + ", " #val2 "=" + std::to_string(expr2) + ")", \ + __FILE__, __LINE__); \ + } while (0) + +/** + * @brief Macro for less-than-or-equal-to check (<=) with warning reporting for time critical tests. + * @param[in] val1 The first value. + * @param[in] val2 The second value. + * @param[in] warningLevel The level of warning to display. + */ +#define SDV_EXPECT_LE_WARN(val1, val2, warningLevel) \ + do { \ + auto expr1 = val1; \ + auto expr2 = val2; \ + sdv_test::ReportWarning((expr1) <= (expr2), warningLevel, \ + "Expected " #val1 " <= " #val2 " (" #val1 "=" + std::to_string(expr1) + ", " #val2 "=" + std::to_string(expr2) + ")", \ + __FILE__, __LINE__); \ + } while (0) + +#endif // SDV_TEST_MACRO_H diff --git a/tests/include/test_watchdog.h b/tests/include/test_watchdog.h index e40c0d6..2f515de 100644 --- a/tests/include/test_watchdog.h +++ b/tests/include/test_watchdog.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef _WIN32 // Prevent reassignment of "interface" #pragma push_macro("interface") @@ -96,8 +97,8 @@ private: } } - bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate. - std::thread m_threadWatchdog; ///< The watchdog thread. + std::atomic_bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate. + std::thread m_threadWatchdog; ///< The watchdog thread. }; #endif // !defined TEST_WATCHDOG_H diff --git a/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp b/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp index 0594c21..4c407c9 100644 --- a/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp +++ b/tests/manual_tests/silkit_can_com_tests/src/can_com_test_silkit.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -121,7 +122,7 @@ public: bool m_messageSent = false; private: - bool m_StopThread = false; + std::atomic_bool m_StopThread = false; std::thread m_thSend2DatalinkThread; mutable std::mutex m_mtxReceivers; std::set m_setReceivers; diff --git a/tests/unit_tests/asc_format/asc_writer_test.cpp b/tests/unit_tests/asc_format/asc_writer_test.cpp index 0c264ae..1672fbe 100644 --- a/tests/unit_tests/asc_format/asc_writer_test.cpp +++ b/tests/unit_tests/asc_format/asc_writer_test.cpp @@ -3,7 +3,7 @@ #include #include #include "../../../global/exec_dir_helper.h" -#include +#include "../../include/sdv_test_macro.h" TEST(CAscWriterTest, AddSamplesDirect) { @@ -108,16 +108,16 @@ TEST(CAscWriterTest, AddTimedSamples) if (dDeltaTSGenerated < dDeltaTSGroundThruth - 0.002) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(dDeltaTSGenerated, dDeltaTSGroundThruth - 0.002, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(dDeltaTSGenerated, dDeltaTSGroundThruth - 0.002, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(dDeltaTSGenerated, dDeltaTSGroundThruth - 0.002, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(dDeltaTSGenerated, dDeltaTSGroundThruth - 0.002, sdv_test::WARNING_ENABLED); } if (dDeltaTSGenerated > dDeltaTSGroundThruth + 0.002) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(dDeltaTSGenerated, dDeltaTSGroundThruth + 0.002, sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(dDeltaTSGenerated, dDeltaTSGroundThruth + 0.002, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(dDeltaTSGenerated, dDeltaTSGroundThruth + 0.002, sdv::TEST::WarningLevel::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(dDeltaTSGenerated, dDeltaTSGroundThruth + 0.002, sdv_test::WARNING_ENABLED); } std::cout << "TIMING: Expected generated timestamp <= ground truth + 2ms (generated=" << dDeltaTSGenerated << "ms, ground truth=" << dDeltaTSGroundThruth << ")..." << std::endl; EXPECT_EQ(prSampleGroundTruth.first.uiChannel, prSampleGenerated.first.uiChannel); @@ -136,7 +136,7 @@ TEST(CAscWriterTest, AddTimedSamples) TEST(CAscWriterTest, ExtendedID) { asc::CAscReader readerGroundThruth; - SDV_EXPECT_TRUE(readerGroundThruth.Read(GetExecDirectory() / "asc_reader_ext_id_test.asc"), sdv::TEST::WarningLevel::WARNING_REDUCED); + SDV_EXPECT_TRUE(readerGroundThruth.Read(GetExecDirectory() / "asc_reader_ext_id_test.asc"), sdv_test::WARNING_REDUCED); // Add all samples asc::CAscWriter writer; diff --git a/tests/unit_tests/basic_types/serdes.cpp b/tests/unit_tests/basic_types/serdes.cpp index 37cc821..c099629 100644 --- a/tests/unit_tests/basic_types/serdes.cpp +++ b/tests/unit_tests/basic_types/serdes.cpp @@ -80,10 +80,10 @@ TEST_F(CSerdesTest, DeserializeSimpleEndianSwap) TEST_F(CSerdesTest, SerializeArray) { - srand((unsigned int)time(0)); + std::srand((unsigned int)time(0)); int16_t rguiBuffer[4096]; for (size_t nIndex = 0; nIndex < 4096; nIndex++) - rguiBuffer[nIndex] = static_cast(rand()); + rguiBuffer[nIndex] = static_cast(std::rand()); sdv::serializer serializer; serializer << rguiBuffer; @@ -95,10 +95,10 @@ TEST_F(CSerdesTest, SerializeArray) TEST_F(CSerdesTest, DeserializeArray) { - srand((unsigned int)time(0)); + std::srand((unsigned int)time(0)); int16_t rguiBuffer[4096]; for (size_t nIndex = 0; nIndex < 4096; nIndex++) - rguiBuffer[nIndex] = static_cast(rand()); + rguiBuffer[nIndex] = static_cast(std::rand()); sdv::serializer serializer; serializer << rguiBuffer; @@ -113,10 +113,10 @@ TEST_F(CSerdesTest, DeserializeArray) TEST_F(CSerdesTest, SerializeArrayEndianSwap) { - srand((unsigned int)time(0)); + std::srand((unsigned int)time(0)); int16_t rguiBuffer[4096]; for (size_t nIndex = 0; nIndex < 4096; nIndex++) - rguiBuffer[nIndex] = static_cast(rand()); + rguiBuffer[nIndex] = static_cast(std::rand()); static constexpr sdv::EEndian eEndian = sdv::GetPlatformEndianess() == sdv::EEndian::little_endian ? sdv::EEndian::big_endian : sdv::EEndian::little_endian; @@ -139,10 +139,10 @@ TEST_F(CSerdesTest, SerializeArrayEndianSwap) TEST_F(CSerdesTest, DeserializeArrayEndianSwap) { - srand((unsigned int)time(0)); + std::srand((unsigned int)time(0)); int16_t rguiBuffer[4096]; for (size_t nIndex = 0; nIndex < 4096; nIndex++) - rguiBuffer[nIndex] = static_cast(rand()); + rguiBuffer[nIndex] = static_cast(std::rand()); static constexpr sdv::EEndian eEndian = sdv::GetPlatformEndianess() == sdv::EEndian::little_endian ? sdv::EEndian::big_endian : sdv::EEndian::little_endian; diff --git a/tests/unit_tests/commandline_parser/commandline_parser_test.h b/tests/unit_tests/commandline_parser/commandline_parser_test.h index f87e85d..af30c99 100644 --- a/tests/unit_tests/commandline_parser/commandline_parser_test.h +++ b/tests/unit_tests/commandline_parser/commandline_parser_test.h @@ -5,33 +5,33 @@ #include /** -* \brief Test class for instantiation tests. +* @brief Test class for instantiation tests. */ class CCommandLineParserTest : public testing::Test { public: /** - * \brief Constructor + * @brief Constructor */ CCommandLineParserTest() = default; /** - * \brief Set up the test suite. + * @brief Set up the test suite. */ static void SetUpTestCase(); /** - * \brief Tear down the test suite. + * @brief Tear down the test suite. */ static void TearDownTestCase(); /** - * \brief Test setup. + * @brief Test setup. */ void SetUp() override; /** - * \brief Test teardown. + * @brief Test teardown. */ void TearDown() override; }; diff --git a/tests/unit_tests/concurrency/concurrency_tests.cpp b/tests/unit_tests/concurrency/concurrency_tests.cpp index 6067d62..a443e4b 100644 --- a/tests/unit_tests/concurrency/concurrency_tests.cpp +++ b/tests/unit_tests/concurrency/concurrency_tests.cpp @@ -98,7 +98,7 @@ struct SMakeNoice rthread.join(); } - bool bShutdown = false; ///< Run threads until shutdown is set. + std::atomic_bool bShutdown = false; ///< Run threads until shutdown is set. std::atomic_size_t nStarted = 0; ///< Amount of threads that were started. This is to wait for all threads to start. std::thread rgThreads[nAmount]; ///< The noise generating threads. }; @@ -383,7 +383,7 @@ TEST(ConcurrencyTest, ConditionVarWaitForPrediction_SeparateMutexForEachThread_I std::atomic_size_t nCnt = 0; std::atomic_size_t nThreadCnt = 0; std::atomic_size_t nViolationCnt = 0; - bool b1 = false, b2 = false, b3 = false, b4 = false; + std::atomic_bool b1 = false, b2 = false, b3 = false, b4 = false; SMakeNoice noice; std::thread thread1([&]() { diff --git a/tests/unit_tests/dbc_parser/dbc_parser_test.h b/tests/unit_tests/dbc_parser/dbc_parser_test.h index 96c6670..0db7129 100644 --- a/tests/unit_tests/dbc_parser/dbc_parser_test.h +++ b/tests/unit_tests/dbc_parser/dbc_parser_test.h @@ -5,33 +5,33 @@ #include /** -* \brief Test class for instantiation tests. +* @brief Test class for instantiation tests. */ class CDbcParserTest : public testing::Test { public: /** - * \brief Constructor + * @brief Constructor */ CDbcParserTest() = default; /** - * \brief Set up the test suite. + * @brief Set up the test suite. */ static void SetUpTestCase(); /** - * \brief Tear down the test suite. + * @brief Tear down the test suite. */ static void TearDownTestCase(); /** - * \brief Test setup. + * @brief Test setup. */ void SetUp() override; /** - * \brief Test teardown. + * @brief Test teardown. */ void TearDown() override; }; diff --git a/tests/unit_tests/idl_compiler/lexer_test.h b/tests/unit_tests/idl_compiler/lexer_test.h index 303c0fe..9a559a6 100644 --- a/tests/unit_tests/idl_compiler/lexer_test.h +++ b/tests/unit_tests/idl_compiler/lexer_test.h @@ -2,33 +2,33 @@ #define LEXER_TEST_H /** -* \brief Test class for instantiation tests. +* @brief Test class for instantiation tests. */ class CLexerTest : public testing::Test { public: /** - * \brief Constructor + * @brief Constructor */ CLexerTest() = default; /** - * \brief Set up the test suite. + * @brief Set up the test suite. */ static void SetUpTestCase(); /** - * \brief Tear down the test suite. + * @brief Tear down the test suite. */ static void TearDownTestCase(); /** - * \brief Test setup. + * @brief Test setup. */ void SetUp() override; /** - * \brief Test teardown. + * @brief Test teardown. */ void TearDown() override; diff --git a/tests/unit_tests/idl_compiler/parser_test.h b/tests/unit_tests/idl_compiler/parser_test.h index c97364c..b35ae60 100644 --- a/tests/unit_tests/idl_compiler/parser_test.h +++ b/tests/unit_tests/idl_compiler/parser_test.h @@ -2,33 +2,33 @@ #define PARSER_TEST_H /** -* \brief Test class for instantiation tests. +* @brief Test class for instantiation tests. */ class CParserTest : public testing::Test { public: /** - * \brief Constructor + * @brief Constructor */ CParserTest() = default; /** - * \brief Set up the test suite. + * @brief Set up the test suite. */ static void SetUpTestCase(); /** - * \brief Tear down the test suite. + * @brief Tear down the test suite. */ static void TearDownTestCase(); /** - * \brief Test setup. + * @brief Test setup. */ void SetUp() override; /** - * \brief Test teardown. + * @brief Test teardown. */ void TearDown() override; diff --git a/tests/unit_tests/install_package_composer/composer_test_suite.cpp b/tests/unit_tests/install_package_composer/composer_test_suite.cpp index c9468be..c14c1cf 100644 --- a/tests/unit_tests/install_package_composer/composer_test_suite.cpp +++ b/tests/unit_tests/install_package_composer/composer_test_suite.cpp @@ -9,8 +9,10 @@ #include "../../../sdv_services/core/installation_composer.cpp" #include "../../../sdv_services/core/toml_parser/parser_toml.cpp" #include "../../../sdv_services/core/toml_parser/lexer_toml.cpp" +#include "../../../sdv_services/core/toml_parser/lexer_toml_token.cpp" #include "../../../sdv_services/core/toml_parser/parser_node_toml.cpp" #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" +#include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include #if defined(_WIN32) && defined(_UNICODE) diff --git a/tests/unit_tests/install_package_composer/composer_tests.cpp b/tests/unit_tests/install_package_composer/composer_tests.cpp index 10b8002..fade90c 100644 --- a/tests/unit_tests/install_package_composer/composer_tests.cpp +++ b/tests/unit_tests/install_package_composer/composer_tests.cpp @@ -2108,7 +2108,7 @@ TEST_F(CInstallPackageComposerTest, DetectPackageCorruptionContent) for (size_t n = 0; n < 25; n++) { // Corrupt the package at the content (from 100 until the size minus 32) - size_t nPos = static_cast(rand()) * (ptrPackage.size() - 132) / RAND_MAX + 100; + size_t nPos = static_cast(std::rand()) * (ptrPackage.size() - 132) / RAND_MAX + 100; uint8_t uiStoredByte = ptrPackage.get()[nPos]; ptrPackage.get()[nPos] = ~uiStoredByte; @@ -2337,7 +2337,7 @@ TEST_F(CInstallPackageComposerTest, VerifyIntegrityCorruptionContent) for (size_t n = 0; n < 25; n++) { // Corrupt the package at the content (from 100 until the size minus 32) - size_t nPos = static_cast(rand()) * (ptrPackage.size() - 132) / RAND_MAX + 100; + size_t nPos = static_cast(std::rand()) * (ptrPackage.size() - 132) / RAND_MAX + 100; uint8_t uiStoredByte = ptrPackage.get()[nPos]; ptrPackage.get()[nPos] = ~uiStoredByte; diff --git a/tests/unit_tests/ipc_com/main.cpp b/tests/unit_tests/ipc_com/main.cpp index 1c86357..a88b885 100644 --- a/tests/unit_tests/ipc_com/main.cpp +++ b/tests/unit_tests/ipc_com/main.cpp @@ -3,6 +3,7 @@ #include "../../../sdv_services/ipc_com/com_ctrl.cpp" #include "../../../sdv_services/ipc_com/com_channel.cpp" #include "../../../sdv_services/ipc_com/marshall_object.cpp" +#include /** * @brief Main function @@ -13,8 +14,29 @@ extern "C" int wmain(int argc, wchar_t* argv[]) extern "C" int main(int argc, char* argv[]) #endif { - CProcessWatchdog watchdog; + // Check for the --gtest_repeat option. + bool bRepeatEnabled = false; + for (int iIndex = 0; iIndex < argc; iIndex++) + { + if (!argv[iIndex]) continue; +#if defined(_WIN32) && defined(_UNICODE) + bRepeatEnabled |= std::wcsncmp(argv[iIndex], L"--gtest_repeat", 14) == 0; +#else + bRepeatEnabled |= std::strncmp(argv[iIndex], "--gtest_repeat", 14) == 0; +#endif + } - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + // When repeat is enabled, do not enable the watchdog. + if (bRepeatEnabled) + { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } + else + { + CProcessWatchdog watchdog; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } } diff --git a/tests/unit_tests/logger_fifo/fifo_test.cpp b/tests/unit_tests/logger_fifo/fifo_test.cpp index 8bb2385..b5157e5 100644 --- a/tests/unit_tests/logger_fifo/fifo_test.cpp +++ b/tests/unit_tests/logger_fifo/fifo_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include TEST(TraceFifoTest, Connect_Disconnect) { @@ -190,7 +191,7 @@ TEST(TraceFifoTest, Simple_Publish_Monitor) EXPECT_TRUE(fifoReader.IsOpened()); std::vector vecSent, vecReceived; - bool bShutdown = false; + std::atomic_bool bShutdown = false; // Start receiving thread until shutdown flag is set. std::thread thread([&]() @@ -238,7 +239,7 @@ TEST(TraceFifoTest, Simple_Publish_Monitor_Multi) EXPECT_TRUE(fifoReader2.IsOpened()); std::vector vecSent, vecReceived1, vecReceived2; - bool bShutdown = false; + std::atomic_bool bShutdown = false; // Start receiving thread until shutdown flag is set. std::thread thread1([&]() @@ -297,7 +298,7 @@ TEST(TraceFifoTest, Simple_Publish_Beyond_Buffer_With_Reading) EXPECT_TRUE(fifoReader.IsOpened()); std::vector vecSent, vecReceived; - bool bShutdown = false; + std::atomic_bool bShutdown = false; // Start receiving thread until shutdown flag is set. std::thread thread([&]() diff --git a/tests/unit_tests/module_control/main.cpp b/tests/unit_tests/module_control/main.cpp index f625b82..a410299 100644 --- a/tests/unit_tests/module_control/main.cpp +++ b/tests/unit_tests/module_control/main.cpp @@ -3,8 +3,10 @@ #include "mock.h" #include "../../../sdv_services/core/toml_parser/parser_toml.cpp" #include "../../../sdv_services/core/toml_parser/lexer_toml.cpp" +#include "../../../sdv_services/core/toml_parser/lexer_toml_token.cpp" #include "../../../sdv_services/core/toml_parser/parser_node_toml.cpp" #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" +#include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../../sdv_services/core/module_control.cpp" #include "../../../sdv_services/core/module.cpp" #include "../../../sdv_services/core/app_config.cpp" diff --git a/tests/unit_tests/module_control/mock.h b/tests/unit_tests/module_control/mock.h index 6b492e9..a49ca26 100644 --- a/tests/unit_tests/module_control/mock.h +++ b/tests/unit_tests/module_control/mock.h @@ -11,13 +11,7 @@ class CMock { public: - CMock() : m_root("root") - { - auto ptrElement = std::make_shared("Install"); - m_root.AddElement(ptrElement); - auto ptrValue = std::make_shared("Directory", "install/test"); - ptrElement->AddElement(ptrValue); - } + CMock() {} void DestroyModuleObjects(sdv::core::TModuleID) {} bool IsStandaloneApplication() { return true; } bool IsEssentialApplication() { return false; } @@ -40,9 +34,8 @@ public: sdv::core::TModuleID ContextLoad(const std::filesystem::path&, const sdv::u8string&) { return 0; } bool ContextUnload(sdv::core::TModuleID, bool) { return false; } - CNormalTable m_root; - bool m_bIsMain = false; - bool m_bIsIsolated = false; + bool m_bIsMain = false; + bool m_bIsIsolated = false; }; inline CMock& GetMock() diff --git a/tests/unit_tests/named_mutex/named_mutex_test.cpp b/tests/unit_tests/named_mutex/named_mutex_test.cpp index cc86223..6bb8468 100644 --- a/tests/unit_tests/named_mutex/named_mutex_test.cpp +++ b/tests/unit_tests/named_mutex/named_mutex_test.cpp @@ -3,6 +3,7 @@ #include "../../../global/ipc_named_mutex.h" #include #include +#include #if defined(_WIN32) && defined(_UNICODE) extern "C" int wmain(int argc, wchar_t* argv[]) @@ -28,19 +29,19 @@ TEST(NamedMutexTest, CritSectSyncManualLock) // The checking is manipulated by the bEnable flag. When disabled, no sync will be done and the check will fail. When enabled, // sync will be done and the check will succeed. int32_t iCnt = 0; - bool bSuccess = true; - bool bEnable = false; + std::atomic_bool bSuccess = true; + std::atomic_bool bEnable = false; auto fn = [&]() { ipc::named_mutex mtx("HELLO"); if (bEnable) mtx.lock(); - bSuccess &= (iCnt == 0); + bSuccess = bSuccess && (iCnt == 0); iCnt++; std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100)); iCnt--; - bSuccess &= (iCnt == 0); + bSuccess = bSuccess && (iCnt == 0); if (bEnable) mtx.unlock(); }; @@ -69,17 +70,17 @@ TEST(NamedMutexTest, CritSectSyncAutoLock) { // Counter function check for correct counter value. int32_t iCnt = 0; - bool bSuccess = true; + std::atomic_bool bSuccess = true; auto fn = [&]() { ipc::named_mutex mtx("HELLO"); std::unique_lock lock(mtx); - bSuccess &= (iCnt == 0); + bSuccess = bSuccess && (iCnt == 0); iCnt++; std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 100)); iCnt--; - bSuccess &= (iCnt == 0); + bSuccess = bSuccess && (iCnt == 0); }; // Test sync @@ -94,7 +95,7 @@ TEST(NamedMutexTest, CritSectSyncAutoLock) TEST(NamedMutexTest, TryLock) { - bool bRunning = false; + std::atomic_bool bRunning = false; auto fn = [&]() { ipc::named_mutex mtx("HELLO"); diff --git a/tests/unit_tests/repository/main.cpp b/tests/unit_tests/repository/main.cpp index 4748361..dba1448 100644 --- a/tests/unit_tests/repository/main.cpp +++ b/tests/unit_tests/repository/main.cpp @@ -3,8 +3,10 @@ #include "mock.h" #include "../../../sdv_services/core/toml_parser/parser_toml.cpp" #include "../../../sdv_services/core/toml_parser/lexer_toml.cpp" +#include "../../../sdv_services/core/toml_parser/lexer_toml_token.cpp" #include "../../../sdv_services/core/toml_parser/parser_node_toml.cpp" #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" +#include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #include "../../../sdv_services/core/module_control.cpp" #include "../../../sdv_services/core/module.cpp" #include "../../../sdv_services/core/repository.cpp" diff --git a/tests/unit_tests/sdv_macro_test/sdv_macro_test.cpp b/tests/unit_tests/sdv_macro_test/sdv_macro_test.cpp index a27c005..d1094a5 100644 --- a/tests/unit_tests/sdv_macro_test/sdv_macro_test.cpp +++ b/tests/unit_tests/sdv_macro_test/sdv_macro_test.cpp @@ -1,6 +1,6 @@ #include #include "../../../global/process_watchdog.h" -#include "support/sdv_test_macro.h" +#include "../../include/sdv_test_macro.h" #if defined(_WIN32) && defined(_UNICODE) extern "C" int wmain(int argc, wchar_t* argv[]) @@ -19,9 +19,9 @@ TEST(SDVTestMacros, TestExpectEq) int val1 = 5; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_EQ(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_EQ(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_EQ(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_EQ(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertEq) @@ -29,9 +29,9 @@ TEST(SDVTestMacros, TestAssertEq) int val1 = 5; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_EQ(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_EQ(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_EQ(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_EQ(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectNe) @@ -39,9 +39,9 @@ TEST(SDVTestMacros, TestExpectNe) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_NE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_NE(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_NE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_NE(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertNe) @@ -49,45 +49,45 @@ TEST(SDVTestMacros, TestAssertNe) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_NE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_NE(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_NE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_NE(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectTrue) { bool condition = true; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_TRUE(condition, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_TRUE(condition, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_TRUE(condition, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_TRUE(condition, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertTrue) { bool condition = true; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_TRUE(condition, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_TRUE(condition, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_TRUE(condition, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_TRUE(condition, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectFalse) { bool condition = false; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_FALSE(condition, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_FALSE(condition, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_FALSE(condition, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_FALSE(condition, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertFalse) { bool condition = false; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_FALSE(condition, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_FALSE(condition, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_FALSE(condition, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_FALSE(condition, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectLt) @@ -95,9 +95,9 @@ TEST(SDVTestMacros, TestExpectLt) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_LT(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_LT(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_LT(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_LT(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertLt) @@ -105,9 +105,9 @@ TEST(SDVTestMacros, TestAssertLt) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_LT(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_LT(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_LT(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_LT(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectLe) @@ -115,9 +115,9 @@ TEST(SDVTestMacros, TestExpectLe) int val1 = 5; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_LE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_LE(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_LE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_LE(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertLe) @@ -125,9 +125,9 @@ TEST(SDVTestMacros, TestAssertLe) int val1 = 5; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_LE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_LE(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_LE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_LE(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectGt) @@ -135,9 +135,9 @@ TEST(SDVTestMacros, TestExpectGt) int val1 = 6; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_GT(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_GT(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_GT(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_GT(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertGt) @@ -145,9 +145,9 @@ TEST(SDVTestMacros, TestAssertGt) int val1 = 6; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_GT(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_GT(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_GT(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_GT(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectGe) @@ -155,9 +155,9 @@ TEST(SDVTestMacros, TestExpectGe) int val1 = 6; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_GE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_GE(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_GE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_GE(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertGe) @@ -165,9 +165,9 @@ TEST(SDVTestMacros, TestAssertGe) int val1 = 6; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_GE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_GE(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_GE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_GE(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectStreq) @@ -175,9 +175,9 @@ TEST(SDVTestMacros, TestExpectStreq) std::string str1 = "test"; std::string str2 = "test"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_STREQ(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_STREQ(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_STREQ(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_STREQ(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertStreq) @@ -185,9 +185,9 @@ TEST(SDVTestMacros, TestAssertStreq) std::string str1 = "test"; std::string str2 = "test"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_STREQ(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_STREQ(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_STREQ(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_STREQ(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectStrne) @@ -195,9 +195,9 @@ TEST(SDVTestMacros, TestExpectStrne) std::string str1 = "test"; std::string str2 = "test1"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_STRNE(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_STRNE(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_STRNE(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_STRNE(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertStrne) @@ -205,9 +205,9 @@ TEST(SDVTestMacros, TestAssertStrne) std::string str1 = "test"; std::string str2 = "test1"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_STRNE(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_STRNE(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_STRNE(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_STRNE(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectStrcaseeq) @@ -215,9 +215,9 @@ TEST(SDVTestMacros, TestExpectStrcaseeq) std::string str1 = "test"; std::string str2 = "TEST"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_STRCASEEQ(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_STRCASEEQ(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_STRCASEEQ(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_STRCASEEQ(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertStrcaseeq) @@ -225,9 +225,9 @@ TEST(SDVTestMacros, TestAssertStrcaseeq) std::string str1 = "test"; std::string str2 = "TEST"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_STRCASEEQ(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_STRCASEEQ(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_STRCASEEQ(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_STRCASEEQ(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestExpectStrcasene) @@ -235,9 +235,9 @@ TEST(SDVTestMacros, TestExpectStrcasene) std::string str1 = "test"; std::string str2 = "TEST1"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_EXPECT_STRCASENE(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_STRCASENE(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_EXPECT_STRCASENE(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_STRCASENE(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestAssertStrcasene) @@ -245,9 +245,9 @@ TEST(SDVTestMacros, TestAssertStrcasene) std::string str1 = "test"; std::string str2 = "TEST1"; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_ASSERT_STRCASENE(str1, str2, sdv::TEST::WARNING_REDUCED); + SDV_ASSERT_STRCASENE(str1, str2, sdv_test::WARNING_REDUCED); else - SDV_ASSERT_STRCASENE(str1, str2, sdv::TEST::WARNING_ENABLED); + SDV_ASSERT_STRCASENE(str1, str2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestTimingExpectEq) @@ -255,9 +255,9 @@ TEST(SDVTestMacros, TestTimingExpectEq) int val1 = 5; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestTimingExpectNe) @@ -265,9 +265,9 @@ TEST(SDVTestMacros, TestTimingExpectNe) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_NE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_NE_WARN(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_NE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_NE_WARN(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestTimingExpectGt) @@ -275,9 +275,9 @@ TEST(SDVTestMacros, TestTimingExpectGt) int val1 = 6; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GT(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_GT_WARN(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GT(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_GT_WARN(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestTimingExpectLt) @@ -285,9 +285,9 @@ TEST(SDVTestMacros, TestTimingExpectLt) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LT(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_LT_WARN(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LT(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_LT_WARN(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestTimingExpectGe) @@ -295,9 +295,9 @@ TEST(SDVTestMacros, TestTimingExpectGe) int val1 = 6; int val2 = 5; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_GE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_GE_WARN(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_GE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_GE_WARN(val1, val2, sdv_test::WARNING_ENABLED); } TEST(SDVTestMacros, TestTimingExpectLe) @@ -305,7 +305,7 @@ TEST(SDVTestMacros, TestTimingExpectLe) int val1 = 5; int val2 = 6; if (SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_LE(val1, val2, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_LE_WARN(val1, val2, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_LE(val1, val2, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_LE_WARN(val1, val2, sdv_test::WARNING_ENABLED); } diff --git a/tests/unit_tests/shared_mem/CMakeLists.txt b/tests/unit_tests/shared_mem/CMakeLists.txt index f45ec2b..faece76 100644 --- a/tests/unit_tests/shared_mem/CMakeLists.txt +++ b/tests/unit_tests/shared_mem/CMakeLists.txt @@ -53,16 +53,11 @@ endif() # Add the communication unittest add_test(NAME UnitTest_InprocMemTests COMMAND UnitTest_InprocMemTests) -#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring -# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a -# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134 -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) # Execute the test add_custom_command(TARGET UnitTest_InprocMemTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_InprocMemTests.xml VERBATIM ) -endif() # Shared mem buffer test add_executable(UnitTest_SharedMemBufferTests @@ -85,16 +80,11 @@ endif() # Add the communication unittest add_test(NAME UnitTest_SharedMemBufferTests COMMAND UnitTest_SharedMemBufferTests) -#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring -# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a -# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134 -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) # Execute the test add_custom_command(TARGET UnitTest_SharedMemBufferTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SharedMemBufferTests.xml VERBATIM ) -endif() # Shared mem connection test add_executable(UnitTest_SharedMemConnectTests @@ -115,16 +105,11 @@ endif() # Add the communication unittest add_test(NAME UnitTest_SharedMemConnectTests COMMAND UnitTest_SharedMemConnectTests) -#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring -# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a -# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134 -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) # Execute the test add_custom_command(TARGET UnitTest_SharedMemConnectTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SharedMemConnectTests.xml VERBATIM ) -endif() # Shared mem large data test @@ -146,16 +131,11 @@ endif() # Add the communication unittest add_test(NAME UnitTest_SharedMemLargeDataTests COMMAND UnitTest_SharedMemLargeDataTests) -#TODO Shared memory tests during complete rebuild fail on Windows when compiling with MINGW. This is due to race conditions occuring -# only when the system is under heavy load (like during a complete rebuild). The tests have been disabled for the moment and a -# bug report is filed here: https://dev.azure.com/SW4ZF/AZP-074_DivDI_SofDCarResearch/_workitems/edit/608134 -if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32)) # Execute the test add_custom_command(TARGET UnitTest_SharedMemLargeDataTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake "$" --gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_SharedMemLargeDataTests.xml VERBATIM ) -endif() # Build dependencies add_dependencies(UnitTest_SharedMemTests_App_Repeater core_services) diff --git a/tests/unit_tests/shared_mem/app_connect.cpp b/tests/unit_tests/shared_mem/app_connect.cpp index e4ab91e..c92bfdc 100644 --- a/tests/unit_tests/shared_mem/app_connect.cpp +++ b/tests/unit_tests/shared_mem/app_connect.cpp @@ -4,7 +4,7 @@ #define TIME_TRACKING #include "../../../sdv_services/ipc_shared_mem/channel_mgnt.cpp" -#include "../../../sdv_services/ipc_shared_mem/connection.cpp" +#include "../../../sdv_services/ipc_shared_mem/connection.cpp" // Tracing is enabled/disabled in the connection.h file #include "../../../sdv_services/ipc_shared_mem/watchdog.cpp" #include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp" #include @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef __GNUC__ #include @@ -67,6 +68,8 @@ public: { // Send the same data back again (if needed). std::unique_lock lock(m_mtxData); + m_nReceiveCallCnt++; + m_nPackageReceiveCnt += seqData.size(); m_queueSendData.push(seqData); lock.unlock(); @@ -93,7 +96,10 @@ public: // Send the data back to the sender if (m_pSend) + { m_pSend->SendData(seqData); + m_nSendCallCnt++; + } } } @@ -184,6 +190,33 @@ public: m_threadSender.join(); } + /** + * @brief Get the receive call count. + * @return The amount of receive calls that has been made. + */ + size_t GetReceiveCallCount() const + { + return m_nReceiveCallCnt; + } + + /** + * @brief Get the package call count. + * @return The amount of packages that have been received. + */ + size_t GetPackageReceiveCount() const + { + return m_nPackageReceiveCnt; + } + + /** + * @brief Get the send call count. + * @return The amount of send calls that has been made. + */ + size_t GetSendCallCount() const + { + return m_nSendCallCnt; + } + private: sdv::ipc::IDataSend* m_pSend = nullptr; ///< Send interface to implement repeating function. mutable std::mutex m_mtxData; ///< Protect data access. @@ -191,9 +224,12 @@ private: std::condition_variable m_cvDisconnect; ///< Disconnect event. std::condition_variable m_cvReceived; ///< Receive event. std::thread m_threadSender; ///< Thread to send data. - bool m_bConnected = false; ///< Set when connected was triggered. - bool m_bDisconnect = false; ///< Set when shutdown was triggered. - bool m_bShutdown = false; ///< Set when shutdown is processed. + std::atomic_bool m_bConnected = false; ///< Set when connected was triggered. + std::atomic_bool m_bDisconnect = false; ///< Set when shutdown was triggered. + std::atomic_bool m_bShutdown = false; ///< Set when shutdown is processed. + std::atomic_size_t m_nReceiveCallCnt = 0; ///< Receive call counter. + std::atomic_size_t m_nPackageReceiveCnt = 0; ///< Package receive counter. + std::atomic_size_t m_nSendCallCnt = 0; ///< Send call counter. }; @@ -280,15 +316,18 @@ extern "C" int main(int argc, char* argv[]) // Open the control channel endpoint sdv::TObjectPtr ptrControlConnection; + sdv::ipc::IConnect* pControlConnect = nullptr; CReceiver receiverControl; + uint64_t uiControlEventCookie = 0; if (!ssControlConnectString.empty()) { TRACE(bServer ? "Server" : "Client", ": Start of control channel connection..."); ptrControlConnection = mgntControlMgntChannel.Access(ssControlConnectString); if (!ptrControlConnection) return -12; - sdv::ipc::IConnect* pControlConnect = ptrControlConnection.GetInterface(); + pControlConnect = ptrControlConnection.GetInterface(); if (!pControlConnect) return -13; - if (!pControlConnect->RegisterStatusEventCallback(&receiverControl)) return -20; + uiControlEventCookie = pControlConnect->RegisterStatusEventCallback(&receiverControl); + if (!uiControlEventCookie) return -20; if (!pControlConnect->AsyncConnect(&receiverControl)) return -14; if (!pControlConnect->WaitForConnection(250)) return -5; // Note: Connection should be possible within 250ms. if (pControlConnect->GetStatus() != sdv::ipc::EConnectStatus::connected) return -15; @@ -339,8 +378,10 @@ Size = 1024000 // Establish the connection sdv::ipc::IConnect* pDataConnect = ptrDataConnection.GetInterface(); + uint64_t uiDataEventCookie = 0; if (!pDataConnect) return -3; - if (!pDataConnect->RegisterStatusEventCallback(&receiverData)) return -21; + uiDataEventCookie = pDataConnect->RegisterStatusEventCallback(&receiverData); + if (!uiDataEventCookie) return -21; if (!pDataConnect->AsyncConnect(&receiverData)) return -4; if (!pDataConnect->WaitForConnection(10000)) return -5; // Note: Connection should be possible within 10000ms. if (pDataConnect->GetStatus() != sdv::ipc::EConnectStatus::connected) return -5; @@ -358,12 +399,17 @@ Size = 1024000 // Repeat data until disconnect occurrs (differentiate between forced and not forced to allow two apps to start at the // same time). receiverData.WaitUntilDisconnect(bForceTerminate ? 800 : 1600); - std::cout << "App " << (bServer ? "server" : "client") << " connect process disconnecting..." << std::endl; + TRACE("App ", bServer ? "server" : "client", " connect process disconnecting..."); } + // Statistics + TRACE("Receive was called ", receiverData.GetReceiveCallCount(), " times (", receiverData.GetPackageReceiveCount(), + " packages)."); + TRACE("Send was called ", receiverData.GetSendCallCount(), " times."); + if (bForceTerminate) { - std::cout << "Forced termination of app " << (bServer ? "server" : "client") << " connect process..." << std::endl; + TRACE("Forced termination of app ", bServer ? "server" : "client", " connect process..."); #ifdef _MSC_VER _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); #endif @@ -371,9 +417,11 @@ Size = 1024000 } // Initiate shutdown + if (pDataConnect && uiDataEventCookie) pDataConnect->UnregisterStatusEventCallback(uiDataEventCookie); ptrDataConnection.Clear(); mgntDataMgntChannel.Shutdown(); if (mgntDataMgntChannel.GetStatus() != sdv::EObjectStatus::destruction_pending) return -6; + if (pControlConnect && uiControlEventCookie) pControlConnect->UnregisterStatusEventCallback(uiControlEventCookie); ptrControlConnection.Clear(); mgntControlMgntChannel.Shutdown(); if (mgntControlMgntChannel.GetStatus() != sdv::EObjectStatus::destruction_pending) return -16; diff --git a/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp b/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp index 78609cf..d051994 100644 --- a/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp +++ b/tests/unit_tests/shared_mem/in_process_mem_buffer_tests.cpp @@ -2,6 +2,7 @@ #include "../../../sdv_services/ipc_shared_mem/in_process_mem_buffer.h" #include #include "pattern_gen.h" +#include TEST(InProcessMemoryBufferTest, Instantiate) { @@ -26,7 +27,7 @@ TEST(InProcessMemoryBufferTest, TriggerTestRx) CInProcMemBufferRx receiver(sender.GetConnectionString()); EXPECT_TRUE(receiver.IsValid()); - bool bShutdown = false; + std::atomic_bool bShutdown = false; size_t nCorrectCnt = 0; std::condition_variable cvStart; std::mutex mtxStart; @@ -75,7 +76,7 @@ TEST(InProcessMemoryBufferTest, TriggerTestTx) CInProcMemBufferRx receiver(sender.GetConnectionString()); EXPECT_TRUE(receiver.IsValid()); - bool bShutdown = false; + std::atomic_bool bShutdown = false; size_t nCorrectCnt = 0; std::condition_variable cvStart; std::mutex mtxStart; @@ -124,7 +125,7 @@ TEST(InProcessMemoryBufferTest, TriggerTestRxTx) CInProcMemBufferRx receiver(sender.GetConnectionString()); EXPECT_TRUE(receiver.IsValid()); - bool bShutdown = false; + std::atomic_bool bShutdown = false; size_t nCorrectCnt = 0; std::condition_variable cvSenderStart, cvReceiverStart; std::mutex mtxReceiverStart; diff --git a/tests/unit_tests/shared_mem/main.cpp b/tests/unit_tests/shared_mem/main.cpp index 1fc4076..e391824 100644 --- a/tests/unit_tests/shared_mem/main.cpp +++ b/tests/unit_tests/shared_mem/main.cpp @@ -5,6 +5,7 @@ #include "../../../sdv_services/ipc_shared_mem/channel_mgnt.cpp" #include "../../../sdv_services/ipc_shared_mem/watchdog.cpp" #include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp" +#include /** * @brief Load support modules to publish the needed services. @@ -28,8 +29,30 @@ extern "C" int wmain(int argc, wchar_t* argv[]) extern "C" int main(int argc, char* argv[]) #endif { - CProcessWatchdog watchdog; + // Check for the --gtest_repeat option. + bool bRepeatEnabled = false; + for (int iIndex = 0; iIndex < argc; iIndex++) + { + if (!argv[iIndex]) + continue; +#if defined(_WIN32) && defined(_UNICODE) + bRepeatEnabled |= std::wcsncmp(argv[iIndex], L"--gtest_repeat", 14) == 0; +#else + bRepeatEnabled |= std::strncmp(argv[iIndex], "--gtest_repeat", 14) == 0; +#endif + } - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + // When repeat is enabled, do not enable the watchdog. + if (bRepeatEnabled) + { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } + else + { + CProcessWatchdog watchdog; + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); + } } diff --git a/tests/unit_tests/shared_mem/pattern_gen.cpp b/tests/unit_tests/shared_mem/pattern_gen.cpp index f59e85f..8f79d64 100644 --- a/tests/unit_tests/shared_mem/pattern_gen.cpp +++ b/tests/unit_tests/shared_mem/pattern_gen.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "pattern_gen.h" #include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.h" diff --git a/tests/unit_tests/shared_mem/pattern_gen.h b/tests/unit_tests/shared_mem/pattern_gen.h index 1c679b9..9208d09 100644 --- a/tests/unit_tests/shared_mem/pattern_gen.h +++ b/tests/unit_tests/shared_mem/pattern_gen.h @@ -2,6 +2,7 @@ #define PATTERN_GEN_H #include +#include #include "../../../sdv_services/ipc_shared_mem/mem_buffer_accessor.h" /** @@ -43,8 +44,8 @@ public: private: CMemBufferAccessorTx& m_raccessorOut; //!< Reference to the output accessor std::thread m_thread; //!< Processing thread - bool m_bStarted = false; //!< Set by the thread when started. - bool m_bShutdown = false; //!< When set, shutdown the thread. + std::atomic_bool m_bStarted = false; //!< Set by the thread when started. + std::atomic_bool m_bShutdown = false; //!< When set, shutdown the thread. uint32_t m_uiDelayMs = 0u; //!< Delay (in ms) to insert while processing. uint32_t m_uiCycleCnt = 0u; //!< Amount of packets uint32_t m_uiPacketCnt = 0u; //!< Amount of packets diff --git a/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp b/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp index 6d4d334..05335e0 100644 --- a/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp +++ b/tests/unit_tests/shared_mem/shared_mem_buffer_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "gtest/gtest.h" #include "pattern_gen.h" #include "../../../sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h" @@ -28,10 +29,14 @@ TEST(SharedMemoryBufferTest, CreateBuffer) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); appcontrol.Shutdown(); } @@ -42,12 +47,16 @@ TEST(SharedMemoryBufferTest, TriggerTestRx) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); - bool bShutdown = false; + std::atomic_bool bShutdown = false; size_t nCorrectCnt = 0; std::condition_variable cvStart; std::mutex mtxStart; @@ -91,12 +100,16 @@ TEST(SharedMemoryBufferTest, TriggerTestTx) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); - bool bShutdown = false; + std::atomic_bool bShutdown = false; size_t nCorrectCnt = 0; std::condition_variable cvStart; std::mutex mtxStart; @@ -140,16 +153,21 @@ TEST(SharedMemoryBufferTest, TriggerTestRxTx) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); - bool bShutdown = false; + std::atomic_bool bShutdown = false; size_t nCorrectCnt = 0; std::condition_variable cvSenderStart, cvReceiverStart; std::mutex mtxReceiverStart; std::mutex mtxSenderStart; + auto fnWaitForTriggerReceiver = [&]() { std::unique_lock lockReceiver(mtxReceiverStart); @@ -166,9 +184,6 @@ TEST(SharedMemoryBufferTest, TriggerTestRxTx) std::unique_lock lockSender(mtxSenderStart); lockSender.unlock(); cvSenderStart.notify_all(); - std::unique_lock lockReceiver(mtxReceiverStart); - cvReceiverStart.wait(lockReceiver); - lockReceiver.unlock(); while (!bShutdown) { bool bResult = sender.WaitForFreeSpace(200); @@ -179,14 +194,15 @@ TEST(SharedMemoryBufferTest, TriggerTestRxTx) }; std::unique_lock lockStartSender(mtxSenderStart); + std::unique_lock lockStartReceiver(mtxReceiverStart); std::thread threadSender(fnWaitForTriggerSender); + std::thread threadReceiver(fnWaitForTriggerReceiver); cvSenderStart.wait(lockStartSender); lockStartSender.unlock(); - std::unique_lock lockStartReceiver(mtxReceiverStart); - std::thread threadReceiver(fnWaitForTriggerReceiver); + //CHECKPOINT(); cvReceiverStart.wait(lockStartReceiver); lockStartReceiver.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(25)); // Needed for the threads to enter their loop. + std::this_thread::sleep_for(std::chrono::milliseconds(25)); // Needed for the threads to enter their loop. for (size_t n = 0; n < 200; n++) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); @@ -210,10 +226,14 @@ TEST(SharedMemoryBufferTest, SimpleSynchronousWriteRead) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); EXPECT_TRUE(sender.TryWrite("HELLO", 6)); auto optPacket = receiver.TryRead(); @@ -229,10 +249,14 @@ TEST(SharedMemoryBufferTest, ReadWithoutSending) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); auto optPacket = receiver.TryRead(); EXPECT_FALSE(optPacket); @@ -246,10 +270,14 @@ TEST(SharedMemoryBufferTest, RequestReadPacketSize) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); EXPECT_TRUE(sender.TryWrite("HELLO", 5)); @@ -266,10 +294,14 @@ TEST(SharedMemoryBufferTest, FragmentRead) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); EXPECT_TRUE(sender.TryWrite("HELLO", 6)); EXPECT_TRUE(sender.TryWrite("HELLO2", 7)); @@ -296,10 +328,14 @@ TEST(SharedMemoryBufferTest, BufferBoundary) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender(256); - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); // The buffer header has 16 bytes // Each allocation is 8 bytes header, 6 bytes data and 2 bytes alignment @@ -353,10 +389,14 @@ TEST(SharedMemoryBufferTest, ReserveCommitAccessReleaseNonChronologicalOrder) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender(256); - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); // Reserve buffers for strings // The buffer header has 16 bytes @@ -464,10 +504,14 @@ TEST(SharedMemoryBufferTest, SendReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); CPatternReceiver pattern_inspector(receiver); CPatternSender pattern_generator(sender); @@ -501,10 +545,14 @@ TEST(SharedMemoryBufferTest, DelayedSendReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); CPatternReceiver pattern_inspector(receiver); CPatternSender pattern_generator(sender, 10); @@ -538,10 +586,14 @@ TEST(SharedMemoryBufferTest, SendDelayedReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx sender; - EXPECT_TRUE(sender.IsValid()); + if (!sender.IsValid()) + std::cout << "ERROR sender: " << sender.GetError() << std::endl; + ASSERT_TRUE(sender.IsValid()); CSharedMemBufferRx receiver(sender.GetConnectionString()); - EXPECT_TRUE(receiver.IsValid()); + if (!receiver.IsValid()) + std::cout << "ERROR receiver: " << receiver.GetError() << std::endl; + ASSERT_TRUE(receiver.IsValid()); CPatternReceiver pattern_inspector(receiver, 10); CPatternSender pattern_generator(sender); @@ -575,9 +627,13 @@ TEST(SharedMemoryBufferTest, SendRepeatReceivePattern) ASSERT_TRUE(appcontrol.Startup("")); CSharedMemBufferTx bufferTX; - EXPECT_TRUE(bufferTX.IsValid()); + if (!bufferTX.IsValid()) + std::cout << "ERROR TX: " << bufferTX.GetError() << std::endl; + ASSERT_TRUE(bufferTX.IsValid()); CSharedMemBufferRx bufferRX; - EXPECT_TRUE(bufferRX.IsValid()); + if (!bufferTX.IsValid()) + std::cout << "ERROR RX: " << bufferTX.GetError() << std::endl; + ASSERT_TRUE(bufferRX.IsValid()); // The connection string containing the RX and TX strings for the repeater std::string ssConnectionString = bufferTX.GetConnectionString() + "\n" + bufferRX.GetConnectionString(); @@ -630,9 +686,13 @@ Mode = "Essential")code")); LoadSupportServices(); CSharedMemBufferTx bufferTX; - EXPECT_TRUE(bufferTX.IsValid()); + if (!bufferTX.IsValid()) + std::cout << "ERROR TX: " << bufferTX.GetError() << std::endl; + ASSERT_TRUE(bufferTX.IsValid()); CSharedMemBufferRx bufferRX; - EXPECT_TRUE(bufferRX.IsValid()); + if (!bufferTX.IsValid()) + std::cout << "ERROR RX: " << bufferTX.GetError() << std::endl; + ASSERT_TRUE(bufferRX.IsValid()); // Start process sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject("ProcessControlService"); diff --git a/tests/unit_tests/shared_mem/shared_mem_connect.cpp b/tests/unit_tests/shared_mem/shared_mem_connect.cpp index e470104..3a5e87c 100644 --- a/tests/unit_tests/shared_mem/shared_mem_connect.cpp +++ b/tests/unit_tests/shared_mem/shared_mem_connect.cpp @@ -10,6 +10,7 @@ #include #include #include +#include /** * @brief Receiver helper class. @@ -21,8 +22,7 @@ public: * @brief Constructor * @param[in] bEnableEvent When set, enable the connection event callback interface. */ - CConnectReceiver(bool bEnableEvent = false) : m_bEnableEvent(bEnableEvent), - m_threadDecoupledSend(&CConnectReceiver::DecoupledSendThread, this) + CConnectReceiver(bool bEnableEvent = false) : m_bEnableEvent(bEnableEvent) {} /** @@ -31,6 +31,8 @@ public: ~CConnectReceiver() { m_bShutdown = true; + std::unique_lock lock(m_mtxData); + lock.unlock(); if (m_threadDecoupledSend.joinable()) m_threadDecoupledSend.join(); } @@ -64,6 +66,10 @@ public: // Copy the data m_seqDataCopy = seqData; + // Start the processing thread if needed + if (!m_threadDecoupledSend.joinable()) m_threadDecoupledSend = + std::thread(&CConnectReceiver::DecoupledSendThread, this); + // Store data into the queue for sending. m_queueDecoupledSend.push(std::move(seqData)); m_cvDecoupledSend.notify_all(); @@ -170,7 +176,7 @@ private: std::thread m_threadDecoupledSend; ///< Decoupled send thread. std::queue>> m_queueDecoupledSend; ///< Data queue for sending. std::condition_variable m_cvDecoupledSend; ///< Trigger decoupled sending. - bool m_bShutdown = false; ///< Shutdown send thread. + std::atomic_bool m_bShutdown = false; ///< Shutdown send thread. }; /** diff --git a/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp b/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp index 2e3e6da..84cdcbf 100644 --- a/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp +++ b/tests/unit_tests/shared_mem/shared_mem_large_data_tests.cpp @@ -7,10 +7,11 @@ #include <../global/base64.h> #include #include -#include +#include "../../include/sdv_test_macro.h" #include #include #include +#include /** * @brief Load support modules to publish the needed services. @@ -27,8 +28,7 @@ public: * @brief Constructor * @param[in] bEnableEvent When set, enable the connection event callback interface. */ - CLargeDataReceiver(bool bEnableEvent = false) : m_bEnableEvent(bEnableEvent), - m_threadDecoupledSend(&CLargeDataReceiver::DecoupledSendThread, this) + CLargeDataReceiver(bool bEnableEvent = false) : m_bEnableEvent(bEnableEvent) {} /** @@ -37,6 +37,8 @@ public: ~CLargeDataReceiver() { m_bShutdown = true; + std::unique_lock lock(m_mtxData); + lock.unlock(); if (m_threadDecoupledSend.joinable()) m_threadDecoupledSend.join(); } @@ -73,6 +75,10 @@ public: for (const sdv::pointer& rptrData : seqData) m_queueDataCopy.push(rptrData); + // Start the processing thread if needed + if (!m_threadDecoupledSend.joinable()) + m_threadDecoupledSend = std::thread(&CLargeDataReceiver::DecoupledSendThread, this); + // Store data into the queue for sending. m_queueDecoupledSend.push(std::move(seqData)); m_cvDecoupledSend.notify_all(); @@ -87,7 +93,7 @@ public: /** * @brief Wait until the caller hasn't sent anything anymore for 1 second. */ - void WaitForNoActivity(sdv::IInterfaceAccess* pSender, size_t nCount = 1, uint32_t uiTimeoutMs = 20000) + void WaitForNoActivity(sdv::IInterfaceAccess* pSender, [[maybe_unused]] size_t nCount = 1, uint32_t uiTimeoutMs = 1000) { CConnection* pConnection = dynamic_cast(pSender); double dTimeout = static_cast(uiTimeoutMs) / 1000.0; @@ -237,16 +243,16 @@ private: sdv::ipc::IDataSend* m_pSend = nullptr; ///< Send interface to implement repeating function. mutable std::mutex m_mtxData; ///< Protect data access. std::queue> m_queueDataCopy; ///< Copy of the data. - sdv::ipc::EConnectStatus m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current received status. + std::atomic m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current received status. bool m_bConnectError = false; ///< Connection error ocurred. bool m_bCommError = false; ///< Communication error occurred. bool m_bForcedDisconnect = false; ///< Force disconnect. - size_t m_nCount = 0; ///< Receive counter. + std::atomic_size_t m_nCount = 0; ///< Receive counter. std::condition_variable m_cvReceived; ///< Receive event. std::thread m_threadDecoupledSend; ///< Decoupled send thread. std::queue>> m_queueDecoupledSend; ///< Data queue for sending. std::condition_variable m_cvDecoupledSend; ///< Trigger decoupled sending. - bool m_bShutdown = false; ///< Shutdown send thread. + std::atomic_bool m_bShutdown = false; ///< Shutdown send thread. }; TEST(SharedMemChannelService, CommunicateOneLargeBlock) @@ -341,6 +347,7 @@ Size = 1024000 EXPECT_NO_THROW(ptrServerConnection.Clear()); EXPECT_NO_THROW(mgntServer.Shutdown()); + EXPECT_NO_THROW(mgntClient.Shutdown()); EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending); @@ -371,6 +378,7 @@ Size = 1024000 EXPECT_NE(sChannelEndpoint.pConnection, nullptr); EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty()); + sdv::TObjectPtr ptrServerConnection(sChannelEndpoint.pConnection); sdv::TObjectPtr ptrClientConnection = mgntClient.Access(sChannelEndpoint.ssConnectString); EXPECT_TRUE(ptrServerConnection); @@ -439,7 +447,7 @@ Size = 1024000 } EXPECT_EQ(nCnt, 30); - nCnt = 0; + nCnt = 0; bCorrect = true; EXPECT_EQ(receiverClient.GetReceiveCount(), 30u); while (bCorrect) @@ -467,6 +475,7 @@ Size = 1024000 EXPECT_NO_THROW(ptrServerConnection.Clear()); EXPECT_NO_THROW(mgntServer.Shutdown()); + EXPECT_NO_THROW(mgntClient.Shutdown()); EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending); @@ -582,6 +591,7 @@ Size = 1024000 EXPECT_NO_THROW(ptrServerConnection.Clear()); EXPECT_NO_THROW(mgntServer.Shutdown()); + EXPECT_NO_THROW(mgntClient.Shutdown()); EXPECT_EQ(mgntServer.GetStatus(), sdv::EObjectStatus::destruction_pending); @@ -650,6 +660,7 @@ Size = 1024000 // Try send; should succeed, since connected EXPECT_TRUE(pServerSend->SendData(seqPattern)); receiverServer.WaitForNoActivity(ptrServerConnection); + EXPECT_EQ(receiverServer.GetReceiveCount(), 1u); auto ptrServerPattern = receiverServer.GetData(); ASSERT_EQ(ptrServerPattern.size(), nCount * sizeof(uint32_t)); pData = reinterpret_cast(ptrServerPattern.get()); @@ -730,13 +741,15 @@ Size = 1024000 TRACE("Connection estabished..."); appcontrol.SetRunningMode(); + EXPECT_EQ(receiverServer.GetReceiveCount(), 0u); + // Try send; should succeed, since connected for (size_t nCnt = 0; nCnt < 30; nCnt++) { if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(pServerSend->SendData(seqPattern), true, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(pServerSend->SendData(seqPattern), true, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(pServerSend->SendData(seqPattern), true, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(pServerSend->SendData(seqPattern), true, sdv_test::WARNING_ENABLED); } receiverServer.WaitForNoActivity(ptrServerConnection, 30); @@ -744,9 +757,9 @@ Size = 1024000 size_t nCnt = 0; bool bCorrect = true; if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(receiverServer.GetReceiveCount(), 30u, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(receiverServer.GetReceiveCount(), 30u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(receiverServer.GetReceiveCount(), 30u, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(receiverServer.GetReceiveCount(), 30u, sdv_test::WARNING_ENABLED); while (bCorrect) { auto ptrServerPattern = receiverServer.GetData(); @@ -765,9 +778,9 @@ Size = 1024000 } } if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD) - SDV_TIMING_EXPECT_EQ(nCnt, 30, sdv::TEST::WARNING_REDUCED); + SDV_EXPECT_EQ_WARN(nCnt, 30u, sdv_test::WARNING_REDUCED); else - SDV_TIMING_EXPECT_EQ(nCnt, 30, sdv::TEST::WARNING_ENABLED); + SDV_EXPECT_EQ_WARN(nCnt, 30u, sdv_test::WARNING_ENABLED); appcontrol.SetConfigMode(); diff --git a/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp b/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp index bfefb30..da866e7 100644 --- a/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp +++ b/tests/unit_tests/socket_can_com_tests/src/can_com_test_socket.cpp @@ -1,5 +1,6 @@ #include "../../../../global/process_watchdog.h" #include "../include/can_com_test_helper.h" +#include #ifdef __GNUC__ #pragma GCC diagnostic push @@ -201,7 +202,7 @@ void ShutDownCanComObject(CTestCANSocket& canComObj, MockCANReceiver& mockRcv) ASSERT_NO_THROW(canComObj.Shutdown()); } -void SendThread(bool& stop, CTestCANSocket& canComObj, sdv::can::SMessage& testData) +void SendThread(std::atomic_bool& stop, CTestCANSocket& canComObj, sdv::can::SMessage& testData) { while (!stop) { @@ -465,7 +466,7 @@ TEST_F(CANSocketTest, StressTestWith3Objects) auto testData2 = testHelper.CreateTestData(20, 7); // Testdata to send to vcan3, size = 7 auto testData3 = testHelper.CreateTestData(30, 8); // Testdata to send to vcan1, size = 8 - bool stopSendThread = false; + std::atomic_bool stopSendThread = false; std::cout << "Start thread sending messages..." << std::endl; std::thread thSendThread1(SendThread, std::ref(stopSendThread), std::ref(canComObj1), std::ref(testData1)); std::this_thread::sleep_for(std::chrono::milliseconds(500)); diff --git a/tests/unit_tests/toml_parser/CMakeLists.txt b/tests/unit_tests/toml_parser/CMakeLists.txt index 8ad1533..385f331 100644 --- a/tests/unit_tests/toml_parser/CMakeLists.txt +++ b/tests/unit_tests/toml_parser/CMakeLists.txt @@ -4,7 +4,10 @@ add_executable(UnitTest_TOMLParser "lexer_tests.cpp" "parser_tests.cpp" "main.cpp" - "generate_toml_tests.cpp") + "generate_toml_tests.cpp" + "content_modifications.cpp" + "statement_boundary_detection.cpp" + "miscellaneous_tests.cpp") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_link_libraries(UnitTest_TOMLParser GTest::GTest ${CMAKE_THREAD_LIBS_INIT}) if (WIN32) diff --git a/tests/unit_tests/toml_parser/character_reader_tests.cpp b/tests/unit_tests/toml_parser/character_reader_tests.cpp index 1b03ae7..b38cf36 100644 --- a/tests/unit_tests/toml_parser/character_reader_tests.cpp +++ b/tests/unit_tests/toml_parser/character_reader_tests.cpp @@ -183,46 +183,108 @@ TEST(UTF8_CharacterReader, InputValidation) for (const auto& inv : GetInvalidUTF8Bytes()) { std::string ssUTF8InputInvalid = "Invalid input: " + inv + " test"; - EXPECT_THROW(CCharacterReaderUTF8 reader(ssUTF8InputInvalid), sdv::toml::XTOMLParseException); + EXPECT_THROW(toml_parser::CCharacterReaderUTF8 reader(ssUTF8InputInvalid), sdv::toml::XTOMLParseException); } // Test for a variety of invalid sequences for (const auto& inv : GetInvalidUTF8Sequences()) { std::string UTF8InputInvalid = "Invalid input: " + inv + "test"; - EXPECT_THROW(CCharacterReaderUTF8 reader(UTF8InputInvalid), sdv::toml::XTOMLParseException); + EXPECT_THROW(toml_parser::CCharacterReaderUTF8 reader(UTF8InputInvalid), sdv::toml::XTOMLParseException); } // Test for a sample of ASCII, 2-Byte-, 3-Byte- and 4-Byte-Sequences that they will be accepted as valid input - EXPECT_NO_THROW(CCharacterReaderUTF8 reader(UTF8InputValid)); + EXPECT_NO_THROW(toml_parser::CCharacterReaderUTF8 reader(UTF8InputValid)); - EXPECT_NO_THROW(CCharacterReaderUTF8 reader(TestInputUTF8)); + EXPECT_NO_THROW(toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8)); } TEST(UTF8_CharacterReader, PeekAndConsume_ReadCharacters) { - // Peek() and Consume() read the next character + // Peek() and Consume() read the next character + { + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + for (int i = 0; i < 12; ++i) + { + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Peek()); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Consume()); + } + } + + // Read all characters and the an empty string must be read + { + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + std::size_t nSize = TestInputUTF8.size(); + size_t nCnt = 0; + while (nCnt < nSize) + { + EXPECT_FALSE(reader.Peek().empty()); + std::string ssCharacters = reader.Consume(); + size_t nCharCnt = ssCharacters.size(); + EXPECT_NE(nCharCnt, 0); + if (!nCharCnt) break; + nCnt += nCharCnt; + } + EXPECT_EQ(nCnt, nSize); + EXPECT_TRUE(reader.Peek().empty()); + EXPECT_TRUE(reader.Consume().empty()); + } +} + +TEST(UTF8_CharacterReader, PeekAndConsume_ReadCharactersSkip) +{ + // Peek(n) and Consume(n) read with skip of every two characters { - CCharacterReaderUTF8 reader(TestInputUTF8); - for (int i = 0; i < 12; ++i) + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + for (int i = 1; i < 12; i += 2) { - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Peek()); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Consume()); + + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Peek(1)); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Consume(1)); } } - // Peek(n) and Consume(n) read the next n-th character - { - for (int i = 0; i < 12; ++i) - { - CCharacterReaderUTF8 reader(TestInputUTF8); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Peek(i + 1)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i], reader.Consume(i + 1)); - } - } - // PeekUntil(a) and ConsumeUntil(a) read until a given character + + // Peek(0)/Consume(0) will return the first character in the string and Peek(n)/Consume(n) return empty string if they read out + // of bounds + { + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Peek(0)); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Consume(0)); + std::size_t biggerIndex = TestInputUTF8.size(); // Assure this is bigger than the number of characters in TestInputUTF8 + EXPECT_EQ("", reader.Peek(biggerIndex)); + EXPECT_EQ("", reader.Consume(biggerIndex)); + } +} + +TEST(UTF8_CharacterReader, PeekAndConsume_ReadCharactersMulti) +{ + // Peek() and Consume() read the next character + { + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + for (int i = 0; i < 12; i += 2) + { + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i] + TestInputUTF8_CharacterIndexMap[i + 1], reader.Peek(0, 2)); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i] + TestInputUTF8_CharacterIndexMap[i + 1], reader.Consume(0, 2)); + } + } + + // Peek() and Consume() skip one and then read two read the next character + { + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + for (int i = 0; i < 12; i += 3) + { + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i + 1] + TestInputUTF8_CharacterIndexMap[i + 2], reader.Peek(1, 2)); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[i + 1] + TestInputUTF8_CharacterIndexMap[i + 2], reader.Consume(1, 2)); + } + } +} + +TEST(UTF8_CharacterReader, PeekAndConsume_ReadCharactersUntil) +{ + // PeekUntil(a) and ConsumeUntil(a) read until a given character { std::size_t byteIndex = 0; for (int i = 0; i < 12; ++i) { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); std::vector Codepoints; Codepoints.push_back(TestInputUTF8_CharacterIndexMap[i]); std::string wantedSubstring = TestInputUTF8.substr(0, byteIndex); @@ -233,28 +295,19 @@ TEST(UTF8_CharacterReader, PeekAndConsume_ReadCharacters) } // PeekUntil(a) and ConsumeUntil(a) read only until EOF if they don't find a matching character { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); std::string notInTestString = "g"; EXPECT_EQ(std::string::npos, TestInputUTF8.find(notInTestString)); EXPECT_EQ(TestInputUTF8, reader.PeekUntil({notInTestString})); EXPECT_EQ(TestInputUTF8, reader.ConsumeUntil({notInTestString})); } - // Peek(0)/Peek(n) and Consume(0)/Consume(n) return empty string if they would read out of bounds - { - CCharacterReaderUTF8 reader(TestInputUTF8); - EXPECT_EQ("", reader.Peek(0)); - EXPECT_EQ("", reader.Consume(0)); - std::size_t biggerIndex = TestInputUTF8.size(); // Assure this is bigger than the number of characters in TestInputUTF8 - EXPECT_EQ("", reader.Peek(biggerIndex)); - EXPECT_EQ("", reader.Consume(biggerIndex)); - } } TEST(UTF8_CharacterReader, Peek_NoAdvance) { ASSERT_NE(TestInputUTF8_CharacterIndexMap[0], TestInputUTF8_CharacterIndexMap[1]); ASSERT_NE(TestInputUTF8_CharacterIndexMap[1], TestInputUTF8_CharacterIndexMap[2]); - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); // Peek() does not advance the read location { EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Peek()); @@ -263,10 +316,10 @@ TEST(UTF8_CharacterReader, Peek_NoAdvance) } // Peek(n) does not advance the read location { - EXPECT_EQ("", reader.Peek(0)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Peek(1)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[1], reader.Peek(2)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[2], reader.Peek(3)); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Peek(0)); // Read pos 0 + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[1], reader.Peek(1)); // Read pos 1 + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[2], reader.Peek(2)); // Read pos 2 + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[3], reader.Peek(3)); // Read pos 3 } // PeekUntil(a) does not advance the read location { @@ -286,22 +339,22 @@ TEST(UTF8_CharacterReader, Consume_Advance) ASSERT_NE(TestInputUTF8_CharacterIndexMap[3], TestInputUTF8_CharacterIndexMap[4]); // Consume() does advance the read location to the next character { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Consume()); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[1], reader.Consume()); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[2], reader.Consume()); } // Consume(n) does advance the read location to the next n-th character { - CCharacterReaderUTF8 reader(TestInputUTF8); - EXPECT_EQ("", reader.Consume(0)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Consume(1)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[2], reader.Consume(2)); - EXPECT_EQ(TestInputUTF8_CharacterIndexMap[5], reader.Consume(3)); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.Consume(0)); // Read pos 0; advance to pos 1 + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[2], reader.Consume(1)); // Read pos 2; advance to pos 3 + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[5], reader.Consume(2)); // Read pos 5; advance to pos 6 + EXPECT_EQ(TestInputUTF8_CharacterIndexMap[9], reader.Consume(3)); // Read pos 9; advance to pos 10 } // ConsumeUntil(a) does advance the read location { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.ConsumeUntil({TestInputUTF8_CharacterIndexMap[1]})); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[1], reader.ConsumeUntil({TestInputUTF8_CharacterIndexMap[2]})); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[2], reader.ConsumeUntil({TestInputUTF8_CharacterIndexMap[3]})); @@ -312,7 +365,7 @@ TEST(UTF8_CharacterReader, PeekUntilConsumeUntil_FindAny) { // PeekUntil(a) works for a collection of characters { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.PeekUntil({TestInputUTF8_CharacterIndexMap[2], TestInputUTF8_CharacterIndexMap[1]})); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0] + TestInputUTF8_CharacterIndexMap[1] + TestInputUTF8_CharacterIndexMap[2], @@ -320,7 +373,7 @@ TEST(UTF8_CharacterReader, PeekUntilConsumeUntil_FindAny) } // ConsumeUntil(a) works for a collection of characters { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[0], reader.ConsumeUntil({TestInputUTF8_CharacterIndexMap[2], TestInputUTF8_CharacterIndexMap[1]})); EXPECT_EQ(TestInputUTF8_CharacterIndexMap[1] + TestInputUTF8_CharacterIndexMap[2], @@ -331,7 +384,7 @@ TEST(UTF8_CharacterReader, PeekUntilConsumeUntil_FindAny) TEST(UTF8_CharacterReader, PeekAndConsume_SameOutput) { { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); for (int i = 0; i < 12; ++i) { std::string p = reader.Peek(); @@ -342,7 +395,7 @@ TEST(UTF8_CharacterReader, PeekAndConsume_SameOutput) { for (int i = 1; i < 13; ++i) { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); std::string p = reader.Peek(i); std::string c = reader.Consume(i); EXPECT_EQ(p, c); @@ -351,7 +404,7 @@ TEST(UTF8_CharacterReader, PeekAndConsume_SameOutput) { for (int i = 1; i < 13; ++i) { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); std::vector CodePoints; CodePoints.push_back(reader.Peek(i)); std::string p = reader.PeekUntil(CodePoints); @@ -365,7 +418,7 @@ TEST(UTF8_CharacterReader, EOFTests) { // Check all calls that are not to trigger EOF { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_FALSE(reader.IsEOF()); reader.Peek(); reader.Peek(1); @@ -379,20 +432,20 @@ TEST(UTF8_CharacterReader, EOFTests) } // Check that reading the last character with Consume(n) triggers EOF { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_FALSE(reader.IsEOF()); reader.Consume(TestInputUTF8_CharacterIndexMap.size()); // Last Character EXPECT_TRUE(reader.IsEOF()); } // Check that reading out of bounds with Consume(n) triggers EOF { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_FALSE(reader.IsEOF()); reader.Consume(TestInputUTF8_CharacterIndexMap.size() + 1); // Out of bounds EXPECT_TRUE(reader.IsEOF()); } { - CCharacterReaderUTF8 reader(TestInputUTF8); + toml_parser::CCharacterReaderUTF8 reader(TestInputUTF8); EXPECT_FALSE(reader.IsEOF()); reader.ConsumeUntil({TestInputUTF8_CharacterIndexMap[11]}); // Second last character EXPECT_FALSE(reader.IsEOF()); @@ -400,7 +453,7 @@ TEST(UTF8_CharacterReader, EOFTests) EXPECT_TRUE(reader.IsEOF()); } { - CCharacterReaderUTF8 reader(""); + toml_parser::CCharacterReaderUTF8 reader(""); EXPECT_TRUE(reader.IsEOF()); } } diff --git a/tests/unit_tests/toml_parser/content_modifications.cpp b/tests/unit_tests/toml_parser/content_modifications.cpp new file mode 100644 index 0000000..29499e1 --- /dev/null +++ b/tests/unit_tests/toml_parser/content_modifications.cpp @@ -0,0 +1,449 @@ +#include + +#include "../../../sdv_services/core/toml_parser/parser_node_toml.h" +#include "../../../sdv_services/core/toml_parser/parser_toml.h" + +// Delete nodes (for all types of nodes, between all types of nodes) +// - beginning +// - middle +// - end +// - Using deleted node info -- error +// - Last node (empty after that) +// - Smart delete (comments/whitespace around) + +// Insert nodes (for all types of nodes, between all types of nodes) +// Before and after: +// - beginning +// - middle +// - end +// - Being the first item in a TOML file +// - Inserted and straight away deleted +// - Inserted with false/deleted reference --error +// - Inserted values before (okay) and behind (error) tables +// - Inserted duplicate value -- error +// - Smart insert (comments/whitespace around) + +// Shift nodes (for all types of nodes, between root, tables and arrays) + +/* + * @brief Delete a key from the TOML string. + * @param[in] rssTOMLInput Reference to the TOML string. + * @param[in] rssKey Reference to the key to delete. + * @param[in] rssOuput Reference to the expected ouput. + * @return Returns 'true' on success. + */ +bool TestDelete(const std::string& rssTOMLInput, const std::string& rssKey, const std::string& rssOutput) +{ + toml_parser::CParser parser; + bool bRes = true; + EXPECT_NO_THROW(bRes = parser.Process(rssTOMLInput)); + EXPECT_TRUE(bRes); + if (!bRes) return bRes; + auto ptrNode = parser.Root().Direct(rssKey); + EXPECT_TRUE(ptrNode); + if (!ptrNode) return false; + EXPECT_TRUE(bRes = ptrNode->DeleteNode()); + if (!bRes) return bRes; + std::string ssTOML = parser.GenerateTOML(); + EXPECT_EQ(ssTOML, rssOutput); + if (ssTOML != rssOutput) return false; + return true; +}; + +TEST(TOMLContentModifications, DISABLED_DeleteValues) +{ + // Delete a key from the begin + EXPECT_TRUE(TestDelete(R"toml( +key = 10 # value key +bare_key = "value" # value bare_key +bare-key = false # value bare-key +)toml", + "key", + R"toml( +bare_key = "value" # value bare_key +bare-key = false # value bare-key +)toml")); + + // Delete a key from the middle + EXPECT_TRUE(TestDelete(R"toml( +key = 10 # value key +bare_key = "value" # value bare_key +bare-key = false # value bare-key +)toml", + "bare_key", + R"toml( +key = 10 # value key +bare-key = false # value bare-key +)toml")); + + // Delete a key from the end + EXPECT_TRUE(TestDelete(R"toml( +key = 10 # value key +bare_key = "value" # value bare_key +bare-key = false # value bare-key +)toml", + "bare-key", + R"toml( +key = 10 # value key +bare_key = "value" # value bare_key +)toml")); +} + +TEST(TOMLContentModifications, DISABLED_DeleteInlineTableValues) +{ + // Delete key from the inline table + EXPECT_TRUE(TestDelete(R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml", + "1234.y", + R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, z = 2, str = "abc"} +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml", + "1234.x", + R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {y = 1, z = 2, str = "abc"} +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml", + "1234.str", + R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2} +)toml")); + + // Delete key from the inline sub-table + EXPECT_TRUE(TestDelete(R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} +)toml", + "1234.tbl.b", + R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, c=3}} +)toml")); + + // Delete table + EXPECT_TRUE(TestDelete(R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} +)toml", + "1234.tbl", + R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} +)toml", + "1234", + R"toml( +key = 10 +bare_key = "value" +bare-key = false +)toml")); +} + +TEST(TOMLContentModifications, DISABLED_DeleteTableValues) +{ + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +)toml", + "my_table.key", + R"toml( +[my_table] +bare_key = "value" +bare-key = false +)toml")); + + // Delete a key from the middle + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +)toml", + "my_table.bare_key", + R"toml( +[my_table] +key = 10 +bare-key = false +)toml")); + + // Delete a key from the end + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +)toml", + "my_table.bare-key", + R"toml( +[my_table] +key = 10 +bare_key = "value" +)toml")); + + // Delete key from the inline table in a table + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml", + "my_table.1234.y", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, z = 2, str = "abc"} +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml", + "my_table.1234.x", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {y = 1, z = 2, str = "abc"} +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml", + "my_table.1234.str", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2} +)toml")); + + // Delete key from the inline sub-table + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} +)toml", + "my_table.1234.tbl.b", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, c=3}} +)toml")); + + // Delete table + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} +)toml", + "my_table.1234.tbl", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc"} +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +1234 = {x = 0, y = 1, z = 2, str = "abc", tbl={a =1, b=2, c=3}} +)toml", + "my_table.1234", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +)toml")); + + // Delete key from the child-table + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +str = "abc" +)toml", + "my_table.1234.y", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +z = 2 +str = "abc" +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +str = "abc" +)toml", + "my_table.1234.x", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +y = 1 +z = 2 +str = "abc" +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +str = "abc" +)toml", + "my_table.1234.str", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +)toml")); + + // Delete table + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +str = "abc" +[my_table.1234.tbl] +a =1 +b=2 +c=3 +)toml", + "my_table.1234.tbl", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +str = "abc" +)toml")); + EXPECT_TRUE(TestDelete(R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +[my_table.1234] +x = 0 +y = 1 +z = 2 +str = "abc" +[my_table.1234.tbl] +a =1 +b=2 +c=3 +)toml", + "my_table.1234", + R"toml( +[my_table] +key = 10 +bare_key = "value" +bare-key = false +)toml")); +} + +TEST(TOMLContentModifications, DISABLED_DeleteArrayValues) +{ + EXPECT_TRUE(TestDelete(R"toml( +key = [10, 20, 30] +bare_key = ["value1", "value2", 3030] +bare-key = [{a = false, b = true}, 2020] +)toml", + "key[1]", + R"toml( +key = [10, 30] +bare_key = ["value1", "value2", 3030] +bare-key = [{a = false, b = true}, 2020] +)toml")); +} \ No newline at end of file diff --git a/tests/unit_tests/toml_parser/generate_toml_tests.cpp b/tests/unit_tests/toml_parser/generate_toml_tests.cpp index caae827..11ec269 100644 --- a/tests/unit_tests/toml_parser/generate_toml_tests.cpp +++ b/tests/unit_tests/toml_parser/generate_toml_tests.cpp @@ -2,81 +2,137 @@ #include "../../../sdv_services/core/toml_parser/parser_toml.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" -inline void Trim(std::string& rss) -{ - // Remove front whitespace - size_t nStart = rss.find_first_not_of(" \t\f\r\n\v"); - if (nStart) rss.erase(0, nStart); - - // Remove rear whitespace - size_t nStop = rss.find_last_not_of(" \t\f\r\n\v"); - rss.erase(nStop + 1); -} - TEST(GenerateTOML, Comment) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# This is a full-line comment -key = "value" # This is a comment at the end of a line -another = "# This is not a comment")code"; - std::string ssTOMLOutput = R"code(key = "value" -another = "# This is not a comment")code"; + std::string ssTOML = R"code(# This is a full-line comment)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } -TEST(GenerateTOML, TransferComment) +TEST(GenerateTOML, NodeComment) { - CParserTOML parser; + toml_parser::CParser parser; + + std::string ssTOML = R"code(# This is a full-line comment +key = "value" # This is a comment at the end of a line +another = "# This is not a comment")code"; + + EXPECT_NO_THROW(parser.Process(ssTOML)); + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); +} + +TEST(GenerateTOML, NodeCommentWithSpaces) +{ + toml_parser::CParser parser; + + std::string ssTOML = R"code( + # This is a full-line comment + key = "value" # This is a comment at the end of a line +)code"; + + parser.Process(ssTOML); + EXPECT_NO_THROW(parser.Process(ssTOML)); + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); +} + +TEST(GenerateTOML, UnattachedComment) +{ + toml_parser::CParser parser; + + std::string ssTOML = R"code(# Comment not belonging to node + +# This is a full-line comment +key = "value" # This is a comment at the end of a line +another = "# This is not a comment" + +# Comment not belonging to node)code"; + + EXPECT_NO_THROW(parser.Process(ssTOML)); + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); +} + +TEST(GenerateTOML, ArrayComment) +{ + toml_parser::CParser parser; + + std::string ssTOML = R"code( +# Pre-array +array = [ 1, # Value #1 + 2, # Value #2 + 3, # Value #3 + 4, # Value #4 + 5, # Value #5 + 6, # Value #6 + ] # Post-array +)code"; + + EXPECT_NO_THROW(parser.Process(ssTOML)); + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); +} + +TEST(GenerateTOML, TransferNodeComment) +{ + toml_parser::CParser parser; std::string ssTOMLInput = R"code(# This is a full-line comment key = "value" # This is a comment at the end of a line another = "# This is not a comment")code"; std::string ssTOMLOutput = R"code([tree.branch] -key = "value" +# This is a full-line comment +key = "value" # This is a comment at the end of a line another = "# This is not a comment")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, Keys) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(key = "value" -bare_key = "value" -bare-key = "value" -1234 = "value")code"; - std::string ssTOMLOutput = R"code(key = "value" + std::string ssTOML = R"code(key = "value" bare_key = "value" bare-key = "value" 1234 = "value")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferKeys) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(key = "value" bare_key = "value" @@ -91,39 +147,32 @@ bare-key = "value" EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, QuotedKeys) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = u8R"code("127.0.0.1" = "value" -"character encoding" = "value" -"ÊŽÇÊž" = "value" -'key2' = "value" -'quoted "value"' = "value")code"; - std::string ssTOMLOutput = u8R"code("127.0.0.1" = "value" + std::string ssTOML = u8R"code("127.0.0.1" = "value" "character encoding" = "value" "ÊŽÇÊž" = "value" 'key2' = "value" 'quoted "value"' = "value")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferQuotedKeys) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = u8R"code("127.0.0.1" = "value" "character encoding" = "value" @@ -140,31 +189,25 @@ TEST(GenerateTOML, TransferQuotedKeys) EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, BlankKeys) { - std::string ssTOMLInput1 = R"code("" = "blank" # VALID but discouraged)code"; - std::string ssTOMLInput2 = R"code('' = 'blank' # VALID but discouraged)code"; - std::string ssTOMLOutput1 = R"code("" = "blank")code"; - std::string ssTOMLOutput2 = R"code('' = "blank")code"; + std::string ssTOML1 = R"code("" = "blank" # VALID but discouraged)code"; + std::string ssTOML2 = R"code('' = 'blank' # VALID but discouraged)code"; - CParserTOML parser1, parser2; - EXPECT_NO_THROW(parser1.Process(ssTOMLInput1)); - EXPECT_NO_THROW(parser2.Process(ssTOMLInput2)); + toml_parser::CParser parser1, parser2; + EXPECT_NO_THROW(parser1.Process(ssTOML1)); + EXPECT_NO_THROW(parser2.Process(ssTOML2)); std::string ssGenerated1, ssGenerated2; - EXPECT_NO_THROW(ssGenerated1 = parser1.CreateTOMLText()); - Trim(ssGenerated1); - EXPECT_NO_THROW(ssGenerated2 = parser2.CreateTOMLText()); - Trim(ssGenerated2); - - EXPECT_EQ(ssGenerated1, ssTOMLOutput1); - EXPECT_EQ(ssGenerated2, ssTOMLOutput2); + EXPECT_NO_THROW(ssGenerated1 = parser1.GenerateTOML()); + EXPECT_NO_THROW(ssGenerated2 = parser2.GenerateTOML()); + EXPECT_EQ(ssGenerated1, ssTOML1); + EXPECT_EQ(ssGenerated2, ssTOML2); } TEST(GenerateTOML, TransferBlankKeys) @@ -172,19 +215,17 @@ TEST(GenerateTOML, TransferBlankKeys) std::string ssTOMLInput1 = R"code("" = "blank" # VALID but discouraged)code"; std::string ssTOMLInput2 = R"code('' = 'blank' # VALID but discouraged)code"; std::string ssTOMLOutput1 = R"code([tree.branch] -"" = "blank")code"; +"" = "blank" # VALID but discouraged)code"; std::string ssTOMLOutput2 = R"code([tree.branch] -'' = "blank")code"; +'' = 'blank' # VALID but discouraged)code"; - CParserTOML parser1, parser2; + toml_parser::CParser parser1, parser2; EXPECT_NO_THROW(parser1.Process(ssTOMLInput1)); EXPECT_NO_THROW(parser2.Process(ssTOMLInput2)); std::string ssGenerated1, ssGenerated2; - EXPECT_NO_THROW(ssGenerated1 = parser1.CreateTOMLText("tree.branch")); - Trim(ssGenerated1); - EXPECT_NO_THROW(ssGenerated2 = parser2.CreateTOMLText("tree.branch")); - Trim(ssGenerated2); + EXPECT_NO_THROW(ssGenerated1 = parser1.GenerateTOML("tree.branch")); + EXPECT_NO_THROW(ssGenerated2 = parser2.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated1, ssTOMLOutput1); EXPECT_EQ(ssGenerated2, ssTOMLOutput2); @@ -192,33 +233,24 @@ TEST(GenerateTOML, TransferBlankKeys) TEST(GenerateTOML, DottedKeys) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(name = "Orange" + std::string ssTOML = R"code(name = "Orange" physical.color = "orange" physical.shape = "round" site."google.com" = true)code"; - std::string ssTOMLOutput = R"code(name = "Orange" -[physical] -color = "orange" -shape = "round" - -[site] -"google.com" = true)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferDottedKeys) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(name = "Orange" physical.color = "orange" @@ -226,70 +258,59 @@ physical.shape = "round" site."google.com" = true)code"; std::string ssTOMLOutput = R"code([tree.branch] name = "Orange" - -[tree.branch.physical] -color = "orange" -shape = "round" - -[tree.branch.site] -"google.com" = true)code"; +physical.color = "orange" +physical.shape = "round" +site."google.com" = true)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, WhitespaceKeys) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(fruit.name = "banana" # this is best practice + std::string ssTOML = R"code(fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color fruit . flavor = "banana" # same as fruit.flavor)code"; - std::string ssTOMLOutput = R"code([fruit] -name = "banana" -color = "yellow" -flavor = "banana")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferWhitespaceKeys) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color fruit . flavor = "banana" # same as fruit.flavor)code"; - std::string ssTOMLOutput = R"code([tree.branch.fruit] -name = "banana" -color = "yellow" -flavor = "banana")code"; + std::string ssTOMLOutput = R"code([tree.branch] +fruit.name = "banana" # this is best practice +fruit. color = "yellow" # same as fruit.color +fruit . flavor = "banana" # same as fruit.flavor)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, OutOfOrderKeys) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(apple.type = "fruit" + std::string ssTOML = R"code(apple.type = "fruit" orange.type = "fruit" apple.skin = "thin" @@ -297,28 +318,18 @@ orange.skin = "thick" apple.color = "red" orange.color = "orange")code"; - std::string ssTOMLOutput = R"code([apple] -type = "fruit" -skin = "thin" -color = "red" -[orange] -type = "fruit" -skin = "thick" -color = "orange")code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferOutOfOrderKeys) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(apple.type = "fruit" orange.type = "fruit" @@ -328,78 +339,71 @@ orange.skin = "thick" apple.color = "red" orange.color = "orange")code"; - std::string ssTOMLOutput = R"code([tree.branch.apple] -type = "fruit" -skin = "thin" -color = "red" + std::string ssTOMLOutput = R"code([tree.branch] +apple.type = "fruit" +orange.type = "fruit" -[tree.branch.orange] -type = "fruit" -skin = "thick" -color = "orange")code"; +apple.skin = "thin" +orange.skin = "thick" + +apple.color = "red" +orange.color = "orange")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, FloatLookingAlikeKeys) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(3.14159 = "pi")code"; - std::string ssTOMLOutput = R"code([3] -14159 = "pi")code"; + std::string ssTOML = R"code(3.14159 = "pi")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferFloatLookingAlikeKeys) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(3.14159 = "pi")code"; - std::string ssTOMLOutput = R"code([tree.branch.3] -14159 = "pi")code"; + std::string ssTOMLOutput = R"code([tree.branch] +3.14159 = "pi")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, BasicStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; - std::string ssTOMLOutput = R"code(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; + std::string ssTOML = R"code(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferBasicStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code"; std::string ssTOMLOutput = R"code([tree.branch] @@ -408,54 +412,52 @@ str = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.")code" EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, MultiLineStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str1 = """ + std::string ssTOML = R"code(str1 = """ Roses are red Violets are blue""")code"; - std::string ssTOMLOutput = R"code(str1 = "Roses are red\nViolets are blue")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferMultiLineStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(str1 = """ Roses are red Violets are blue""")code"; std::string ssTOMLOutput = R"code([tree.branch] -str1 = "Roses are red\nViolets are blue")code"; +str1 = """ +Roses are red +Violets are blue""")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, LongMultiLineStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str1 = "The quick brown fox jumps over the lazy dog." + std::string ssTOML = R"code(str1 = "The quick brown fox jumps over the lazy dog." str2 = """ The quick brown \ @@ -469,22 +471,18 @@ str3 = """\ fox jumps over \ the lazy dog.\ """)code"; - std::string ssTOMLOutput = R"code(str1 = "The quick brown fox jumps over the lazy dog." -str2 = "The quick brown fox jumps over the lazy dog." -str3 = "The quick brown fox jumps over the lazy dog.")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferLongMultiLineStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(str1 = "The quick brown fox jumps over the lazy dog." @@ -502,46 +500,51 @@ str3 = """\ """)code"; std::string ssTOMLOutput = R"code([tree.branch] str1 = "The quick brown fox jumps over the lazy dog." -str2 = "The quick brown fox jumps over the lazy dog." -str3 = "The quick brown fox jumps over the lazy dog.")code"; + +str2 = """ +The quick brown \ + + + fox jumps over \ + the lazy dog.""" + +str3 = """\ + The quick brown \ + fox jumps over \ + the lazy dog.\ + """)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, QuotingStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(str4 = """Here are two quotation marks: "". Simple enough.""" + std::string ssTOML = R"code(str4 = """Here are two quotation marks: "". Simple enough.""" # str5 = """Here are three quotation marks: """.""" # INVALID str5 = """Here are three quotation marks: ""\".""" str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" # "This," she said, "is just a pointless statement." str7 = """"This," she said, "is just a pointless statement."""")code"; - std::string ssTOMLOutput = R"code(str4 = "Here are two quotation marks: \"\". Simple enough." -str5 = "Here are three quotation marks: \"\"\"." -str6 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"." -str7 = "\"This,\" she said, \"is just a pointless statement.\"")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferQuotingStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(str4 = """Here are two quotation marks: "". Simple enough.""" # str5 = """Here are three quotation marks: """.""" # INVALID @@ -551,91 +554,84 @@ str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" # "This," she said, "is just a pointless statement." str7 = """"This," she said, "is just a pointless statement."""")code"; std::string ssTOMLOutput = R"code([tree.branch] -str4 = "Here are two quotation marks: \"\". Simple enough." -str5 = "Here are three quotation marks: \"\"\"." -str6 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"." -str7 = "\"This,\" she said, \"is just a pointless statement.\"")code"; +str4 = """Here are two quotation marks: "". Simple enough.""" +# str5 = """Here are three quotation marks: """.""" # INVALID +str5 = """Here are three quotation marks: ""\".""" +str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + +# "This," she said, "is just a pointless statement." +str7 = """"This," she said, "is just a pointless statement."""")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, LiteralStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(winpath = 'C:\Users\nodejs\templates' + std::string ssTOML = R"code(winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' regex = '<\i\c*\s*>')code"; - std::string ssTOMLOutput = R"code(winpath = "C:\\Users\\nodejs\\templates" -winpath2 = "\\\\ServerX\\admin$\\system32\\" -quoted = "Tom \"Dubs\" Preston-Werner" -regex = "<\\i\\c*\\s*>")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferLiteralStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' regex = '<\i\c*\s*>')code"; std::string ssTOMLOutput = R"code([tree.branch] -winpath = "C:\\Users\\nodejs\\templates" -winpath2 = "\\\\ServerX\\admin$\\system32\\" -quoted = "Tom \"Dubs\" Preston-Werner" -regex = "<\\i\\c*\\s*>")code"; +winpath = 'C:\Users\nodejs\templates' +winpath2 = '\\ServerX\admin$\system32\' +quoted = 'Tom "Dubs" Preston-Werner' +regex = '<\i\c*\s*>')code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, MultiLineLiteralStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(regex2 = '''I [dw]on't need \d{2} apples''' + std::string ssTOML = R"code(regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is trimmed in raw strings. All other whitespace is preserved. ''')code"; - std::string ssTOMLOutput = R"code(regex2 = "I [dw]on't need \\d{2} apples" -lines = "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferMultiLineLiteralStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' @@ -645,45 +641,45 @@ trimmed in raw strings. is preserved. ''')code"; std::string ssTOMLOutput = R"code([tree.branch] -regex2 = "I [dw]on't need \\d{2} apples" -lines = "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n")code"; +regex2 = '''I [dw]on't need \d{2} apples''' +lines = ''' +The first newline is +trimmed in raw strings. + All other whitespace + is preserved. +''')code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, QuotedLiteralStrings) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' + std::string ssTOML = R"code(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' # apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID apos15 = "Here are fifteen apostrophes: '''''''''''''''" # 'That,' she said, 'is still pointless.' str = ''''That,' she said, 'is still pointless.'''')code"; - std::string ssTOMLOutput = R"code(quot15 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"" -apos15 = "Here are fifteen apostrophes: '''''''''''''''" -str = "'That,' she said, 'is still pointless.'")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferQuotedLiteralStrings) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(quot15 = '''Here are fifteen quotation marks: """""""""""""""''' @@ -693,51 +689,49 @@ apos15 = "Here are fifteen apostrophes: '''''''''''''''" # 'That,' she said, 'is still pointless.' str = ''''That,' she said, 'is still pointless.'''')code"; std::string ssTOMLOutput = R"code([tree.branch] -quot15 = "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"" +quot15 = '''Here are fifteen quotation marks: """""""""""""""''' + +# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID apos15 = "Here are fifteen apostrophes: '''''''''''''''" -str = "'That,' she said, 'is still pointless.'")code"; + +# 'That,' she said, 'is still pointless.' +str = ''''That,' she said, 'is still pointless.'''')code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, Integers) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(int1 = +99 -int2 = 42 -int3 = 0 -int4 = -17)code"; - std::string ssTOMLOutput = R"code(int1 = 99 + std::string ssTOML = R"code(int1 = +99 int2 = 42 int3 = 0 int4 = -17)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferIntegers) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(int1 = +99 int2 = 42 int3 = 0 int4 = -17)code"; std::string ssTOMLOutput = R"code([tree.branch] -int1 = 99 +int1 = +99 int2 = 42 int3 = 0 int4 = -17)code"; @@ -745,62 +739,55 @@ int4 = -17)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, ReadibleIntegers) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(int5 = 1_000 + std::string ssTOML = R"code(int5 = 1_000 int6 = 5_349_221 int7 = 53_49_221 # Indian number system grouping int8 = 1_2_3_4_5 # VALID but discouraged)code"; - std::string ssTOMLOutput = R"code(int5 = 1000 -int6 = 5349221 -int7 = 5349221 -int8 = 12345)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferReadibleIntegers) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(int5 = 1_000 int6 = 5_349_221 int7 = 53_49_221 # Indian number system grouping int8 = 1_2_3_4_5 # VALID but discouraged)code"; std::string ssTOMLOutput = R"code([tree.branch] -int5 = 1000 -int6 = 5349221 -int7 = 5349221 -int8 = 12345)code"; +int5 = 1_000 +int6 = 5_349_221 +int7 = 53_49_221 # Indian number system grouping +int8 = 1_2_3_4_5 # VALID but discouraged)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, OtherBaseIntegers) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# hexadecimal with prefix `0x` + std::string ssTOML = R"code(# hexadecimal with prefix `0x` hex1 = 0xDEADBEEF hex2 = 0xdeadbeef hex3 = 0xdead_beef @@ -811,25 +798,18 @@ oct2 = 0o755 # useful for Unix file permissions # binary with prefix `0b` bin1 = 0b11010110)code"; - std::string ssTOMLOutput = R"code(hex1 = 3735928559 -hex2 = 3735928559 -hex3 = 3735928559 -oct1 = 342391 -oct2 = 493 -bin1 = 214)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferOtherBaseIntegers) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(# hexadecimal with prefix `0x` hex1 = 0xDEADBEEF @@ -843,27 +823,31 @@ oct2 = 0o755 # useful for Unix file permissions # binary with prefix `0b` bin1 = 0b11010110)code"; std::string ssTOMLOutput = R"code([tree.branch] -hex1 = 3735928559 -hex2 = 3735928559 -hex3 = 3735928559 -oct1 = 342391 -oct2 = 493 -bin1 = 214)code"; +# hexadecimal with prefix `0x` +hex1 = 0xDEADBEEF +hex2 = 0xdeadbeef +hex3 = 0xdead_beef + +# octal with prefix `0o` +oct1 = 0o01234567 +oct2 = 0o755 # useful for Unix file permissions + +# binary with prefix `0b` +bin1 = 0b11010110)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, FloatingPoints) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# fractional + std::string ssTOML = R"code(# fractional flt1 = +1.0 flt2 = 3.1415 flt3 = -0.01 @@ -874,27 +858,19 @@ flt5 = 1e06 flt6 = -2E-2 # both -flt7 = 6.626e-34)code"; - std::string ssTOMLOutput = R"code(flt1 = 1 -flt2 = 3.1415 -flt3 = -0.01 -flt4 = 5e+22 -flt5 = 1000000 -flt6 = -0.02 flt7 = 6.626e-34)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferFloatingPoints) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(# fractional flt1 = +1.0 @@ -909,61 +885,62 @@ flt6 = -2E-2 # both flt7 = 6.626e-34)code"; std::string ssTOMLOutput = R"code([tree.branch] -flt1 = 1 +# fractional +flt1 = +1.0 flt2 = 3.1415 flt3 = -0.01 + +# exponent flt4 = 5e+22 -flt5 = 1000000 -flt6 = -0.02 +flt5 = 1e06 +flt6 = -2E-2 + +# both flt7 = 6.626e-34)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, ReadibleFloatingPoints) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(flt8 = 224_617.445_991_228)code"; - std::string ssTOMLOutput = R"code(flt8 = 224617.445991228)code"; + std::string ssTOML = R"code(flt8 = 224_617.445_991_228)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferReadibleFloatingPoints) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(flt8 = 224_617.445_991_228)code"; std::string ssTOMLOutput = R"code([tree.branch] -flt8 = 224617.445991228)code"; +flt8 = 224_617.445_991_228)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, SpecialFloatingPoints) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# infinity + std::string ssTOML = R"code(# infinity sf1 = inf # positive infinity sf2 = +inf # positive infinity sf3 = -inf # negative infinity @@ -972,33 +949,18 @@ sf3 = -inf # negative infinity sf4 = nan # actual sNaN/qNaN encoding is implementation-specific sf5 = +nan # same as `nan` sf6 = -nan # valid, actual encoding is implementation-specific)code"; -#if defined __GNUC__ && defined _WIN32 - std::string ssTOMLOutput = R"code(sf1 = inf -sf2 = inf -sf3 = -inf -sf4 = nan -sf5 = nan -sf6 = nan)code"; -#else - std::string ssTOMLOutput = R"code(sf1 = inf -sf2 = inf -sf3 = -inf -sf4 = nan -sf5 = nan -sf6 = -nan)code"; -#endif - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferSpecialFloatingPoints) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(# infinity sf1 = inf # positive infinity @@ -1009,54 +971,43 @@ sf3 = -inf # negative infinity sf4 = nan # actual sNaN/qNaN encoding is implementation-specific sf5 = +nan # same as `nan` sf6 = -nan # valid, actual encoding is implementation-specific)code"; -#if defined __GNUC__ && defined _WIN32 std::string ssTOMLOutput = R"code([tree.branch] -sf1 = inf -sf2 = inf -sf3 = -inf -sf4 = nan -sf5 = nan -sf6 = nan)code"; -#else - std::string ssTOMLOutput = R"code([tree.branch] -sf1 = inf -sf2 = inf -sf3 = -inf -sf4 = nan -sf5 = nan -sf6 = -nan)code"; -#endif +# infinity +sf1 = inf # positive infinity +sf2 = +inf # positive infinity +sf3 = -inf # negative infinity + +# not a number +sf4 = nan # actual sNaN/qNaN encoding is implementation-specific +sf5 = +nan # same as `nan` +sf6 = -nan # valid, actual encoding is implementation-specific)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, Booleans) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(bool1 = true -bool2 = false)code"; - std::string ssTOMLOutput = R"code(bool1 = true + std::string ssTOML = R"code(bool1 = true bool2 = false)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferBooleans) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(bool1 = true bool2 = false)code"; @@ -1067,35 +1018,30 @@ bool2 = false)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, DISABLED_OffsetDateTimes) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(odt1 = 1979-05-27T07:32:00Z -odt2 = 1979-05-27T00:32:00-07:00 -odt3 = 1979-05-27T00:32:00.999999-07:00)code"; - std::string ssTOMLOutput = R"code(odt1 = 1979-05-27T07:32:00Z + std::string ssTOML = R"code(odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 odt3 = 1979-05-27T00:32:00.999999-07:00)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, DISABLED_TransferOffsetDateTimes) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(odt1 = 1979-05-27T07:32:00Z odt2 = 1979-05-27T00:32:00-07:00 @@ -1108,66 +1054,59 @@ odt3 = 1979-05-27T00:32:00.999999-07:00)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, DISABLED_ReadibleOffsetDateTimes) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(odt4 = 1979-05-27 07:32:00Z)code"; - std::string ssTOMLOutput = R"code(odt4 = 1979-05-27T07:32:00Z)code"; + std::string ssTOML = R"code(odt4 = 1979-05-27 07:32:00Z)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, DISABLED_TransferReadibleOffsetDateTimes) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(odt4 = 1979-05-27 07:32:00Z)code"; std::string ssTOMLOutput = R"code([tree.branch] -odt4 = 1979-05-27T07:32:00Z)code"; +odt4 = 1979-05-27 07:32:00Z)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, DISABLED_LocalDateTimes) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(ldt1 = 1979-05-27T07:32:00 -ldt2 = 1979-05-27T00:32:00.999999)code"; - std::string ssTOMLOutput = R"code(ldt1 = 1979-05-27T07:32:00 + std::string ssTOML = R"code(ldt1 = 1979-05-27T07:32:00 ldt2 = 1979-05-27T00:32:00.999999)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, DISABLED_TransferLocalDateTimes) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(ldt1 = 1979-05-27T07:32:00 ldt2 = 1979-05-27T00:32:00.999999)code"; @@ -1178,31 +1117,28 @@ ldt2 = 1979-05-27T00:32:00.999999)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, DISABLED_LocalDates) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(ld1 = 1979-05-27)code"; - std::string ssTOMLOutput = R"code(ld1 = 1979-05-27)code"; + std::string ssTOML = R"code(ld1 = 1979-05-27)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, DISABLED_TransferLocalDates) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(ld1 = 1979-05-27)code"; std::string ssTOMLOutput = R"code([tree.branch] @@ -1211,33 +1147,29 @@ ld1 = 1979-05-27)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, DISABLED_LocalTimes) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(lt1 = 07:32:00 -lt2 = 00:32:00.999999)code"; - std::string ssTOMLOutput = R"code(lt1 = 07:32:00 + std::string ssTOML = R"code(lt1 = 07:32:00 lt2 = 00:32:00.999999)code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, DISABLED_TransferLocalTimes) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(lt1 = 07:32:00 lt2 = 00:32:00.999999)code"; @@ -1248,17 +1180,16 @@ lt2 = 00:32:00.999999)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, Arrays) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(integers = [ 1, 2, 3 ] + std::string ssTOML = R"code(integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] @@ -1266,30 +1197,28 @@ string_array = [ "all", 'strings', """are the same""", '''type''' ] # Mixed-type arrays are allowed numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] -contributors = [ - "Foo Bar ", - { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } -])code"; - std::string ssTOMLOutput = R"code(integers = [1, 2, 3] -colors = ["red", "yellow", "green"] -nested_arrays_of_ints = [[1, 2], [3, 4, 5]] -nested_mixed_array = [[1, 2], ["a", "b", "c"]] -string_array = ["all", "strings", "are the same", "type"] -numbers = [0.1, 0.2, 0.5, 1, 2, 5] -contributors = ["Foo Bar ", {name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux"}])code"; +contributors = + [ + "Foo Bar ", + { + name = "Baz Qux", + email = "bazqux@example.com", + url = "https://example.com/bazqux" + } + ])code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + parser.Process(ssTOML); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferArrays) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] @@ -1304,28 +1233,32 @@ contributors = [ { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } ])code"; std::string ssTOMLOutput = R"code([tree.branch] -integers = [1, 2, 3] -colors = ["red", "yellow", "green"] -nested_arrays_of_ints = [[1, 2], [3, 4, 5]] -nested_mixed_array = [[1, 2], ["a", "b", "c"]] -string_array = ["all", "strings", "are the same", "type"] -numbers = [0.1, 0.2, 0.5, 1, 2, 5] -contributors = ["Foo Bar ", {name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux"}])code"; +integers = [ 1, 2, 3 ] +colors = [ "red", "yellow", "green" ] +nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] +nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ] +string_array = [ "all", 'strings', """are the same""", '''type''' ] + +# Mixed-type arrays are allowed +numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] +contributors = [ + "Foo Bar ", + { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } +])code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, MultiLineArrays) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(integers2 = [ + std::string ssTOML = R"code(integers2 = [ 1, 2, 3 ] @@ -1333,21 +1266,18 @@ integers3 = [ 1, 2, # this is ok ])code"; - std::string ssTOMLOutput = R"code(integers2 = [1, 2, 3] -integers3 = [1, 2])code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferMultiLineArrays) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(integers2 = [ 1, 2, 3 @@ -1358,23 +1288,28 @@ integers3 = [ 2, # this is ok ])code"; std::string ssTOMLOutput = R"code([tree.branch] -integers2 = [1, 2, 3] -integers3 = [1, 2])code"; +integers2 = [ + 1, 2, 3 +] + +integers3 = [ + 1, + 2, # this is ok +])code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, Tables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code([table] + std::string ssTOML = R"code([table] [table-1] key1 = "some string" @@ -1383,26 +1318,18 @@ key2 = 123 [table-2] key1 = "another string" key2 = 456)code"; - std::string ssTOMLOutput = R"code([table-1] -key1 = "some string" -key2 = 123 -[table-2] -key1 = "another string" -key2 = 456)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code([table] @@ -1413,7 +1340,9 @@ key2 = 123 [table-2] key1 = "another string" key2 = 456)code"; - std::string ssTOMLOutput = R"code([tree.branch.table-1] + std::string ssTOMLOutput = R"code([tree.branch.table] + +[tree.branch.table-1] key1 = "some string" key2 = 123 @@ -1424,53 +1353,48 @@ key2 = 456)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, QuotedKeyTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code([dog."tater.man"] + std::string ssTOML = R"code([dog."tater.man"] type.name = "pug")code"; - std::string ssTOMLOutput = R"code([dog."tater.man".type] -name = "pug")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferQuotedKeyTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code([dog."tater.man"] type.name = "pug")code"; - std::string ssTOMLOutput = R"code([tree.branch.dog."tater.man".type] -name = "pug")code"; + std::string ssTOMLOutput = R"code([tree.branch.dog."tater.man"] +type.name = "pug")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, WhitespaceKeyTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = u8R"code([a.b.c] # this is best practice + std::string ssTOML = u8R"code([a.b.c] # this is best practice x = 1 [ d.e.f ] # same as [d.e.f] y = 1 @@ -1478,30 +1402,18 @@ y = 1 z = 1 [ j . "Êž" . 'l' ] # same as [j."Êž".'l'] a = 1)code"; - std::string ssTOMLOutput = u8R"code([a.b.c] -x = 1 -[d.e.f] -y = 1 - -[g.h.i] -z = 1 - -[j."Êž".'l'] -a = 1)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferWhitespaceKeyTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = u8R"code([a.b.c] # this is best practice x = 1 @@ -1511,59 +1423,46 @@ y = 1 z = 1 [ j . "Êž" . 'l' ] # same as [j."Êž".'l'] a = 1)code"; - std::string ssTOMLOutput = u8R"code([tree.branch.a.b.c] + std::string ssTOMLOutput = u8R"code([tree.branch.a.b.c] # this is best practice x = 1 - -[tree.branch.d.e.f] +[tree.branch. d.e.f ] # same as [d.e.f] y = 1 - -[tree.branch.g.h.i] +[tree.branch. g . h . i ] # same as [g.h.i] z = 1 - -[tree.branch.j."Êž".'l'] +[tree.branch. j . "Êž" . 'l' ] # same as [j."Êž".'l'] a = 1)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, MixedOrderTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# VALID BUT DISCOURAGED + std::string ssTOML = R"code(# VALID BUT DISCOURAGED [fruit.apple] a = 1 [animal] b = 2 [fruit.orange] aa = 11)code"; - std::string ssTOMLOutput = R"code([fruit.apple] -a = 1 -[fruit.orange] -aa = 11 - -[animal] -b = 2)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferMixedOrderTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(# VALID BUT DISCOURAGED [fruit.apple] @@ -1572,29 +1471,27 @@ a = 1 b = 2 [fruit.orange] aa = 11)code"; - std::string ssTOMLOutput = R"code([tree.branch.fruit.apple] + std::string ssTOMLOutput = R"code(# VALID BUT DISCOURAGED +[tree.branch.fruit.apple] a = 1 - -[tree.branch.fruit.orange] -aa = 11 - [tree.branch.animal] -b = 2)code"; +b = 2 +[tree.branch.fruit.orange] +aa = 11)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, MixedValueAndTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(# Top-level table begins. + std::string ssTOML = R"code(# Top-level table begins. name = "Fido" breed = "pug" @@ -1602,25 +1499,18 @@ breed = "pug" [owner] name = "Regina Dogman" member_since = 1999)code"; - std::string ssTOMLOutput = R"code(name = "Fido" -breed = "pug" -[owner] -name = "Regina Dogman" -member_since = 1999)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferMixedValueAndTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(# Top-level table begins. name = "Fido" @@ -1631,9 +1521,11 @@ breed = "pug" name = "Regina Dogman" member_since = 1999)code"; std::string ssTOMLOutput = R"code([tree.branch] +# Top-level table begins. name = "Fido" breed = "pug" +# Top-level table ends. [tree.branch.owner] name = "Regina Dogman" member_since = 1999)code"; @@ -1641,59 +1533,58 @@ member_since = 1999)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, AutomaticTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(fruit.apple.color = "red" + std::string ssTOML = R"code(fruit.apple.color = "red" fruit.apple.taste.sweet = true)code"; - std::string ssTOMLOutput = R"code([fruit.apple] -color = "red" + std::string ssTOMLOutput2 = R"code(apple.color = "red" +apple.taste.sweet = true)code"; -[fruit.apple.taste] -sweet = true)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + EXPECT_EQ(ssGenerated, ssTOML); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.Root().Direct("fruit")->GenerateTOML()); + EXPECT_EQ(ssGenerated, ssTOMLOutput2); } TEST(GenerateTOML, TransferAutomaticTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(fruit.apple.color = "red" fruit.apple.taste.sweet = true)code"; - std::string ssTOMLOutput = R"code([tree.branch.fruit.apple] -color = "red" - -[tree.branch.fruit.apple.taste] -sweet = true)code"; + std::string ssTOMLOutput = R"code([tree.branch] +fruit.apple.color = "red" +fruit.apple.taste.sweet = true)code"; + std::string ssTOMLOutput2 = R"code([tree.branch] +apple.color = "red" +apple.taste.sweet = true)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); + + EXPECT_NO_THROW(ssGenerated = parser.Root().Direct("fruit")->GenerateTOML(toml_parser::CGenContext("tree.branch"))); + EXPECT_EQ(ssGenerated, ssTOMLOutput2); } TEST(GenerateTOML, MixedAutomaticTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code([fruit] + std::string ssTOML = R"code([fruit] apple.color = "red" apple.taste.sweet = true @@ -1702,27 +1593,18 @@ apple.taste.sweet = true [fruit.apple.texture] # you can add sub-tables smooth = true)code"; - std::string ssTOMLOutput = R"code([fruit.apple] -color = "red" -[fruit.apple.taste] -sweet = true - -[fruit.apple.texture] -smooth = true)code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferMixedAutomaticTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code([fruit] apple.color = "red" @@ -1733,83 +1615,100 @@ apple.taste.sweet = true [fruit.apple.texture] # you can add sub-tables smooth = true)code"; - std::string ssTOMLOutput = R"code([tree.branch.fruit.apple] -color = "red" + std::string ssTOMLOutput = R"code([tree.branch.fruit] +apple.color = "red" +apple.taste.sweet = true -[tree.branch.fruit.apple.taste] -sweet = true +# [fruit.apple] # INVALID +# [fruit.apple.taste] # INVALID -[tree.branch.fruit.apple.texture] +[tree.branch.fruit.apple.texture] # you can add sub-tables smooth = true)code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, InlineTables) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(name = { first = "Tom", last = "Preston-Werner" } + std::string ssTOML = R"code(name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } animal = { type.name = "pug" })code"; - std::string ssTOMLOutput = R"code([name] -first = "Tom" -last = "Preston-Werner" -[point] -x = 1 -y = 2 - -[animal.type] -name = "pug")code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); - - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferInlineTables) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(name = { first = "Tom", last = "Preston-Werner" } point = { x = 1, y = 2 } animal = { type.name = "pug" })code"; - std::string ssTOMLOutput = R"code([tree.branch.name] -first = "Tom" -last = "Preston-Werner" + std::string ssTOMLOutput = R"code([tree.branch] +name = { first = "Tom", last = "Preston-Werner" } +point = { x = 1, y = 2 } +animal = { type.name = "pug" })code"; + EXPECT_NO_THROW(parser.Process(ssTOMLInput)); -[tree.branch.point] -x = 1 -y = 2 + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + + EXPECT_EQ(ssGenerated, ssTOMLOutput); +} -[tree.branch.animal.type] -name = "pug")code"; +TEST(GenerateTOML, EmbeddedInlineTables) +{ + toml_parser::CParser parser; + + std::string ssTOML = R"code(test=[{ first = "Tom", last = "Preston-Werner" }, +{ x = 1, y = 2 }, +{ type.name = "pug" }])code"; + + EXPECT_NO_THROW(parser.Process(ssTOML)); + + std::string ssGenerated; + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + + EXPECT_EQ(ssGenerated, ssTOML); +} + +TEST(GenerateTOML, TransferEmbeddedInlineTables) +{ + toml_parser::CParser parser; + + std::string ssTOMLInput = R"code(test=[{ first = "Tom", last = "Preston-Werner" }, +{ x = 1, y = 2 }, +{ type.name = "pug" }])code"; + std::string ssTOMLOutput = R"code([tree.branch] +test=[{ first = "Tom", last = "Preston-Werner" }, +{ x = 1, y = 2 }, +{ type.name = "pug" }])code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); - + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); + EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, TableArrays) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code([[products]] + std::string ssTOML = R"code([[products]] name = "Hammer" sku = 738594937 @@ -1820,29 +1719,18 @@ name = "Nail" sku = 284758393 color = "gray")code"; - std::string ssTOMLOutput = R"code([[products]] -name = "Hammer" -sku = 738594937 -[[products]] - -[[products]] -name = "Nail" -sku = 284758393 -color = "gray")code"; - - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferTableArrays) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code([[products]] name = "Hammer" @@ -1859,27 +1747,27 @@ color = "gray")code"; name = "Hammer" sku = 738594937 -[[tree.branch.products]] +[[tree.branch.products]] # empty table within the array [[tree.branch.products]] name = "Nail" sku = 284758393 + color = "gray")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, MixedTableAndTableArrays) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code([[fruits]] + std::string ssTOML = R"code([[fruits]] name = "apple" [fruits.physical] # subtable @@ -1898,37 +1786,72 @@ name = "banana" [[fruits.varieties]] name = "plantain")code"; - std::string ssTOMLOutput = R"code([[fruits]] -name = "apple" - -[fruits.physical] + std::string ssTOMLFruits1Physical = R"code( +[physical] # subtable color = "red" shape = "round" - -[[fruits.varieties]] +)code"; + std::string ssTOMLFruits1Varieties = R"code( +[[varieties]] # nested array of tables name = "red delicious" -[[fruits.varieties]] +[[varieties]] name = "granny smith" - -[[fruits]] -name = "banana" - -[[fruits.varieties]] +)code"; + std::string ssTOMLFruits1Variety1 = R"code( +[variety] # nested array of tables +name = "red delicious" +)code"; + std::string ssTOMLFruits1Variety2 = R"code( +[variety] +name = "granny smith" +)code"; + std::string ssTOMLFruits2Variety1 = R"code( +[variety] name = "plantain")code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); + // Identical output std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); + EXPECT_EQ(ssGenerated, ssTOML); + + // Physical from fruits[0] + auto ptrFruits1Physical = parser.Root().Direct("fruits[0].physical"); + ASSERT_TRUE(ptrFruits1Physical); + EXPECT_NO_THROW(ssGenerated = ptrFruits1Physical->GenerateTOML(toml_parser::CGenContext("physical"))); + EXPECT_EQ(ssGenerated, ssTOMLFruits1Physical); + + // Varieties from fruits[0] + auto ptrTOMLFruits1Varieties = parser.Root().Direct("fruits[0].varieties"); + ASSERT_TRUE(ptrTOMLFruits1Varieties); + EXPECT_NO_THROW(ssGenerated = ptrTOMLFruits1Varieties->GenerateTOML()); + EXPECT_EQ(ssGenerated, ssTOMLFruits1Varieties); + + // Varieties[0] from fruits[0] + auto ptrTOMLFruits1Variety1 = parser.Root().Direct("fruits[0].varieties[0]"); + ASSERT_TRUE(ptrTOMLFruits1Variety1); + EXPECT_NO_THROW(ssGenerated = ptrTOMLFruits1Variety1->GenerateTOML(toml_parser::CGenContext("variety"))); + EXPECT_EQ(ssGenerated, ssTOMLFruits1Variety1); + + // Varieties[1] from fruits[0] + auto ptrTOMLFruits1Variety2 = parser.Root().Direct("fruits[0].varieties[1]"); + ASSERT_TRUE(ptrTOMLFruits1Variety2); + EXPECT_NO_THROW(ssGenerated = ptrTOMLFruits1Variety2->GenerateTOML(toml_parser::CGenContext("variety"))); + EXPECT_EQ(ssGenerated, ssTOMLFruits1Variety2); + + // Varieties[0] from fruits[1] + auto ptrTOMLFruits2Variety1 = parser.Root().Direct("fruits[1].varieties[0]"); + ASSERT_TRUE(ptrTOMLFruits2Variety1); + EXPECT_NO_THROW(ssGenerated = ptrTOMLFruits2Variety1->GenerateTOML(toml_parser::CGenContext("variety"))); + EXPECT_EQ(ssGenerated, ssTOMLFruits2Variety1); - EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, TransferMixedTableAndTableArrays) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code([[fruits]] name = "apple" @@ -1952,16 +1875,17 @@ name = "plantain")code"; std::string ssTOMLOutput = R"code([[tree.branch.fruits]] name = "apple" -[tree.branch.fruits.physical] +[tree.branch.fruits.physical] # subtable color = "red" shape = "round" -[[tree.branch.fruits.varieties]] +[[tree.branch.fruits.varieties]] # nested array of tables name = "red delicious" [[tree.branch.fruits.varieties]] name = "granny smith" + [[tree.branch.fruits]] name = "banana" @@ -1971,46 +1895,42 @@ name = "plantain")code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } TEST(GenerateTOML, InlineTableArrays) { - CParserTOML parser; + toml_parser::CParser parser; - std::string ssTOMLInput = R"code(points = [ { x = 1, y = 2, z = 3 }, + std::string ssTOML = R"code(points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, { x = 2, y = 4, z = 8 } ])code"; - std::string ssTOMLOutput = R"code(points = [{x = 1, y = 2, z = 3}, {x = 7, y = 8, z = 9}, {x = 2, y = 4, z = 8}])code"; - EXPECT_NO_THROW(parser.Process(ssTOMLInput)); + EXPECT_NO_THROW(parser.Process(ssTOML)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText()); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML()); - EXPECT_EQ(ssGenerated, ssTOMLOutput); + EXPECT_EQ(ssGenerated, ssTOML); } TEST(GenerateTOML, TransferInlineTableArrays) { - CParserTOML parser; + toml_parser::CParser parser; std::string ssTOMLInput = R"code(points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, { x = 2, y = 4, z = 8 } ])code"; std::string ssTOMLOutput = R"code([tree.branch] -points = [{x = 1, y = 2, z = 3}, {x = 7, y = 8, z = 9}, {x = 2, y = 4, z = 8}])code"; - +points = [ { x = 1, y = 2, z = 3 }, + { x = 7, y = 8, z = 9 }, + { x = 2, y = 4, z = 8 } ])code"; EXPECT_NO_THROW(parser.Process(ssTOMLInput)); std::string ssGenerated; - EXPECT_NO_THROW(ssGenerated = parser.CreateTOMLText("tree.branch")); - Trim(ssGenerated); + EXPECT_NO_THROW(ssGenerated = parser.GenerateTOML("tree.branch")); EXPECT_EQ(ssGenerated, ssTOMLOutput); } - diff --git a/tests/unit_tests/toml_parser/lexer_tests.cpp b/tests/unit_tests/toml_parser/lexer_tests.cpp index f0a6d6e..f10fb13 100644 --- a/tests/unit_tests/toml_parser/lexer_tests.cpp +++ b/tests/unit_tests/toml_parser/lexer_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "../../../sdv_services/core/toml_parser/lexer_toml.h" #include "../../../sdv_services/core/toml_parser/exception.h" @@ -9,7 +10,7 @@ * - Control characters other than tab (U+0000 to U+0008, U+000A to U+001F, U+007F) are not permitted in comments * - The Lexer has to recognize key-value pairs * - with value data types - * - token_string + * - token_quoted_string * - token_integer * - token_float * - token_boolean @@ -29,7 +30,7 @@ * - dotted keys are bare keys and dotted keys joined by a dot '.' * - Strings * - may only contain valid UTF-8 characters - * - Basic token_string + * - Basic token_quoted_string * - surrounded by '"' * - chracters that must be escaped: '"', '\', and the control characters other than tab (U+0000 to U+0008, U+000A to U+001F, U+007F) @@ -44,7 +45,7 @@ \UXXXXXXXX - unicode (U+XXXXXXXX) Any Unicode character may be escaped with the \uXXXX or \UXXXXXXXX forms. The escape codes must be valid Unicode scalar values. All other escape sequences not listed above are reserved; if they are used, TOML should produce an error. - * - Multiline Basic token_string + * - Multiline Basic token_quoted_string - surrounded by three quotation marks '"""' on each side - A newline immediately following the opening delimiter will be trimmed - All other whitespace and newline characters remain intact @@ -81,7 +82,7 @@ * - invalid input results in an token_error-Token * + invalid bare key * + invalid quoted key - * + invalid token_string + * + invalid token_quoted_string * + invalid token_integer * + invalid token_float * + invalid token_boolean @@ -94,8 +95,7 @@ * + Peek(n) returns the next n-th token without advancing the read location * + Consume() returns the next token and advances the read location directly after the returned token * + Consume(n) returns the next n-th token and advances the read location directly after the returned token - * + if Consume(n) or Peek(n) are given a n < 1, they return an token_empty-Token - * + when Peek or Consume would access a token after token_eof, they return a token_eof-Token, + * + when Peek or Consume would access a token after EOF, they return an empty token, * if lexing was terminated, they return a TerminatedToken * + Exceptions thrown by the CharacterReaderare caught and result in a token_terminated-Token and no further lexing will be done */ @@ -104,12 +104,15 @@ TEST(TOMLLexerTest, Keys) { using namespace std::string_literals; // U+1F92B is the Finger-On-Lips Shushing emoji with UTF-8 byte representation 0xF09FA4AB - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( key1 = "value1" "key\u0032" = "value2" "key3\U0001F92B" = "abc" '' = "valid" 'literal "key"' = "also valid" + "quoted 'key'" = "also valid" + _0xabcd = 0xabcd + 1234 = 1234 )"s); // Tokens: // NewLine @@ -118,95 +121,289 @@ TEST(TOMLLexerTest, Keys) // key30xF09FA4AB, '=', abc, NewLine // , '=', valid, NewLine // literal "key", '=', also valid - // token_eof - CLexerTOML::SToken key1 = lexer.Consume(2); - CLexerTOML::SToken key2 = lexer.Consume(4); - CLexerTOML::SToken key3 = lexer.Consume(4); - CLexerTOML::SToken key4 = lexer.Consume(4); - CLexerTOML::SToken key5 = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, key1.eCategory); - EXPECT_EQ("key1", key1.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, key2.eCategory); - EXPECT_EQ("\"key2\"", key2.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, key3.eCategory); - EXPECT_EQ(std::string("\"key3") + static_cast(0xF0) + static_cast(0x9F) + static_cast(0xA4) - + static_cast(0xAB) + '\"', - key3.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, key4.eCategory); - EXPECT_EQ("''", key4.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, key5.eCategory); - EXPECT_EQ("'literal \"key\"'", key5.ssContentString); + const toml_parser::CToken& key1 = lexer.Consume(1); + const toml_parser::CToken& key2 = lexer.Consume(3); + const toml_parser::CToken& key3 = lexer.Consume(3); + const toml_parser::CToken& key4 = lexer.Consume(3); + const toml_parser::CToken& key5 = lexer.Consume(3); + const toml_parser::CToken& key6 = lexer.Consume(3); + const toml_parser::CToken& key7 = lexer.Consume(3); + const toml_parser::CToken& key8 = lexer.Consume(3); + ASSERT_TRUE(key1); + ASSERT_TRUE(key2); + ASSERT_TRUE(key3); + ASSERT_TRUE(key4); + ASSERT_TRUE(key5); + ASSERT_TRUE(key6); + ASSERT_TRUE(key7); + ASSERT_TRUE(key8); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key1.Category()); + EXPECT_EQ("key1", key1.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key2.Category()); + EXPECT_EQ("key\u0032", key2.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key3.Category()); + EXPECT_EQ(u8"key3\U0001F92B",key3.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key4.Category()); + EXPECT_EQ("", key4.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key5.Category()); + EXPECT_EQ("literal \"key\"", key5.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key6.Category()); + EXPECT_EQ("quoted 'key'", key6.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key7.Category()); + EXPECT_EQ("_0xabcd", key7.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, key8.Category()); + EXPECT_EQ("1234", key8.StringValue()); } TEST(TOMLLexerTest, SyntaxToken_NewLine) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( key1 = "value" )"s); // Tokens: // NewLine // key1, '=', value, NewLine - // token_eof - CLexerTOML::SToken newLine1 = lexer.Peek(1); - CLexerTOML::SToken newLine2 = lexer.Peek(5); + const toml_parser::CToken& newLine1 = lexer.Peek(0); + const toml_parser::CToken& newLine2 = lexer.Peek(4); + ASSERT_TRUE(newLine1); + ASSERT_TRUE(newLine2); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_new_line, newLine1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_new_line, newLine2.eCategory); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, newLine1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, newLine2.Category()); } TEST(TOMLLexerTest, SyntaxToken_Bracket) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( [table] array = [[1,2],[3,4]] emptyArray = [] + [["table array"]] )"s); // Tokens: // NewLine // '[', table, ']', NewLine // array, '=', '[', '[', 1, ',', 2, ']', ',', '[', 3, ',', 4, ']', ']', NewLine // emptyArray, '=', '[', ']', NewLine - // token_eof - CLexerTOML::SToken tableBracketOpen = lexer.Peek(2); - CLexerTOML::SToken tableBracketClose = lexer.Peek(4); - CLexerTOML::SToken arrayOfArraysBracketOpen = lexer.Peek(8); - CLexerTOML::SToken array1BracketOpen = lexer.Peek(9); - CLexerTOML::SToken array1BracketClose = lexer.Peek(13); - CLexerTOML::SToken array2BracketOpen = lexer.Peek(15); - CLexerTOML::SToken array2BracketClose = lexer.Peek(19); - CLexerTOML::SToken arrayOfArraysBracketClose = lexer.Peek(20); - CLexerTOML::SToken emptyArrayOpen = lexer.Peek(24); - CLexerTOML::SToken emptyArrayClose = lexer.Peek(25); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_table_open, tableBracketOpen.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_table_close, tableBracketClose.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_array_open, arrayOfArraysBracketOpen.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_array_open, array1BracketOpen.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_array_close, array1BracketClose.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_array_open, array2BracketOpen.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_array_close, array2BracketClose.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_array_close, arrayOfArraysBracketClose.eCategory); + // '[[', "table array", ']]', NewLine + const toml_parser::CToken& tableBracketOpen = lexer.Peek(1); + const toml_parser::CToken& tableBracketClose = lexer.Peek(3); + const toml_parser::CToken& arrayOfArraysBracketOpen = lexer.Peek(7); + const toml_parser::CToken& array1BracketOpen = lexer.Peek(8); + const toml_parser::CToken& array1BracketClose = lexer.Peek(12); + const toml_parser::CToken& array2BracketOpen = lexer.Peek(14); + const toml_parser::CToken& array2BracketClose = lexer.Peek(18); + const toml_parser::CToken& arrayOfArraysBracketClose = lexer.Peek(19); + const toml_parser::CToken& emptyArrayOpen = lexer.Peek(23); + const toml_parser::CToken& emptyArrayClose = lexer.Peek(24); + const toml_parser::CToken& tableArrayBracketOpen = lexer.Peek(26); + const toml_parser::CToken& tableArrayBracketClose = lexer.Peek(28); + + ASSERT_TRUE(tableBracketOpen); + ASSERT_TRUE(tableBracketClose); + ASSERT_TRUE(arrayOfArraysBracketOpen); + ASSERT_TRUE(array1BracketOpen); + ASSERT_TRUE(array1BracketClose); + ASSERT_TRUE(array2BracketOpen); + ASSERT_TRUE(array2BracketClose); + ASSERT_TRUE(arrayOfArraysBracketClose); + ASSERT_TRUE(emptyArrayOpen); + ASSERT_TRUE(emptyArrayClose); + ASSERT_TRUE(tableArrayBracketOpen); + ASSERT_TRUE(tableArrayBracketClose); + + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_open, tableBracketOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_close, tableBracketClose.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_open, arrayOfArraysBracketOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_open, array1BracketOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_close, array1BracketClose.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_open, array2BracketOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_close, array2BracketClose.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_close, arrayOfArraysBracketClose.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_open, tableArrayBracketOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayBracketClose.Category()); } TEST(TOMLLexerTest, SyntaxToken_Assignment) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexerNormal(R"( key = "value" )"s); // Tokens: // NewLine // key, '=', value, NewLine - // token_eof - CLexerTOML::SToken assignment = lexer.Peek(3); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_assignment, assignment.eCategory); + const toml_parser::CToken& rKeyNormal = lexerNormal.Consume(1); + const toml_parser::CToken& rAssignmentNormal = lexerNormal.Consume(); + const toml_parser::CToken& rValueNormal = lexerNormal.Consume(); + EXPECT_TRUE(rKeyNormal); + EXPECT_TRUE(rAssignmentNormal); + EXPECT_TRUE(rValueNormal); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNormal.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNormal.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, rValueNormal.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, rValueNormal.StringType()); + EXPECT_EQ(rKeyNormal.StringValue(), "key"); + EXPECT_EQ(rValueNormal.StringValue(), "value"); + + toml_parser::CLexer lexerNoSpace(R"(key="value")"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpace = lexerNoSpace.Consume(); + const toml_parser::CToken& rAssignmentNoSpace = lexerNoSpace.Consume(); + const toml_parser::CToken& rValueNoSpace = lexerNoSpace.Consume(); + EXPECT_TRUE(rKeyNoSpace); + EXPECT_TRUE(rAssignmentNoSpace); + EXPECT_TRUE(rValueNoSpace); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpace.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpace.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, rValueNoSpace.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, rValueNoSpace.StringType()); + EXPECT_EQ(rKeyNoSpace.StringValue(), "key"); + EXPECT_EQ(rValueNoSpace.StringValue(), "value"); +} + +TEST(TOMLLexerTest, Comments) +{ + using namespace std::string_literals; + toml_parser::CLexer lexerNormal(R"( + #pre + key = "value" #follow + #next + )"s); + // Tokens: + // NewLine + // key, '=', value, NewLine + const toml_parser::CToken& rKeyNormal = lexerNormal.Consume(2); + const toml_parser::CToken& rAssignmentNormal = lexerNormal.Consume(); + const toml_parser::CToken& rValueNormal = lexerNormal.Consume(); + EXPECT_TRUE(rKeyNormal); + EXPECT_TRUE(rAssignmentNormal); + EXPECT_TRUE(rValueNormal); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNormal.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNormal.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, rValueNormal.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, rValueNormal.StringType()); + EXPECT_EQ(rKeyNormal.StringValue(), "key"); + EXPECT_EQ(rValueNormal.StringValue(), "value"); + + toml_parser::CLexer lexerNoSpaceString(R"(key="value"#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpaceString = lexerNoSpaceString.Consume(); + const toml_parser::CToken& rAssignmentNoSpaceString = lexerNoSpaceString.Consume(); + const toml_parser::CToken& rValueNoSpaceString = lexerNoSpaceString.Consume(); + EXPECT_TRUE(rKeyNoSpaceString); + EXPECT_TRUE(rAssignmentNoSpaceString); + EXPECT_TRUE(rValueNoSpaceString); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceString.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpaceString.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, rValueNoSpaceString.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, rValueNoSpaceString.StringType()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rValueNoSpaceString.Next().Category()); + EXPECT_EQ(rKeyNoSpaceString.StringValue(), "key"); + EXPECT_EQ(rValueNoSpaceString.StringValue(), "value"); + + toml_parser::CLexer lexerNoSpaceInteger(R"(key=1234#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpaceInteger = lexerNoSpaceInteger.Consume(); + const toml_parser::CToken& rAssignmentNoSpaceInteger = lexerNoSpaceInteger.Consume(); + const toml_parser::CToken& rValueNoSpaceInteger = lexerNoSpaceInteger.Consume(); + EXPECT_TRUE(rKeyNoSpaceInteger); + EXPECT_TRUE(rAssignmentNoSpaceInteger); + EXPECT_TRUE(rValueNoSpaceInteger); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceInteger.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpaceInteger.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, rValueNoSpaceInteger.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rValueNoSpaceInteger.Next().Category()); + EXPECT_EQ(rKeyNoSpaceInteger.StringValue(), "key"); + EXPECT_EQ(rValueNoSpaceInteger.IntegerValue(), 1234); + + toml_parser::CLexer lexerNoSpaceBoolean(R"(key=true#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpaceBoolean = lexerNoSpaceBoolean.Consume(); + const toml_parser::CToken& rAssignmentNoSpaceBoolean = lexerNoSpaceBoolean.Consume(); + const toml_parser::CToken& rValueNoSpaceBoolean = lexerNoSpaceBoolean.Consume(); + EXPECT_TRUE(rKeyNoSpaceBoolean); + EXPECT_TRUE(rAssignmentNoSpaceBoolean); + EXPECT_TRUE(rValueNoSpaceBoolean); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceBoolean.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpaceBoolean.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_boolean, rValueNoSpaceBoolean.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rValueNoSpaceBoolean.Next().Category()); + EXPECT_EQ(rKeyNoSpaceBoolean.StringValue(), "key"); + EXPECT_EQ(rValueNoSpaceBoolean.BooleanValue(), true); + + toml_parser::CLexer lexerNoSpaceFloat(R"(key=123.456e78#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpaceFloat = lexerNoSpaceFloat.Consume(); + const toml_parser::CToken& rAssignmentNoSpaceFloat = lexerNoSpaceFloat.Consume(); + const toml_parser::CToken& rValueNoSpaceFloat = lexerNoSpaceFloat.Consume(); + EXPECT_TRUE(rKeyNoSpaceFloat); + EXPECT_TRUE(rAssignmentNoSpaceFloat); + EXPECT_TRUE(rValueNoSpaceFloat); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceFloat.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpaceFloat.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, rValueNoSpaceFloat.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rValueNoSpaceFloat.Next().Category()); + EXPECT_EQ(rKeyNoSpaceFloat.StringValue(), "key"); + EXPECT_EQ(rValueNoSpaceFloat.FloatValue(), 123.456e78); + + toml_parser::CLexer lexerNoSpaceInlineTable(R"(key={x=1,y=2,z=3}#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpaceInlineTable = lexerNoSpaceInlineTable.Consume(); + const toml_parser::CToken& rAssignmentNoSpaceInlineTable = lexerNoSpaceInlineTable.Consume(); + const toml_parser::CToken& rValueNoSpaceInlineTable = lexerNoSpaceInlineTable.Consume(); + EXPECT_TRUE(rKeyNoSpaceInlineTable); + EXPECT_TRUE(rAssignmentNoSpaceInlineTable); + EXPECT_TRUE(rValueNoSpaceInlineTable); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceInlineTable.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpaceInlineTable.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_open, rValueNoSpaceInlineTable.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rValueNoSpaceInlineTable.Next(12).Category()); + EXPECT_EQ(rKeyNoSpaceInlineTable.StringValue(), "key"); + + toml_parser::CLexer lexerNoSpaceArray(R"(key=[1,2,3]#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rKeyNoSpaceArray = lexerNoSpaceArray.Consume(); + const toml_parser::CToken& rAssignmentNoSpaceArray = lexerNoSpaceArray.Consume(); + const toml_parser::CToken& rValueNoSpaceArray = lexerNoSpaceArray.Consume(); + EXPECT_TRUE(rKeyNoSpaceArray); + EXPECT_TRUE(rAssignmentNoSpaceArray); + EXPECT_TRUE(rValueNoSpaceArray); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceArray.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, rAssignmentNoSpaceArray.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_array_open, rValueNoSpaceArray.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rValueNoSpaceArray.Next(6).Category()); + EXPECT_EQ(rKeyNoSpaceArray.StringValue(), "key"); + + toml_parser::CLexer lexerNoSpaceTable(R"([key]#follow)"s); + // Tokens: + // key, '=', value + const toml_parser::CToken& rNoSpaceTableOpen = lexerNoSpaceTable.Consume(); + EXPECT_TRUE(rNoSpaceTableOpen); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_open, rNoSpaceTableOpen.Category()); + const toml_parser::CToken& rKeyNoSpaceTable = lexerNoSpaceTable.Consume(); + EXPECT_TRUE(rKeyNoSpaceTable); + EXPECT_EQ(rKeyNoSpaceTable.StringValue(), "key"); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, rKeyNoSpaceTable.Category()); + const toml_parser::CToken& rNoSpaceTableClose = lexerNoSpaceTable.Consume(); + EXPECT_TRUE(rNoSpaceTableClose); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_close, rNoSpaceTableClose.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_comment, rNoSpaceTableClose.Next().Category()); } TEST(TOMLLexerTest, SyntaxToken_Dot) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color fruit . flavor = "banana" # same as fruit.flavor @@ -220,25 +417,30 @@ TEST(TOMLLexerTest, SyntaxToken_Dot) // fruit, '.', flavor, '=', banana, NewLine // a, '.', b, '.', c, '=', 2, NewLine // '[', dog, '.', tater.man, ']', NewLine - // token_eof - CLexerTOML::SToken dot1 = lexer.Peek(3); - CLexerTOML::SToken dot2 = lexer.Peek(9); - CLexerTOML::SToken dot3 = lexer.Peek(15); - CLexerTOML::SToken dot4 = lexer.Peek(21); - CLexerTOML::SToken dot5 = lexer.Peek(23); - CLexerTOML::SToken dot6 = lexer.Peek(30); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_dot, dot1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_dot, dot2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_dot, dot3.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_dot, dot4.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_dot, dot5.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_dot, dot6.eCategory); + const toml_parser::CToken& dot1 = lexer.Peek(2); + const toml_parser::CToken& dot2 = lexer.Peek(8); + const toml_parser::CToken& dot3 = lexer.Peek(14); + const toml_parser::CToken& dot4 = lexer.Peek(20); + const toml_parser::CToken& dot5 = lexer.Peek(22); + const toml_parser::CToken& dot6 = lexer.Peek(29); + ASSERT_TRUE(dot1); + ASSERT_TRUE(dot2); + ASSERT_TRUE(dot3); + ASSERT_TRUE(dot4); + ASSERT_TRUE(dot5); + ASSERT_TRUE(dot6); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot4.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot5.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_dot, dot6.Category()); } TEST(TOMLLexerTest, SyntaxToken_Braces) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( inlineTable = { name = "Some Name", type = "Some Type" } nestedInlineTable = { a = { a = 0, b = "b"}, e = { c = 0, d = "d"} } emptyTable = {} @@ -247,70 +449,135 @@ TEST(TOMLLexerTest, SyntaxToken_Braces) // NewLine // inlineTable, '=', '{', name, '=', Some Name, ',', type, '=', Some Type, '}', NewLine // nestedInlineTable, '=', '{', a, '=', '{', a, '=', 0, ',' b '=', b, '}', ',' b, '=', '{', c, '=', 0, ',' d, '=', d, '}' '}', - // NewLine emptyTable, '=', '{', '}', NewLine token_eof - CLexerTOML::SToken braceOpen = lexer.Peek(4); - CLexerTOML::SToken braceClose = lexer.Peek(12); - CLexerTOML::SToken nestedOpen1 = lexer.Peek(16); - CLexerTOML::SToken nestedOpen2 = lexer.Peek(19); - CLexerTOML::SToken nestedClose2 = lexer.Peek(27); - CLexerTOML::SToken nestedOpen3 = lexer.Peek(31); - CLexerTOML::SToken nestedClose3 = lexer.Peek(39); - CLexerTOML::SToken nestedClose1 = lexer.Peek(40); - CLexerTOML::SToken emptyTableOpen = lexer.Peek(44); - CLexerTOML::SToken emptyTableClose = lexer.Peek(45); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_open, braceOpen.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_close, braceClose.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_open, nestedOpen1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_close, nestedClose1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_open, nestedOpen2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_close, nestedClose2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_open, nestedOpen3.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_close, nestedClose3.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_open, emptyTableOpen.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_inline_table_close, emptyTableClose.eCategory); + // NewLine emptyTable, '=', '{', '}', NewLine + const toml_parser::CToken& braceOpen = lexer.Peek(3); + const toml_parser::CToken& braceClose = lexer.Peek(11); + const toml_parser::CToken& nestedOpen1 = lexer.Peek(15); + const toml_parser::CToken& nestedOpen2 = lexer.Peek(18); + const toml_parser::CToken& nestedClose2 = lexer.Peek(26); + const toml_parser::CToken& nestedOpen3 = lexer.Peek(30); + const toml_parser::CToken& nestedClose3 = lexer.Peek(38); + const toml_parser::CToken& nestedClose1 = lexer.Peek(39); + const toml_parser::CToken& emptyTableOpen = lexer.Peek(43); + const toml_parser::CToken& emptyTableClose = lexer.Peek(44); + ASSERT_TRUE(braceOpen); + ASSERT_TRUE(braceClose); + ASSERT_TRUE(nestedOpen1); + ASSERT_TRUE(nestedOpen2); + ASSERT_TRUE(nestedClose2); + ASSERT_TRUE(nestedOpen3); + ASSERT_TRUE(nestedClose3); + ASSERT_TRUE(nestedClose1); + ASSERT_TRUE(emptyTableOpen); + ASSERT_TRUE(emptyTableClose); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_open, braceOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_close, braceClose.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_open, nestedOpen1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_close, nestedClose1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_open, nestedOpen2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_close, nestedClose2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_open, nestedOpen3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_close, nestedClose3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_open, emptyTableOpen.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_inline_table_close, emptyTableClose.Category()); - CLexerTOML::SToken a = lexer.Peek(17); - CLexerTOML::SToken a_a = lexer.Peek(20); - CLexerTOML::SToken a_b = lexer.Peek(24); - CLexerTOML::SToken e = lexer.Peek(29); - CLexerTOML::SToken e_c = lexer.Peek(32); - CLexerTOML::SToken e_d = lexer.Peek(36); - CLexerTOML::SToken emptyTable = lexer.Peek(42); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, a.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, a_a.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, a_b.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, e.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, e_c.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, e_d.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, emptyTable.eCategory); + const toml_parser::CToken& a = lexer.Peek(16); + const toml_parser::CToken& a_a = lexer.Peek(19); + const toml_parser::CToken& a_b = lexer.Peek(23); + const toml_parser::CToken& e = lexer.Peek(28); + const toml_parser::CToken& e_c = lexer.Peek(31); + const toml_parser::CToken& e_d = lexer.Peek(35); + const toml_parser::CToken& emptyTable = lexer.Peek(41); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, a.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, a_a.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, a_b.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, e.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, e_c.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, e_d.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, emptyTable.Category()); } TEST(TOMLLexerTest, SyntaxToken_ArrayTable) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( [[tableArray]] [[fruits.varieties]] + [["this is also a 'key'"]] + [['this is also a "key"']] + [[a."b.c"]] )"s); // Tokens: // NewLine // '[[', tableArray, ']]', NewLine // '[[', fruits, '.', varieties, ']]', Newline - // token_eof - CLexerTOML::SToken tableArrayOpen1 = lexer.Peek(2); - CLexerTOML::SToken tableArrayClose1 = lexer.Peek(4); - CLexerTOML::SToken tableArrayOpen2 = lexer.Peek(6); - CLexerTOML::SToken tableArrayClose2 = lexer.Peek(10); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_table_array_open, tableArrayOpen1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_table_array_close, tableArrayClose1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_table_array_open, tableArrayOpen2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_table_array_close, tableArrayClose2.eCategory); + // '[[', "this is also a 'key'", ']]', NewLine + // '[[', 'this is also a "key"', ']]', NewLine + // '[[', a, '.', "b.c", ']]', Newline + const toml_parser::CToken& tableArrayOpen1 = lexer.Peek(1); + const toml_parser::CToken& tableArrayName1 = lexer.Peek(2); + const toml_parser::CToken& tableArrayClose1 = lexer.Peek(3); + const toml_parser::CToken& tableArrayOpen2 = lexer.Peek(5); + const toml_parser::CToken& tableArrayName2a = lexer.Peek(6); + const toml_parser::CToken& tableArrayName2b = lexer.Peek(8); + const toml_parser::CToken& tableArrayClose2 = lexer.Peek(9); + const toml_parser::CToken& tableArrayOpen3 = lexer.Peek(11); + const toml_parser::CToken& tableArrayName3 = lexer.Peek(12); + const toml_parser::CToken& tableArrayClose3 = lexer.Peek(13); + const toml_parser::CToken& tableArrayOpen4 = lexer.Peek(15); + const toml_parser::CToken& tableArrayName4 = lexer.Peek(16); + const toml_parser::CToken& tableArrayClose4 = lexer.Peek(17); + const toml_parser::CToken& tableArrayOpen5 = lexer.Peek(19); + const toml_parser::CToken& tableArrayName5a = lexer.Peek(20); + const toml_parser::CToken& tableArrayName5b = lexer.Peek(22); + const toml_parser::CToken& tableArrayClose5 = lexer.Peek(23); + ASSERT_TRUE(tableArrayOpen1); + ASSERT_TRUE(tableArrayName1); + ASSERT_TRUE(tableArrayClose1); + ASSERT_TRUE(tableArrayOpen2); + ASSERT_TRUE(tableArrayName2a); + ASSERT_TRUE(tableArrayName2b); + ASSERT_TRUE(tableArrayClose2); + ASSERT_TRUE(tableArrayOpen3); + ASSERT_TRUE(tableArrayName3); + ASSERT_TRUE(tableArrayClose3); + ASSERT_TRUE(tableArrayOpen4); + ASSERT_TRUE(tableArrayName4); + ASSERT_TRUE(tableArrayClose4); + ASSERT_TRUE(tableArrayOpen5); + ASSERT_TRUE(tableArrayName5a); + ASSERT_TRUE(tableArrayName5b); + ASSERT_TRUE(tableArrayClose5); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_open, tableArrayOpen1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName1.Category()); + EXPECT_EQ(tableArrayName1.StringValue(), "tableArray"); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayClose1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_open, tableArrayOpen2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName2a.Category()); + EXPECT_EQ(tableArrayName2a.StringValue(), "fruits"); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName2b.Category()); + EXPECT_EQ(tableArrayName2b.StringValue(), "varieties"); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayClose2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_open, tableArrayOpen3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName3.Category()); + EXPECT_EQ(tableArrayName3.StringValue(), "this is also a 'key'"); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayClose3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_open, tableArrayOpen4.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName4.Category()); + EXPECT_EQ(tableArrayName4.StringValue(), "this is also a \"key\""); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayClose4.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_open, tableArrayOpen5.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName5a.Category()); + EXPECT_EQ(tableArrayName5a.StringValue(), "a"); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, tableArrayName5b.Category()); + EXPECT_EQ(tableArrayName5b.StringValue(), "b.c"); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_table_array_close, tableArrayClose5.Category()); } TEST(TOMLLexerTest, Datatype_Integer) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( int1 = 1 int2 = +13 int3 = -37 @@ -329,7 +596,18 @@ TEST(TOMLLexerTest, Datatype_Integer) bin1 = 0b11111111 bin2 = 0b00000001 bin3 = 0b01010101 + intErr1 = _1234 + intErr2 = 1__234 + intErr3 = 1234_ + intErr4 = +0xabcd + intErr5 = -0xabcd + intErr6 = 0xabc__d + intErr7 = 0x_abcd + intErr8 = 0xabcd_ + intErr9 = 0_xabcd + intErr10 = 0XDEADBEEF )"s); + // Tokens: // NewLine // int1, '=', 1, Newline @@ -350,69 +628,126 @@ TEST(TOMLLexerTest, Datatype_Integer) // bin1, '=', 0xFF, NewLine // bin2, '=', 0x01, NewLine // bin3, '=', 0x55, NewLine - // token_eof - CLexerTOML::SToken int1 = lexer.Consume(4); - CLexerTOML::SToken int2 = lexer.Consume(4); - CLexerTOML::SToken int3 = lexer.Consume(4); - CLexerTOML::SToken int4 = lexer.Consume(4); - CLexerTOML::SToken int5 = lexer.Consume(4); - CLexerTOML::SToken int6 = lexer.Consume(4); - CLexerTOML::SToken int7 = lexer.Consume(4); - CLexerTOML::SToken int8 = lexer.Consume(4); - CLexerTOML::SToken int9 = lexer.Consume(4); - CLexerTOML::SToken int10 = lexer.Consume(4); - CLexerTOML::SToken hex1 = lexer.Consume(4); - CLexerTOML::SToken hex2 = lexer.Consume(4); - CLexerTOML::SToken hex3 = lexer.Consume(4); - CLexerTOML::SToken oct1 = lexer.Consume(4); - CLexerTOML::SToken oct2 = lexer.Consume(4); - CLexerTOML::SToken bin1 = lexer.Consume(4); - CLexerTOML::SToken bin2 = lexer.Consume(4); - CLexerTOML::SToken bin3 = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int1.eCategory); - EXPECT_EQ(1, int1.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int2.eCategory); - EXPECT_EQ(13, int2.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int3.eCategory); - EXPECT_EQ(-37, int3.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int4.eCategory); - EXPECT_EQ(1000, int4.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int5.eCategory); - EXPECT_EQ(5349221, int5.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int6.eCategory); - EXPECT_EQ(5349221, int6.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int7.eCategory); - EXPECT_EQ(12345, int7.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int8.eCategory); - EXPECT_EQ(0, int8.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int9.eCategory); - EXPECT_EQ(0, int9.iContentInteger); - EXPECT_EQ(int8.iContentInteger, int9.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, int10.eCategory); - EXPECT_EQ(0, int10.iContentInteger); - EXPECT_EQ(int8.iContentInteger, int10.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, hex1.eCategory); - EXPECT_EQ(0xDEADBEEF, hex1.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, hex2.eCategory); - EXPECT_EQ(0xDEADBEEF, hex2.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, hex3.eCategory); - EXPECT_EQ(0xDEADBEEF, hex3.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, oct1.eCategory); - EXPECT_EQ(01234567, oct1.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, oct2.eCategory); - EXPECT_EQ(0755, oct2.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, bin1.eCategory); - EXPECT_EQ(0xFF, bin1.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, bin2.eCategory); - EXPECT_EQ(0x01, bin2.iContentInteger); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_integer, bin3.eCategory); - EXPECT_EQ(0x55, bin3.iContentInteger); + // intErr1, '=', _1234, NewLine + // intErr2, '=', 1__234, NewLine + // intErr3, '=', 1234_, NewLine + // intErr4, '=', +0xabcd, NewLine + // intErr5, '=', -0xabcd, NewLine + // intErr6, '=', 0xabc__d, NewLine + // intErr7, '=', 0x_abcd, NewLine + // intErr8, '=', 0xabcd_, NewLine + // intErr9, '=', 0_xabcd, NewLine + // intErr10, '=', 0XDEADBEEF, NewLine + const toml_parser::CToken& int1 = lexer.Consume(3); + const toml_parser::CToken& int2 = lexer.Consume(3); + const toml_parser::CToken& int3 = lexer.Consume(3); + const toml_parser::CToken& int4 = lexer.Consume(3); + const toml_parser::CToken& int5 = lexer.Consume(3); + const toml_parser::CToken& int6 = lexer.Consume(3); + const toml_parser::CToken& int7 = lexer.Consume(3); + const toml_parser::CToken& int8 = lexer.Consume(3); + const toml_parser::CToken& int9 = lexer.Consume(3); + const toml_parser::CToken& int10 = lexer.Consume(3); + const toml_parser::CToken& hex1 = lexer.Consume(3); + const toml_parser::CToken& hex2 = lexer.Consume(3); + const toml_parser::CToken& hex3 = lexer.Consume(3); + const toml_parser::CToken& oct1 = lexer.Consume(3); + const toml_parser::CToken& oct2 = lexer.Consume(3); + const toml_parser::CToken& bin1 = lexer.Consume(3); + const toml_parser::CToken& bin2 = lexer.Consume(3); + const toml_parser::CToken& bin3 = lexer.Consume(3); + const toml_parser::CToken& intErr1 = lexer.Consume(3); + const toml_parser::CToken& intErr2 = lexer.Consume(3); + const toml_parser::CToken& intErr3 = lexer.Consume(3); + const toml_parser::CToken& intErr4 = lexer.Consume(3); + const toml_parser::CToken& intErr5 = lexer.Consume(3); + const toml_parser::CToken& intErr6 = lexer.Consume(3); + const toml_parser::CToken& intErr7 = lexer.Consume(3); + const toml_parser::CToken& intErr8 = lexer.Consume(3); + const toml_parser::CToken& intErr9 = lexer.Consume(3); + const toml_parser::CToken& intErr10 = lexer.Consume(3); + ASSERT_TRUE(int1); + ASSERT_TRUE(int2); + ASSERT_TRUE(int3); + ASSERT_TRUE(int4); + ASSERT_TRUE(int5); + ASSERT_TRUE(int6); + ASSERT_TRUE(int7); + ASSERT_TRUE(int8); + ASSERT_TRUE(int9); + ASSERT_TRUE(int10); + ASSERT_TRUE(hex1); + ASSERT_TRUE(hex2); + ASSERT_TRUE(hex3); + ASSERT_TRUE(oct1); + ASSERT_TRUE(oct2); + ASSERT_TRUE(bin1); + ASSERT_TRUE(bin2); + ASSERT_TRUE(bin3); + ASSERT_TRUE(intErr1); + ASSERT_TRUE(intErr2); + ASSERT_TRUE(intErr3); + ASSERT_TRUE(intErr4); + ASSERT_TRUE(intErr5); + ASSERT_TRUE(intErr6); + ASSERT_TRUE(intErr7); + ASSERT_TRUE(intErr8); + ASSERT_TRUE(intErr9); + ASSERT_TRUE(intErr10); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int1.Category()); + EXPECT_EQ(1, int1.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int2.Category()); + EXPECT_EQ(13, int2.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int3.Category()); + EXPECT_EQ(-37, int3.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int4.Category()); + EXPECT_EQ(1000, int4.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int5.Category()); + EXPECT_EQ(5349221, int5.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int6.Category()); + EXPECT_EQ(5349221, int6.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int7.Category()); + EXPECT_EQ(12345, int7.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int8.Category()); + EXPECT_EQ(0, int8.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int9.Category()); + EXPECT_EQ(0, int9.IntegerValue()); + EXPECT_EQ(int8.IntegerValue(), int9.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, int10.Category()); + EXPECT_EQ(0, int10.IntegerValue()); + EXPECT_EQ(int8.IntegerValue(), int10.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, hex1.Category()); + EXPECT_EQ(0xDEADBEEF, hex1.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, hex2.Category()); + EXPECT_EQ(0xDEADBEEF, hex2.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, hex3.Category()); + EXPECT_EQ(0xDEADBEEF, hex3.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, oct1.Category()); + EXPECT_EQ(01234567, oct1.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, oct2.Category()); + EXPECT_EQ(0755, oct2.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, bin1.Category()); + EXPECT_EQ(0xFF, bin1.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, bin2.Category()); + EXPECT_EQ(0x01, bin2.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_integer, bin3.Category()); + EXPECT_EQ(0x55, bin3.IntegerValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr4.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr5.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr6.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr7.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr8.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr9.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, intErr10.Category()); } TEST(TOMLLexerTest, Datatype_Float) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( float1 = +1.0 float2 = 3.1415 float3 = -0.01 @@ -420,15 +755,27 @@ TEST(TOMLLexerTest, Datatype_Float) float5 = 1e06 float6 = -2E-2 float7 = 6.626e-34 - float8 = 224_617.445_991_228 - float9 = -0.0 - float10 = +0.0 - specialFloat1 = inf - specialFloat2 = +inf - specialFloat3 = -inf - specialFloat4 = nan - specialFloat5 = +nan - specialFloat6 = -nan + float8 = 6.6_2_6e-3_4 + float9 = 224_617.445_991_228 + float10 = -0.0 + float11 = +0.0 + specialFloat12 = inf + specialFloat13 = +inf + specialFloat14 = -inf + specialFloat15 = nan + specialFloat16 = +nan + specialFloat17 = -nan + errFloat18 = 224__617.445_991_228 + errFloat19 = _224_617.445_991_228 + errFloat20 = 224_617_.445_991_228 + errFloat21 = 224_617._445_991_228 + errFloat22 = 224_617.445_991_228_ + errFloat23 = 224_617.445_991__228 + errFloat24 = 6.626_e-34 + errFloat25 = 6.626e_-34 + errFloat26 = 6.626e-_34 + errFloat27 = 6.626e-34_ + errFloat28 = 6.626_e-34 )"s); // Tokens: // NewLine @@ -439,52 +786,106 @@ TEST(TOMLLexerTest, Datatype_Float) // float5, '=', 1e+06, Newline // float6, '=', -2e-02, Newline // float7, '=', 6.626e-34, Newline - // float8, '=', 224617.445991228, Newline - // float9, '=', -0.0f, Newline - // float10, '=', 0.0f, Newline - // specialFloat1, '=', inf, NewLine - // specialFloat2, '=', inf, NewLine - // specialFloat3, '=', -inf, NewLine - // specialFloat4, '=', qnan, NewLine - // specialFloat5, '=', qnan, NewLine - // specialFloat6, '=', -qnan, NewLine - // token_eof - CLexerTOML::SToken float1 = lexer.Consume(4); - CLexerTOML::SToken float2 = lexer.Consume(4); - CLexerTOML::SToken float3 = lexer.Consume(4); - CLexerTOML::SToken float4 = lexer.Consume(4); - CLexerTOML::SToken float5 = lexer.Consume(4); - CLexerTOML::SToken float6 = lexer.Consume(4); - CLexerTOML::SToken float7 = lexer.Consume(4); - CLexerTOML::SToken float8 = lexer.Consume(4); - CLexerTOML::SToken float9 = lexer.Consume(4); - CLexerTOML::SToken float10 = lexer.Consume(4); - CLexerTOML::SToken specialFloat1 = lexer.Consume(4); - CLexerTOML::SToken specialFloat2 = lexer.Consume(4); - CLexerTOML::SToken specialFloat3 = lexer.Consume(4); - CLexerTOML::SToken specialFloat4 = lexer.Consume(4); - CLexerTOML::SToken specialFloat5 = lexer.Consume(4); - CLexerTOML::SToken specialFloat6 = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float1.eCategory); - EXPECT_EQ(1.0, float1.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float2.eCategory); - EXPECT_EQ(3.1415, float2.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float3.eCategory); - EXPECT_EQ(-0.01, float3.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float4.eCategory); - EXPECT_EQ(5e+22, float4.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float5.eCategory); - EXPECT_EQ(1e+06, float5.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float6.eCategory); - EXPECT_EQ(-2e-02, float6.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float7.eCategory); - EXPECT_EQ(6.626e-34, float7.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float8.eCategory); - EXPECT_EQ(224617.445991228, float8.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float9.eCategory); - EXPECT_EQ(-0.0, float9.dContentFloatingpoint); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, float10.eCategory); - EXPECT_EQ(+0.0, float10.dContentFloatingpoint); + // float8, '=', 6.6_2_6e-3_4, Newline + // float9, '=', 224617.445991228, Newline + // float10, '=', -0.0f, Newline + // float11, '=', 0.0f, Newline + // specialFloat12, '=', inf, NewLine + // specialFloat13, '=', inf, NewLine + // specialFloat14, '=', -inf, NewLine + // specialFloat15, '=', qnan, NewLine + // specialFloat16, '=', qnan, NewLine + // specialFloat17, '=', -qnan, NewLine + // errFloat18, '=', 224__617.445_991_228, NewLine + // errFloat19, '=', _224_617.445_991_228, NewLine + // errFloat20, '=', 224_617_.445_991_228, NewLine + // errFloat21, '=', 224_617._445_991_228, NewLine + // errFloat22, '=', 224_617.445_991_228_, NewLine + // errFloat23, '=', 224_617.445_991__228, NewLine + // errFloat24, '=', 6.626_e-34, NewLine + // errFloat25, '=', 6.626e_-34, NewLine + // errFloat26, '=', 6.626e-_34, NewLine + // errFloat27, '=', 6.626e-34_, NewLine + // errFloat28, '=', 6.626_e-34, NewLine + const toml_parser::CToken& float1 = lexer.Consume(3); + const toml_parser::CToken& float2 = lexer.Consume(3); + const toml_parser::CToken& float3 = lexer.Consume(3); + const toml_parser::CToken& float4 = lexer.Consume(3); + const toml_parser::CToken& float5 = lexer.Consume(3); + const toml_parser::CToken& float6 = lexer.Consume(3); + const toml_parser::CToken& float7 = lexer.Consume(3); + const toml_parser::CToken& float8 = lexer.Consume(3); + const toml_parser::CToken& float9 = lexer.Consume(3); + const toml_parser::CToken& float10 = lexer.Consume(3); + const toml_parser::CToken& float11 = lexer.Consume(3); + const toml_parser::CToken& specialFloat12 = lexer.Consume(3); + const toml_parser::CToken& specialFloat13 = lexer.Consume(3); + const toml_parser::CToken& specialFloat14 = lexer.Consume(3); + const toml_parser::CToken& specialFloat15 = lexer.Consume(3); + const toml_parser::CToken& specialFloat16 = lexer.Consume(3); + const toml_parser::CToken& specialFloat17 = lexer.Consume(3); + const toml_parser::CToken& errFloat18 = lexer.Consume(3); + const toml_parser::CToken& errFloat19 = lexer.Consume(3); + const toml_parser::CToken& errFloat20 = lexer.Consume(3); + const toml_parser::CToken& errFloat21 = lexer.Consume(3); + const toml_parser::CToken& errFloat22 = lexer.Consume(3); + const toml_parser::CToken& errFloat23 = lexer.Consume(3); + const toml_parser::CToken& errFloat24 = lexer.Consume(3); + const toml_parser::CToken& errFloat25 = lexer.Consume(3); + const toml_parser::CToken& errFloat26 = lexer.Consume(3); + const toml_parser::CToken& errFloat27 = lexer.Consume(3); + const toml_parser::CToken& errFloat28 = lexer.Consume(3); + ASSERT_TRUE(float1); + ASSERT_TRUE(float2); + ASSERT_TRUE(float3); + ASSERT_TRUE(float4); + ASSERT_TRUE(float5); + ASSERT_TRUE(float6); + ASSERT_TRUE(float7); + ASSERT_TRUE(float8); + ASSERT_TRUE(float9); + ASSERT_TRUE(float10); + ASSERT_TRUE(float11); + ASSERT_TRUE(specialFloat12); + ASSERT_TRUE(specialFloat13); + ASSERT_TRUE(specialFloat14); + ASSERT_TRUE(specialFloat15); + ASSERT_TRUE(specialFloat16); + ASSERT_TRUE(specialFloat17); + ASSERT_TRUE(errFloat18); + ASSERT_TRUE(errFloat19); + ASSERT_TRUE(errFloat20); + ASSERT_TRUE(errFloat21); + ASSERT_TRUE(errFloat22); + ASSERT_TRUE(errFloat23); + ASSERT_TRUE(errFloat24); + ASSERT_TRUE(errFloat25); + ASSERT_TRUE(errFloat26); + ASSERT_TRUE(errFloat27); + ASSERT_TRUE(errFloat28); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float1.Category()); + EXPECT_EQ(1.0, float1.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float2.Category()); + EXPECT_EQ(3.1415, float2.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float3.Category()); + EXPECT_EQ(-0.01, float3.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float4.Category()); + EXPECT_EQ(5e+22, float4.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float5.Category()); + EXPECT_EQ(1e+06, float5.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float6.Category()); + EXPECT_EQ(-2e-02, float6.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float7.Category()); + EXPECT_EQ(6.626e-34, float7.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float8.Category()); + EXPECT_EQ(6.626e-34, float8.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float9.Category()); + EXPECT_EQ(224617.445991228, float9.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float10.Category()); + EXPECT_EQ(-0.0, float10.FloatValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, float11.Category()); + EXPECT_EQ(+0.0, float11.FloatValue()); + #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif @@ -498,30 +899,42 @@ TEST(TOMLLexerTest, Datatype_Float) } temp{ d }; return temp.ui; }; - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, specialFloat1.eCategory); - EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat1.dContentFloatingpoint)); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, specialFloat2.eCategory); - EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat2.dContentFloatingpoint)); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, specialFloat3.eCategory); - EXPECT_EQ(0xFFF0000000000000ull, fnMakeUInt(specialFloat3.dContentFloatingpoint)); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, specialFloat4.eCategory); - EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat4.dContentFloatingpoint) & 0xFFF0000000000000ull); - EXPECT_NE(0, fnMakeUInt(specialFloat4.dContentFloatingpoint) & 0x000FFFFFFFFFFFFFull); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, specialFloat5.eCategory); - EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat5.dContentFloatingpoint) & 0xFFF0000000000000ull); - EXPECT_NE(0, fnMakeUInt(specialFloat5.dContentFloatingpoint) & 0x000FFFFFFFFFFFFFull); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_float, specialFloat6.eCategory); - EXPECT_EQ(0xFFF0000000000000ull, fnMakeUInt(specialFloat6.dContentFloatingpoint) & 0xFFF0000000000000ull); - EXPECT_NE(0, fnMakeUInt(specialFloat6.dContentFloatingpoint) & 0x000FFFFFFFFFFFFFull); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, specialFloat12.Category()); + EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat12.FloatValue())); // inf + EXPECT_EQ(toml_parser::ETokenCategory::token_float, specialFloat13.Category()); + EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat13.FloatValue())); // +inf + EXPECT_EQ(toml_parser::ETokenCategory::token_float, specialFloat14.Category()); + EXPECT_EQ(0xFFF0000000000000ull, fnMakeUInt(specialFloat14.FloatValue())); // -inf + EXPECT_EQ(toml_parser::ETokenCategory::token_float, specialFloat15.Category()); + EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat15.FloatValue()) & 0xFFF0000000000000ull); // nan + EXPECT_NE(0, fnMakeUInt(specialFloat15.FloatValue()) & 0x000FFFFFFFFFFFFFull); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, specialFloat16.Category()); + EXPECT_EQ(0x7FF0000000000000ull, fnMakeUInt(specialFloat16.FloatValue()) & 0xFFF0000000000000ull); + EXPECT_NE(0, fnMakeUInt(specialFloat16.FloatValue()) & 0x000FFFFFFFFFFFFFull); + EXPECT_EQ(toml_parser::ETokenCategory::token_float, specialFloat17.Category()); + EXPECT_EQ(0xFFF0000000000000ull, fnMakeUInt(specialFloat17.FloatValue()) & 0xFFF0000000000000ull); + EXPECT_NE(0, fnMakeUInt(specialFloat17.FloatValue()) & 0x000FFFFFFFFFFFFFull); #ifdef __GNUC__ #pragma GCC diagnostic pop #endif + + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat18.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat19.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat20.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat21.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat22.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat23.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat24.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat25.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat26.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat27.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, errFloat28.Category()); } TEST(TOMLLexerTest, Datatype_Boolean) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( key1 = true key2 = false )"s); @@ -529,19 +942,20 @@ TEST(TOMLLexerTest, Datatype_Boolean) // NewLine // key1, =, 1, NewLine // key2, =, 0, NewLine - // token_eof - CLexerTOML::SToken trueToken = lexer.Consume(4); - CLexerTOML::SToken falseToken = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_boolean, trueToken.eCategory); - EXPECT_TRUE(trueToken.bContentBoolean); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_boolean, falseToken.eCategory); - EXPECT_FALSE(falseToken.bContentBoolean); + const toml_parser::CToken& trueToken = lexer.Consume(3); + const toml_parser::CToken& falseToken = lexer.Consume(3); + ASSERT_TRUE(trueToken); + ASSERT_TRUE(falseToken); + EXPECT_EQ(toml_parser::ETokenCategory::token_boolean, trueToken.Category()); + EXPECT_TRUE(trueToken.BooleanValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_boolean, falseToken.Category()); + EXPECT_FALSE(falseToken.BooleanValue()); } TEST(TOMLLexerTest, Datatype_String_BasicString) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( basicString = "this is a string" basicString_esc1 = "Backspace\b" basicString_esc2 = "Tab\t" @@ -565,43 +979,62 @@ TEST(TOMLLexerTest, Datatype_String_BasicString) // basicString_esc7, '=', backslash\, Newline // basicString_esc8, '=', ã®, Newline // basicString_esc9, '=', ð… , Newline - // token_eof - CLexerTOML::SToken string = lexer.Consume(4); - CLexerTOML::SToken string_esc1 = lexer.Consume(4); - CLexerTOML::SToken string_esc2 = lexer.Consume(4); - CLexerTOML::SToken string_esc3 = lexer.Consume(4); - CLexerTOML::SToken string_esc4 = lexer.Consume(4); - CLexerTOML::SToken string_esc5 = lexer.Consume(4); - CLexerTOML::SToken string_esc6 = lexer.Consume(4); - CLexerTOML::SToken string_esc7 = lexer.Consume(4); - CLexerTOML::SToken string_esc8 = lexer.Consume(4); - CLexerTOML::SToken string_esc9 = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string.eCategory); - EXPECT_EQ("this is a string", string.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc1.eCategory); - EXPECT_EQ("Backspace\b", string_esc1.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc2.eCategory); - EXPECT_EQ("Tab\t", string_esc2.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc3.eCategory); - EXPECT_EQ("Linefeed\n", string_esc3.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc4.eCategory); - EXPECT_EQ("Formfeed\f", string_esc4.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc5.eCategory); - EXPECT_EQ("carriage return\r", string_esc5.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc6.eCategory); - EXPECT_EQ("quote\"", string_esc6.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc7.eCategory); - EXPECT_EQ("backslash\\", string_esc7.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc8.eCategory); - EXPECT_EQ("unicode hiragana 'no': ã®", string_esc8.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc9.eCategory); - EXPECT_EQ("Musical eighth note: ð… ", string_esc9.ssContentString); + const toml_parser::CToken& string = lexer.Consume(3); + const toml_parser::CToken& string_esc1 = lexer.Consume(3); + const toml_parser::CToken& string_esc2 = lexer.Consume(3); + const toml_parser::CToken& string_esc3 = lexer.Consume(3); + const toml_parser::CToken& string_esc4 = lexer.Consume(3); + const toml_parser::CToken& string_esc5 = lexer.Consume(3); + const toml_parser::CToken& string_esc6 = lexer.Consume(3); + const toml_parser::CToken& string_esc7 = lexer.Consume(3); + const toml_parser::CToken& string_esc8 = lexer.Consume(3); + const toml_parser::CToken& string_esc9 = lexer.Consume(3); + ASSERT_TRUE(string); + ASSERT_TRUE(string_esc1); + ASSERT_TRUE(string_esc2); + ASSERT_TRUE(string_esc3); + ASSERT_TRUE(string_esc4); + ASSERT_TRUE(string_esc5); + ASSERT_TRUE(string_esc6); + ASSERT_TRUE(string_esc7); + ASSERT_TRUE(string_esc8); + ASSERT_TRUE(string_esc9); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string.StringType()); + EXPECT_EQ("this is a string", string.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc1.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc1.StringType()); + EXPECT_EQ("Backspace\b", string_esc1.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc2.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc2.StringType()); + EXPECT_EQ("Tab\t", string_esc2.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc3.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc3.StringType()); + EXPECT_EQ("Linefeed\n", string_esc3.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc4.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc4.StringType()); + EXPECT_EQ("Formfeed\f", string_esc4.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc5.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc5.StringType()); + EXPECT_EQ("carriage return\r", string_esc5.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc6.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc6.StringType()); + EXPECT_EQ("quote\"", string_esc6.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc7.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc7.StringType()); + EXPECT_EQ("backslash\\", string_esc7.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc8.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc8.StringType()); + EXPECT_EQ("unicode hiragana 'no': ã®", string_esc8.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc9.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, string_esc9.StringType()); + EXPECT_EQ("Musical eighth note: ð… ", string_esc9.StringValue()); } TEST(TOMLLexerTest, Datatype_String_BasicStringMultiline) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( basicStringMultiline1 = """ this is a multiline string""" @@ -654,54 +1087,79 @@ multiline string""" // basicStringMultiline_esc7, '=', backslash\multiline, Newline // basicStringMultiline_esc8, '=', unicode hiragana 'no': ã® multiline, Newline // basicStringMultiline_esc9, '=', Musical eighth note: ð…  multiline, Newline - // token_eof - CLexerTOML::SToken string1 = lexer.Consume(4); - CLexerTOML::SToken string2 = lexer.Consume(4); - CLexerTOML::SToken string3 = lexer.Consume(4); - CLexerTOML::SToken string4 = lexer.Consume(4); - CLexerTOML::SToken string_esc1 = lexer.Consume(4); - CLexerTOML::SToken string_esc2 = lexer.Consume(4); - CLexerTOML::SToken string_esc3 = lexer.Consume(4); - CLexerTOML::SToken string_esc4 = lexer.Consume(4); - CLexerTOML::SToken string_esc5 = lexer.Consume(4); - CLexerTOML::SToken string_esc6 = lexer.Consume(4); - CLexerTOML::SToken string_esc7 = lexer.Consume(4); - CLexerTOML::SToken string_esc8 = lexer.Consume(4); - CLexerTOML::SToken string_esc9 = lexer.Consume(4); + const toml_parser::CToken& string1 = lexer.Consume(3); + const toml_parser::CToken& string2 = lexer.Consume(3); + const toml_parser::CToken& string3 = lexer.Consume(3); + const toml_parser::CToken& string4 = lexer.Consume(3); + const toml_parser::CToken& string_esc1 = lexer.Consume(3); + const toml_parser::CToken& string_esc2 = lexer.Consume(3); + const toml_parser::CToken& string_esc3 = lexer.Consume(3); + const toml_parser::CToken& string_esc4 = lexer.Consume(3); + const toml_parser::CToken& string_esc5 = lexer.Consume(3); + const toml_parser::CToken& string_esc6 = lexer.Consume(3); + const toml_parser::CToken& string_esc7 = lexer.Consume(3); + const toml_parser::CToken& string_esc8 = lexer.Consume(3); + const toml_parser::CToken& string_esc9 = lexer.Consume(3); + ASSERT_TRUE(string1); + ASSERT_TRUE(string2); + ASSERT_TRUE(string3); + ASSERT_TRUE(string4); + ASSERT_TRUE(string_esc1); + ASSERT_TRUE(string_esc2); + ASSERT_TRUE(string_esc3); + ASSERT_TRUE(string_esc4); + ASSERT_TRUE(string_esc5); + ASSERT_TRUE(string_esc6); + ASSERT_TRUE(string_esc7); + ASSERT_TRUE(string_esc8); + ASSERT_TRUE(string_esc9); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string1.eCategory); - EXPECT_EQ("this is a\nmultiline string", string1.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string2.eCategory); - EXPECT_EQ("this is a nice multiline string", string2.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string3.eCategory); - EXPECT_EQ("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".", string3.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string4.eCategory); - EXPECT_EQ("\"This,\" she said, \"is just a pointless statement.\"", string4.ssContentString); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string1.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string1.StringType()); + EXPECT_EQ("this is a\nmultiline string", string1.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string2.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string2.StringType()); + EXPECT_EQ("this is a nice multiline string", string2.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string3.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string3.StringType()); + EXPECT_EQ("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".", string3.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string4.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string4.StringType()); + EXPECT_EQ("\"This,\" she said, \"is just a pointless statement.\"", string4.StringValue()); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc1.eCategory); - EXPECT_EQ("Backspace\b multiline", string_esc1.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc2.eCategory); - EXPECT_EQ("Tab\tmultiline", string_esc2.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc3.eCategory); - EXPECT_EQ("Linefeed\nmultiline", string_esc3.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc4.eCategory); - EXPECT_EQ("Formfeed\fmultiline", string_esc4.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc5.eCategory); - EXPECT_EQ("carriage return\rmultiline", string_esc5.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc6.eCategory); - EXPECT_EQ("quote\" multiline", string_esc6.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc7.eCategory); - EXPECT_EQ("backslash\\multiline", string_esc7.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc8.eCategory); - EXPECT_EQ("unicode hiragana 'no': ã® multiline", string_esc8.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, string_esc9.eCategory); - EXPECT_EQ("Musical eighth note: ð…  multiline", string_esc9.ssContentString); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc1.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc1.StringType()); + EXPECT_EQ("Backspace\b multiline", string_esc1.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc2.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc2.StringType()); + EXPECT_EQ("Tab\tmultiline", string_esc2.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc3.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc3.StringType()); + EXPECT_EQ("Linefeed\nmultiline", string_esc3.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc4.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc4.StringType()); + EXPECT_EQ("Formfeed\fmultiline", string_esc4.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc5.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc5.StringType()); + EXPECT_EQ("carriage return\rmultiline", string_esc5.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc6.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc6.StringType()); + EXPECT_EQ("quote\" multiline", string_esc6.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc7.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc7.StringType()); + EXPECT_EQ("backslash\\multiline", string_esc7.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc8.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc8.StringType()); + EXPECT_EQ("unicode hiragana 'no': ã® multiline", string_esc8.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, string_esc9.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_quoted, string_esc9.StringType()); + EXPECT_EQ("Musical eighth note: ð…  multiline", string_esc9.StringValue()); } TEST(TOMLLexerTest, Datatype_String_LiteralString) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( winpath = 'C:\Users\nodejs\templates' winpath2 = '\\ServerX\admin$\system32\' quoted = 'Tom "Dubs" Preston-Werner' @@ -714,24 +1172,32 @@ TEST(TOMLLexerTest, Datatype_String_LiteralString) // quoted, '=', Tom "Dubs" Preston-Werner, NewLine // regex, '=', <\i\c*\s*>, NewLine - CLexerTOML::SToken winpath = lexer.Consume(4); - CLexerTOML::SToken winpath2 = lexer.Consume(4); - CLexerTOML::SToken quoted = lexer.Consume(4); - CLexerTOML::SToken regex = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, winpath.eCategory); - EXPECT_EQ("C:\\Users\\nodejs\\templates", winpath.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, winpath2.eCategory); - EXPECT_EQ("\\\\ServerX\\admin$\\system32\\", winpath2.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, quoted.eCategory); - EXPECT_EQ("Tom \"Dubs\" Preston-Werner", quoted.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, regex.eCategory); - EXPECT_EQ("<\\i\\c*\\s*>", regex.ssContentString); + const toml_parser::CToken& winpath = lexer.Consume(3); + const toml_parser::CToken& winpath2 = lexer.Consume(3); + const toml_parser::CToken& quoted = lexer.Consume(3); + const toml_parser::CToken& regex = lexer.Consume(3); + ASSERT_TRUE(winpath); + ASSERT_TRUE(winpath2); + ASSERT_TRUE(quoted); + ASSERT_TRUE(regex); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, winpath.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::literal_string, winpath.StringType()); + EXPECT_EQ("C:\\Users\\nodejs\\templates", winpath.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, winpath2.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::literal_string, winpath2.StringType()); + EXPECT_EQ("\\\\ServerX\\admin$\\system32\\", winpath2.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, quoted.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::literal_string, quoted.StringType()); + EXPECT_EQ("Tom \"Dubs\" Preston-Werner", quoted.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, regex.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::literal_string, regex.StringType()); + EXPECT_EQ("<\\i\\c*\\s*>", regex.StringValue()); } TEST(TOMLLexerTest, Datatype_String_LiteralStringMultiline) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( regex2 = '''I [dw]on't need \d{2} apples''' lines = ''' The first newline is @@ -750,22 +1216,31 @@ str = ''''That,' she said, 'is still pointless.'''' // quot15, '=', Here are fifteen quotation marks: """"""""""""""", NewLine // apos15, '=', Here are fifteen apostropes: ''''''''''''''', NewLine // str, '=', 'That,' she said, 'is still pointless.', NewLine - // token_eof - CLexerTOML::SToken regex2 = lexer.Consume(4); - CLexerTOML::SToken lines = lexer.Consume(4); - CLexerTOML::SToken quot15 = lexer.Consume(4); - CLexerTOML::SToken apos15 = lexer.Consume(4); - CLexerTOML::SToken str = lexer.Consume(4); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, regex2.eCategory); - EXPECT_EQ("I [dw]on't need \\d{2} apples", regex2.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, lines.eCategory); - EXPECT_EQ("The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", lines.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, quot15.eCategory); - EXPECT_EQ("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"", quot15.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, apos15.eCategory); - EXPECT_EQ("Here are fifteen apostrophes: '''''''''''''''", apos15.ssContentString); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_string, str.eCategory); - EXPECT_EQ("'That,' she said, 'is still pointless.'", str.ssContentString); + const toml_parser::CToken& regex2 = lexer.Consume(3); + const toml_parser::CToken& lines = lexer.Consume(3); + const toml_parser::CToken& quot15 = lexer.Consume(3); + const toml_parser::CToken& apos15 = lexer.Consume(3); + const toml_parser::CToken& str = lexer.Consume(3); + ASSERT_TRUE(regex2); + ASSERT_TRUE(lines); + ASSERT_TRUE(quot15); + ASSERT_TRUE(apos15); + ASSERT_TRUE(str); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, regex2.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_literal, regex2.StringType()); + EXPECT_EQ("I [dw]on't need \\d{2} apples", regex2.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, lines.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_literal, lines.StringType()); + EXPECT_EQ("The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", lines.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, quot15.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_literal, quot15.StringType()); + EXPECT_EQ("Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"", quot15.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, apos15.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::quoted_string, apos15.StringType()); + EXPECT_EQ("Here are fifteen apostrophes: '''''''''''''''", apos15.StringValue()); + EXPECT_EQ(toml_parser::ETokenCategory::token_string, str.Category()); + EXPECT_EQ(toml_parser::ETokenStringType::multi_line_literal, str.StringType()); + EXPECT_EQ("'That,' she said, 'is still pointless.'", str.StringValue()); } // TEST(TOMLLexerTest, Datatype_OffsetDateTime) @@ -791,7 +1266,7 @@ str = ''''That,' she said, 'is still pointless.'''' TEST(TOMLLexerTest, Invalid_Key) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( keyä = "value1" "key\u123" = "value2" "key\U1234" = "value3" @@ -803,43 +1278,50 @@ TEST(TOMLLexerTest, Invalid_Key) // ErrorToken, '=', value2, NewLine // ErrorToken, '=', value3, NewLine // ErrorToken, '=', value4, NewLine - // token_eof - CLexerTOML::SToken invBareKey = lexer.Consume(2); // only [a-zA-Z0-9-_]allowed for bare key - CLexerTOML::SToken invQuotedKey1 = lexer.Consume(4); // \u has to be followed by 4 times [0-9A-Fa-F] - CLexerTOML::SToken invQuotedKey2 = lexer.Consume(4); // \U has to be followed by 8 times [0-9A-Fa-F] - CLexerTOML::SToken invQuotedKey3 = lexer.Consume(4); // any escape sequence except specified in the TOML secs are invalid - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invBareKey.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invQuotedKey1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invQuotedKey2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invQuotedKey3.eCategory); + const toml_parser::CToken& invBareKey = lexer.Consume(1); // only [a-zA-Z0-9-_]allowed for bare key + const toml_parser::CToken& invQuotedKey1 = lexer.Consume(3); // \u has to be followed by 4 times [0-9A-Fa-F] + const toml_parser::CToken& invQuotedKey2 = lexer.Consume(3); // \U has to be followed by 8 times [0-9A-Fa-F] + const toml_parser::CToken& invQuotedKey3 = lexer.Consume(3); // any escape sequence except specified in the TOML secs are invalid + ASSERT_TRUE(invBareKey); // only [a-zA-Z0-9-_]allowed for bare key + ASSERT_TRUE(invQuotedKey1); // \u has to be followed by 4 times [0-9A-Fa-F] + ASSERT_TRUE(invQuotedKey2); // \U has to be followed by 8 times [0-9A-Fa-F] + ASSERT_TRUE(invQuotedKey3); // any escape sequence except specified in the TOML secs are invalid + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invBareKey.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invQuotedKey1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invQuotedKey2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invQuotedKey3.Category()); } TEST(TOMLLexerTest, Invalid_String) { using namespace std::string_literals; - CLexerTOML lexer1(R"(key1 = "invalid escape sequence \h)"s); - CLexerTOML lexer2(R"(key2 = """invalid escape sequence \h""")"s); - CLexerTOML lexer3(R"(key3 = "\u000")"s); - CLexerTOML lexer4(R"(key4 = "\U0000000")"s); + toml_parser::CLexer lexer1(R"(key1 = "invalid escape sequence \h)"s); + toml_parser::CLexer lexer2(R"(key2 = """invalid escape sequence \h""")"s); + toml_parser::CLexer lexer3(R"(key3 = "\u000")"s); + toml_parser::CLexer lexer4(R"(key4 = "\U0000000")"s); // Tokens: - // key1, '=', ErrorToken, token_eof - // key2, '=', ErrorToken, token_eof - // key3, '=', ErrorToken, token_eof - // key4, '=', ErrorToken, token_eof - CLexerTOML::SToken invString1 = lexer1.Consume(3); - CLexerTOML::SToken invString2 = lexer2.Consume(3); - CLexerTOML::SToken invString3 = lexer3.Consume(3); - CLexerTOML::SToken invString4 = lexer4.Consume(3); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invString1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invString2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invString3.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invString4.eCategory); + // key1, '=', ErrorToken + // key2, '=', ErrorToken + // key3, '=', ErrorToken + // key4, '=', ErrorToken + const toml_parser::CToken& invString1 = lexer1.Consume(2); + const toml_parser::CToken& invString2 = lexer2.Consume(2); + const toml_parser::CToken& invString3 = lexer3.Consume(2); + const toml_parser::CToken& invString4 = lexer4.Consume(2); + ASSERT_TRUE(invString1); + ASSERT_TRUE(invString2); + ASSERT_TRUE(invString3); + ASSERT_TRUE(invString4); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invString1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invString2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invString3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invString4.Category()); } TEST(TOMLLexerTest, Invalid_Integer) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( invInteger3 = 01 invInteger4 = 0a1234 invInteger5 = 1234_ @@ -869,39 +1351,51 @@ TEST(TOMLLexerTest, Invalid_Integer) // invInteger12, '=', ErrorToken, NewLine // invInteger12, '=', ErrorToken, NewLine // invInteger12, '=', ErrorToken, NewLine - // token_eof - CLexerTOML::SToken invInteger3 = lexer.Consume(4); // no leading zeros - CLexerTOML::SToken invInteger4 = lexer.Consume(4); // invalid prefix - CLexerTOML::SToken invInteger5 = lexer.Consume(4); // underscores only allowed between two digits - CLexerTOML::SToken invInteger6 = lexer.Consume(4); // underscores only allowed between two digits - CLexerTOML::SToken invInteger7 = lexer.Consume(4); // underscores not allowed after prefix - CLexerTOML::SToken invInteger8 = lexer.Consume(4); // underscores not allowed after prefix - CLexerTOML::SToken invInteger9 = lexer.Consume(4); // underscores not allowed after prefix - CLexerTOML::SToken invInteger10 = lexer.Consume(4); // no leading + allowed - CLexerTOML::SToken invInteger11 = lexer.Consume(4); // no leading + allowed - CLexerTOML::SToken invInteger12 = lexer.Consume(4); // no leading + allowed - CLexerTOML::SToken invInteger13 = lexer.Consume(4); // no negative number representation allowed with hex format - CLexerTOML::SToken invInteger14 = lexer.Consume(4); // no negative number representation allowed with ocatal format - CLexerTOML::SToken invInteger15 = lexer.Consume(4); // no negative number representation allowed with binary format - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger3.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger4.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger5.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger6.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger7.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger8.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger9.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger10.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger11.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger12.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger13.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger14.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invInteger15.eCategory); + const toml_parser::CToken& invInteger3 = lexer.Consume(3); // no leading zeros + const toml_parser::CToken& invInteger4 = lexer.Consume(3); // invalid prefix + const toml_parser::CToken& invInteger5 = lexer.Consume(3); // underscores only allowed between two digits + const toml_parser::CToken& invInteger6 = lexer.Consume(3); // underscores only allowed between two digits + const toml_parser::CToken& invInteger7 = lexer.Consume(3); // underscores not allowed after prefix + const toml_parser::CToken& invInteger8 = lexer.Consume(3); // underscores not allowed after prefix + const toml_parser::CToken& invInteger9 = lexer.Consume(3); // underscores not allowed after prefix + const toml_parser::CToken& invInteger10 = lexer.Consume(3); // no leading + allowed + const toml_parser::CToken& invInteger11 = lexer.Consume(3); // no leading + allowed + const toml_parser::CToken& invInteger12 = lexer.Consume(3); // no leading + allowed + const toml_parser::CToken& invInteger13 = lexer.Consume(3); // no negative number representation allowed with hex format + const toml_parser::CToken& invInteger14 = lexer.Consume(3); // no negative number representation allowed with ocatal format + const toml_parser::CToken& invInteger15 = lexer.Consume(3); // no negative number representation allowed with binary format + ASSERT_TRUE(invInteger3); // no leading zeros + ASSERT_TRUE(invInteger4); // invalid prefix + ASSERT_TRUE(invInteger5); // underscores only allowed between two digits + ASSERT_TRUE(invInteger6); // underscores only allowed between two digits + ASSERT_TRUE(invInteger7); // underscores not allowed after prefix + ASSERT_TRUE(invInteger8); // underscores not allowed after prefix + ASSERT_TRUE(invInteger9); // underscores not allowed after prefix + ASSERT_TRUE(invInteger10); // no leading + allowed + ASSERT_TRUE(invInteger11); // no leading + allowed + ASSERT_TRUE(invInteger12); // no leading + allowed + ASSERT_TRUE(invInteger13); // no negative number representation allowed with hex format + ASSERT_TRUE(invInteger14); // no negative number representation allowed with ocatal format + ASSERT_TRUE(invInteger15); // no negative number representation allowed with binary format + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger4.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger5.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger6.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger7.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger8.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger9.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger10.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger11.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger12.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger13.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger14.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invInteger15.Category()); } TEST(TOMLLexerTest, Invalid_Float) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( invFloat1 = .7 invFloat2 = 1. invFloat3 = 3.e4 @@ -915,23 +1409,27 @@ TEST(TOMLLexerTest, Invalid_Float) // invFloat3, '=', ErrorToken, NewLine // invFloat4, '=', ErrorToken, NewLine // invFloat5, '=', ErrorToken, NewLine - // token_eof - CLexerTOML::SToken invFloat1 = lexer.Consume(4); // decimal point has to be between two digits - CLexerTOML::SToken invFloat2 = lexer.Consume(4); // decimal point has to be between two digits - CLexerTOML::SToken invFloat3 = lexer.Consume(4); // decimal point has to be between two digits - CLexerTOML::SToken invFloat4 = lexer.Consume(4); // special float values are always lower case - CLexerTOML::SToken invFloat5 = lexer.Consume(4); // special float values are always lower case - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invFloat1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invFloat2.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invFloat3.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invFloat4.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invFloat5.eCategory); + const toml_parser::CToken& invFloat1 = lexer.Consume(3); // decimal point has to be between two digits + const toml_parser::CToken& invFloat2 = lexer.Consume(3); // decimal point has to be between two digits + const toml_parser::CToken& invFloat3 = lexer.Consume(3); // decimal point has to be between two digits + const toml_parser::CToken& invFloat4 = lexer.Consume(3); // special float values are always lower case + const toml_parser::CToken& invFloat5 = lexer.Consume(3); // special float values are always lower case + ASSERT_TRUE(invFloat1); // decimal point has to be between two digits + ASSERT_TRUE(invFloat2); // decimal point has to be between two digits + ASSERT_TRUE(invFloat3); // decimal point has to be between two digits + ASSERT_TRUE(invFloat4); // special float values are always lower case + ASSERT_TRUE(invFloat5); // special float values are always lower case + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invFloat1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invFloat2.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invFloat3.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invFloat4.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invFloat5.Category()); } TEST(TOMLLexerTest, Invalid_Boolean) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( invBool1 = True invBool2 = False )"s); @@ -939,11 +1437,12 @@ TEST(TOMLLexerTest, Invalid_Boolean) // NewLine // invBool1, '=', ErrorToken, NewLine // invBool2, '=', ErrorToken, NewLine - // token_eof - CLexerTOML::SToken invBool1 = lexer.Consume(4); // keyword for true has to be lower case - CLexerTOML::SToken invBool2 = lexer.Consume(4); // keyword for false has to be lower case - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invBool1.eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_error, invBool2.eCategory); + const toml_parser::CToken& invBool1 = lexer.Consume(3); // keyword for true has to be lower case + const toml_parser::CToken& invBool2 = lexer.Consume(3); // keyword for false has to be lower case + ASSERT_TRUE(invBool1); // keyword for true has to be lower case + ASSERT_TRUE(invBool2); // keyword for false has to be lower case + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invBool1.Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_error, invBool2.Category()); } // TEST(TOMLLexerTest, Invalid_OffsetDateTime) @@ -969,7 +1468,7 @@ TEST(TOMLLexerTest, Invalid_Boolean) TEST(TOMLLexerTest, Peek_NoAdvance) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( key1 = "value1" key2 = "value2" )"s); @@ -977,17 +1476,20 @@ TEST(TOMLLexerTest, Peek_NoAdvance) // NewLine // key1, '=', value1, NewLine // key2, '=', value2, NewLine - // token_eof - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_new_line, lexer.Peek().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_new_line, lexer.Peek().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, lexer.Peek(2).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, lexer.Peek(2).eCategory); + ASSERT_TRUE(lexer.Peek()); + ASSERT_TRUE(lexer.Peek()); + ASSERT_TRUE(lexer.Peek(1)); + ASSERT_TRUE(lexer.Peek(1)); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, lexer.Peek().Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, lexer.Peek().Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, lexer.Peek(1).Category()); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, lexer.Peek(1).Category()); } TEST(TOMLLexerTest, Consume_Advance) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( key1 = "value1" key2 = "value2" )"s); @@ -995,17 +1497,24 @@ TEST(TOMLLexerTest, Consume_Advance) // NewLine // key1, '=', value1, NewLine // key2, '=', value2, NewLine - // token_eof - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_new_line, lexer.Consume().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_key, lexer.Consume().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_new_line, lexer.Consume(3).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_syntax_assignment, lexer.Consume(2).eCategory); + auto ptr = lexer.Consume(); + ASSERT_TRUE(ptr); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, ptr.Category()); + ptr = lexer.Consume(); + ASSERT_TRUE(ptr); + EXPECT_EQ(toml_parser::ETokenCategory::token_key, ptr.Category()); + ptr = lexer.Consume(2); + ASSERT_TRUE(ptr); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_new_line, ptr.Category()); + ptr = lexer.Consume(1); + ASSERT_TRUE(ptr); + EXPECT_EQ(toml_parser::ETokenCategory::token_syntax_assignment, ptr.Category()); } TEST(TOMLLexerTest, PeekConsume_BoundsCheck) { using namespace std::string_literals; - CLexerTOML lexer(R"( + toml_parser::CLexer lexer(R"( key1 = "value1" key2 = "value2" )"s); @@ -1013,33 +1522,29 @@ TEST(TOMLLexerTest, PeekConsume_BoundsCheck) // NewLine // key1, '=', value1, NewLine // key2, '=', value2, NewLine - // token_eof - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Consume(11).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Consume().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Peek().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Peek(2).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Peek(0).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Peek(-1).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Consume(0).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Consume(-1).eCategory); + EXPECT_FALSE(lexer.Consume(10)); + EXPECT_FALSE(lexer.Consume()); + EXPECT_FALSE(lexer.Peek()); + EXPECT_FALSE(lexer.Peek(2)); + EXPECT_FALSE(lexer.Peek(0)); + EXPECT_FALSE(lexer.Consume(0)); } TEST(TOMLLexerTest, PeekConsume_EmptyInput) { using namespace std::string_literals; - CLexerTOML lexer(R"()"s); + toml_parser::CLexer lexer(R"()"s); // Tokens: - // token_eof - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Peek().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Peek(1).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Consume().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_eof, lexer.Consume(1).eCategory); + EXPECT_FALSE(lexer.Peek()); + EXPECT_FALSE(lexer.Peek(1)); + EXPECT_FALSE(lexer.Consume()); + EXPECT_FALSE(lexer.Consume(1)); } TEST(TOMLLexerTest, ExceptionHandling) { using namespace std::string_literals; - CLexerTOML lexer; + toml_parser::CLexer lexer; try { lexer.Feed(std::string("") + static_cast(0xC1)); @@ -1050,12 +1555,192 @@ TEST(TOMLLexerTest, ExceptionHandling) // Tokens: // token_terminated-Token - EXPECT_EQ(CLexerTOML::ETokenCategory::token_terminated, lexer.Consume(11).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_terminated, lexer.Consume().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_terminated, lexer.Peek().eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_terminated, lexer.Peek(2).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Peek(0).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Peek(-1).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Consume(0).eCategory); - EXPECT_EQ(CLexerTOML::ETokenCategory::token_empty, lexer.Consume(-1).eCategory); + EXPECT_EQ(toml_parser::ETokenCategory::token_terminated, lexer.Consume().Category()); + EXPECT_FALSE(lexer.Consume()); + EXPECT_FALSE(lexer.Peek()); + EXPECT_FALSE(lexer.Peek(2)); + EXPECT_FALSE(lexer.Peek(0)); + EXPECT_FALSE(lexer.Consume(0)); +} + +TEST(TOMLLexerTest, DISABLED_RegenerateTOML) +{ + using namespace std::string_literals; + std::string ssOrginal = R"( + # original comments + key1 = "value1" # follow comments + "key\u0032" = "value2" + "key3\U0001F92B" = "abc" + '' = "valid" + 'literal "key"' = "also valid" + "quoted 'key'" = "also valid" + _0xabcd = 0xabcd + 1234 = 1234 + [table] + array = [[1,2],[3,4]] + emptyArray = [] + [["table array"]] + fruit.name = "banana" # this is best practice + fruit. color = "yellow" # same as fruit.color + fruit . flavor = "banana" # same as fruit.flavor + a.b.c = 2 + [dog."tater.man"] + inlineTable = { name = "Some Name", type = "Some Type" } + nestedInlineTable = { a = { a = 0, b = "b"}, e = { c = 0, d = "d"} } + emptyTable = {} + [[tableArray]] + [[fruits.varieties]] + [["this is also a 'key'"]] + [['this is also a "key"']] + [[a."b.c"]] + int1 = 1 + int2 = +13 + int3 = -37 + int4 = 1_000 + int5 = 5_349_221 + int6 = 53_49_221 + int7 = 1_2_3_4_5 + int8 = 0 + int9 = +0 + int10 = -0 + hex1 = 0xDEADBEEF + hex2 = 0xdeadbeef + hex3 = 0xdead_beef + oct1 = 0o01234567 + oct2 = 0o755 + bin1 = 0b11111111 + bin2 = 0b00000001 + bin3 = 0b01010101 + intErr1 = _1234 + intErr2 = 1__234 + intErr3 = 1234_ + intErr4 = +0xabcd + intErr5 = -0xabcd + intErr6 = 0xabc__d + intErr7 = 0x_abcd + intErr8 = 0xabcd_ + intErr9 = 0_xabcd + intErr10 = 0XDEADBEEF + float1 = +1.0 + float2 = 3.1415 + float3 = -0.01 + float4 = 5e+22 + float5 = 1e06 + float6 = -2E-2 + float7 = 6.626e-34 + float8 = 6.6_2_6e-3_4 + float9 = 224_617.445_991_228 + float10 = -0.0 + float11 = +0.0 + specialFloat12 = inf + specialFloat13 = +inf + specialFloat14 = -inf + specialFloat15 = nan + specialFloat16 = +nan + specialFloat17 = -nan + errFloat18 = 224__617.445_991_228 + errFloat19 = _224_617.445_991_228 + errFloat20 = 224_617_.445_991_228 + errFloat21 = 224_617._445_991_228 + errFloat22 = 224_617.445_991_228_ + errFloat23 = 224_617.445_991__228 + errFloat24 = 6.626_e-34 + errFloat25 = 6.626e_-34 + errFloat26 = 6.626e-_34 + errFloat27 = 6.626e-34_ + errFloat28 = 6.626_e-34 + key1 = true + key2 = false + basicString = "this is a string" + basicString_esc1 = "Backspace\b" + basicString_esc2 = "Tab\t" + basicString_esc3 = "Linefeed\n" + basicString_esc4 = "Formfeed\f" + basicString_esc5 = "carriage return\r" + basicString_esc6 = "quote\"" + basicString_esc7 = "backslash\\" + basicString_esc8 = "unicode hiragana 'no': \u306e" + basicString_esc9 = "Musical eighth note: \U0001D160" + basicStringMultiline1 = """ +this is a +multiline string""" + basicStringMultiline2 = """\ + this is a nice \ + + multiline string""" + basicStringMultiline3 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + basicStringMultiline4 = """"This," she said, "is just a pointless statement."""" + basicStringMultiline_esc1 = """\ + Backspace\b \ + multiline""" + basicStringMultiline_esc2 = """\ + Tab\t\ + multiline""" + basicStringMultiline_esc3 = """\ + Linefeed\n\ + multiline""" + basicStringMultiline_esc4 = """\ + Formfeed\f\ + multiline""" + basicStringMultiline_esc5 = """\ + carriage return\r\ + multiline""" + basicStringMultiline_esc6 = """\ + quote\" \ + multiline""" + basicStringMultiline_esc7 = """\ + backslash\\\ + multiline""" + basicStringMultiline_esc8 = """\ + unicode hiragana 'no': \u306e \ + multiline""" + basicStringMultiline_esc9 = """\ + Musical eighth note: \U0001D160 \ + multiline""" + winpath = 'C:\Users\nodejs\templates' + winpath2 = '\\ServerX\admin$\system32\' + quoted = 'Tom "Dubs" Preston-Werner' + regex = '<\i\c*\s*>' + regex2 = '''I [dw]on't need \d{2} apples''' + lines = ''' + The first newline is + trimmed in raw strings. + All other whitespace + is preserved. + ''' + quot15 = '''Here are fifteen quotation marks: """""""""""""""''' + apos15 = "Here are fifteen apostrophes: '''''''''''''''" + str = ''''That,' she said, 'is still pointless.'''' + keyä = "value1" + "key\u123" = "value2" + "key\U1234" = "value3" + "key\k" = "value4" + invInteger3 = 01 + invInteger4 = 0a1234 + invInteger5 = 1234_ + invInteger6 = _1234 + invInteger7 = 0x_1234 + invInteger8 = 0o_1234 + invInteger9 = 0b_1010 + invInteger10 = +0x1234 + invInteger11 = +0o1234 + invInteger12 = +0b1010 + invInteger13 = -0x1234 + invInteger14 = -0o1234 + invInteger15 = -0b1010 + invFloat1 = .7 + invFloat2 = 1. + invFloat3 = 3.e4 + invFloat4 = Inf + invFloat5 = NaN + invBool1 = True + invBool2 = False + )"s; + + toml_parser::CLexer lexer(ssOrginal); + + // TODO EVE + //std::string ssCopy = lexer.GenerateTOML(); + + //EXPECT_EQ(ssOrginal, ssCopy); } diff --git a/tests/unit_tests/toml_parser/main.cpp b/tests/unit_tests/toml_parser/main.cpp index 30f21ad..5a353a9 100644 --- a/tests/unit_tests/toml_parser/main.cpp +++ b/tests/unit_tests/toml_parser/main.cpp @@ -2,8 +2,10 @@ #include "../../../global/process_watchdog.h" #include "../../../sdv_services/core/toml_parser/character_reader_utf_8.cpp" #include "../../../sdv_services/core/toml_parser/lexer_toml.cpp" +#include "../../../sdv_services/core/toml_parser/lexer_toml_token.cpp" #include "../../../sdv_services/core/toml_parser/parser_toml.cpp" #include "../../../sdv_services/core/toml_parser/parser_node_toml.cpp" +#include "../../../sdv_services/core/toml_parser/miscellaneous.cpp" #if defined(_WIN32) && defined(_UNICODE) extern "C" int wmain(int argc, wchar_t* argv[]) diff --git a/tests/unit_tests/toml_parser/miscellaneous_tests.cpp b/tests/unit_tests/toml_parser/miscellaneous_tests.cpp new file mode 100644 index 0000000..8b1fb86 --- /dev/null +++ b/tests/unit_tests/toml_parser/miscellaneous_tests.cpp @@ -0,0 +1,981 @@ +#include +#include +#include + +#include "../../../sdv_services/core/toml_parser/exception.h" +#include "../../../sdv_services/core/toml_parser/miscellaneous.h" + +TEST(TOMLMiscellaneousTests, Hex2Dec) +{ + // No string + std::string ssEmpty; + EXPECT_THROW(toml_parser::HexadecimalToDecimal(ssEmpty), sdv::toml::XTOMLParseException); + + // Invalid string + std::string ssNoNumber = "xyz"; + EXPECT_THROW(toml_parser::HexadecimalToDecimal(ssNoNumber), sdv::toml::XTOMLParseException); + + // Parse value + EXPECT_EQ(toml_parser::HexadecimalToDecimal("10"), 16u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("a"), 10u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("AbCd1234"), 2882343476u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("0"), 0u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("7fffffff"), 2147483647u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("80000000"), 2147483648u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("FFFFFFFF"), 4294967295u); + + // Parse up to max value + EXPECT_EQ(toml_parser::HexadecimalToDecimal("0FFFFFFFF"), 4294967295u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("FFFFFFFFA"), 4294967295u); + + // Parse until unknown character + EXPECT_EQ(toml_parser::HexadecimalToDecimal("0xyz"), 0u); + EXPECT_EQ(toml_parser::HexadecimalToDecimal("10xyz"), 16u); +} + +TEST(TOMLMiscellaneousTests, Dec2Dec) +{ + // No string + std::string ssEmpty; + EXPECT_THROW(toml_parser::DecimalToDecimal(ssEmpty), sdv::toml::XTOMLParseException); + + // Invalid string + std::string ssNoNumber = "f123"; + EXPECT_THROW(toml_parser::DecimalToDecimal(ssNoNumber), sdv::toml::XTOMLParseException); + + // Parse value + EXPECT_EQ(toml_parser::DecimalToDecimal("16"), 16u); + EXPECT_EQ(toml_parser::DecimalToDecimal("10"), 10u); + EXPECT_EQ(toml_parser::DecimalToDecimal("2882343476"), 2882343476u); + EXPECT_EQ(toml_parser::DecimalToDecimal("0"), 0u); + EXPECT_EQ(toml_parser::DecimalToDecimal("2147483647"), 2147483647u); + EXPECT_EQ(toml_parser::DecimalToDecimal("2147483648"), 2147483648u); + EXPECT_EQ(toml_parser::DecimalToDecimal("4294967295"), 4294967295u); + + // Parse up to max value + EXPECT_EQ(toml_parser::DecimalToDecimal("04294967295"), 4294967295u); + EXPECT_EQ(toml_parser::DecimalToDecimal("42949672950"), 4294967295u); + + // Parse until unknown character + EXPECT_EQ(toml_parser::DecimalToDecimal("0xyz"), 0u); + EXPECT_EQ(toml_parser::DecimalToDecimal("10xyz"), 10u); +} + +TEST(TOMLMiscellaneousTests, Oct2Dec) +{ + // No string + std::string ssEmpty; + EXPECT_THROW(toml_parser::OctalToDecimal(ssEmpty), sdv::toml::XTOMLParseException); + + // Invalid string + std::string ssNoNumber = "890"; + EXPECT_THROW(toml_parser::OctalToDecimal(ssNoNumber), sdv::toml::XTOMLParseException); + + // Parse value + EXPECT_EQ(toml_parser::OctalToDecimal("20"), 16u); + EXPECT_EQ(toml_parser::OctalToDecimal("12"), 10u); + EXPECT_EQ(toml_parser::OctalToDecimal("25363211064"), 2882343476u); + EXPECT_EQ(toml_parser::OctalToDecimal("0"), 0u); + EXPECT_EQ(toml_parser::OctalToDecimal("17777777777"), 2147483647u); + EXPECT_EQ(toml_parser::OctalToDecimal("20000000000"), 2147483648u); + EXPECT_EQ(toml_parser::OctalToDecimal("37777777777"), 4294967295u); + + // Parse up to max value + EXPECT_EQ(toml_parser::OctalToDecimal("037777777777"), 4294967295u); + EXPECT_EQ(toml_parser::OctalToDecimal("377777777770"), 4294967295u); + + // Parse until unknown character + EXPECT_EQ(toml_parser::OctalToDecimal("0xyz"), 0u); + EXPECT_EQ(toml_parser::OctalToDecimal("12xyz"), 10u); +} + +TEST(TOMLMiscellaneousTests, Bin2Dec) +{ + // No string + std::string ssEmpty; + EXPECT_THROW(toml_parser::BinaryToDecimal(ssEmpty), sdv::toml::XTOMLParseException); + + // Invalid string + std::string ssNoNumber = "234"; + EXPECT_THROW(toml_parser::BinaryToDecimal(ssNoNumber), sdv::toml::XTOMLParseException); + + // Parse value + EXPECT_EQ(toml_parser::BinaryToDecimal("10000"), 16u); + EXPECT_EQ(toml_parser::BinaryToDecimal("1010"), 10u); + EXPECT_EQ(toml_parser::BinaryToDecimal("10101011110011010001001000110100"), 2882343476u); + EXPECT_EQ(toml_parser::BinaryToDecimal("0"), 0u); + EXPECT_EQ(toml_parser::BinaryToDecimal("1111111111111111111111111111111"), 2147483647u); + EXPECT_EQ(toml_parser::BinaryToDecimal("10000000000000000000000000000000"), 2147483648u); + EXPECT_EQ(toml_parser::BinaryToDecimal("11111111111111111111111111111111"), 4294967295u); + + // Parse up to max value + EXPECT_EQ(toml_parser::BinaryToDecimal("011111111111111111111111111111111"), 4294967295u); + EXPECT_EQ(toml_parser::BinaryToDecimal("111111111111111111111111111111110"), 4294967295u); + + // Parse until unknown character + EXPECT_EQ(toml_parser::BinaryToDecimal("0xyz"), 0u); + EXPECT_EQ(toml_parser::BinaryToDecimal("1010yz"), 10u); +} + +TEST(TOMLMiscellaneousTests, UnicodeCharacter) +{ + // No string + std::string ssEmpty; + EXPECT_THROW(toml_parser::EscapedUnicodeCharacterToUTF8(ssEmpty), sdv::toml::XTOMLParseException); + + // Invalid string + std::string ssNoNumber = "xyz"; + EXPECT_THROW(toml_parser::EscapedUnicodeCharacterToUTF8(ssNoNumber), sdv::toml::XTOMLParseException); + + // Conversion + EXPECT_EQ(toml_parser::EscapedUnicodeCharacterToUTF8("042f"), u8"\u042f"); + EXPECT_EQ(toml_parser::EscapedUnicodeCharacterToUTF8("0000042f"), u8"\U0000042f"); + EXPECT_EQ(toml_parser::EscapedUnicodeCharacterToUTF8("0001F600"), u8"\U0001F600"); +} + +TEST(TOMLMiscellaneousTests, SplitKeyStringEmpty) +{ + std::string ssKeyEmpty; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKeyEmpty); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitKeyFirstPartOnly) +{ + std::string ssKey = "abc"; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "_abc"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "_abc"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "-abc"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "-abc"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "1234"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1234"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Although invalid bare key + ssKey = "abc/"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid bare key + ssKey = "abc\\"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid bare key + ssKey = "abc def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitStandardBareKey) +{ + std::string ssKey = "abc.def"; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "def"); + + ssKey = "_abc._def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "_abc"); + EXPECT_EQ(prSplittedKey.second, "_def"); + + ssKey = "-abc.-def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "-abc"); + EXPECT_EQ(prSplittedKey.second, "-def"); + + ssKey = "1234.5678"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1234"); + EXPECT_EQ(prSplittedKey.second, "5678"); + + ssKey = "1234.5678.90"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1234"); + EXPECT_EQ(prSplittedKey.second, "5678.90"); + + // Invalid bare key + ssKey = "/abc./def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid bare key + ssKey = "\\abc.\\def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitBareKeyWithSpace) +{ + std::string ssKey = " abc.def"; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "def"); + + ssKey = "abc . def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, " def"); + + ssKey = "\tabc.\tdef"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "\tdef"); + + ssKey = "abc\n.\ndef"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "\ndef"); + + // Invalid key + ssKey = "abc def . ghi jkl"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitLiteralKey) +{ + std::string ssKey = "'abc'"; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "'abc.def'"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc.def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "'abc'.'def'"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "'def'"); + + ssKey = "'\"cool\" key'.'very \"cool\" key'"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "\"cool\" key"); + EXPECT_EQ(prSplittedKey.second, "'very \"cool\" key'"); + + ssKey = "'abc/def'"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc/def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "'abc\\ndef'"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\\ndef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "'abc.def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "cool' 'key"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitQuotedKey) +{ + std::string ssKey = "\"abc\""; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc.def\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc.def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\".\"def\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "\"def\""); + + ssKey = "\"'cool' key\".\"very 'cool' key\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "'cool' key"); + EXPECT_EQ(prSplittedKey.second, "\"very 'cool' key\""); + + ssKey = "\"abc/def\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc/def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "\"abc.def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "cool\" \"key"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitEscapedQuotedKey) +{ + std::string ssKey = "\"abc\\bdef\""; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\bdef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\tdef\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\tdef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\ndef\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\ndef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\fdef\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\fdef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\rdef\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\rdef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\\"def\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\"def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\\\def\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc\\def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\u042fdef\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, u8"abc\u042fdef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\U0000042fdef\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, u8"abc\U0000042fdef"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "\"abc\\U0001F600def\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, u8"abc\U0001F600def"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "\"abc\\uxyz\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "\"abc\\Uxyz\""; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SplitTableKey) +{ + std::string ssKey = "abc.def"; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "def"); + + ssKey = "abc.def.ghi"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "def.ghi"); + + ssKey = ".abc.def.ghi"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "def.ghi"); +} + +TEST(TOMLMiscellaneousTests, SplitArrayKey) +{ + std::string ssKey = "abc[1]"; + auto prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "[1]"); + + ssKey = "abc[1][2]"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "[1][2]"); + + ssKey = "[1]"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1"); + EXPECT_TRUE(prSplittedKey.second.empty()); + + ssKey = "[1][2]"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1"); + EXPECT_EQ(prSplittedKey.second, "[2]"); + + ssKey = "[1].abc"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1"); + EXPECT_EQ(prSplittedKey.second, "abc"); + + ssKey = ".[1].abc"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "1"); + EXPECT_EQ(prSplittedKey.second, "abc"); + + ssKey = "abc [ 1 ] [ 2 ] [ 3 ] . def"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_EQ(prSplittedKey.first, "abc"); + EXPECT_EQ(prSplittedKey.second, "[ 1 ] [ 2 ] [ 3 ] . def"); + + // Invalid key + ssKey = "[1]abc"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "[1.2][2]"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "[1"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); + + // Invalid key + ssKey = "[1[2]]"; + prSplittedKey = toml_parser::SplitNodeKey(ssKey); + EXPECT_TRUE(prSplittedKey.first.empty()); + EXPECT_TRUE(prSplittedKey.second.empty()); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteBareKeys) +{ + EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::smart_key), "abc"); + EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::smart_key), "123"); + EXPECT_EQ(toml_parser::QuoteText("ABC", toml_parser::EQuoteRequest::smart_key), "ABC"); + EXPECT_EQ(toml_parser::QuoteText("abc_def", toml_parser::EQuoteRequest::smart_key), "abc_def"); + EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::smart_key), "ABC-DEF"); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteSpecialCharsKeys) +{ + EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::smart_key), "\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::smart_key), "\"abc def\""); + EXPECT_EQ(toml_parser::QuoteText(".abc", toml_parser::EQuoteRequest::smart_key), "\".abc\""); +#ifndef __GNUC__ + EXPECT_EQ(toml_parser::QuoteText(u8"µ", toml_parser::EQuoteRequest::smart_key), + "\"\\u00B5\""); // Only supported on Windows with MS compiler +#endif + EXPECT_EQ(toml_parser::QuoteText(u8"\u00a1", toml_parser::EQuoteRequest::smart_key), "\"\\u00A1\""); + EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::smart_key), "\"abc/\""); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteEscapeCharsKeys) +{ + EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::smart_key), "\"abc\\tdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::smart_key), "\"abc\\\\def\""); + EXPECT_EQ(toml_parser::QuoteText("\"abc\"", toml_parser::EQuoteRequest::smart_key), "\"\\\"abc\\\"\""); + EXPECT_EQ(toml_parser::QuoteText("'abc'", toml_parser::EQuoteRequest::smart_key), "\"'abc'\""); + EXPECT_EQ(toml_parser::QuoteText("abc\bdef", toml_parser::EQuoteRequest::smart_key), "\"abc\\bdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\ndef", toml_parser::EQuoteRequest::smart_key), "\"abc\\ndef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\fdef", toml_parser::EQuoteRequest::smart_key), "\"abc\\fdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::smart_key), "\"abc\\rdef\""); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteControlCharsKeys) +{ + EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::smart_key), "\"\\u0000\""); + EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::smart_key), "\"\\u0001\""); + EXPECT_EQ(toml_parser::QuoteText("\u0002", toml_parser::EQuoteRequest::smart_key), "\"\\u0002\""); + EXPECT_EQ(toml_parser::QuoteText("\u0003", toml_parser::EQuoteRequest::smart_key), "\"\\u0003\""); + EXPECT_EQ(toml_parser::QuoteText("\u0004", toml_parser::EQuoteRequest::smart_key), "\"\\u0004\""); + EXPECT_EQ(toml_parser::QuoteText("\u0005", toml_parser::EQuoteRequest::smart_key), "\"\\u0005\""); + EXPECT_EQ(toml_parser::QuoteText("\u0006", toml_parser::EQuoteRequest::smart_key), "\"\\u0006\""); + EXPECT_EQ(toml_parser::QuoteText("\u0007", toml_parser::EQuoteRequest::smart_key), "\"\\u0007\""); + // 0008 = backspace (\b), 0009 = tab (\t), 000a = linefeed (\n) + EXPECT_EQ(toml_parser::QuoteText("\u000b", toml_parser::EQuoteRequest::smart_key), "\"\\u000B\""); + // 000c = form feed (\f), 000d = carriage return (\r) + EXPECT_EQ(toml_parser::QuoteText("\u000e", toml_parser::EQuoteRequest::smart_key), "\"\\u000E\""); + EXPECT_EQ(toml_parser::QuoteText("\u000f", toml_parser::EQuoteRequest::smart_key), "\"\\u000F\""); + EXPECT_EQ(toml_parser::QuoteText("\u0010", toml_parser::EQuoteRequest::smart_key), "\"\\u0010\""); + EXPECT_EQ(toml_parser::QuoteText("\u0011", toml_parser::EQuoteRequest::smart_key), "\"\\u0011\""); + EXPECT_EQ(toml_parser::QuoteText("\u0012", toml_parser::EQuoteRequest::smart_key), "\"\\u0012\""); + EXPECT_EQ(toml_parser::QuoteText("\u0013", toml_parser::EQuoteRequest::smart_key), "\"\\u0013\""); + EXPECT_EQ(toml_parser::QuoteText("\u0014", toml_parser::EQuoteRequest::smart_key), "\"\\u0014\""); + EXPECT_EQ(toml_parser::QuoteText("\u0015", toml_parser::EQuoteRequest::smart_key), "\"\\u0015\""); + EXPECT_EQ(toml_parser::QuoteText("\u0016", toml_parser::EQuoteRequest::smart_key), "\"\\u0016\""); + EXPECT_EQ(toml_parser::QuoteText("\u0017", toml_parser::EQuoteRequest::smart_key), "\"\\u0017\""); + EXPECT_EQ(toml_parser::QuoteText("\u0018", toml_parser::EQuoteRequest::smart_key), "\"\\u0018\""); + EXPECT_EQ(toml_parser::QuoteText("\u0019", toml_parser::EQuoteRequest::smart_key), "\"\\u0019\""); + EXPECT_EQ(toml_parser::QuoteText("\u001a", toml_parser::EQuoteRequest::smart_key), "\"\\u001A\""); + EXPECT_EQ(toml_parser::QuoteText("\u001b", toml_parser::EQuoteRequest::smart_key), "\"\\u001B\""); + EXPECT_EQ(toml_parser::QuoteText("\u001c", toml_parser::EQuoteRequest::smart_key), "\"\\u001C\""); + EXPECT_EQ(toml_parser::QuoteText("\u001d", toml_parser::EQuoteRequest::smart_key), "\"\\u001D\""); + EXPECT_EQ(toml_parser::QuoteText("\u001e", toml_parser::EQuoteRequest::smart_key), "\"\\u001E\""); + EXPECT_EQ(toml_parser::QuoteText("\u001f", toml_parser::EQuoteRequest::smart_key), "\"\\u001F\""); + // 0020..0021 are treated as characters (partly quotation needed) + // 0022 = quote (\") + // 0023..005b are treated as characters (partly quotation needed) + // 005c = backslash (\\) + // 005d..007e are treated as characters (partly quotation needed) + EXPECT_EQ(toml_parser::QuoteText("\u007f", toml_parser::EQuoteRequest::smart_key), "\"\\u007F\""); + // 0080... and higher are treated as unicode character (quotation needed) +} + +TEST(TOMLMiscellaneousTests, SmartQuoteText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::smart_text), "\"abc\""); + EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::smart_text), "\"123\""); + EXPECT_EQ(toml_parser::QuoteText("ABC", toml_parser::EQuoteRequest::smart_text), "\"ABC\""); + EXPECT_EQ(toml_parser::QuoteText("abc_def", toml_parser::EQuoteRequest::smart_text), "\"abc_def\""); + EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::smart_text), "\"ABC-DEF\""); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteSpecialCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::smart_text), "\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::smart_text), "\"abc def\""); + EXPECT_EQ(toml_parser::QuoteText(".abc", toml_parser::EQuoteRequest::smart_text), "\".abc\""); +#ifndef __GNUC__ + EXPECT_EQ(toml_parser::QuoteText(u8"µ", toml_parser::EQuoteRequest::smart_text), + "\"\\u00B5\""); // Only supported on Windows with MS compiler +#endif + EXPECT_EQ(toml_parser::QuoteText(u8"\u00a1", toml_parser::EQuoteRequest::smart_text), "\"\\u00A1\""); + EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::smart_text), "\"abc/\""); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteEscapeCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::smart_text), "\"abc\\tdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::smart_text), "'abc\\def'"); + EXPECT_EQ(toml_parser::QuoteText("\"abc\"", toml_parser::EQuoteRequest::smart_text), "'\"abc\"'"); // becomes literal text + EXPECT_EQ(toml_parser::QuoteText("'abc'", toml_parser::EQuoteRequest::smart_text), "\"'abc'\""); // becomes literal text + EXPECT_EQ(toml_parser::QuoteText("abc\bdef", toml_parser::EQuoteRequest::smart_text), "\"abc\\bdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\ndef", toml_parser::EQuoteRequest::smart_text), "\"abc\\ndef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\fdef", toml_parser::EQuoteRequest::smart_text), "\"abc\\fdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::smart_text), "\"abc\\rdef\""); +} + +TEST(TOMLMiscellaneousTests, SmartQuoteControlCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::smart_text), "\"\\u0000\""); + EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::smart_text), "\"\\u0001\""); + EXPECT_EQ(toml_parser::QuoteText("\u0002", toml_parser::EQuoteRequest::smart_text), "\"\\u0002\""); + EXPECT_EQ(toml_parser::QuoteText("\u0003", toml_parser::EQuoteRequest::smart_text), "\"\\u0003\""); + EXPECT_EQ(toml_parser::QuoteText("\u0004", toml_parser::EQuoteRequest::smart_text), "\"\\u0004\""); + EXPECT_EQ(toml_parser::QuoteText("\u0005", toml_parser::EQuoteRequest::smart_text), "\"\\u0005\""); + EXPECT_EQ(toml_parser::QuoteText("\u0006", toml_parser::EQuoteRequest::smart_text), "\"\\u0006\""); + EXPECT_EQ(toml_parser::QuoteText("\u0007", toml_parser::EQuoteRequest::smart_text), "\"\\u0007\""); + // 0008 = backspace (\b), 0009 = tab (\t), 000a = linefeed (\n) + EXPECT_EQ(toml_parser::QuoteText("\u000b", toml_parser::EQuoteRequest::smart_text), "\"\\u000B\""); + // 000c = form feed (\f), 000d = carriage return (\r) + EXPECT_EQ(toml_parser::QuoteText("\u000e", toml_parser::EQuoteRequest::smart_text), "\"\\u000E\""); + EXPECT_EQ(toml_parser::QuoteText("\u000f", toml_parser::EQuoteRequest::smart_text), "\"\\u000F\""); + EXPECT_EQ(toml_parser::QuoteText("\u0010", toml_parser::EQuoteRequest::smart_text), "\"\\u0010\""); + EXPECT_EQ(toml_parser::QuoteText("\u0011", toml_parser::EQuoteRequest::smart_text), "\"\\u0011\""); + EXPECT_EQ(toml_parser::QuoteText("\u0012", toml_parser::EQuoteRequest::smart_text), "\"\\u0012\""); + EXPECT_EQ(toml_parser::QuoteText("\u0013", toml_parser::EQuoteRequest::smart_text), "\"\\u0013\""); + EXPECT_EQ(toml_parser::QuoteText("\u0014", toml_parser::EQuoteRequest::smart_text), "\"\\u0014\""); + EXPECT_EQ(toml_parser::QuoteText("\u0015", toml_parser::EQuoteRequest::smart_text), "\"\\u0015\""); + EXPECT_EQ(toml_parser::QuoteText("\u0016", toml_parser::EQuoteRequest::smart_text), "\"\\u0016\""); + EXPECT_EQ(toml_parser::QuoteText("\u0017", toml_parser::EQuoteRequest::smart_text), "\"\\u0017\""); + EXPECT_EQ(toml_parser::QuoteText("\u0018", toml_parser::EQuoteRequest::smart_text), "\"\\u0018\""); + EXPECT_EQ(toml_parser::QuoteText("\u0019", toml_parser::EQuoteRequest::smart_text), "\"\\u0019\""); + EXPECT_EQ(toml_parser::QuoteText("\u001a", toml_parser::EQuoteRequest::smart_text), "\"\\u001A\""); + EXPECT_EQ(toml_parser::QuoteText("\u001b", toml_parser::EQuoteRequest::smart_text), "\"\\u001B\""); + EXPECT_EQ(toml_parser::QuoteText("\u001c", toml_parser::EQuoteRequest::smart_text), "\"\\u001C\""); + EXPECT_EQ(toml_parser::QuoteText("\u001d", toml_parser::EQuoteRequest::smart_text), "\"\\u001D\""); + EXPECT_EQ(toml_parser::QuoteText("\u001e", toml_parser::EQuoteRequest::smart_text), "\"\\u001E\""); + EXPECT_EQ(toml_parser::QuoteText("\u001f", toml_parser::EQuoteRequest::smart_text), "\"\\u001F\""); + // 0020..0021 are treated as characters (partly quotation needed) + // 0022 = quote (\") + // 0023..005b are treated as characters (partly quotation needed) + // 005c = backslash (\\) + // 005d..007e are treated as characters (partly quotation needed) + EXPECT_EQ(toml_parser::QuoteText("\u007f", toml_parser::EQuoteRequest::smart_text), "\"\\u007F\""); + // 0080... and higher are treated as unicode character (quotation needed) +} + +TEST(TOMLMiscellaneousTests, QuotedText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::quoted_text), "\"abc\""); + EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::quoted_text), "\"123\""); + EXPECT_EQ(toml_parser::QuoteText("ABC", toml_parser::EQuoteRequest::quoted_text), "\"ABC\""); + EXPECT_EQ(toml_parser::QuoteText("abc_def", toml_parser::EQuoteRequest::quoted_text), "\"abc_def\""); + EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::quoted_text), "\"ABC-DEF\""); +} + +TEST(TOMLMiscellaneousTests, QuotedSpecialCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::quoted_text), "\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::quoted_text), "\"abc def\""); + EXPECT_EQ(toml_parser::QuoteText(".abc", toml_parser::EQuoteRequest::quoted_text), "\".abc\""); +#ifndef __GNUC__ + EXPECT_EQ(toml_parser::QuoteText(u8"µ", toml_parser::EQuoteRequest::quoted_text), + "\"\\u00B5\""); // Only supported on Windows with MS compiler +#endif + EXPECT_EQ(toml_parser::QuoteText(u8"\u00a1", toml_parser::EQuoteRequest::quoted_text), "\"\\u00A1\""); + EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::quoted_text), "\"abc/\""); +} + +TEST(TOMLMiscellaneousTests, QuotedEscapeCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\tdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::quoted_text), "\"abc\\\\def\""); + EXPECT_EQ(toml_parser::QuoteText("\"abc\"", toml_parser::EQuoteRequest::quoted_text), "\"\\\"abc\\\"\""); + EXPECT_EQ(toml_parser::QuoteText("'abc'", toml_parser::EQuoteRequest::quoted_text), "\"'abc'\""); + EXPECT_EQ(toml_parser::QuoteText("abc\bdef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\bdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\ndef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\ndef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\fdef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\fdef\""); + EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::quoted_text), "\"abc\\rdef\""); +} + +TEST(TOMLMiscellaneousTests, QuotedControlCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::quoted_text), "\"\\u0000\""); + EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::quoted_text), "\"\\u0001\""); + EXPECT_EQ(toml_parser::QuoteText("\u0002", toml_parser::EQuoteRequest::quoted_text), "\"\\u0002\""); + EXPECT_EQ(toml_parser::QuoteText("\u0003", toml_parser::EQuoteRequest::quoted_text), "\"\\u0003\""); + EXPECT_EQ(toml_parser::QuoteText("\u0004", toml_parser::EQuoteRequest::quoted_text), "\"\\u0004\""); + EXPECT_EQ(toml_parser::QuoteText("\u0005", toml_parser::EQuoteRequest::quoted_text), "\"\\u0005\""); + EXPECT_EQ(toml_parser::QuoteText("\u0006", toml_parser::EQuoteRequest::quoted_text), "\"\\u0006\""); + EXPECT_EQ(toml_parser::QuoteText("\u0007", toml_parser::EQuoteRequest::quoted_text), "\"\\u0007\""); + // 0008 = backspace (\b), 0009 = tab (\t), 000a = linefeed (\n) + EXPECT_EQ(toml_parser::QuoteText("\u000b", toml_parser::EQuoteRequest::quoted_text), "\"\\u000B\""); + // 000c = form feed (\f), 000d = carriage return (\r) + EXPECT_EQ(toml_parser::QuoteText("\u000e", toml_parser::EQuoteRequest::quoted_text), "\"\\u000E\""); + EXPECT_EQ(toml_parser::QuoteText("\u000f", toml_parser::EQuoteRequest::quoted_text), "\"\\u000F\""); + EXPECT_EQ(toml_parser::QuoteText("\u0010", toml_parser::EQuoteRequest::quoted_text), "\"\\u0010\""); + EXPECT_EQ(toml_parser::QuoteText("\u0011", toml_parser::EQuoteRequest::quoted_text), "\"\\u0011\""); + EXPECT_EQ(toml_parser::QuoteText("\u0012", toml_parser::EQuoteRequest::quoted_text), "\"\\u0012\""); + EXPECT_EQ(toml_parser::QuoteText("\u0013", toml_parser::EQuoteRequest::quoted_text), "\"\\u0013\""); + EXPECT_EQ(toml_parser::QuoteText("\u0014", toml_parser::EQuoteRequest::quoted_text), "\"\\u0014\""); + EXPECT_EQ(toml_parser::QuoteText("\u0015", toml_parser::EQuoteRequest::quoted_text), "\"\\u0015\""); + EXPECT_EQ(toml_parser::QuoteText("\u0016", toml_parser::EQuoteRequest::quoted_text), "\"\\u0016\""); + EXPECT_EQ(toml_parser::QuoteText("\u0017", toml_parser::EQuoteRequest::quoted_text), "\"\\u0017\""); + EXPECT_EQ(toml_parser::QuoteText("\u0018", toml_parser::EQuoteRequest::quoted_text), "\"\\u0018\""); + EXPECT_EQ(toml_parser::QuoteText("\u0019", toml_parser::EQuoteRequest::quoted_text), "\"\\u0019\""); + EXPECT_EQ(toml_parser::QuoteText("\u001a", toml_parser::EQuoteRequest::quoted_text), "\"\\u001A\""); + EXPECT_EQ(toml_parser::QuoteText("\u001b", toml_parser::EQuoteRequest::quoted_text), "\"\\u001B\""); + EXPECT_EQ(toml_parser::QuoteText("\u001c", toml_parser::EQuoteRequest::quoted_text), "\"\\u001C\""); + EXPECT_EQ(toml_parser::QuoteText("\u001d", toml_parser::EQuoteRequest::quoted_text), "\"\\u001D\""); + EXPECT_EQ(toml_parser::QuoteText("\u001e", toml_parser::EQuoteRequest::quoted_text), "\"\\u001E\""); + EXPECT_EQ(toml_parser::QuoteText("\u001f", toml_parser::EQuoteRequest::quoted_text), "\"\\u001F\""); + // 0020..0021 are treated as characters (partly quotation needed) + // 0022 = quote (\") + // 0023..005b are treated as characters (partly quotation needed) + // 005c = backslash (\\) + // 005d..007e are treated as characters (partly quotation needed) + EXPECT_EQ(toml_parser::QuoteText("\u007f", toml_parser::EQuoteRequest::quoted_text), "\"\\u007F\""); + // 0080... and higher are treated as unicode character (quotation needed) +} + +TEST(TOMLMiscellaneousTests, LiteralText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::literal_text), "'abc'"); + EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::literal_text), "'123'"); + EXPECT_EQ(toml_parser::QuoteText("ABC", toml_parser::EQuoteRequest::literal_text), "'ABC'"); + EXPECT_EQ(toml_parser::QuoteText("abc_def", toml_parser::EQuoteRequest::literal_text), "'abc_def'"); + EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::literal_text), "'ABC-DEF'"); +} + +TEST(TOMLMiscellaneousTests, LiteralSpecialCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::literal_text), "''"); + EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::literal_text), "'abc def'"); + EXPECT_EQ(toml_parser::QuoteText(".abc", toml_parser::EQuoteRequest::literal_text), "'.abc'"); +#ifndef __GNUC__ + EXPECT_EQ(toml_parser::QuoteText(u8"µ", toml_parser::EQuoteRequest::literal_text), + u8"'\u00B5'"); // Only supported on Windows with MS compiler +#endif + EXPECT_EQ(toml_parser::QuoteText(u8"\u00a1", toml_parser::EQuoteRequest::literal_text), u8"'\u00A1'"); + EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::literal_text), "'abc/'"); +} + +TEST(TOMLMiscellaneousTests, LiteralEscapeCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::literal_text), "\"abc\\tdef\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::literal_text), "'abc\\def'"); + EXPECT_EQ(toml_parser::QuoteText("\"abc\"", toml_parser::EQuoteRequest::literal_text), "'\"abc\"'"); + EXPECT_EQ(toml_parser::QuoteText("'abc'", toml_parser::EQuoteRequest::literal_text), "\"'abc'\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\bdef", toml_parser::EQuoteRequest::literal_text), "\"abc\\bdef\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\ndef", toml_parser::EQuoteRequest::literal_text), "\"abc\\ndef\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\fdef", toml_parser::EQuoteRequest::literal_text), "\"abc\\fdef\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::literal_text), "\"abc\\rdef\""); // Becomes quoted +} + +TEST(TOMLMiscellaneousTests, LiteralControlCharsText) +{ + // All become quoted insteda of literal (due to control character). + EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::literal_text), "\"\\u0000\""); + EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::literal_text), "\"\\u0001\""); + EXPECT_EQ(toml_parser::QuoteText("\u0002", toml_parser::EQuoteRequest::literal_text), "\"\\u0002\""); + EXPECT_EQ(toml_parser::QuoteText("\u0003", toml_parser::EQuoteRequest::literal_text), "\"\\u0003\""); + EXPECT_EQ(toml_parser::QuoteText("\u0004", toml_parser::EQuoteRequest::literal_text), "\"\\u0004\""); + EXPECT_EQ(toml_parser::QuoteText("\u0005", toml_parser::EQuoteRequest::literal_text), "\"\\u0005\""); + EXPECT_EQ(toml_parser::QuoteText("\u0006", toml_parser::EQuoteRequest::literal_text), "\"\\u0006\""); + EXPECT_EQ(toml_parser::QuoteText("\u0007", toml_parser::EQuoteRequest::literal_text), "\"\\u0007\""); + // 0008 = backspace (\b), 0009 = tab (\t), 000a = linefeed (\n) + EXPECT_EQ(toml_parser::QuoteText("\u000b", toml_parser::EQuoteRequest::literal_text), "\"\\u000B\""); + // 000c = form feed (\f), 000d = carriage return (\r) + EXPECT_EQ(toml_parser::QuoteText("\u000e", toml_parser::EQuoteRequest::literal_text), "\"\\u000E\""); + EXPECT_EQ(toml_parser::QuoteText("\u000f", toml_parser::EQuoteRequest::literal_text), "\"\\u000F\""); + EXPECT_EQ(toml_parser::QuoteText("\u0010", toml_parser::EQuoteRequest::literal_text), "\"\\u0010\""); + EXPECT_EQ(toml_parser::QuoteText("\u0011", toml_parser::EQuoteRequest::literal_text), "\"\\u0011\""); + EXPECT_EQ(toml_parser::QuoteText("\u0012", toml_parser::EQuoteRequest::literal_text), "\"\\u0012\""); + EXPECT_EQ(toml_parser::QuoteText("\u0013", toml_parser::EQuoteRequest::literal_text), "\"\\u0013\""); + EXPECT_EQ(toml_parser::QuoteText("\u0014", toml_parser::EQuoteRequest::literal_text), "\"\\u0014\""); + EXPECT_EQ(toml_parser::QuoteText("\u0015", toml_parser::EQuoteRequest::literal_text), "\"\\u0015\""); + EXPECT_EQ(toml_parser::QuoteText("\u0016", toml_parser::EQuoteRequest::literal_text), "\"\\u0016\""); + EXPECT_EQ(toml_parser::QuoteText("\u0017", toml_parser::EQuoteRequest::literal_text), "\"\\u0017\""); + EXPECT_EQ(toml_parser::QuoteText("\u0018", toml_parser::EQuoteRequest::literal_text), "\"\\u0018\""); + EXPECT_EQ(toml_parser::QuoteText("\u0019", toml_parser::EQuoteRequest::literal_text), "\"\\u0019\""); + EXPECT_EQ(toml_parser::QuoteText("\u001a", toml_parser::EQuoteRequest::literal_text), "\"\\u001A\""); + EXPECT_EQ(toml_parser::QuoteText("\u001b", toml_parser::EQuoteRequest::literal_text), "\"\\u001B\""); + EXPECT_EQ(toml_parser::QuoteText("\u001c", toml_parser::EQuoteRequest::literal_text), "\"\\u001C\""); + EXPECT_EQ(toml_parser::QuoteText("\u001d", toml_parser::EQuoteRequest::literal_text), "\"\\u001D\""); + EXPECT_EQ(toml_parser::QuoteText("\u001e", toml_parser::EQuoteRequest::literal_text), "\"\\u001E\""); + EXPECT_EQ(toml_parser::QuoteText("\u001f", toml_parser::EQuoteRequest::literal_text), "\"\\u001F\""); + // 0020..0021 are treated as characters (partly quotation needed) + // 0022 = quote (\") + // 0023..005b are treated as characters (partly quotation needed) + // 005c = backslash (\\) + // 005d..007e are treated as characters (partly quotation needed) + EXPECT_EQ(toml_parser::QuoteText("\u007f", toml_parser::EQuoteRequest::literal_text), "\"\\u007F\""); + // 0080... and higher are treated as unicode character (quotation needed) +} + +TEST(TOMLMiscellaneousTests, MultiLineQuotedText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"123\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("ABC", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"ABC\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc_def", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc_def\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"ABC-DEF\"\"\""); +} + +TEST(TOMLMiscellaneousTests, MultiLineQuotedSpecialCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc def\"\"\""); + EXPECT_EQ(toml_parser::QuoteText(".abc", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\".abc\"\"\""); +#ifndef __GNUC__ + EXPECT_EQ(toml_parser::QuoteText(u8"µ", toml_parser::EQuoteRequest::multi_line_quoted_text), + "\"\"\"\\u00B5\"\"\""); // Only supported on Windows with MS compiler +#endif + EXPECT_EQ(toml_parser::QuoteText(u8"\u00a1", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u00A1\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc/\"\"\""); +} + +TEST(TOMLMiscellaneousTests, MultiLineQuotedEscapeCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\\tdef\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\\\\def\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\"abc\"", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\\"abc\\\"\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("'abc'", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"'abc'\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc\bdef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\\bdef\"\"\""); + // Multi line + EXPECT_EQ(toml_parser::QuoteText("abc\ndef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\ndef\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("abc\fdef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\\fdef\"\"\""); + // Multi line + EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"abc\rdef\"\"\""); +} + +TEST(TOMLMiscellaneousTests, MultiLineQuotedControlCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0000\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0001\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0002", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0002\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0003", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0003\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0004", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0004\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0005", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0005\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0006", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0006\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0007", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0007\"\"\""); + // 0008 = backspace (\b), 0009 = tab (\t), 000a = linefeed (\n) + EXPECT_EQ(toml_parser::QuoteText("\u000b", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u000B\"\"\""); + // 000c = form feed (\f), 000d = carriage return (\r) + EXPECT_EQ(toml_parser::QuoteText("\u000e", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u000E\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u000f", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u000F\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0010", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0010\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0011", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0011\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0012", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0012\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0013", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0013\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0014", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0014\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0015", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0015\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0016", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0016\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0017", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0017\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0018", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0018\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0019", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u0019\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001a", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u001A\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001b", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u001B\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001c", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u001C\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001d", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u001D\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001e", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u001E\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001f", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u001F\"\"\""); + // 0020..0021 are treated as characters (partly quotation needed) + // 0022 = quote (\") + // 0023..005b are treated as characters (partly quotation needed) + // 005c = backslash (\\) + // 005d..007e are treated as characters (partly quotation needed) + EXPECT_EQ(toml_parser::QuoteText("\u007f", toml_parser::EQuoteRequest::multi_line_quoted_text), "\"\"\"\\u007F\"\"\""); + // 0080... and higher are treated as unicode character (quotation needed) +} + +TEST(TOMLMiscellaneousTests, MultiLineLiteralText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc'''"); + EXPECT_EQ(toml_parser::QuoteText("123", toml_parser::EQuoteRequest::multi_line_literal_text), "'''123'''"); + EXPECT_EQ(toml_parser::QuoteText("ABC", toml_parser::EQuoteRequest::multi_line_literal_text), "'''ABC'''"); + EXPECT_EQ(toml_parser::QuoteText("abc_def", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc_def'''"); + EXPECT_EQ(toml_parser::QuoteText("ABC-DEF", toml_parser::EQuoteRequest::multi_line_literal_text), "'''ABC-DEF'''"); +} + +TEST(TOMLMiscellaneousTests, MultiLineLiteralSpecialCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("", toml_parser::EQuoteRequest::multi_line_literal_text), "''''''"); + EXPECT_EQ(toml_parser::QuoteText("abc def", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc def'''"); + EXPECT_EQ(toml_parser::QuoteText(".abc", toml_parser::EQuoteRequest::multi_line_literal_text), "'''.abc'''"); +#ifndef __GNUC__ + EXPECT_EQ(toml_parser::QuoteText(u8"µ", toml_parser::EQuoteRequest::multi_line_literal_text), + u8"'''\u00B5'''"); // Only supported on Windows with MS compiler +#endif + EXPECT_EQ(toml_parser::QuoteText(u8"\u00a1", toml_parser::EQuoteRequest::multi_line_literal_text), u8"'''\u00A1'''"); + EXPECT_EQ(toml_parser::QuoteText("abc/", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc/'''"); +} + +TEST(TOMLMiscellaneousTests, MultiLineLiteralEscapeCharsText) +{ + EXPECT_EQ(toml_parser::QuoteText("abc\tdef", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"abc\\tdef\"\"\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\\def", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc\\def'''"); + EXPECT_EQ(toml_parser::QuoteText("\"abc\"", toml_parser::EQuoteRequest::multi_line_literal_text), "'''\"abc\"'''"); + EXPECT_EQ(toml_parser::QuoteText("'abc'", toml_parser::EQuoteRequest::multi_line_literal_text), "''''abc''''"); + EXPECT_EQ( + toml_parser::QuoteText("abc\bdef", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"abc\\bdef\"\"\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\ndef", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc\ndef'''"); + EXPECT_EQ(toml_parser::QuoteText("abc\fdef", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"abc\\fdef\"\"\""); // Becomes quoted + EXPECT_EQ(toml_parser::QuoteText("abc\rdef", toml_parser::EQuoteRequest::multi_line_literal_text), "'''abc\rdef'''"); +} + +TEST(TOMLMiscellaneousTests, MultiLineLiteralControlCharsText) +{ + // All become quoted insteda of literal (due to control character). + EXPECT_EQ(toml_parser::QuoteText(std::string(1, '\0'), toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0000\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0001", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0001\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0002", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0002\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0003", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0003\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0004", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0004\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0005", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0005\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0006", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0006\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0007", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0007\"\"\""); + // 0008 = backspace (\b), 0009 = tab (\t), 000a = linefeed (\n) + EXPECT_EQ(toml_parser::QuoteText("\u000b", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u000B\"\"\""); + // 000c = form feed (\f), 000d = carriage return (\r) + EXPECT_EQ(toml_parser::QuoteText("\u000e", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u000E\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u000f", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u000F\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0010", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0010\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0011", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0011\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0012", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0012\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0013", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0013\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0014", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0014\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0015", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0015\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0016", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0016\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0017", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0017\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0018", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0018\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u0019", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u0019\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001a", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u001A\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001b", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u001B\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001c", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u001C\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001d", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u001D\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001e", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u001E\"\"\""); + EXPECT_EQ(toml_parser::QuoteText("\u001f", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u001F\"\"\""); + // 0020..0021 are treated as characters (partly quotation needed) + // 0022 = quote (\") + // 0023..005b are treated as characters (partly quotation needed) + // 005c = backslash (\\) + // 005d..007e are treated as characters (partly quotation needed) + EXPECT_EQ(toml_parser::QuoteText("\u007f", toml_parser::EQuoteRequest::multi_line_literal_text), "\"\"\"\\u007F\"\"\""); + // 0080... and higher are treated as unicode character (quotation needed) +} + +TEST(TOMLMiscellaneousTests, ExtractBareKeyName) +{ + EXPECT_EQ(toml_parser::ExtractKeyName(""), ""); + EXPECT_EQ(toml_parser::ExtractKeyName("abc.def"), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc"), "abc"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc.def.ghi"), "ghi"); + EXPECT_EQ(toml_parser::ExtractKeyName("_abc.def"), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("_abc._def"), "_def"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc-.def"), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc-.def-"), "def-"); + EXPECT_EQ(toml_parser::ExtractKeyName("1234"), "1234"); +} + +TEST(TOMLMiscellaneousTests, ExtractQuotedKeyName) +{ + EXPECT_EQ(toml_parser::ExtractKeyName("\"\""), ""); + EXPECT_EQ(toml_parser::ExtractKeyName("\"abc\""), "abc"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc.\"def\""), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("\"abc.def\""), "abc.def"); + EXPECT_EQ(toml_parser::ExtractKeyName("\"abc\\tdef\""), "abc\tdef"); + EXPECT_EQ(toml_parser::ExtractKeyName("\"'This' is a \\\"very\\\" quoted key!\""), "'This' is a \"very\" quoted key!"); + EXPECT_EQ(toml_parser::ExtractKeyName("\"[1]\""), "[1]"); + EXPECT_EQ(toml_parser::ExtractKeyName("\"abc"), ""); // Failure + EXPECT_EQ(toml_parser::ExtractKeyName("abc\"def\""), ""); // Failure +} + +TEST(TOMLMiscellaneousTests, ExtractLiteralKeyName) +{ + EXPECT_EQ(toml_parser::ExtractKeyName("''"), ""); + EXPECT_EQ(toml_parser::ExtractKeyName("'abc'"), "abc"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc.'def'"), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("'abc.def'"), "abc.def"); + EXPECT_EQ(toml_parser::ExtractKeyName("'abc def'"), "abc def"); + EXPECT_EQ(toml_parser::ExtractKeyName("'abc\\tdef'"), "abc\\tdef"); // No escape uences supported + EXPECT_EQ(toml_parser::ExtractKeyName("'This is a \"very\" literal key!'"), "This is a \"very\" literal key!"); + EXPECT_EQ(toml_parser::ExtractKeyName("'[1]'"), "[1]"); + EXPECT_EQ(toml_parser::ExtractKeyName("'abc"), ""); + EXPECT_EQ(toml_parser::ExtractKeyName("abc'def"), ""); // Failure +} + +TEST(TOMLMiscellaneousTests, ExtractIndexKeyName) +{ + EXPECT_EQ(toml_parser::ExtractKeyName("[0]"), "0"); + EXPECT_EQ(toml_parser::ExtractKeyName("[0][1]"), "1"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc[0][1]"), "1"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc[0].def"), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc[0].\"def\""), "def"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc[0].def[1]"), "1"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc[0].\"def\"[1]"), "1"); + EXPECT_EQ(toml_parser::ExtractKeyName("abc[0]def"), ""); // Failure +} diff --git a/tests/unit_tests/toml_parser/parser_tests.cpp b/tests/unit_tests/toml_parser/parser_tests.cpp index 51006b3..818c3a8 100644 --- a/tests/unit_tests/toml_parser/parser_tests.cpp +++ b/tests/unit_tests/toml_parser/parser_tests.cpp @@ -2,34 +2,42 @@ #include "../../../sdv_services/core/toml_parser/parser_toml.h" #include "../../../sdv_services/core/toml_parser/parser_node_toml.h" -/* Requirements TOML CParserTOML +/* Requirements TOML toml_parser::CParser * - The output after parsing is a tree structure only if parsing is successful * - No recovery will be attempted when syntax errors occur * - all errors and features (except Date-Time) as defined at https://toml.io/en/v1.0.0 have to result in an invalid or valid - * outcome respectively + * outcome respectively */ +TEST(RecognizeTypes, Root) +{ + toml_parser::CParser parser(""); + auto& rroot = parser.Root(); + EXPECT_EQ(rroot.GetParent(), nullptr); + EXPECT_EQ(rroot.GetIndex(), sdv::toml::npos); +} + TEST(RecognizeTypes, Table) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( [newTable] [secondTable.nestedTable] )"s); - auto table1 = parser.GetRoot().GetDirect("newTable"); + auto table1 = parser.Root().Direct("newTable"); EXPECT_EQ(table1->GetType(), sdv::toml::ENodeType::node_table); EXPECT_EQ(table1->GetName(), "newTable"); EXPECT_EQ(table1->GetValue(), sdv::any_t()); EXPECT_NE(static_cast(table1.get())->GetInterface(), nullptr); - auto table2 = parser.GetRoot().GetDirect("secondTable"); + auto table2 = parser.Root().Direct("secondTable"); EXPECT_EQ(table2->GetType(), sdv::toml::ENodeType::node_table); EXPECT_EQ(table2->GetName(), "secondTable"); EXPECT_EQ(table2->GetValue(), sdv::any_t()); EXPECT_NE(((sdv::IInterfaceAccess*) table2.get())->GetInterface(), nullptr); - auto table3 = parser.GetRoot().GetDirect("secondTable.nestedTable"); + auto table3 = parser.Root().Direct("secondTable.nestedTable"); EXPECT_EQ(table3->GetType(), sdv::toml::ENodeType::node_table); EXPECT_EQ(table3->GetName(), "nestedTable"); EXPECT_EQ(table3->GetValue(), sdv::any_t()); @@ -39,7 +47,7 @@ TEST(RecognizeTypes, Table) TEST(RecognizeTypes, Key_Value) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( name = "Hammer" id = 42 pi = 3.1415926 @@ -49,41 +57,47 @@ TEST(RecognizeTypes, Key_Value) )"s); - auto value_name = parser.GetRoot().GetDirect("name"); + auto value_name = parser.Root().Direct("name"); EXPECT_EQ(value_name->GetType(), sdv::toml::ENodeType::node_string); EXPECT_EQ(value_name->GetValue(), sdv::any_t("Hammer")); + EXPECT_EQ(value_name->GetIndex(), 0u); - auto value_id = parser.GetRoot().GetDirect("id"); + auto value_id = parser.Root().Direct("id"); EXPECT_EQ(value_id->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(value_id->GetValue(), 42); + EXPECT_EQ(value_id->GetIndex(), 1u); - auto value_pi = parser.GetRoot().GetDirect("pi"); + auto value_pi = parser.Root().Direct("pi"); EXPECT_EQ(value_pi->GetType(), sdv::toml::ENodeType::node_floating_point); EXPECT_EQ(value_pi->GetValue(), 3.1415926); + EXPECT_EQ(value_pi->GetIndex(), 2u); - auto value_boolean = parser.GetRoot().GetDirect("boolean"); + auto value_boolean = parser.Root().Direct("boolean"); EXPECT_EQ(value_boolean->GetType(), sdv::toml::ENodeType::node_boolean); EXPECT_EQ(value_boolean->GetValue(), true); + EXPECT_EQ(value_boolean->GetIndex(), 3u); - auto value_array = parser.GetRoot().GetDirect("array"); + auto value_array = parser.Root().Direct("array"); EXPECT_EQ(value_array->GetType(), sdv::toml::ENodeType::node_array); EXPECT_EQ(value_array->GetValue(), sdv::any_t()); + EXPECT_EQ(value_array->GetIndex(), 4u); - auto value_table = parser.GetRoot().GetDirect("table"); + auto value_table = parser.Root().Direct("table"); EXPECT_EQ(value_table->GetType(), sdv::toml::ENodeType::node_table); EXPECT_EQ(value_table->GetValue(), sdv::any_t()); + EXPECT_EQ(value_table->GetIndex(), 5u); } TEST(RecognizeTypes, TableArray) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( [[newTableArray]] [[newTableArray]] [[table.nestedTableArray]] )"s); - auto tableArray1 = parser.GetRoot().GetDirect("newTableArray"); + auto tableArray1 = parser.Root().Direct("newTableArray"); EXPECT_EQ(tableArray1->GetType(), sdv::toml::ENodeType::node_array); EXPECT_NE(static_cast(tableArray1.get())->GetInterface(), nullptr); EXPECT_EQ(tableArray1->GetName(), "newTableArray"); @@ -97,21 +111,23 @@ TEST(RecognizeTypes, TableArray) EXPECT_NE(pTableNode1, nullptr); EXPECT_EQ(pArrayCollection->GetNode(2), nullptr); - auto table1 = parser.GetRoot().GetDirect("newTableArray[0]"); + auto table1 = parser.Root().Direct("newTableArray[0]"); + ASSERT_TRUE(table1); EXPECT_EQ(table1->GetType(), sdv::toml::ENodeType::node_table); EXPECT_EQ(static_cast(table1.get()), pTableNode0); - EXPECT_TRUE(table1->GetName().empty()); + EXPECT_EQ(table1->GetName(), "newTableArray"); - auto table2 = parser.GetRoot().GetDirect("newTableArray[1]"); + auto table2 = parser.Root().Direct("newTableArray[1]"); + ASSERT_TRUE(table2); EXPECT_EQ(static_cast(table2.get()), pTableNode1); EXPECT_EQ(table2->GetType(), sdv::toml::ENodeType::node_table); - EXPECT_TRUE(table2->GetName().empty()); + EXPECT_EQ(table2->GetName(), "newTableArray"); } TEST(NestedContent, Array) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( arr_mixed = [ 1.0, 2, "test string", [ 1, 2 ], { pi = 3.14, e = 2.71828 }, true] arr_ints = [ 1, 2, 3, 4] arr_ints_trailing_comma = [ 1, 2, 3, 4, ] @@ -124,7 +140,7 @@ TEST(NestedContent, Array) { - auto array_ints = parser.GetRoot().GetDirect("arr_ints"); + auto array_ints = parser.Root().Direct("arr_ints"); EXPECT_EQ(array_ints->GetType(), sdv::toml::ENodeType::node_array); sdv::toml::INodeCollection* pIntArrayCollection = static_cast(array_ints.get())->GetInterface(); @@ -135,104 +151,131 @@ TEST(NestedContent, Array) EXPECT_NE(pIntArrayCollection->GetNode(2), nullptr); EXPECT_NE(pIntArrayCollection->GetNode(3), nullptr); EXPECT_EQ(pIntArrayCollection->GetNode(4), nullptr); - auto array_ints_0 = parser.GetRoot().GetDirect("arr_ints[0]"); + auto array_ints_0 = parser.Root().Direct("arr_ints[0]"); ASSERT_NE(array_ints_0, nullptr); EXPECT_EQ(array_ints_0->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_0->GetValue(), 1); - auto array_ints_1 = parser.GetRoot().GetDirect("arr_ints[1]"); + EXPECT_EQ(array_ints_0->GetIndex(), 0u); + auto array_ints_1 = parser.Root().Direct("arr_ints[1]"); ASSERT_NE(array_ints_1, nullptr); EXPECT_EQ(array_ints_1->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_1->GetValue(), 2); - auto array_ints_2 = parser.GetRoot().GetDirect("arr_ints[2]"); + EXPECT_EQ(array_ints_1->GetIndex(), 1u); + auto array_ints_2 = parser.Root().Direct("arr_ints[2]"); ASSERT_NE(array_ints_2, nullptr); EXPECT_EQ(array_ints_2->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_2->GetValue(), 3); - auto array_ints_3 = parser.GetRoot().GetDirect("arr_ints[3]"); + EXPECT_EQ(array_ints_2->GetIndex(), 2u); + auto array_ints_3 = parser.Root().Direct("arr_ints[3]"); ASSERT_NE(array_ints_3, nullptr); EXPECT_EQ(array_ints_3->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_3->GetValue(), 4); - auto array_ints_4 = parser.GetRoot().GetDirect("arr_ints[4]"); + EXPECT_EQ(array_ints_3->GetIndex(), 3u); + auto array_ints_4 = parser.Root().Direct("arr_ints[4]"); EXPECT_EQ(array_ints_4, nullptr); } { - auto array_ints_trailing_comma = parser.GetRoot().GetDirect("arr_ints_trailing_comma"); - auto array_ints_trailing_comma_0 = parser.GetRoot().GetDirect("arr_ints_trailing_comma[0]"); - auto array_ints_trailing_comma_1 = parser.GetRoot().GetDirect("arr_ints_trailing_comma[1]"); - auto array_ints_trailing_comma_2 = parser.GetRoot().GetDirect("arr_ints_trailing_comma[2]"); - auto array_ints_trailing_comma_3 = parser.GetRoot().GetDirect("arr_ints_trailing_comma[3]"); - auto array_ints_trailing_comma_4 = parser.GetRoot().GetDirect("arr_ints_trailing_comma[4]"); + auto array_ints_trailing_comma = parser.Root().Direct("arr_ints_trailing_comma"); + auto array_ints_trailing_comma_0 = parser.Root().Direct("arr_ints_trailing_comma[0]"); + auto array_ints_trailing_comma_1 = parser.Root().Direct("arr_ints_trailing_comma[1]"); + auto array_ints_trailing_comma_2 = parser.Root().Direct("arr_ints_trailing_comma[2]"); + auto array_ints_trailing_comma_3 = parser.Root().Direct("arr_ints_trailing_comma[3]"); + auto array_ints_trailing_comma_4 = parser.Root().Direct("arr_ints_trailing_comma[4]"); EXPECT_EQ(array_ints_trailing_comma->GetType(), sdv::toml::ENodeType::node_array); ASSERT_NE(array_ints_trailing_comma_0, nullptr); EXPECT_EQ(array_ints_trailing_comma_0->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_trailing_comma_0->GetValue(), 1); + EXPECT_EQ(array_ints_trailing_comma_0->GetIndex(), 0u); ASSERT_NE(array_ints_trailing_comma_1, nullptr); EXPECT_EQ(array_ints_trailing_comma_1->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_trailing_comma_1->GetValue(), 2); + EXPECT_EQ(array_ints_trailing_comma_1->GetIndex(), 1u); ASSERT_NE(array_ints_trailing_comma_2, nullptr); EXPECT_EQ(array_ints_trailing_comma_2->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_trailing_comma_2->GetValue(), 3); + EXPECT_EQ(array_ints_trailing_comma_2->GetIndex(), 2u); ASSERT_NE(array_ints_trailing_comma_3, nullptr); EXPECT_EQ(array_ints_trailing_comma_3->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_ints_trailing_comma_3->GetValue(), 4); + EXPECT_EQ(array_ints_trailing_comma_3->GetIndex(), 3u); EXPECT_EQ(array_ints_trailing_comma_4, nullptr); } { - auto array_mixed = parser.GetRoot().GetDirect("arr_mixed"); - auto array_mixed_0 = parser.GetRoot().GetDirect("arr_mixed[0]"); - auto array_mixed_1 = parser.GetRoot().GetDirect("arr_mixed[1]"); - auto array_mixed_2 = parser.GetRoot().GetDirect("arr_mixed[2]"); - auto array_mixed_3 = parser.GetRoot().GetDirect("arr_mixed[3]"); - auto array_mixed_3_2 = parser.GetRoot().GetDirect("arr_mixed[3][1]"); - auto array_mixed_4 = parser.GetRoot().GetDirect("arr_mixed[4]"); - auto array_mixed_4_pi = parser.GetRoot().GetDirect("arr_mixed[4].pi"); - auto array_mixed_5 = parser.GetRoot().GetDirect("arr_mixed[5]"); - auto array_mixed_6 = parser.GetRoot().GetDirect("arr_mixed[6]"); + auto array_mixed = parser.Root().Direct("arr_mixed"); + auto array_mixed_0 = parser.Root().Direct("arr_mixed[0]"); + auto array_mixed_1 = parser.Root().Direct("arr_mixed[1]"); + auto array_mixed_2 = parser.Root().Direct("arr_mixed[2]"); + auto array_mixed_3 = parser.Root().Direct("arr_mixed[3]"); + auto array_mixed_3_1 = parser.Root().Direct("arr_mixed[3][0]"); + auto array_mixed_3_2 = parser.Root().Direct("arr_mixed[3][1]"); + auto array_mixed_4 = parser.Root().Direct("arr_mixed[4]"); + auto array_mixed_4_pi = parser.Root().Direct("arr_mixed[4].pi"); + auto array_mixed_4_e = parser.Root().Direct("arr_mixed[4].e"); + auto array_mixed_5 = parser.Root().Direct("arr_mixed[5]"); + auto array_mixed_6 = parser.Root().Direct("arr_mixed[6]"); EXPECT_EQ(array_mixed->GetType(), sdv::toml::ENodeType::node_array); ASSERT_NE(array_mixed_0, nullptr); EXPECT_EQ(array_mixed_0->GetType(), sdv::toml::ENodeType::node_floating_point); EXPECT_EQ(array_mixed_0->GetValue(), 1.0); + EXPECT_EQ(array_mixed_0->GetIndex(), 0u); ASSERT_NE(array_mixed_1, nullptr); EXPECT_EQ(array_mixed_1->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_mixed_1->GetValue(), 2); + EXPECT_EQ(array_mixed_1->GetIndex(), 1u); ASSERT_NE(array_mixed_2, nullptr); EXPECT_EQ(array_mixed_2->GetType(), sdv::toml::ENodeType::node_string); EXPECT_EQ(static_cast(array_mixed_2->GetValue()), "test string"); + EXPECT_EQ(array_mixed_2->GetIndex(), 2u); ASSERT_NE(array_mixed_3, nullptr); EXPECT_EQ(array_mixed_3->GetType(), sdv::toml::ENodeType::node_array); + EXPECT_EQ(array_mixed_3->GetIndex(), 3u); + EXPECT_EQ(array_mixed_3_1->GetType(), sdv::toml::ENodeType::node_integer); + EXPECT_EQ(array_mixed_3_1->GetValue(), 1); + EXPECT_EQ(array_mixed_3_1->GetIndex(), 0u); EXPECT_EQ(array_mixed_3_2->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(array_mixed_3_2->GetValue(), 2); + EXPECT_EQ(array_mixed_3_2->GetIndex(), 1u); ASSERT_NE(array_mixed_4, nullptr); EXPECT_EQ(array_mixed_4->GetType(), sdv::toml::ENodeType::node_table); + EXPECT_EQ(array_mixed_4->GetIndex(), 4u); EXPECT_EQ(array_mixed_4_pi->GetType(), sdv::toml::ENodeType::node_floating_point); EXPECT_EQ(array_mixed_4_pi->GetValue(), 3.14); + EXPECT_EQ(array_mixed_4_pi->GetIndex(), 0u); + EXPECT_EQ(array_mixed_4_e->GetType(), sdv::toml::ENodeType::node_floating_point); + EXPECT_EQ(array_mixed_4_e->GetValue(), 2.71828); + EXPECT_EQ(array_mixed_4_e->GetIndex(), 1u); ASSERT_NE(array_mixed_5, nullptr); EXPECT_EQ(array_mixed_5->GetType(), sdv::toml::ENodeType::node_boolean); EXPECT_EQ(array_mixed_5->GetValue(), true); EXPECT_EQ(array_mixed_5->GetValue(), sdv::any_t()); + EXPECT_EQ(array_mixed_5->GetIndex(), 5u); EXPECT_EQ(array_mixed_6, nullptr); } { - auto array_multiline = parser.GetRoot().GetDirect("arr_multiline"); - auto array_multiline_0 = parser.GetRoot().GetDirect("arr_multiline[0]"); - auto array_multiline_1 = parser.GetRoot().GetDirect("arr_multiline[1]"); - auto array_multiline_2 = parser.GetRoot().GetDirect("arr_multiline[2]"); - auto array_multiline_3 = parser.GetRoot().GetDirect("arr_multiline[3]"); + auto array_multiline = parser.Root().Direct("arr_multiline"); + auto array_multiline_0 = parser.Root().Direct("arr_multiline[0]"); + auto array_multiline_1 = parser.Root().Direct("arr_multiline[1]"); + auto array_multiline_2 = parser.Root().Direct("arr_multiline[2]"); + auto array_multiline_3 = parser.Root().Direct("arr_multiline[3]"); EXPECT_EQ(array_multiline->GetType(), sdv::toml::ENodeType::node_array); ASSERT_NE(array_multiline_0, nullptr); EXPECT_EQ(array_multiline_0->GetType(), sdv::toml::ENodeType::node_string); EXPECT_EQ(static_cast(array_multiline_0->GetValue()), "first line"); + EXPECT_EQ(array_multiline_0->GetIndex(), 0u); ASSERT_NE(array_multiline_1, nullptr); EXPECT_EQ(array_multiline_1->GetType(), sdv::toml::ENodeType::node_string); EXPECT_EQ(static_cast(array_multiline_1->GetValue()), "second line"); + EXPECT_EQ(array_multiline_1->GetIndex(), 1u); ASSERT_NE(array_multiline_2, nullptr); EXPECT_EQ(array_multiline_2->GetType(), sdv::toml::ENodeType::node_string); EXPECT_EQ(static_cast(array_multiline_2->GetValue()), "third_line"); + EXPECT_EQ(array_multiline_2->GetIndex(), 2u); EXPECT_EQ(array_multiline_3, nullptr); } } @@ -240,7 +283,7 @@ TEST(NestedContent, Array) TEST(NestedContent, Table) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( [table] a = 2 b = 1.2 @@ -252,12 +295,12 @@ TEST(NestedContent, Table) d = [] )"s); - auto table_a = parser.GetRoot().GetDirect("table.a"); - auto table_b = parser.GetRoot().GetDirect("table.b"); - auto anotherTable_a = parser.GetRoot().GetDirect("anotherTable.a"); - auto anotherTable_c = parser.GetRoot().GetDirect("anotherTable.c"); - auto fourthTable_a = parser.GetRoot().GetDirect("thirdTable.fourthTable.a"); - auto fourthTable_d = parser.GetRoot().GetDirect("thirdTable.fourthTable.d"); + auto table_a = parser.Root().Direct("table.a"); + auto table_b = parser.Root().Direct("table.b"); + auto anotherTable_a = parser.Root().Direct("anotherTable.a"); + auto anotherTable_c = parser.Root().Direct("anotherTable.c"); + auto fourthTable_a = parser.Root().Direct("thirdTable.fourthTable.a"); + auto fourthTable_d = parser.Root().Direct("thirdTable.fourthTable.d"); ASSERT_NE(table_a, nullptr); EXPECT_EQ(table_a->GetType(), sdv::toml::ENodeType::node_integer); @@ -281,7 +324,7 @@ TEST(NestedContent, Table) TEST(NestedContent, TableArray) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( [[table.test]] a = 2 b = 1.2 @@ -293,53 +336,59 @@ TEST(NestedContent, TableArray) d = [] )"s); - auto table_test_1_a = parser.GetRoot().GetDirect("table.test[0].a"); - auto table_test_1_b = parser.GetRoot().GetDirect("table.test[0].b"); - auto table_test_2_a = parser.GetRoot().GetDirect("table.test[1].a"); - auto table_test_2_c = parser.GetRoot().GetDirect("table.test[1].c"); - auto table_test_3_a = parser.GetRoot().GetDirect("table.test[2].a"); - auto table_test_3_d = parser.GetRoot().GetDirect("table.test[2].d"); + auto table_test_1_a = parser.Root().Direct("table.test[0].a"); + auto table_test_1_b = parser.Root().Direct("table.test[0].b"); + auto table_test_2_a = parser.Root().Direct("table.test[1].a"); + auto table_test_2_c = parser.Root().Direct("table.test[1].c"); + auto table_test_3_a = parser.Root().Direct("table.test[2].a"); + auto table_test_3_d = parser.Root().Direct("table.test[2].d"); ASSERT_NE(table_test_1_a, nullptr); EXPECT_EQ(table_test_1_a->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(table_test_1_a->GetValue(), 2); + EXPECT_EQ(table_test_1_a->GetIndex(), 0u); ASSERT_NE(table_test_1_b, nullptr); EXPECT_EQ(table_test_1_b->GetType(), sdv::toml::ENodeType::node_floating_point); EXPECT_EQ(table_test_1_b->GetValue(), 1.2); + EXPECT_EQ(table_test_1_b->GetIndex(), 1u); ASSERT_NE(table_test_2_a, nullptr); EXPECT_EQ(table_test_2_a->GetType(), sdv::toml::ENodeType::node_integer); EXPECT_EQ(table_test_2_a->GetValue(), 4); + EXPECT_EQ(table_test_2_a->GetIndex(), 0u); ASSERT_NE(table_test_2_c, nullptr); EXPECT_EQ(table_test_2_c->GetType(), sdv::toml::ENodeType::node_boolean); EXPECT_EQ(table_test_2_c->GetValue(), false); + EXPECT_EQ(table_test_2_c->GetIndex(), 1u); ASSERT_NE(table_test_3_a, nullptr); EXPECT_EQ(table_test_3_a->GetType(), sdv::toml::ENodeType::node_string); EXPECT_EQ(static_cast(table_test_3_a->GetValue()), "five"); + EXPECT_EQ(table_test_3_a->GetIndex(), 0u); ASSERT_NE(table_test_3_d, nullptr); EXPECT_EQ(table_test_3_d->GetType(), sdv::toml::ENodeType::node_array); + EXPECT_EQ(table_test_3_d->GetIndex(), 1u); } TEST(NestedContent, InlineTable) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( table1 = { a = 0, b = 1.2, c = "string" } table2 = { a = [], b = true, e = 2.71828 } table3 = { a = { a = "a", b = "A" }, b = {a = "b", b = "B"}, e = {a = "e", b = "E"} } )"s); - auto table1_a = parser.GetRoot().GetDirect("table1.a"); - auto table1_b = parser.GetRoot().GetDirect("table1.b"); - auto table1_c = parser.GetRoot().GetDirect("table1.c"); - auto table2_a = parser.GetRoot().GetDirect("table2.a"); - auto table2_b = parser.GetRoot().GetDirect("table2.b"); - auto table2_e = parser.GetRoot().GetDirect("table2.e"); - auto table3_a_a = parser.GetRoot().GetDirect("table3.a.a"); - auto table3_a_b = parser.GetRoot().GetDirect("table3.a.b"); - auto table3_b_a = parser.GetRoot().GetDirect("table3.b.a"); - auto table3_b_b = parser.GetRoot().GetDirect("table3.b.b"); - auto table3_e_a = parser.GetRoot().GetDirect("table3.e.a"); - auto table3_e_b = parser.GetRoot().GetDirect("table3.e.b"); + auto table1_a = parser.Root().Direct("table1.a"); + auto table1_b = parser.Root().Direct("table1.b"); + auto table1_c = parser.Root().Direct("table1.c"); + auto table2_a = parser.Root().Direct("table2.a"); + auto table2_b = parser.Root().Direct("table2.b"); + auto table2_e = parser.Root().Direct("table2.e"); + auto table3_a_a = parser.Root().Direct("table3.a.a"); + auto table3_a_b = parser.Root().Direct("table3.a.b"); + auto table3_b_a = parser.Root().Direct("table3.b.a"); + auto table3_b_b = parser.Root().Direct("table3.b.b"); + auto table3_e_a = parser.Root().Direct("table3.e.a"); + auto table3_e_b = parser.Root().Direct("table3.e.b"); ASSERT_NE(table1_a, nullptr); EXPECT_EQ(table1_a->GetType(), sdv::toml::ENodeType::node_integer); @@ -381,7 +430,7 @@ TEST(NestedContent, InlineTable) TEST(SpecialCases, Keys) { using namespace std::string_literals; - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( "127.0.0.1" = "value" "character encoding" = "value" "ÊŽÇÊž" = "value" @@ -389,21 +438,21 @@ TEST(SpecialCases, Keys) 'quoted "value"' = "value" )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( key = "value" bare_key = "value" bare-key = "value" 1234 = "value" )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( "" = "blank" # VALID but discouraged )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( '' = 'blank' # VALID but discouraged )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( name = "Orange" physical.color = "orange" physical.shape = "round" @@ -411,20 +460,20 @@ TEST(SpecialCases, Keys) )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( fruit.name = "banana" # this is best practice fruit. color = "yellow" # same as fruit.color fruit . flavor = "banana" # same as fruit.flavor )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( # This makes the key "fruit" into a table. fruit.apple.smooth = true # So then you can add to the table "fruit" like so: fruit.orange = 2 )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( # VALID BUT DISCOURAGED apple.type = "fruit" orange.type = "fruit" @@ -434,15 +483,15 @@ TEST(SpecialCases, Keys) orange.color = "orange" )"s)); - EXPECT_NO_THROW(CParserTOML(R"( + EXPECT_NO_THROW(toml_parser::CParser(R"( 3.1415 = 3.1415 )"s)); { - CParserTOML parser(R"( + toml_parser::CParser parser(R"( 3.1415 = 3.1415 )"s); - auto table = parser.GetRoot().GetDirect("3"); - auto pi = parser.GetRoot().GetDirect("3.1415"); + auto table = parser.Root().Direct("3"); + auto pi = parser.Root().Direct("3.1415"); ASSERT_NE(table, nullptr); EXPECT_EQ(table->GetType(), sdv::toml::ENodeType::node_table); ASSERT_NE(pi, nullptr); @@ -454,7 +503,7 @@ TEST(SpecialCases, Keys) TEST(SpecialCases, Arrays) { using namespace std::string_literals; - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( integers = [ 1, 2, 3 ] colors = [ "red", "yellow", "green" ] nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ] @@ -462,7 +511,7 @@ TEST(SpecialCases, Arrays) string_array = [ "all", 'strings', """are the same""", '''type''' ] )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ] contributors = [ "Foo Bar ", @@ -470,7 +519,7 @@ TEST(SpecialCases, Arrays) ] )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( integers3 = [ 1, 2, # this is ok @@ -481,7 +530,7 @@ TEST(SpecialCases, Arrays) TEST(SpecialCases, Tables) { using namespace std::string_literals; - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( [table-1] key1 = "some string" key2 = 123 @@ -491,19 +540,19 @@ TEST(SpecialCases, Tables) key2 = 456 )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( [dog."tater.man"] type.name = "pug" )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( [a.b.c] # this is best practice [ d.e.f ] # same as [d.e.f] [ g . h . i ] # same as [g.h.i] [ j . "Êž" . 'l' ] # same as [j."Êž".'l'] )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( # [x] you # [x.y] don't # [x.y.z] need these @@ -511,14 +560,14 @@ TEST(SpecialCases, Tables) [x] # defining a super-table afterward is ok )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( # VALID BUT DISCOURAGED [fruit.apple] [animal] [fruit.orange] )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( [fruit] apple.color = "red" apple.taste.sweet = true @@ -529,7 +578,7 @@ TEST(SpecialCases, Tables) TEST(SpecialCases, TableArrays) { using namespace std::string_literals; - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( [[products]] name = "Hammer" sku = 738594937 @@ -540,7 +589,7 @@ TEST(SpecialCases, TableArrays) color = "gray" )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( [[fruits]] name = "apple" [fruits.physical] # subtable @@ -556,7 +605,7 @@ TEST(SpecialCases, TableArrays) name = "plantain" )"s)); - EXPECT_NO_THROW(CParserTOML parser(R"( + EXPECT_NO_THROW(toml_parser::CParser parser(R"( points = [ { x = 1, y = 2, z = 3 }, { x = 7, y = 8, z = 9 }, { x = 2, y = 4, z = 8 } ] @@ -566,32 +615,32 @@ TEST(SpecialCases, TableArrays) TEST(ErrorCases, KeyValue) { using namespace std::string_literals; - EXPECT_THROW(CParserTOML parser(R"(key = # node_invalid)"s), sdv::toml::XTOMLParseException); + EXPECT_THROW(toml_parser::CParser parser(R"(key = # node_invalid)"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"(first = "Tom" last = "Preston-Werner" # node_invalid)"s), sdv::toml::XTOMLParseException); + EXPECT_THROW(toml_parser::CParser parser(R"(first = "Tom" last = "Preston-Werner" # node_invalid)"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"(= "no key name" # node_invalid)"s), sdv::toml::XTOMLParseException); + EXPECT_THROW(toml_parser::CParser parser(R"(= "no key name" # node_invalid)"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( name = "Tom" name = "Pradyun" )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML(R"( + EXPECT_THROW(toml_parser::CParser(R"( fruit . flavor = "banana" # same as fruit.flavor fruit.flavor = "banana" )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML(R"( + EXPECT_THROW(toml_parser::CParser(R"( spelling = "favorite" "spelling" = "favourite" )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML(R"( + EXPECT_THROW(toml_parser::CParser(R"( # This defines the value of fruit.apple to be an integer. fruit.apple = 1 # But then this treats fruit.apple like it's a table. @@ -604,13 +653,19 @@ TEST(ErrorCases, KeyValue) TEST(ErrorCases, Tables) { using namespace std::string_literals; - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [ j . "Êž" . 'l' ] [j."Êž".'l'] )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( + [ j . "Êž" . 'l' ] + ["j".'Êž'."l"] + )"s), + sdv::toml::XTOMLParseException); + + EXPECT_THROW(toml_parser::CParser parser(R"( [fruit] apple = "red" [fruit] @@ -618,7 +673,7 @@ TEST(ErrorCases, Tables) )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [fruit] apple = "red" [fruit.apple] @@ -626,14 +681,14 @@ TEST(ErrorCases, Tables) )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [fruit] apple.color = "red" apple.taste.sweet = true [fruit.apple] # INVALID )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [fruit] apple.color = "red" apple.taste.sweet = true @@ -646,12 +701,12 @@ TEST(ErrorCases, InlineTables) { using namespace std::string_literals; - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( type = { name = "Nail" } type.edible = false # INVALID )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [product] type.name = "Nail" type = { edible = false } # INVALID @@ -662,7 +717,7 @@ TEST(ErrorCases, InlineTables) TEST(ErrorCases, TableArrays) { using namespace std::string_literals; - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [fruit.physical] # subtable, but to which parent element should it belong? color = "red" shape = "round" @@ -672,13 +727,13 @@ TEST(ErrorCases, TableArrays) )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( fruits = [] [[fruits]] # Not allowed )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [[fruits]] name = "apple" [[fruits.varieties]] @@ -688,7 +743,7 @@ TEST(ErrorCases, TableArrays) name = "granny smith" )"s), sdv::toml::XTOMLParseException); - EXPECT_THROW(CParserTOML parser(R"( + EXPECT_THROW(toml_parser::CParser parser(R"( [[fruits]] name = "apple" [fruits.physical] @@ -704,13 +759,13 @@ TEST(ErrorCases, TableArrays) TEST(Ordering, Array) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] )"s); - auto two = parser.GetRoot().GetDirect("array[2]"); - auto eleven = parser.GetRoot().GetDirect("array[11]"); - const auto arr = parser.GetRoot().GetDirect("array"); + auto two = parser.Root().Direct("array[2]"); + auto eleven = parser.Root().Direct("array[11]"); + const auto arr = parser.Root().Direct("array"); // with direct access ASSERT_NE(two, nullptr); @@ -720,7 +775,7 @@ TEST(Ordering, Array) // with indirect access through iterating ASSERT_NE(arr, nullptr); - auto ptrArray = arr->GetArray(); + auto ptrArray = arr->Cast(); EXPECT_EQ(ptrArray->GetCount(), 12u); for (uint32_t uiIndex = 0; uiIndex < ptrArray->GetCount(); uiIndex++) EXPECT_EQ(ptrArray->Get(uiIndex)->GetValue(), (int64_t)uiIndex); @@ -729,7 +784,7 @@ TEST(Ordering, Array) TEST(Ordering, TableAray) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( [[tableArray]] a = 0 [[tableArray]] @@ -756,19 +811,19 @@ TEST(Ordering, TableAray) a = 11 )"s); - auto tableArray = parser.GetRoot().GetDirect("tableArray"); + auto tableArray = parser.Root().Direct("tableArray"); ASSERT_NE(tableArray, nullptr); - auto ptrArray = tableArray->GetArray(); + auto ptrArray = tableArray->Cast(); EXPECT_EQ(ptrArray->GetCount(), 12u); for (uint32_t uiIndex = 0; uiIndex < ptrArray->GetCount(); uiIndex++) - EXPECT_EQ(ptrArray->Get(uiIndex)->GetTable()->Find("a")->GetValue(), (int64_t) uiIndex); + EXPECT_EQ(ptrArray->Get(uiIndex)->Cast()->Direct("a")->GetValue(), (int64_t) uiIndex); } TEST(Ordering, NodeGetDirect) { using namespace std::string_literals; - CParserTOML parser(R"( + toml_parser::CParser parser(R"( [[table.test]] a = 2 b = 1.2 @@ -782,18 +837,89 @@ TEST(Ordering, NodeGetDirect) { x = 2, y = 4, z = 8 }] )"s); - auto table_test_1_a = parser.GetRoot().GetDirect("table.test[0].a"); - auto table_test_1_b = parser.GetRoot().GetDirect("table.test[0].b"); - auto table_test_2_a = parser.GetRoot().GetDirect("table.test[1].a"); - auto table_test_2_c = parser.GetRoot().GetDirect("table.test[1].c"); - auto table_test_3_a = parser.GetRoot().GetDirect("table.test[2].a"); - auto table_test_3_d = parser.GetRoot().GetDirect("table.test[2].d"); + auto table_test_1_a = parser.Root().Direct("table.test[0].a"); + auto table_test_1_b = parser.Root().Direct("table.test[0].b"); + auto table_test_2_a = parser.Root().Direct("table.test[1].a"); + auto table_test_2_c = parser.Root().Direct("table.test[1].c"); + auto table_test_3_a = parser.Root().Direct("table.test[2].a"); + auto table_test_3_d = parser.Root().Direct("table.test[2].d"); EXPECT_TRUE(table_test_1_a); EXPECT_TRUE(table_test_1_b); EXPECT_EQ(table_test_3_d->GetType(), sdv::toml::ENodeType::node_array); - auto table_test_3 = parser.GetRoot().GetDirect("table.test[2]"); - auto table_test_3_2nd = table_test_3->GetTable()->GetNodeDirect("d[2].x"); + auto table_test_3 = parser.Root().Direct("table.test[2]"); + auto table_test_3_2nd = table_test_3->Cast()->GetNodeDirect("d[2].x"); EXPECT_NE(table_test_3_2nd, nullptr); -} \ No newline at end of file +} + +TEST(NodeAccess, Parent) +{ + using namespace std::string_literals; + toml_parser::CParser parser(R"( + [[table.test]] + a = 2 + b = 1.2 + [[table.test]] + a = 4 + c = false + [[table.test]] + a = "five" + d = [ { x = 1, y = 2, z = 3 }, + { x = 7, y = 8, z = 9 }, + { x = 2, y = 4, z = 8 }] + )"s); + + auto fnGetName = [](sdv::IInterfaceAccess* pNode) -> std::string + { + sdv::toml::INodeInfo* pNodeInfo = sdv::TInterfaceAccessPtr(pNode).GetInterface(); + if (!pNodeInfo) return {}; + return pNodeInfo->GetPath(true); + }; + + auto root = &parser.Root(); + auto table = parser.Root().Direct("table"); + ASSERT_TRUE(table); + EXPECT_EQ(table->GetParent(), root); + EXPECT_EQ(fnGetName(table->GetParent()), ""); + auto table_test = parser.Root().Direct("table.test"); + ASSERT_TRUE(table_test); + EXPECT_EQ(table_test->GetParent(), table.get()); + EXPECT_EQ(fnGetName(table_test->GetParent()), "table"); + auto table_test_1 = parser.Root().Direct("table.test[0]"); + ASSERT_TRUE(table_test_1); + EXPECT_EQ(table_test_1->GetParent(), table_test.get()); + EXPECT_EQ(fnGetName(table_test_1->GetParent()), "table.test"); + auto table_test_1_a = parser.Root().Direct("table.test[0].a"); + ASSERT_TRUE(table_test_1_a); + EXPECT_EQ(table_test_1_a->GetParent(), table_test_1.get()); + EXPECT_EQ(fnGetName(table_test_1_a->GetParent()), "table.test[0]"); + auto table_test_1_b = parser.Root().Direct("table.test[0].b"); + ASSERT_TRUE(table_test_1_b); + EXPECT_EQ(table_test_1_b->GetParent(), table_test_1.get()); + EXPECT_EQ(fnGetName(table_test_1_b->GetParent()), "table.test[0]"); + auto table_test_2 = parser.Root().Direct("table.test[1]"); + ASSERT_TRUE(table_test_2); + EXPECT_EQ(table_test_2->GetParent(), table_test.get()); + EXPECT_EQ(fnGetName(table_test_2->GetParent()), "table.test"); + auto table_test_2_a = parser.Root().Direct("table.test[1].a"); + ASSERT_TRUE(table_test_2_a); + EXPECT_EQ(table_test_2_a->GetParent(), table_test_2.get()); + EXPECT_EQ(fnGetName(table_test_2_a->GetParent()), "table.test[1]"); + auto table_test_2_c = parser.Root().Direct("table.test[1].c"); + ASSERT_TRUE(table_test_2_c); + EXPECT_EQ(table_test_2_c->GetParent(), table_test_2.get()); + EXPECT_EQ(fnGetName(table_test_2_c->GetParent()), "table.test[1]"); + auto table_test_3 = parser.Root().Direct("table.test[2]"); + ASSERT_TRUE(table_test_3); + EXPECT_EQ(table_test_3->GetParent(), table_test.get()); + EXPECT_EQ(fnGetName(table_test_3->GetParent()), "table.test"); + auto table_test_3_a = parser.Root().Direct("table.test[2].a"); + ASSERT_TRUE(table_test_3_a); + EXPECT_EQ(table_test_3_a->GetParent(), table_test_3.get()); + EXPECT_EQ(fnGetName(table_test_3_a->GetParent()), "table.test[2]"); + auto table_test_3_d = parser.Root().Direct("table.test[2].d"); + ASSERT_TRUE(table_test_3_d); + EXPECT_EQ(table_test_3_d->GetParent(), table_test_3.get()); + EXPECT_EQ(fnGetName(table_test_3_d->GetParent()), "table.test[2]"); +} diff --git a/tests/unit_tests/toml_parser/statement_boundary_detection.cpp b/tests/unit_tests/toml_parser/statement_boundary_detection.cpp new file mode 100644 index 0000000..4f26910 --- /dev/null +++ b/tests/unit_tests/toml_parser/statement_boundary_detection.cpp @@ -0,0 +1,1256 @@ +#include +#include +#include +#include "../../../sdv_services/core/toml_parser/lexer_toml.h" +#include "../../../sdv_services/core/toml_parser/exception.h" +#include "../../../sdv_services/core/toml_parser/miscellaneous.h" + +std::string DumpTokenList(toml_parser::CLexer& rLexer) +{ + auto eNavMode = rLexer.NavigationMode(); + rLexer.NavigationMode(toml_parser::CLexer::ENavigationMode::do_not_skip_anything); + rLexer.Reset(); + std::stringstream sstream; + while (!rLexer.IsEnd()) + { + const auto& rToken = rLexer.Consume(); + switch (rToken.Category()) + { + case toml_parser::ETokenCategory::token_none: sstream << ""; break; + case toml_parser::ETokenCategory::token_syntax_assignment: sstream << "="; break; + case toml_parser::ETokenCategory::token_syntax_array_open: sstream << "["; break; + case toml_parser::ETokenCategory::token_syntax_array_close: sstream << "]"; break; + case toml_parser::ETokenCategory::token_syntax_table_open: sstream << "["; break; + case toml_parser::ETokenCategory::token_syntax_table_close: sstream << "]"; break; + case toml_parser::ETokenCategory::token_syntax_table_array_open: sstream << "[{"; break; + case toml_parser::ETokenCategory::token_syntax_table_array_close: sstream << "}]"; break; + case toml_parser::ETokenCategory::token_syntax_inline_table_open: sstream << "{"; break; + case toml_parser::ETokenCategory::token_syntax_inline_table_close: sstream << "}"; break; + case toml_parser::ETokenCategory::token_syntax_comma: sstream << ", "; break; + case toml_parser::ETokenCategory::token_syntax_dot: sstream << "."; break; + case toml_parser::ETokenCategory::token_syntax_new_line: sstream << std::endl; break; + case toml_parser::ETokenCategory::token_key: sstream << rToken.StringValue(); break; + case toml_parser::ETokenCategory::token_string: + sstream << toml_parser::QuoteText(rToken.StringValue(), + rToken.StringType() == toml_parser::ETokenStringType::literal_string ? + toml_parser::EQuoteRequest::quoted_text : toml_parser::EQuoteRequest::literal_text); + break; + case toml_parser::ETokenCategory::token_integer: sstream << rToken.IntegerValue(); break; + case toml_parser::ETokenCategory::token_float: sstream << rToken.FloatValue(); break; + case toml_parser::ETokenCategory::token_boolean: sstream << (rToken.BooleanValue() ? "true" : "false"); break; + case toml_parser::ETokenCategory::token_time_local: sstream << "