tags 1106521 + patch
thanks

Dear Maintainer,

I'm uploading an NMU for hyprlang 0.6.4-0.1 to upgrade this to the
latest upstream version. I've already committed the changes into VCS at
https://salsa.debian.org/Debian/hyprlang.

Attached is the diff relative to the previous upload. I will be
uploading this to DELAYED/2 shortly. Please reach out if you'd like me
to cancel the upload within the next 2 days.

-- 
Kind regards,
Loong Jin
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..224864f
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,101 @@
+WarningsAsErrors: '*'
+HeaderFilterRegex: '.*\.hpp'
+FormatStyle: 'file'
+Checks: >
+  -*,
+  bugprone-*,
+  -bugprone-easily-swappable-parameters,
+  -bugprone-forward-declaration-namespace,
+  -bugprone-forward-declaration-namespace,
+  -bugprone-macro-parentheses,
+  -bugprone-narrowing-conversions,
+  -bugprone-branch-clone,
+  -bugprone-assignment-in-if-condition,
+  concurrency-*,
+  -concurrency-mt-unsafe,
+  cppcoreguidelines-*,
+  -cppcoreguidelines-owning-memory,
+  -cppcoreguidelines-avoid-magic-numbers,
+  -cppcoreguidelines-pro-bounds-constant-array-index,
+  -cppcoreguidelines-avoid-const-or-ref-data-members,
+  -cppcoreguidelines-non-private-member-variables-in-classes,
+  -cppcoreguidelines-avoid-goto,
+  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+  -cppcoreguidelines-avoid-do-while,
+  -cppcoreguidelines-avoid-non-const-global-variables,
+  -cppcoreguidelines-special-member-functions,
+  -cppcoreguidelines-explicit-virtual-functions,
+  -cppcoreguidelines-avoid-c-arrays,
+  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+  -cppcoreguidelines-narrowing-conversions,
+  -cppcoreguidelines-pro-type-union-access,
+  -cppcoreguidelines-pro-type-member-init,
+  -cppcoreguidelines-macro-usage,
+  -cppcoreguidelines-macro-to-enum,
+  -cppcoreguidelines-init-variables,
+  -cppcoreguidelines-pro-type-cstyle-cast,
+  -cppcoreguidelines-pro-type-vararg,
+  -cppcoreguidelines-pro-type-reinterpret-cast,
+  google-global-names-in-headers,
+  -google-readability-casting,
+  google-runtime-operator,
+  misc-*,
+  -misc-unused-parameters,
+  -misc-no-recursion,
+  -misc-non-private-member-variables-in-classes,
+  -misc-include-cleaner,
+  -misc-use-anonymous-namespace,
+  -misc-const-correctness,
+  modernize-*,
+  -modernize-return-braced-init-list,
+  -modernize-use-trailing-return-type,
+  -modernize-use-using,
+  -modernize-use-override,
+  -modernize-avoid-c-arrays,
+  -modernize-macro-to-enum,
+  -modernize-loop-convert,
+  -modernize-use-nodiscard,
+  -modernize-pass-by-value,
+  -modernize-use-auto,
+  performance-*,
+  -performance-avoid-endl,
+  -performance-unnecessary-value-param,
+  portability-std-allocator-const,
+  readability-*,
+  -readability-function-cognitive-complexity,
+  -readability-function-size,
+  -readability-identifier-length,
+  -readability-magic-numbers,
+  -readability-uppercase-literal-suffix,
+  -readability-braces-around-statements,
+  -readability-redundant-access-specifiers,
+  -readability-else-after-return,
+  -readability-container-data-pointer,
+  -readability-implicit-bool-conversion,
+  -readability-avoid-nested-conditional-operator,
+  -readability-redundant-member-init,
+  -readability-redundant-string-init,
+  -readability-avoid-const-params-in-decls,
+  -readability-named-parameter,
+  -readability-convert-member-functions-to-static,
+  -readability-qualified-auto,
+  -readability-make-member-function-const,
+  -readability-isolate-declaration,
+  -readability-inconsistent-declaration-parameter-name,
+  -clang-diagnostic-error,
+
+CheckOptions:
+  performance-for-range-copy.WarnOnAllAutoCopies: true
+  performance-inefficient-string-concatenation.StrictMode: true
+  readability-braces-around-statements.ShortStatementLines: 0
+  readability-identifier-naming.ClassCase: CamelCase
+  readability-identifier-naming.ClassIgnoredRegexp: I.*
+  readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
+  readability-identifier-naming.EnumCase: CamelCase
+  readability-identifier-naming.EnumPrefix: e
+  readability-identifier-naming.EnumConstantCase: UPPER_CASE
+  readability-identifier-naming.FunctionCase: camelBack
+  readability-identifier-naming.NamespaceCase: CamelCase
+  readability-identifier-naming.NamespacePrefix: N
+  readability-identifier-naming.StructPrefix: S
+  readability-identifier-naming.StructCase: CamelCase
diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml
index 5889c5e..772cd1c 100644
--- a/.github/workflows/nix.yml
+++ b/.github/workflows/nix.yml
@@ -13,8 +13,35 @@ jobs:
     steps:
     - uses: actions/checkout@v3
 
-    - uses: DeterminateSystems/nix-installer-action@main
-    - uses: DeterminateSystems/magic-nix-cache-action@main
+    - name: Install Nix
+      uses: nixbuild/nix-quick-install-action@v31
+      with:
+        nix_conf: |
+          keep-env-derivations = true
+          keep-outputs = true
+
+    - name: Restore and save Nix store
+      uses: nix-community/cache-nix-action@v6
+      with:
+        # restore and save a cache using this key
+        primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
+        # if there's no cache hit, restore a cache by this prefix
+        restore-prefixes-first-match: nix-${{ runner.os }}-
+        # collect garbage until the Nix store size (in bytes) is at most this number
+        # before trying to save a new cache
+        # 1G = 1073741824
+        gc-max-store-size-linux: 1G
+        # do purge caches
+        purge: true
+        # purge all versions of the cache
+        purge-prefixes: nix-${{ runner.os }}-
+        # created more than this number of seconds ago
+        purge-created: 0
+        # or, last accessed more than this number of seconds ago
+        # relative to the start of the `Post Restore and save Nix store` phase
+        purge-last-accessed: 0
+        # except any version with the key that is the same as the `primary-key`
+        purge-primary-key: never
 
     # not needed (yet)
     # - uses: cachix/cachix-action@v12
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aa8cdf9..ccb2053 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,9 +28,16 @@ endif()
 add_compile_definitions(HYPRLANG_INTERNAL)
 
 set(CMAKE_CXX_STANDARD 23)
+add_compile_options(
+  -Wall
+  -Wextra
+  -Wpedantic
+  -Wno-unused-parameter
+  -Wno-missing-field-initializers)
+set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
 
 find_package(PkgConfig REQUIRED)
-pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprutils>=0.1.1)
+pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprutils>=0.7.1)
 
 file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/hyprlang.hpp")
 
@@ -47,15 +54,6 @@ set_target_properties(
 
 target_link_libraries(hyprlang PkgConfig::deps)
 
-if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
-  # for std::expected. probably evil. Arch's clang is very outdated tho...
-  target_compile_options(hyprlang PUBLIC -std=gnu++2b -D__cpp_concepts=202002L
-                                         -Wno-macro-redefined)
-  add_compile_options(-stdlib=libc++)
-  add_link_options(-stdlib=libc++)
-  message(STATUS "Using clang++ to compile hyprlang")
-endif()
-
 add_library(hypr::hyprlang ALIAS hyprlang)
 install(TARGETS hyprlang)
 
diff --git a/README.md b/README.md
index 3946498..58b0ded 100644
--- a/README.md
+++ b/README.md
@@ -54,4 +54,4 @@ Visit [hyprland.org/hyprlang](https://hyprland.org/hyprlang) to see the document
 
 ### Example implementation
 
-For an example implmentation, take a look at the `tests/` directory.
+For an example implementation, take a look at the `tests/` directory.
diff --git a/VERSION b/VERSION
index a918a2a..844f6a9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.6.0
+0.6.3
diff --git a/debian/changelog b/debian/changelog
index 69fc49b..45849b4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+hyprlang (0.6.4-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload
+  [ Carl Keinath ]
+  * add gbp conf
+  * simplified watch file
+  * cleanup d/u/metadata
+  * added ci config
+  * cleanup d/rules
+  * folded package deps in d/control
+  * updated copyright
+  * removed trailing whitespaces
+
+  [ Chow Loong Jin ]
+  * New upstream version 0.6.4 (Closes: #1106521)
+
+ -- Chow Loong Jin <[email protected]>  Mon, 29 Sep 2025 11:36:27 +0800
+
 hyprlang (0.6.0-1) unstable; urgency=medium
 
   * New upstream version 0.6.0
diff --git a/debian/control b/debian/control
index 5bac359..0133a8a 100644
--- a/debian/control
+++ b/debian/control
@@ -16,7 +16,9 @@ Rules-Requires-Root: no
 Package: libhyprlang-dev
 Section: libdevel
 Architecture: any
-Depends: ${misc:Depends}, libhyprlang2 (= ${binary:Version})
+Depends:
+ ${misc:Depends},
+ libhyprlang2 (= ${binary:Version}),
 Description: Fast and user-friendly configuration language (dev files)
  The hypr configuration language is an extremely efficient, yet easy to
  work with, configuration language for Linux applications.
@@ -27,7 +29,9 @@ Description: Fast and user-friendly configuration language (dev files)
 
 Package: libhyprlang2
 Architecture: any
-Depends: ${misc:Depends}, ${shlibs:Depends}
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
 Description: Fast and user-friendly configuration language (library files)
  The hypr configuration language is an extremely efficient, yet easy to
  work with, configuration language for Linux applications.
diff --git a/debian/copyright b/debian/copyright
index 477bcaa..5c686d3 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -8,8 +8,9 @@ Copyright: 2023 - 2024, Hypr Development <[email protected]>
 License: LGPL-3
 
 Files: debian/*
-Copyright: 2024, Mo Zhou <[email protected]>
-           2024 Alan M Varghese <[email protected]>
+Copyright: 2024 Mo Zhou <[email protected]>
+           2024-2025 Alan M Varghese <[email protected]>
+           2025 Carl Keinath <[email protected]>
 License: LGPL-3
 
 License: LGPL-3
diff --git a/debian/gbp.conf b/debian/gbp.conf
new file mode 100644
index 0000000..6f440a2
--- /dev/null
+++ b/debian/gbp.conf
@@ -0,0 +1,4 @@
+[DEFAULT]
+debian-branch = debian/latest
+upstream-branch = upstream/latest
+pristine-tar = True
\ No newline at end of file
diff --git a/debian/rules b/debian/rules
index f7f1e0a..d8309f6 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,8 +1,6 @@
 #!/usr/bin/make -f
-#export DH_VERBOSE = 1
+
 export DEB_BUILD_MAINT_OPTIONS = hardening=+all
-#export DEB_CFLAGS_MAINT_APPEND  = -Wall -pedantic
-#export DEB_LDFLAGS_MAINT_APPEND = -Wl,-O1
 
 %:
 	dh $@
diff --git a/debian/salsa-ci.yml b/debian/salsa-ci.yml
new file mode 100644
index 0000000..84d19ed
--- /dev/null
+++ b/debian/salsa-ci.yml
@@ -0,0 +1,3 @@
+---
+include:
+  - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml
\ No newline at end of file
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
index 586d5d6..35f9264 100644
--- a/debian/upstream/metadata
+++ b/debian/upstream/metadata
@@ -1,7 +1,3 @@
-#
-# DEP-12: Per-package machine-readable metadata about Upstream
-#        Please check * https://dep-team.pages.debian.net/deps/dep12/
-#                     * https://wiki.debian.org/UpstreamMetadata
 Bug-Database: https://github.com/hyprwm/hyprlang/issues
 Bug-Submit: https://github.com/hyprwm/hyprlang/issues/new
 Repository: https://github.com/hyprwm/hyprlang
diff --git a/debian/watch b/debian/watch
index 2433a19..3d725c7 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,4 +1,4 @@
 version=4
-opts="searchmode=html" \
-  https://github.com/hyprwm/hyprlang/tags \
-  https://github.com/hyprwm/hyprlang/archive/refs/tags/v(\d+.\d+.\d+)\.tar\.gz
+opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%" \
+	https://github.com/hyprwm/hyprlang/tags \
+	(?:.*?/)?v?(\d[\d.]*)\.tar\.gz
diff --git a/flake.lock b/flake.lock
index 5abff22..8798ba3 100644
--- a/flake.lock
+++ b/flake.lock
@@ -10,11 +10,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1721324102,
-        "narHash": "sha256-WAZ0X6yJW1hFG6otkHBfyJDKRpNP5stsRqdEuHrFRpk=",
+        "lastModified": 1749135356,
+        "narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=",
         "owner": "hyprwm",
         "repo": "hyprutils",
-        "rev": "962582a090bc233c4de9d9897f46794280288989",
+        "rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb",
         "type": "github"
       },
       "original": {
@@ -25,11 +25,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1721138476,
-        "narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=",
+        "lastModified": 1748929857,
+        "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "ad0b5eed1b6031efaed382844806550c3dcb4206",
+        "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index 959d3cf..3b63d6f 100644
--- a/flake.nix
+++ b/flake.nix
@@ -39,7 +39,7 @@
         inputs.hyprutils.overlays.default
         (final: prev: {
           hyprlang = final.callPackage ./nix/default.nix {
-            stdenv = final.gcc13Stdenv;
+            stdenv = final.gcc15Stdenv;
             version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
           };
           hyprlang-with-tests = final.hyprlang.override {doCheck = true;};
diff --git a/include/hyprlang.hpp b/include/hyprlang.hpp
index 863b277..547fcb8 100644
--- a/include/hyprlang.hpp
+++ b/include/hyprlang.hpp
@@ -50,7 +50,7 @@ namespace Hyprlang {
     typedef CConfigCustomValueType CUSTOMTYPE;
 
     /*!
-        A very simple vector type 
+        A very simple vector type
     */
     struct SVector2D {
         float x = 0, y = 0;
@@ -95,12 +95,12 @@ namespace Hyprlang {
         Generic struct for options for the config parser
     */
     struct SConfigOptions {
-        /*! 
+        /*!
             Don't throw errors on missing values.
         */
         int verifyOnly = false;
 
-        /*! 
+        /*!
             Return all errors instead of just the first
         */
         int throwAllErrors = false;
@@ -175,11 +175,11 @@ namespace Hyprlang {
     typedef void (*PCONFIGCUSTOMVALUEDESTRUCTOR)(void** data);
 
     /*!
-        Container for a custom config value type 
+        Container for a custom config value type
         When creating, pass your handler.
         Handler will receive a void** that points to a void* that you can set to your own
         thing. Pass a dtor to free whatever you allocated when the custom value type is being released.
-        data may always be pointing to a nullptr. 
+        data may always be pointing to a nullptr.
     */
     class CConfigCustomValueType {
       public:
@@ -254,7 +254,7 @@ namespace Hyprlang {
         /*!
             Get the contained value as an std::any.
             For strings, this is a const char*.
-            For custom data types, this is a CConfigCustomValueType*.
+            For custom data types, this is a void* representing the data ptr stored by it.
         */
         std::any getValue() const {
             switch (m_eType) {
@@ -271,7 +271,7 @@ namespace Hyprlang {
 
         /*!
             \since 0.3.0
-            
+
             a flag to notify whether this value has been set explicitly by the user,
             or not.
         */
@@ -305,7 +305,7 @@ namespace Hyprlang {
         ~CConfig();
 
         /*!
-            Add a config value, for example myCategory:myValue. 
+            Add a config value, for example myCategory:myValue.
             This has to be done before commence()
             Value provided becomes default.
         */
@@ -319,8 +319,8 @@ namespace Hyprlang {
 
         /*!
             \since 0.3.0
-            
-            Unregister a handler.        
+
+            Unregister a handler.
         */
         void unregisterHandler(const char* name);
 
@@ -362,14 +362,14 @@ namespace Hyprlang {
         CParseResult parse();
 
         /*!
-            Same as parse(), but parse a specific file, without any refreshing. 
+            Same as parse(), but parse a specific file, without any refreshing.
             recommended to use for stuff like source = path.conf
         */
         CParseResult parseFile(const char* file);
 
         /*!
-            Parse a single "line", dynamically. 
-            Values set by this are temporary and will be overwritten 
+            Parse a single "line", dynamically.
+            Values set by this are temporary and will be overwritten
             by default / config on the next parse()
         */
         CParseResult parseDynamic(const char* line);
@@ -377,14 +377,14 @@ namespace Hyprlang {
 
         /*!
             Get a config's value ptr. These are static.
-            nullptr on fail 
+            nullptr on fail
         */
         CConfigValue* getConfigValuePtr(const char* name);
 
         /*!
            Get a special category's config value ptr. These are only static for static (key-less)
            categories.
-           key can be nullptr for static categories. Cannot be nullptr for id-based categories. 
+           key can be nullptr for static categories. Cannot be nullptr for id-based categories.
            nullptr on fail.
         */
         CConfigValue* getSpecialConfigValuePtr(const char* category, const char* name, const char* key = nullptr);
@@ -541,4 +541,4 @@ namespace Hyprlang {
 #undef HYPRLANG_END_MAGIC
 #endif
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/common.cpp b/src/common.cpp
index cfe43bb..a1c1504 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -1,6 +1,6 @@
 #include "public.hpp"
 #include "config.hpp"
-#include <string.h>
+#include <cstring>
 
 using namespace Hyprlang;
 
@@ -30,38 +30,28 @@ CConfigValue::~CConfigValue() {
     }
 }
 
-CConfigValue::CConfigValue(const int64_t value) {
-    m_pData                              = new int64_t;
+CConfigValue::CConfigValue(const int64_t value) : m_eType(CONFIGDATATYPE_INT), m_pData(new int64_t) {
     *reinterpret_cast<int64_t*>(m_pData) = value;
-    m_eType                              = CONFIGDATATYPE_INT;
 }
 
-CConfigValue::CConfigValue(const float value) {
-    m_pData                            = new float;
+CConfigValue::CConfigValue(const float value) : m_eType(CONFIGDATATYPE_FLOAT), m_pData(new float) {
     *reinterpret_cast<float*>(m_pData) = value;
-    m_eType                            = CONFIGDATATYPE_FLOAT;
 }
 
-CConfigValue::CConfigValue(const SVector2D value) {
-    m_pData                                = new SVector2D;
+CConfigValue::CConfigValue(const SVector2D value) : m_eType(CONFIGDATATYPE_VEC2), m_pData(new SVector2D) {
     *reinterpret_cast<SVector2D*>(m_pData) = value;
-    m_eType                                = CONFIGDATATYPE_VEC2;
 }
 
-CConfigValue::CConfigValue(const char* value) {
-    m_pData = new char[strlen(value) + 1];
+CConfigValue::CConfigValue(const char* value) : m_eType(CONFIGDATATYPE_STR), m_pData(new char[strlen(value) + 1]) {
     strncpy((char*)m_pData, value, strlen(value));
     ((char*)m_pData)[strlen(value)] = '\0';
-    m_eType                         = CONFIGDATATYPE_STR;
 }
 
-CConfigValue::CConfigValue(CConfigCustomValueType&& value) {
-    m_pData = new CConfigCustomValueType(value);
-    m_eType = CONFIGDATATYPE_CUSTOM;
+CConfigValue::CConfigValue(CConfigCustomValueType&& value) : m_eType(CONFIGDATATYPE_CUSTOM), m_pData(new CConfigCustomValueType(value)) {
+    ;
 }
 
-CConfigValue::CConfigValue(const CConfigValue& other) {
-    m_eType = other.m_eType;
+CConfigValue::CConfigValue(const CConfigValue& other) : m_eType(other.m_eType) {
     setFrom(&other);
 }
 
@@ -77,11 +67,9 @@ void* const* CConfigValue::getDataStaticPtr() const {
     return &m_pData;
 }
 
-CConfigCustomValueType::CConfigCustomValueType(PCONFIGCUSTOMVALUEHANDLERFUNC handler_, PCONFIGCUSTOMVALUEDESTRUCTOR dtor_, const char* def) {
-    handler    = handler_;
-    dtor       = dtor_;
-    defaultVal = def;
-    lastVal    = def;
+CConfigCustomValueType::CConfigCustomValueType(PCONFIGCUSTOMVALUEHANDLERFUNC handler_, PCONFIGCUSTOMVALUEDESTRUCTOR dtor_, const char* def) :
+    handler(handler_), dtor(dtor_), defaultVal(def), lastVal(def) {
+    ;
 }
 
 CConfigCustomValueType::~CConfigCustomValueType() {
@@ -216,4 +204,4 @@ void CConfigValue::setFrom(std::any ref) {
             throw "bad defaultFrom type";
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/config.cpp b/src/config.cpp
index fdb71db..560fd8f 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -1,7 +1,9 @@
 #include "config.hpp"
+#include <array>
 #include <exception>
 #include <filesystem>
 #include <fstream>
+#include <iostream>
 #include <stdexcept>
 #include <string>
 #include <format>
@@ -12,6 +14,7 @@
 #include <cstring>
 #include <hyprutils/string/VarList.hpp>
 #include <hyprutils/string/String.hpp>
+#include <hyprutils/string/ConstVarList.hpp>
 
 using namespace Hyprlang;
 using namespace Hyprutils::String;
@@ -20,28 +23,51 @@ using namespace Hyprutils::String;
 #include <crt_externs.h>
 #define environ (*_NSGetEnviron())
 #else
+// NOLINTNEXTLINE
 extern "C" char** environ;
 #endif
 
 // defines
-inline constexpr const char* ANONYMOUS_KEY = "__hyprlang_internal_anonymous_key";
+inline constexpr const char* ANONYMOUS_KEY           = "__hyprlang_internal_anonymous_key";
+inline constexpr const char* MULTILINE_SPACE_CHARSET = " \t";
 //
 
 static size_t seekABIStructSize(const void* begin, size_t startOffset, size_t maxSize) {
     for (size_t off = startOffset; off < maxSize; off += 4) {
-        if (*(int*)((unsigned char*)begin + off) == int{HYPRLANG_END_MAGIC})
+        if (*(int*)((unsigned char*)begin + off) == HYPRLANG_END_MAGIC)
             return off;
     }
 
     return 0;
 }
 
-CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) {
+static std::expected<std::string, eGetNextLineFailure> getNextLine(std::istream& str, int& rawLineNum, int& lineNum) {
+    std::string line     = "";
+    std::string nextLine = "";
+
+    if (!std::getline(str, line))
+        return std::unexpected(GETNEXTLINEFAILURE_EOF);
+
+    lineNum = ++rawLineNum;
+
+    while (line.length() > 0 && line.at(line.length() - 1) == '\\') {
+        const auto lastNonSpace = line.length() < 2 ? -1 : line.find_last_not_of(MULTILINE_SPACE_CHARSET, line.length() - 2);
+        line                    = line.substr(0, lastNonSpace + 1);
+
+        if (!std::getline(str, nextLine))
+            return std::unexpected(GETNEXTLINEFAILURE_BACKSLASH);
+
+        ++rawLineNum;
+        line += nextLine;
+    }
+
+    return line;
+}
+
+CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) : impl(new CConfigImpl) {
     SConfigOptions options;
     std::memcpy(&options, &options_, seekABIStructSize(&options_, 16, sizeof(SConfigOptions)));
 
-    impl = new CConfigImpl;
-
     if (options.pathIsStream)
         impl->rawConfigString = path;
     else
@@ -60,7 +86,7 @@ CConfig::CConfig(const char* path, const Hyprlang::SConfigOptions& options_) {
         impl->envVariables.push_back({VARIABLE, VALUE});
     }
 
-    std::sort(impl->envVariables.begin(), impl->envVariables.end(), [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); });
+    std::ranges::sort(impl->envVariables, [&](const auto& a, const auto& b) { return a.name.length() > b.name.length(); });
 
     impl->configOptions = options;
 }
@@ -74,40 +100,42 @@ void CConfig::addConfigValue(const char* name, const CConfigValue& value) {
         throw "Cannot addConfigValue after commence()";
 
     if ((eDataType)value.m_eType != CONFIGDATATYPE_CUSTOM && (eDataType)value.m_eType != CONFIGDATATYPE_STR)
-        impl->defaultValues.emplace(name, SConfigDefaultValue{value.getValue(), (eDataType)value.m_eType});
+        impl->defaultValues.emplace(name, SConfigDefaultValue{.data = value.getValue(), .type = (eDataType)value.m_eType});
     else if ((eDataType)value.m_eType == CONFIGDATATYPE_STR)
-        impl->defaultValues.emplace(name, SConfigDefaultValue{std::string{std::any_cast<const char*>(value.getValue())}, (eDataType)value.m_eType});
+        impl->defaultValues.emplace(name, SConfigDefaultValue{.data = std::string{std::any_cast<const char*>(value.getValue())}, .type = (eDataType)value.m_eType});
     else
         impl->defaultValues.emplace(name,
-                                    SConfigDefaultValue{reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal, (eDataType)value.m_eType,
-                                                        reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler,
-                                                        reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor});
+                                    SConfigDefaultValue{.data    = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal,
+                                                        .type    = (eDataType)value.m_eType,
+                                                        .handler = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler,
+                                                        .dtor    = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor});
 }
 
 void CConfig::addSpecialConfigValue(const char* cat, const char* name, const CConfigValue& value) {
-    const auto IT = std::find_if(impl->specialCategoryDescriptors.begin(), impl->specialCategoryDescriptors.end(), [&](const auto& other) { return other->name == cat; });
+    const auto IT = std::ranges::find_if(impl->specialCategoryDescriptors, [&](const auto& other) { return other->name == cat; });
 
     if (IT == impl->specialCategoryDescriptors.end())
         throw "No such category";
 
     if ((eDataType)value.m_eType != CONFIGDATATYPE_CUSTOM && (eDataType)value.m_eType != CONFIGDATATYPE_STR)
-        IT->get()->defaultValues.emplace(name, SConfigDefaultValue{value.getValue(), (eDataType)value.m_eType});
+        IT->get()->defaultValues.emplace(name, SConfigDefaultValue{.data = value.getValue(), .type = (eDataType)value.m_eType});
     else if ((eDataType)value.m_eType == CONFIGDATATYPE_STR)
-        IT->get()->defaultValues.emplace(name, SConfigDefaultValue{std::string{std::any_cast<const char*>(value.getValue())}, (eDataType)value.m_eType});
+        IT->get()->defaultValues.emplace(name, SConfigDefaultValue{.data = std::string{std::any_cast<const char*>(value.getValue())}, .type = (eDataType)value.m_eType});
     else
         IT->get()->defaultValues.emplace(name,
-                                         SConfigDefaultValue{reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal, (eDataType)value.m_eType,
-                                                             reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler,
-                                                             reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor});
+                                         SConfigDefaultValue{.data    = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->defaultVal,
+                                                             .type    = (eDataType)value.m_eType,
+                                                             .handler = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->handler,
+                                                             .dtor    = reinterpret_cast<CConfigCustomValueType*>(value.m_pData)->dtor});
 
-    const auto CAT = std::find_if(impl->specialCategories.begin(), impl->specialCategories.end(), [cat, name](const auto& other) { return other->name == cat && other->isStatic; });
+    const auto CAT = std::ranges::find_if(impl->specialCategories, [cat](const auto& other) { return other->name == cat && other->isStatic; });
 
     if (CAT != impl->specialCategories.end())
         CAT->get()->values[name].defaultFrom(IT->get()->defaultValues[name]);
 }
 
 void CConfig::removeSpecialConfigValue(const char* cat, const char* name) {
-    const auto IT = std::find_if(impl->specialCategoryDescriptors.begin(), impl->specialCategoryDescriptors.end(), [&](const auto& other) { return other->name == cat; });
+    const auto IT = std::ranges::find_if(impl->specialCategoryDescriptors, [&](const auto& other) { return other->name == cat; });
 
     if (IT == impl->specialCategoryDescriptors.end())
         throw "No such category";
@@ -138,9 +166,8 @@ void CConfig::addSpecialCategory(const char* name, SSpecialCategoryOptions optio
     }
 
     // sort longest to shortest
-    std::sort(impl->specialCategories.begin(), impl->specialCategories.end(), [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); });
-    std::sort(impl->specialCategoryDescriptors.begin(), impl->specialCategoryDescriptors.end(),
-              [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); });
+    std::ranges::sort(impl->specialCategories, [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); });
+    std::ranges::sort(impl->specialCategoryDescriptors, [](const auto& a, const auto& b) -> int { return a->name.length() > b->name.length(); });
 }
 
 void CConfig::removeSpecialCategory(const char* name) {
@@ -195,7 +222,7 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
             if (!r.has_value() || !g.has_value() || !b.has_value())
                 return std::unexpected("failed parsing " + VALUEWITHOUTFUNC);
 
-            return a * (Hyprlang::INT)0x1000000 + r.value() * (Hyprlang::INT)0x10000 + g.value() * (Hyprlang::INT)0x100 + b.value();
+            return (a * (Hyprlang::INT)0x1000000) + (r.value() * (Hyprlang::INT)0x10000) + (g.value() * (Hyprlang::INT)0x100) + b.value();
         } else if (VALUEWITHOUTFUNC.length() == 8) {
             const auto RGBA = parseHex(VALUEWITHOUTFUNC);
 
@@ -203,7 +230,7 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
                 return RGBA;
 
             // now we need to RGBA -> ARGB. The config holds ARGB only.
-            return (RGBA.value() >> 8) + 0x1000000 * (RGBA.value() & 0xFF);
+            return (RGBA.value() >> 8) + (0x1000000 * (RGBA.value() & 0xFF));
         }
 
         return std::unexpected("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values");
@@ -224,7 +251,7 @@ static std::expected<int64_t, std::string> configStringToInt(const std::string&
             if (!r.has_value() || !g.has_value() || !b.has_value())
                 return std::unexpected("failed parsing " + VALUEWITHOUTFUNC);
 
-            return (Hyprlang::INT)0xFF000000 + r.value() * (Hyprlang::INT)0x10000 + g.value() * (Hyprlang::INT)0x100 + b.value();
+            return (Hyprlang::INT)0xFF000000 + (r.value() * (Hyprlang::INT)0x10000) + (g.value() * (Hyprlang::INT)0x100) + b.value();
         } else if (VALUEWITHOUTFUNC.length() == 6) {
             const auto RGB = parseHex(VALUEWITHOUTFUNC);
 
@@ -357,8 +384,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
                     // find suitable key
                     size_t biggest = 0;
                     for (auto& catt : impl->specialCategories) {
-                        if (catt->anonymousID > biggest)
-                            biggest = catt->anonymousID;
+                        biggest = std::max(catt->anonymousID, biggest);
                     }
 
                     biggest++;
@@ -417,7 +443,7 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
                 if (LHS.contains(" ") || RHS.contains(" "))
                     throw std::runtime_error("too many args");
 
-                VALUEIT->second.setFrom(SVector2D{std::stof(LHS), std::stof(RHS)});
+                VALUEIT->second.setFrom(SVector2D{.x = std::stof(LHS), .y = std::stof(RHS)});
             } catch (std::exception& e) {
                 result.setError(std::format("failed parsing a vec2: {}", e.what()));
                 return result;
@@ -451,14 +477,14 @@ CParseResult CConfig::configSetValueSafe(const std::string& command, const std::
 }
 
 CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& rhs, bool dynamic) {
-    auto IT = std::find_if(impl->variables.begin(), impl->variables.end(), [&](const auto& v) { return v.name == lhs.substr(1); });
+    auto IT = std::ranges::find_if(impl->variables, [&](const auto& v) { return v.name == lhs.substr(1); });
 
     if (IT != impl->variables.end())
         IT->value = rhs;
     else {
         impl->variables.push_back({lhs.substr(1), rhs});
-        std::sort(impl->variables.begin(), impl->variables.end(), [](const auto& lhs, const auto& rhs) { return lhs.name.length() > rhs.name.length(); });
-        IT = std::find_if(impl->variables.begin(), impl->variables.end(), [&](const auto& v) { return v.name == lhs.substr(1); });
+        std::ranges::sort(impl->variables, [](const auto& lhs, const auto& rhs) { return lhs.name.length() > rhs.name.length(); });
+        IT = std::ranges::find_if(impl->variables, [&](const auto& v) { return v.name == lhs.substr(1); });
     }
 
     if (dynamic) {
@@ -475,16 +501,121 @@ CParseResult CConfig::parseVariable(const std::string& lhs, const std::string& r
     return result;
 }
 
-void CConfigImpl::parseComment(const std::string& comment) {
+SVariable* CConfigImpl::getVariable(const std::string& name) {
+    for (auto& v : envVariables) {
+        if (v.name == name)
+            return &v;
+    }
+
+    for (auto& v : variables) {
+        if (v.name == name)
+            return &v;
+    }
+
+    return nullptr;
+}
+
+std::optional<std::string> CConfigImpl::parseComment(const std::string& comment) {
     const auto COMMENT = trim(comment);
 
     if (!COMMENT.starts_with("hyprlang"))
-        return;
+        return std::nullopt;
 
-    CVarList args(COMMENT, 0, 's', true);
+    CConstVarList args(COMMENT, 0, 's', true);
 
-    if (args[1] == "noerror")
-        currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set";
+    bool          negated         = false;
+    std::string   ifBlockVariable = "";
+
+    for (size_t i = 1; i < args.size(); ++i) {
+        if (args[i] == "noerror") {
+            if (negated)
+                currentFlags.noError = false;
+            else
+                currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set" || args[2].empty();
+            break;
+        }
+
+        if (args[i] == "endif") {
+            if (!currentFlags.inAnIfBlock)
+                return "stray endif";
+            currentFlags.inAnIfBlock = false;
+            break;
+        }
+
+        if (args[i] == "if") {
+            ifBlockVariable = args[++i];
+            break;
+        }
+    }
+
+    if (!ifBlockVariable.empty()) {
+        if (currentFlags.inAnIfBlock)
+            return "nested if statements are not allowed";
+
+        if (ifBlockVariable.starts_with("!")) {
+            negated = true;
+            ifBlockVariable = ifBlockVariable.substr(1);
+        }
+
+        currentFlags.inAnIfBlock = true;
+
+        if (const auto VAR = getVariable(ifBlockVariable); VAR)
+            currentFlags.ifBlockFailed = negated ? VAR->truthy() : !VAR->truthy();
+        else
+            currentFlags.ifBlockFailed = !negated;
+    }
+
+    return std::nullopt;
+}
+
+std::expected<float, std::string> CConfigImpl::parseExpression(const std::string& s) {
+    // for now, we only support very basic expressions.
+    // + - * / and only one per $()
+    // TODO: something better
+
+    if (s.empty())
+        return std::unexpected("Expression is empty");
+
+    CConstVarList args(s, 0, 's', true);
+
+    if (args[1] != "+" && args[1] != "-" && args[1] != "*" && args[1] != "/")
+        return std::unexpected("Invalid expression type: supported +, -, *, /");
+
+    auto  LHS_VAR = std::ranges::find_if(variables, [&](const auto& v) { return v.name == args[0]; });
+    auto  RHS_VAR = std::ranges::find_if(variables, [&](const auto& v) { return v.name == args[2]; });
+
+    float left  = 0;
+    float right = 0;
+
+    if (LHS_VAR != variables.end()) {
+        try {
+            left = std::stof(LHS_VAR->value);
+        } catch (...) { return std::unexpected("Failed to parse expression: value 1 holds a variable that does not look like a number"); }
+    } else {
+        try {
+            left = std::stof(std::string{args[0]});
+        } catch (...) { return std::unexpected("Failed to parse expression: value 1 does not look like a number or the variable doesn't exist"); }
+    }
+
+    if (RHS_VAR != variables.end()) {
+        try {
+            right = std::stof(RHS_VAR->value);
+        } catch (...) { return std::unexpected("Failed to parse expression: value 1 holds a variable that does not look like a number"); }
+    } else {
+        try {
+            right = std::stof(std::string{args[2]});
+        } catch (...) { return std::unexpected("Failed to parse expression: value 1 does not look like a number or the variable doesn't exist"); }
+    }
+
+    switch (args[1][0]) {
+        case '+': return left + right;
+        case '-': return left - right;
+        case '*': return left * right;
+        case '/': return left / right;
+        default: break;
+    }
+
+    return std::unexpected("Unknown error while parsing expression");
 }
 
 CParseResult CConfig::parseLine(std::string line, bool dynamic) {
@@ -495,10 +626,15 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
     auto commentPos = line.find('#');
 
     if (commentPos == 0) {
-        impl->parseComment(line.substr(1));
+        const auto COMMENT_RESULT = impl->parseComment(line.substr(1));
+        if (COMMENT_RESULT.has_value())
+            result.setError(*COMMENT_RESULT);
         return result;
     }
 
+    if (impl->currentFlags.inAnIfBlock && impl->currentFlags.ifBlockFailed)
+        return result;
+
     size_t lastHashPos = 0;
 
     while (commentPos != std::string::npos) {
@@ -548,6 +684,8 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
         // limit unwrapping iterations to 100. if exceeds, raise error
         for (size_t i = 0; i < 100; ++i) {
             bool anyMatch = false;
+
+            // parse variables
             for (auto& var : impl->variables) {
                 // don't parse LHS variables if this is a variable...
                 const auto LHSIT = ISVARIABLE ? std::string::npos : LHS.find("$" + var.name);
@@ -566,6 +704,47 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
                 anyMatch = true;
             }
 
+            // parse expressions {{somevar + 2}}
+            // We only support single expressions for now
+            while (RHS.contains("{{")) {
+                auto firstUnescaped = RHS.find("{{");
+                // Keep searching until non-escaped expression start is found
+                while (firstUnescaped > 0) {
+                    // Special check to avoid undefined behaviour with std::basic_string::find_last_not_of
+                    auto amountSkipped = 0;
+                    for (int i = firstUnescaped - 1; i >= 0; i--) {
+                        if (RHS.at(i) != '\\')
+                            break;
+                        amountSkipped++;
+                    }
+                    // No escape chars, or even escape chars. means they escaped themselves.
+                    if (amountSkipped % 2 == 0)
+                        break;
+                    // Continue searching for next valid expression start.
+                    firstUnescaped = RHS.find("{{", firstUnescaped + 1);
+                    // Break if the next match is never found
+                    if (firstUnescaped == std::string::npos)
+                        break;
+                }
+                // Real match was never found.
+                if (firstUnescaped == std::string::npos)
+                    break;
+                const auto BEGIN_EXPR = firstUnescaped;
+                // "}}" doesnt need escaping. Would be invalid expression anyways.
+                const auto END_EXPR = RHS.find("}}", BEGIN_EXPR + 2);
+                if (END_EXPR != std::string::npos) {
+                    // try to parse the expression
+                    const auto RESULT = impl->parseExpression(RHS.substr(BEGIN_EXPR + 2, END_EXPR - BEGIN_EXPR - 2));
+                    if (!RESULT.has_value()) {
+                        result.setError(RESULT.error());
+                        return result;
+                    }
+
+                    RHS = RHS.substr(0, BEGIN_EXPR) + std::format("{}", RESULT.value()) + RHS.substr(END_EXPR + 2);
+                } else
+                    break;
+            }
+
             if (!anyMatch)
                 break;
 
@@ -578,6 +757,27 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
         if (ISVARIABLE)
             return parseVariable(LHS, RHS, dynamic);
 
+        // Removing escape chars. -- in the future, maybe map all the chars that can be escaped.
+        // Right now only expression parsing has escapeable chars
+        const char                ESCAPE_CHAR = '\\';
+        const std::array<char, 2> ESCAPE_SET{'{', '}'};
+        for (size_t i = 0; RHS.length() != 0 && i < RHS.length() - 1; i++) {
+            if (RHS.at(i) != ESCAPE_CHAR)
+                continue;
+            //if escaping an escape, remove and skip the next char
+            if (RHS.at(i + 1) == ESCAPE_CHAR) {
+                RHS.erase(i, 1);
+                continue;
+            }
+            //checks if any of the chars were escapable.
+            for (const auto& ESCAPABLE_CHAR : ESCAPE_SET) {
+                if (RHS.at(i + 1) != ESCAPABLE_CHAR)
+                    continue;
+                RHS.erase(i--, 1);
+                break;
+            }
+        }
+
         bool found = false;
 
         for (auto& h : impl->handlers) {
@@ -593,7 +793,7 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
                 size_t idx   = 0;
                 size_t depth = 0;
 
-                while ((colon = HANDLERNAME.find(":", idx)) != std::string::npos && impl->categories.size() > depth) {
+                while ((colon = HANDLERNAME.find(':', idx)) != std::string::npos && impl->categories.size() > depth) {
                     auto actual = HANDLERNAME.substr(idx, colon - idx);
 
                     if (actual != impl->categories[depth])
@@ -695,22 +895,35 @@ CParseResult CConfig::parse() {
 CParseResult CConfig::parseRawStream(const std::string& stream) {
     CParseResult      result;
 
-    std::string       line    = "";
-    int               linenum = 1;
+    int               rawLineNum = 0;
+    int               lineNum    = 0;
 
     std::stringstream str(stream);
 
-    while (std::getline(str, line)) {
-        const auto RET = parseLine(line);
+    while (true) {
+        const auto line = getNextLine(str, rawLineNum, lineNum);
+
+        if (!line) {
+            switch (line.error()) {
+                case GETNEXTLINEFAILURE_EOF: break;
+                case GETNEXTLINEFAILURE_BACKSLASH:
+                    if (!impl->parseError.empty())
+                        impl->parseError += "\n";
+                    impl->parseError += std::format("Config error: Last line ends with backslash");
+                    result.setError(impl->parseError);
+                    break;
+            }
+            break;
+        }
+
+        const auto RET = parseLine(line.value());
 
         if (RET.error && (impl->parseError.empty() || impl->configOptions.throwAllErrors)) {
             if (!impl->parseError.empty())
                 impl->parseError += "\n";
-            impl->parseError += std::format("Config error at line {}: {}", linenum, RET.errorStdString);
+            impl->parseError += std::format("Config error at line {}: {}", lineNum, RET.errorStdString);
             result.setError(impl->parseError);
         }
-
-        ++linenum;
     }
 
     if (!impl->categories.empty()) {
@@ -736,21 +949,33 @@ CParseResult CConfig::parseFile(const char* file) {
         return result;
     }
 
-    std::string line    = "";
-    int         linenum = 1;
+    int rawLineNum = 0;
+    int lineNum    = 0;
 
-    while (std::getline(iffile, line)) {
+    while (true) {
+        const auto line = getNextLine(iffile, rawLineNum, lineNum);
 
-        const auto RET = parseLine(line);
+        if (!line) {
+            switch (line.error()) {
+                case GETNEXTLINEFAILURE_EOF: break;
+                case GETNEXTLINEFAILURE_BACKSLASH:
+                    if (!impl->parseError.empty())
+                        impl->parseError += "\n";
+                    impl->parseError += std::format("Config error in file {}: Last line ends with backslash", file);
+                    result.setError(impl->parseError);
+                    break;
+            }
+            break;
+        }
+
+        const auto RET = parseLine(line.value());
 
         if (!impl->currentFlags.noError && RET.error && (impl->parseError.empty() || impl->configOptions.throwAllErrors)) {
             if (!impl->parseError.empty())
                 impl->parseError += "\n";
-            impl->parseError += std::format("Config error in file {} at line {}: {}", file, linenum, RET.errorStdString);
+            impl->parseError += std::format("Config error in file {} at line {}: {}", file, lineNum, RET.errorStdString);
             result.setError(impl->parseError);
         }
-
-        ++linenum;
     }
 
     iffile.close();
@@ -811,7 +1036,7 @@ CConfigValue* CConfig::getSpecialConfigValuePtr(const char* category, const char
 void CConfig::registerHandler(PCONFIGHANDLERFUNC func, const char* name, SHandlerOptions options_) {
     SHandlerOptions options;
     std::memcpy(&options, &options_, seekABIStructSize(&options_, 0, sizeof(SHandlerOptions)));
-    impl->handlers.push_back(SHandler{name, options, func});
+    impl->handlers.push_back(SHandler{.name = name, .options = options, .func = func});
 }
 
 void CConfig::unregisterHandler(const char* name) {
diff --git a/src/config.hpp b/src/config.hpp
index 46efaf5..63d9840 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -4,6 +4,7 @@
 #include <string>
 #include <vector>
 #include <memory>
+#include <expected>
 
 struct SHandler {
     std::string                  name = "";
@@ -22,6 +23,10 @@ struct SVariable {
     };
 
     std::vector<SVarLine> linesContainingVar; // for dynamic updates
+
+    bool                  truthy() {
+        return value.length() > 0;
+    }
 };
 
 // remember to also edit CConfigValue if editing
@@ -65,6 +70,11 @@ struct SSpecialCategory {
     size_t anonymousID = 0;
 };
 
+enum eGetNextLineFailure : uint8_t {
+    GETNEXTLINEFAILURE_EOF = 0,
+    GETNEXTLINEFAILURE_BACKSLASH,
+};
+
 class CConfigImpl {
   public:
     std::string path         = "";
@@ -89,9 +99,13 @@ class CConfigImpl {
 
     Hyprlang::SConfigOptions                                 configOptions;
 
-    void                                                     parseComment(const std::string& comment);
+    std::optional<std::string>                               parseComment(const std::string& comment);
+    std::expected<float, std::string>                        parseExpression(const std::string& s);
+    SVariable*                                               getVariable(const std::string& name);
 
     struct {
-        bool noError = false;
+        bool noError       = false;
+        bool inAnIfBlock   = false;
+        bool ifBlockFailed = false;
     } currentFlags;
-};
\ No newline at end of file
+};
diff --git a/tests/config/config.conf b/tests/config/config.conf
index c8ea739..e3c372f 100644
--- a/tests/config/config.conf
+++ b/tests/config/config.conf
@@ -14,23 +14,54 @@ $MY_VAR = 1337
 $MY_VAR_2 = $MY_VAR
 testVar = $MY_VAR$MY_VAR_2
 
+$EXPR_VAR = {{MY_VAR + 2}}
+testExpr = {{EXPR_VAR - 4}}
+
+testEscapedExpr = \{{testInt + 7}}
+testEscapedExpr2 = {\{testInt + 7}}
+testEscapedExpr3 = \{\{3 + 8}}
+testEscapedEscape = \\{{10 - 5}}
+testMixedEscapedExpression = {{8 - 10}} \{{ \{{50 + 50}} / \{{10 * 5}} }} 
+testMixedEscapedExpression2 = {\{8\\{{10 + 3}}}} should equal "\{{8\13}}"
+
+$ESCAPED_TEXT = \{{10 + 10}}
+testImbeddedEscapedExpression = $ESCAPED_TEXT
+
+$MOVING_VAR = 1000
+$DYNAMIC_EXPRESSION = moved: {{$MOVING_VAR / 2}} expr: \{{$MOVING_VAR / 2}}
+testDynamicEscapedExpression = \{{ $DYNAMIC_EXPRESSION }}
+
 testEnv = $SHELL
 
 source = ./colors.conf
 
 customType = abc
 
+# hyprlang if !NONEXISTENT_VAR
+
 testStringColon = ee:ee:ee
 
+# hyprlang endif
+
 # hyprlang noerror true
 
 errorVariable = true
 
 # hyprlang noerror false
 
+# hyprlang if NONEXISTENT_VAR
+
+customType = bcd
+
+# hyprlang endif
+
+# hyprlang if MY_VAR
+
 categoryKeyword = oops, this one shouldn't call the handler, not fun
 testUseKeyword = yes
 
+# hyprlang endif
+
 testCategory {
     testValueInt = 123456
     testValueHex = 0xF
@@ -90,6 +121,11 @@ flagsStuff {
     value = 2
 }
 
+multiline = \
+    very \
+        long \
+            command
+
 testCategory:testValueHex = 0xFFfFaAbB
 
 $RECURSIVE1 = a
@@ -103,4 +139,3 @@ doABarrelRoll = woohoo, some, params # Funny!
 flagsabc = test
 #doSomethingFunny = 1, 2, 3, 4 # Funnier!
 #testSpaces =                abc                             ,                                  def                                  # many spaces, should be trimmed
-
diff --git a/tests/config/multiline-errors.conf b/tests/config/multiline-errors.conf
new file mode 100644
index 0000000..9ea2c28
--- /dev/null
+++ b/tests/config/multiline-errors.conf
@@ -0,0 +1,20 @@
+# Careful when modifying this file. Line numbers are part of the test.
+
+multiline = \
+    one \
+    two \
+    three
+
+# Line numbers reported in errors should match the actual line numbers of the source file
+# even after multi-line configs. Any errors reported should use the line number of the
+# first line of any multi-line config.
+
+this \
+ should \
+ cause \
+ error \
+ on \
+ line \
+ 12
+
+# A config file cannot end with a bashslash because we are expecting another line! Even in a comment! \
diff --git a/tests/fuzz/main.cpp b/tests/fuzz/main.cpp
index 3f6effd..b0bfe00 100644
--- a/tests/fuzz/main.cpp
+++ b/tests/fuzz/main.cpp
@@ -11,7 +11,7 @@ std::string garbage() {
 
     std::string chars;
     for (int i = 0; i < len; ++i) {
-        chars += rand() % 254 + 1;
+        chars += std::to_string((rand() % 254) + 1);
     }
 
     return chars;
diff --git a/tests/parse/main.cpp b/tests/parse/main.cpp
old mode 100755
new mode 100644
index 56b40d8..dbf5e70
--- a/tests/parse/main.cpp
+++ b/tests/parse/main.cpp
@@ -48,7 +48,7 @@ static Hyprlang::CParseResult handleFlagsTest(const char* COMMAND, const char* V
 }
 
 static Hyprlang::CParseResult handleCategoryKeyword(const char* COMMAND, const char* VALUE) {
-    categoryKeywordActualValues.push_back(VALUE);
+    categoryKeywordActualValues.emplace_back(VALUE);
 
     return Hyprlang::CParseResult();
 }
@@ -107,8 +107,17 @@ int main(int argc, char** argv, char** envp) {
 
         // setup config
         config.addConfigValue("testInt", (Hyprlang::INT)0);
+        config.addConfigValue("testExpr", (Hyprlang::INT)0);
+        config.addConfigValue("testEscapedExpr", "");
+        config.addConfigValue("testEscapedExpr2", "");
+        config.addConfigValue("testEscapedExpr3", "");
+        config.addConfigValue("testEscapedEscape", "");
+        config.addConfigValue("testMixedEscapedExpression", "");
+        config.addConfigValue("testMixedEscapedExpression2", "");
+        config.addConfigValue("testImbeddedEscapedExpression", "");
+        config.addConfigValue("testDynamicEscapedExpression", "");
         config.addConfigValue("testFloat", 0.F);
-        config.addConfigValue("testVec", Hyprlang::SVector2D{69, 420});
+        config.addConfigValue("testVec", Hyprlang::SVector2D{.x = 69, .y = 420});
         config.addConfigValue("testString", "");
         config.addConfigValue("testStringColon", "");
         config.addConfigValue("testEnv", "");
@@ -131,25 +140,27 @@ int main(int argc, char** argv, char** envp) {
         config.addConfigValue("myColors:random", (Hyprlang::INT)0);
         config.addConfigValue("customType", {Hyprlang::CConfigCustomValueType{&handleCustomValueSet, &handleCustomValueDestroy, "def"}});
 
-        config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {false});
-        config.registerHandler(&handleFlagsTest, "flags", {true});
-        config.registerHandler(&handleSource, "source", {false});
-        config.registerHandler(&handleTestIgnoreKeyword, "testIgnoreKeyword", {false});
-        config.registerHandler(&handleTestUseKeyword, ":testUseKeyword", {false});
-        config.registerHandler(&handleNoop, "testCategory:testUseKeyword", {false});
-        config.registerHandler(&handleCategoryKeyword, "testCategory:categoryKeyword", {false});
+        config.registerHandler(&handleDoABarrelRoll, "doABarrelRoll", {.allowFlags = false});
+        config.registerHandler(&handleFlagsTest, "flags", {.allowFlags = true});
+        config.registerHandler(&handleSource, "source", {.allowFlags = false});
+        config.registerHandler(&handleTestIgnoreKeyword, "testIgnoreKeyword", {.allowFlags = false});
+        config.registerHandler(&handleTestUseKeyword, ":testUseKeyword", {.allowFlags = false});
+        config.registerHandler(&handleNoop, "testCategory:testUseKeyword", {.allowFlags = false});
+        config.registerHandler(&handleCategoryKeyword, "testCategory:categoryKeyword", {.allowFlags = false});
 
-        config.addSpecialCategory("special", {"key"});
+        config.addSpecialCategory("special", {.key = "key"});
         config.addSpecialConfigValue("special", "value", (Hyprlang::INT)0);
 
-        config.addSpecialCategory("specialAnonymous", {nullptr, false, true});
+        config.addSpecialCategory("specialAnonymous", {.key = nullptr, .ignoreMissing = false, .anonymousKeyBased = true});
         config.addSpecialConfigValue("specialAnonymous", "value", (Hyprlang::INT)0);
 
+        config.addConfigValue("multiline", "");
+
         config.commence();
 
-        config.addSpecialCategory("specialGeneric:one", {nullptr, true});
+        config.addSpecialCategory("specialGeneric:one", {.key = nullptr, .ignoreMissing = true});
         config.addSpecialConfigValue("specialGeneric:one", "value", (Hyprlang::INT)0);
-        config.addSpecialCategory("specialGeneric:two", {nullptr, true});
+        config.addSpecialCategory("specialGeneric:two", {.key = nullptr, .ignoreMissing = true});
         config.addSpecialConfigValue("specialGeneric:two", "value", (Hyprlang::INT)0);
 
         const Hyprlang::CConfigValue copyTest = {(Hyprlang::INT)1};
@@ -167,7 +178,7 @@ int main(int argc, char** argv, char** envp) {
         std::cout << " → Testing values\n";
         EXPECT(std::any_cast<int64_t>(config.getConfigValue("testInt")), 123);
         EXPECT(std::any_cast<float>(config.getConfigValue("testFloat")), 123.456f);
-        auto EXP = Hyprlang::SVector2D{69, 420};
+        auto EXP = Hyprlang::SVector2D{.x = 69, .y = 420};
         EXPECT(std::any_cast<Hyprlang::SVector2D>(config.getConfigValue("testVec")), EXP);
         EXPECT(std::any_cast<const char*>(config.getConfigValue("testString")), std::string{"Hello World! # This is not a comment!"});
         EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringQuotes")), std::string{"\"Hello World!\""});
@@ -195,6 +206,22 @@ int main(int argc, char** argv, char** envp) {
         EXPECT(*T3, EXP);
         EXPECT(*T4, "Hello World! # This is not a comment!");
 
+        // test expressions
+        std::cout << " → Testing expressions\n";
+        EXPECT(std::any_cast<int64_t>(config.getConfigValue("testExpr")), 1335);
+
+        // test expression escape
+        std::cout << " → Testing expression escapes\n";
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedExpr")), std::string{"{{testInt + 7}}"});
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedExpr2")), std::string{"{{testInt + 7}}"});
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedExpr3")), std::string{"{{3 + 8}}"});
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testEscapedEscape")), std::string{"\\5"});
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testMixedEscapedExpression")), std::string{"-2 {{ {{50 + 50}} / {{10 * 5}} }}"});
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testMixedEscapedExpression2")), std::string{"{{8\\13}} should equal \"{{8\\13}}\""});
+
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testImbeddedEscapedExpression")), std::string{"{{10 + 10}}"});
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testDynamicEscapedExpression")), std::string{"{{ moved: 500 expr: {{1000 / 2}} }}"});
+
         // test static values
         std::cout << " → Testing static values\n";
         static auto* const PTESTINT = config.getConfigValuePtr("testInt")->getDataStaticPtr();
@@ -241,6 +268,14 @@ int main(int argc, char** argv, char** envp) {
         EXPECT(config.parseDynamic("$RECURSIVE1 = d").error, false);
         EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringRecursive")), std::string{"dbc"});
 
+        // test expression escape with dynamic vars
+        EXPECT(config.parseDynamic("$MOVING_VAR = 500").error, false);
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("testDynamicEscapedExpression")), std::string{"{{ moved: 250 expr: {{500 / 2}} }}"});
+
+        // test dynamic exprs
+        EXPECT(config.parseDynamic("testExpr = {{EXPR_VAR * 2}}").error, false);
+        EXPECT(std::any_cast<int64_t>(config.getConfigValue("testExpr")), 1339L * 2);
+
         // test env variables
         std::cout << " → Testing env variables\n";
         EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv")), std::string{getenv("SHELL")});
@@ -279,6 +314,9 @@ int main(int argc, char** argv, char** envp) {
         std::cout << " → Testing custom types\n";
         EXPECT(*reinterpret_cast<int64_t*>(std::any_cast<void*>(config.getConfigValue("customType"))), (Hyprlang::INT)1);
 
+        // test multiline config
+        EXPECT(std::any_cast<const char*>(config.getConfigValue("multiline")), std::string{"very        long            command"});
+
         std::cout << " → Testing error.conf\n";
         Hyprlang::CConfig errorConfig("./config/error.conf", {.verifyOnly = true, .throwAllErrors = true});
 
@@ -307,6 +345,17 @@ int main(int argc, char** argv, char** envp) {
         EXPECT(ERRORS2.error, true);
         const auto ERRORSTR2 = std::string{ERRORS2.getError()};
         EXPECT(std::count(ERRORSTR2.begin(), ERRORSTR2.end(), '\n'), 9 - 1);
+
+        Hyprlang::CConfig multilineErrorConfig("./config/multiline-errors.conf", {.verifyOnly = true, .throwAllErrors = true});
+        multilineErrorConfig.commence();
+        const auto ERRORS3 = multilineErrorConfig.parse();
+        EXPECT(ERRORS3.error, true);
+        const auto ERRORSTR3 = std::string{ERRORS3.getError()};
+
+        // Error on line 12
+        EXPECT(ERRORSTR3.contains("12"), true);
+        // Backslash at end of file
+        EXPECT(ERRORSTR3.contains("backslash"), true);
     } catch (const char* e) {
         std::cout << Colors::RED << "Error: " << Colors::RESET << e << "\n";
         return 1;

Attachment: signature.asc
Description: PGP signature

Reply via email to