Skip to content

Commit

Permalink
include optional and once attributes, absolute paths (#144)
Browse files Browse the repository at this point in the history
This PR mainly adds support for optional attributes for includes: `[optional]` and `[once]`
   - e.g. `include [once] base.cfg` allows including the same file twice, which may be useful if some base values or 
   structure is defined in a separate file that may be transitively included from multiple places.
   By default, the parser will fail when it encounters a duplicate include, this leads to duplicate key definitions.
   - w.g. `include [optional] file_may_not_exist.cfg` enables a config to reference files that may be missing.
   By default, the parser will fail when it encounters a missing file.

In addition a few housekeeping things:
- tests for include statements
- fail on duplicate includes
- improve nested error reporting with a "backtrace"
- moved the debug flags to cmake so it's easier to enabled/disable
- made the critical errors a little less in your face ;-)
- integrated the pegtl parser tracer in all parse calls (behind flag), and customized it with more support for nested parsing, and providing more information to make it easier to follow when working on grammar
  • Loading branch information
jayv authored Sep 23, 2024
1 parent 4b3aab3 commit eabd187
Show file tree
Hide file tree
Showing 37 changed files with 793 additions and 167 deletions.
3 changes: 1 addition & 2 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Checks: '-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
cert-*,
-cert-err58-cpp,
hicpp-*,
-hicpp-avoid-goto,
modernize-*,
Expand All @@ -18,8 +19,6 @@ Checks: '-*,

HeaderFilterRegex: 'cpp/.*'

#WarningsAsErrors: '*'

CheckOptions:
- { key: readability-function-cognitive-complexity.IgnoreMacros, value: 1 }
- { key: readability-function-cognitive-complexity.Threshold, value: 40 }
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ __pycache__
sandbox
build
build-debug
# CLion
cmake-build*
47 changes: 44 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.14)
cmake_minimum_required(VERSION 3.28)

set(CFG_IS_MAIN_PROJECT OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
Expand All @@ -22,6 +22,8 @@ FetchContent_Declare(
taocpp_pegtl
GIT_REPOSITORY https://github.com/taocpp/PEGTL.git
GIT_TAG 3.2.7
SYSTEM
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(taocpp_pegtl)

Expand All @@ -38,6 +40,8 @@ else(magic_enum_FOUND)
magic_enum
GIT_REPOSITORY https://github.com/Neargye/magic_enum.git
GIT_TAG v0.9.3
SYSTEM
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(magic_enum)
set(magic_enum_INCLUDE_DIR "${magic_enum_SOURCE_DIR}/include")
Expand All @@ -50,6 +54,8 @@ FetchContent_Declare(
GIT_REPOSITORY "https://github.com/fmtlib/fmt.git"
GIT_TAG ${FMT_VERSION_REQUIRED}
CMAKE_ARGS -DFMT_DOC=OFF -DFMT_TEST=OFF -DFMT_INSTALL=ON -DFMT_LIB_DIR=lib
SYSTEM
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(fmt)
set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
Expand All @@ -59,15 +65,25 @@ FetchContent_Declare(
range-v3
GIT_REPOSITORY "https://github.com/ericniebler/range-v3.git"
GIT_TAG 0.12.0
SYSTEM
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(range-v3)

option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" ON)
option(CFG_CLANG_TIDY_WARN_AS_ERROR "Enable warnings as errors for clang-tidy > 13" ON)

if(ENABLE_CLANG_TIDY)
if(NOT CMAKE_CROSSCOMPILING) # adds invalid paths
find_package(ClangTools QUIET)
if(CLANG_TIDY_FOUND)
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_BIN}")
if (COMPILER_VERSION_MAJOR GREATER 13 AND CFG_CLANG_TIDY_WARN_AS_ERROR)
message(STATUS "Clang-Tidy > 13 - warning as errors")
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_BIN}" --warnings-as-errors=*)
else()
message(STATUS "Clang-Tidy - warnings as warnings")
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_BIN}" --warnings-as-errors=)
endif()
else(CLANG_TIDY_FOUND)
message(STATUS "${Yellow}clang-tidy static analysis won't be done.${ColourReset}")
endif(CLANG_TIDY_FOUND)
Expand All @@ -79,13 +95,24 @@ else(ENABLE_CLANG_TIDY)
set(CMAKE_CXX_CLANG_TIDY "")
endif(ENABLE_CLANG_TIDY)

option(CFG_ENABLE_DEBUG "Enable DEBUG logging." OFF)
option(CFG_ENABLE_PARSER_TRACE "Enable parser trace logging." OFF)
option(CFG_ENABLE_TEST "Enable unit tests." ON)
option(CFG_EXAMPLES "Build example applications." OFF)
option(CFG_PYTHON_BINDINGS "Build python bindings." OFF)
option(CFG_PYTHON_INSTALL_DIR "Installation directory of python bindings." "")

add_compile_definitions(-DEXAMPLE_DIR="${CMAKE_SOURCE_DIR}/examples")

if(CFG_ENABLE_DEBUG)
add_compile_definitions(VERBOSE_DEBUG_ACTIONS=1)
message(STATUS "Enabling VERBOSE_DEBUG_ACTIONS")
endif(CFG_ENABLE_DEBUG)

add_definitions(-DEXAMPLE_DIR="${CMAKE_SOURCE_DIR}/examples")
if(CFG_ENABLE_PARSER_TRACE)
add_compile_definitions(ENABLE_PARSER_TRACE=1)
message(STATUS "Enabling ENABLE_PARSER_TRACE")
endif(CFG_ENABLE_PARSER_TRACE)

set(PUBLIC_CFG_HEADERS include/flexi_cfg/reader.h include/flexi_cfg/parser.h)
set(CFG_HEADERS
Expand All @@ -95,7 +122,9 @@ set(CFG_HEADERS
include/flexi_cfg/config/exceptions.h
include/flexi_cfg/config/grammar.h
include/flexi_cfg/config/helpers.h
include/flexi_cfg/config/parser-internal.h
include/flexi_cfg/config/selector.h
include/flexi_cfg/config/trace-internal.h
include/flexi_cfg/logger.h
include/flexi_cfg/math/actions.h
include/flexi_cfg/math/grammar.h
Expand Down Expand Up @@ -138,6 +167,8 @@ if (CFG_PYTHON_BINDINGS)
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.12.0
SYSTEM
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(pybind11)

Expand All @@ -151,9 +182,19 @@ if (CFG_ENABLE_TEST)
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
SYSTEM
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(googletest)

# Silence clang-tidy warnings from googletest
message( STATUS "Silencing CLANG_TIDY for gtest and gmock")
# This doesn't seem to work for clang-13 :(
set_target_properties(gtest PROPERTIES CXX_CLANG_TIDY "")
set_target_properties(gtest_main PROPERTIES CXX_CLANG_TIDY "")
set_target_properties(gmock PROPERTIES CXX_CLANG_TIDY "")
set_target_properties(gmock_main PROPERTIES CXX_CLANG_TIDY "")

enable_testing()
add_subdirectory(tests)
endif()
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,28 @@ In summary, a `proto` that is `reference`d, effectively becomes a `struct`.
another file. This highlights one of the advantages of this syntax: the ability to
define a generic set of templates in one file, which will be used to produce multiple,
repeated concrete structs with different names and parameters in a different file.
The same config files may not be included twice
2. `include_relative` syntax - Same as `include`, except nested paths will resolve from the
included file's path instead of from the base file's path. See `examples/config_example12.cfg`
for an example of this keyword. The list of `include_relative` statements must come after all `include`
statements in a config file.
3. Environment variables - Environment variables may be referenced `include` or `include_relative` statements
3. optional attributes for includes: `[optional]` and `[once]`
- e.g. `include [once] base.cfg` allows including the same file twice, which may be useful if some base values or
structure is defined in a separate file that may be transitively included from multiple places.
By default, the parser will fail when it encounters a duplicate include, this leads to duplicate key definitions.
- e.g. `include [optional] file_may_not_exist.cfg` enables a config to reference files that may be missing.
By default, the parser will fail when it encounters a missing file.
4. Environment variables - Environment variables may be referenced in `include` or `include_relative` statements
using the syntax `${ENV_VAR_NAME}`. If the environment variable is not set, the reference
will be replaced with an empty string.
4. [key-value reference](#key-value-references) - Much like bash, the syntax provides the ability
5. [key-value reference](#key-value-references) - Much like bash, the syntax provides the ability
to reference apreviously defined value by its key, and assign it to another key.
5. Appended keys - While a `proto` defines a templated `struct`, one can add additional keys
6. Appended keys - While a `proto` defines a templated `struct`, one can add additional keys
to the resulting `struct` when the `proto` is referenced.
6. `[override]` - By default, a key can be specified once and only once in the config file. Using
7. `[override]` - By default, a key can be specified once and only once in the config file. Using
the `[override]` keyword allows a value that was previously specified in the config to be
overridden with a new value.
6. Fully qualified keys - One may define the configuration parameters using a combination
8. Fully qualified keys - One may define the configuration parameters using a combination
of the tree-like structure found in json along with fully qualified key value pairs.
These can not be mixed within the same file however.

Expand Down Expand Up @@ -387,4 +394,3 @@ cmake -DCFG_PYTHON_BINDINGS=ON -DCFG_PYTHON_INSTALL_DIR=/my/custom/path ..
ninja
ninja install
```

38 changes: 28 additions & 10 deletions cmake/modules/FindClangTools.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,48 @@
# CLANG_FORMAT_BIN, The path to the clang format binary
# CLANG_TIDY_FOUND, Whether clang format was found

if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(DEFINED $ENV{CLANG_VERSION})
set(COMPILER_VERSION_MAJOR $ENV{CLANG_VERSION})
message(STATUS "ClangTools -- Not Clang, using CLANG_VERSION: ${COMPILER_VERSION_MAJOR}")
else()
set(COMPILER_VERSION_MAJOR 13)
message(STATUS "ClangTools -- Not Clang, using default: ${COMPILER_VERSION_MAJOR}")
endif ()
else ()
# ensure we use Clang Tools matching the compiler version when having multiple versions installed
string(REPLACE "." ";" COMPILER_VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION})
list(GET COMPILER_VERSION_LIST 0 COMPILER_VERSION_MAJOR)
# Debian packages usually have a major version suffix in /usr/bin, e.g. /usr/bin/clang-format-13
message(STATUS "ClangTools -- Detected Major Version: ${COMPILER_VERSION_MAJOR}")
# When CLANG_TOOLS_PATH is set, we can find it there e.g. /usr/lib/llvm-13/bin/clang-format
message(STATUS "ClangTools -- Detected Compiler Path: $ENV{CLANG_TOOLS_PATH}")
endif ()

find_program(CLANG_TIDY_BIN
NAMES clang-tidy
PATHS ${ClangTools_PATH} $ENV{CLANG_TOOLS_PATH} /usr/local/bin /usr/bin
NO_DEFAULT_PATH
NAMES clang-tidy-${COMPILER_VERSION_MAJOR} clang-tidy
PATHS $ENV{CLANG_TOOLS_PATH}/bin usr/local/bin /usr/bin
NO_DEFAULT_PATH
)

if ( "${CLANG_TIDY_BIN}" STREQUAL "CLANG_TIDY_BIN-NOTFOUND" )
set(CLANG_TIDY_FOUND 0)
message("clang-tidy not found")
message(STATUS "clang-tidy not found")
else()
set(CLANG_TIDY_FOUND 1)
message("clang-tidy found at ${CLANG_TIDY_BIN}")
message(STATUS "clang-tidy found at ${CLANG_TIDY_BIN}")
endif()

find_program(CLANG_FORMAT_BIN
NAMES clang-format
PATHS ${ClangTools_PATH} $ENV{CLANG_TOOLS_PATH} /usr/local/bin /usr/bin
NO_DEFAULT_PATH
NAMES clang-format-${COMPILER_VERSION_MAJOR} clang-format
PATHS $ENV{CLANG_TOOLS_PATH}/bin /usr/local/bin /usr/bin
NO_DEFAULT_PATH
)

if ( "${CLANG_FORMAT_BIN}" STREQUAL "CLANG_FORMAT_BIN-NOTFOUND" )
set(CLANG_FORMAT_FOUND 0)
message("clang-format not found")
message(STATUS "clang-format not found")
else()
set(CLANG_FORMAT_FOUND 1)
message("clang-format found at ${CLANG_FORMAT_BIN}")
message(STATUS "clang-format found at ${CLANG_FORMAT_BIN}")
endif()
3 changes: 3 additions & 0 deletions examples/nested/dupe_include.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# a common issue is multiple configs that transitively include the same file
include dupe_include_2.cfg
include dupe_include_3.cfg
3 changes: 3 additions & 0 deletions examples/nested/dupe_include2.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# a common issue is multiple configs that transitively include the same file
include dupe_include_2.cfg
include dupe_include_4.cfg # has an include [once] attribute
2 changes: 2 additions & 0 deletions examples/nested/dupe_include_2.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include dupe_include_dupe.cfg
included.file2 = true
2 changes: 2 additions & 0 deletions examples/nested/dupe_include_3.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include dupe_include_dupe.cfg
included.file3 = true
2 changes: 2 additions & 0 deletions examples/nested/dupe_include_4.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include [once] dupe_include_dupe.cfg # once is equivalent to '#pragme once'
included.file4 = true
1 change: 1 addition & 0 deletions examples/nested/dupe_include_dupe.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
included.dupe=true
1 change: 0 additions & 1 deletion examples/nested/nested_flat_include.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Put this override here so that it can be included separately and not mess stuff up

outer.inner.val [override] = -2.345

1 change: 1 addition & 0 deletions examples/nested/simple_include.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = 123
3 changes: 3 additions & 0 deletions examples/optional/optional_config.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# optional and conditional includes
include [optional] does_not_exist.cfg
include optional_exists.cfg
3 changes: 3 additions & 0 deletions examples/optional/optional_config2.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# optional and conditional includes
include [optional] does_not_exist.cfg
include [optional] optional_exists.cfg
3 changes: 3 additions & 0 deletions examples/optional/optional_config3.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# optional and conditional includes - essentially an empty config
include [optional] does_not_exist.cfg
include [optional] also_does_not_exist.cfg
3 changes: 3 additions & 0 deletions examples/optional/optional_config_non_optional.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# optional and conditional includes
include does_not_exist.cfg
include optional_exists.cfg
1 change: 1 addition & 0 deletions examples/optional/optional_exists.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
optional.exists=true
Loading

0 comments on commit eabd187

Please sign in to comment.