Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package hyprlang for openSUSE:Factory 
checked in at 2025-09-15 19:53:19
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/hyprlang (Old)
 and      /work/SRC/openSUSE:Factory/.hyprlang.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "hyprlang"

Mon Sep 15 19:53:19 2025 rev:7 rq:1304801 version:0.6.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/hyprlang/hyprlang.changes        2025-01-12 
11:28:39.162211761 +0100
+++ /work/SRC/openSUSE:Factory/.hyprlang.new.1977/hyprlang.changes      
2025-09-15 19:57:24.514444652 +0200
@@ -1,0 +2,16 @@
+Wed Sep 10 20:10:57 UTC 2025 - Marcus Rueckert <[email protected]>
+
+- Update to version 0.6.4:
+  - core: add support for conditional statements
+  - Add ability to escape {{EXPRESSION}} syntax from #75 by
+    @JonathanSteininger in #76
+
+-------------------------------------------------------------------
+Mon May 19 01:26:01 UTC 2025 - Soc Virnyl Estela 
<[email protected]>
+
+- Update to version 0.6.3:
+  * parser: change arithmetic syntax
+  * parser: change expression syntax to avoid bash clashes
+  * parser: add support for basic arithmetic
+
+-------------------------------------------------------------------

Old:
----
  hyprlang-0.6.0.tar.gz

New:
----
  hyprlang-0.6.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ hyprlang.spec ++++++
--- /var/tmp/diff_new_pack.H0T3Fu/_old  2025-09-15 19:57:24.986464471 +0200
+++ /var/tmp/diff_new_pack.H0T3Fu/_new  2025-09-15 19:57:24.990464639 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package hyprlang
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %define sover 2
 
 Name:           hyprlang
-Version:        0.6.0
+Version:        0.6.4
 License:        LGPL-3.0-only
 Release:        0
 Summary:        A configuration language for Linux applications
@@ -75,6 +75,5 @@
 %{_libdir}/pkgconfig/%{name}.pc
 
 %files -n lib%{name}%{sover}
-%{_libdir}/lib%{name}.so.%{sover}
-%{_libdir}/lib%{name}.so.%{version}
+%{_libdir}/lib%{name}.so.*
 

++++++ hyprlang-0.6.0.tar.gz -> hyprlang-0.6.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/.clang-tidy 
new/hyprlang-0.6.4/.clang-tidy
--- old/hyprlang-0.6.0/.clang-tidy      1970-01-01 01:00:00.000000000 +0100
+++ new/hyprlang-0.6.4/.clang-tidy      2025-07-25 21:26:39.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/.github/workflows/nix.yml 
new/hyprlang-0.6.4/.github/workflows/nix.yml
--- old/hyprlang-0.6.0/.github/workflows/nix.yml        2024-12-13 
21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/.github/workflows/nix.yml        2025-07-25 
21:26:39.000000000 +0200
@@ -13,8 +13,35 @@
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/CMakeLists.txt 
new/hyprlang-0.6.4/CMakeLists.txt
--- old/hyprlang-0.6.0/CMakeLists.txt   2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/CMakeLists.txt   2025-07-25 21:26:39.000000000 +0200
@@ -28,9 +28,16 @@
 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 @@
 
 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/README.md new/hyprlang-0.6.4/README.md
--- old/hyprlang-0.6.0/README.md        2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/README.md        2025-07-25 21:26:39.000000000 +0200
@@ -54,4 +54,4 @@
 
 ### 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/VERSION new/hyprlang-0.6.4/VERSION
--- old/hyprlang-0.6.0/VERSION  2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/VERSION  2025-07-25 21:26:39.000000000 +0200
@@ -1 +1 @@
-0.6.0
+0.6.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/flake.lock 
new/hyprlang-0.6.4/flake.lock
--- old/hyprlang-0.6.0/flake.lock       2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/flake.lock       2025-07-25 21:26:39.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/flake.nix new/hyprlang-0.6.4/flake.nix
--- old/hyprlang-0.6.0/flake.nix        2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/flake.nix        2025-07-25 21:26:39.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/include/hyprlang.hpp 
new/hyprlang-0.6.4/include/hyprlang.hpp
--- old/hyprlang-0.6.0/include/hyprlang.hpp     2024-12-13 21:48:31.000000000 
+0100
+++ new/hyprlang-0.6.4/include/hyprlang.hpp     2025-07-25 21:26:39.000000000 
+0200
@@ -50,7 +50,7 @@
     typedef CConfigCustomValueType CUSTOMTYPE;
 
     /*!
-        A very simple vector type 
+        A very simple vector type
     */
     struct SVector2D {
         float x = 0, y = 0;
@@ -95,12 +95,12 @@
         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 @@
     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 @@
         /*!
             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 @@
 
         /*!
             \since 0.3.0
-            
+
             a flag to notify whether this value has been set explicitly by the 
user,
             or not.
         */
@@ -305,7 +305,7 @@
         ~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 @@
 
         /*!
             \since 0.3.0
-            
-            Unregister a handler.        
+
+            Unregister a handler.
         */
         void unregisterHandler(const char* name);
 
@@ -362,14 +362,14 @@
         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 @@
 
         /*!
             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 @@
 #undef HYPRLANG_END_MAGIC
 #endif
 
-#endif
\ No newline at end of file
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/src/common.cpp 
new/hyprlang-0.6.4/src/common.cpp
--- old/hyprlang-0.6.0/src/common.cpp   2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/src/common.cpp   2025-07-25 21:26:39.000000000 +0200
@@ -1,6 +1,6 @@
 #include "public.hpp"
 #include "config.hpp"
-#include <string.h>
+#include <cstring>
 
 using namespace Hyprlang;
 
@@ -30,38 +30,28 @@
     }
 }
 
-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 @@
     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 @@
             throw "bad defaultFrom type";
         }
     }
-}
\ No newline at end of file
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/src/config.cpp 
new/hyprlang-0.6.4/src/config.cpp
--- old/hyprlang-0.6.0/src/config.cpp   2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/src/config.cpp   2025-07-25 21:26:39.000000000 +0200
@@ -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 @@
 #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 @@
         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 @@
         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 @@
     }
 
     // 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 @@
             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 @@
                 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 @@
             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 @@
                     // 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 @@
                 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::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 @@
     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;
+
+    CConstVarList args(COMMENT, 0, 's', true);
 
-    CVarList args(COMMENT, 0, 's', true);
+    bool          negated         = false;
+    std::string   ifBlockVariable = "";
 
-    if (args[1] == "noerror")
-        currentFlags.noError = args[2] == "true" || args[2] == "yes" || 
args[2] == "enable" || args[2] == "enabled" || args[2] == "set";
+    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 @@
     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 @@
         // 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 @@
                 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 @@
         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 @@
                 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::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 @@
         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 @@
 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/src/config.hpp 
new/hyprlang-0.6.4/src/config.hpp
--- old/hyprlang-0.6.0/src/config.hpp   2024-12-13 21:48:31.000000000 +0100
+++ new/hyprlang-0.6.4/src/config.hpp   2025-07-25 21:26:39.000000000 +0200
@@ -4,6 +4,7 @@
 #include <string>
 #include <vector>
 #include <memory>
+#include <expected>
 
 struct SHandler {
     std::string                  name = "";
@@ -22,6 +23,10 @@
     };
 
     std::vector<SVarLine> linesContainingVar; // for dynamic updates
+
+    bool                  truthy() {
+        return value.length() > 0;
+    }
 };
 
 // remember to also edit CConfigValue if editing
@@ -65,6 +70,11 @@
     size_t anonymousID = 0;
 };
 
+enum eGetNextLineFailure : uint8_t {
+    GETNEXTLINEFAILURE_EOF = 0,
+    GETNEXTLINEFAILURE_BACKSLASH,
+};
+
 class CConfigImpl {
   public:
     std::string path         = "";
@@ -89,9 +99,13 @@
 
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/tests/config/config.conf 
new/hyprlang-0.6.4/tests/config/config.conf
--- old/hyprlang-0.6.0/tests/config/config.conf 2024-12-13 21:48:31.000000000 
+0100
+++ new/hyprlang-0.6.4/tests/config/config.conf 2025-07-25 21:26:39.000000000 
+0200
@@ -14,23 +14,54 @@
 $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 @@
     value = 2
 }
 
+multiline = \
+    very \
+        long \
+            command
+
 testCategory:testValueHex = 0xFFfFaAbB
 
 $RECURSIVE1 = a
@@ -103,4 +139,3 @@
 flagsabc = test
 #doSomethingFunny = 1, 2, 3, 4 # Funnier!
 #testSpaces =                abc                             ,                 
                 def                                  # many spaces, should be 
trimmed
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/tests/config/multiline-errors.conf 
new/hyprlang-0.6.4/tests/config/multiline-errors.conf
--- old/hyprlang-0.6.0/tests/config/multiline-errors.conf       1970-01-01 
01:00:00.000000000 +0100
+++ new/hyprlang-0.6.4/tests/config/multiline-errors.conf       2025-07-25 
21:26:39.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/tests/fuzz/main.cpp 
new/hyprlang-0.6.4/tests/fuzz/main.cpp
--- old/hyprlang-0.6.0/tests/fuzz/main.cpp      2024-12-13 21:48:31.000000000 
+0100
+++ new/hyprlang-0.6.4/tests/fuzz/main.cpp      2025-07-25 21:26:39.000000000 
+0200
@@ -11,7 +11,7 @@
 
     std::string chars;
     for (int i = 0; i < len; ++i) {
-        chars += rand() % 254 + 1;
+        chars += std::to_string((rand() % 254) + 1);
     }
 
     return chars;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hyprlang-0.6.0/tests/parse/main.cpp 
new/hyprlang-0.6.4/tests/parse/main.cpp
--- old/hyprlang-0.6.0/tests/parse/main.cpp     2024-12-13 21:48:31.000000000 
+0100
+++ new/hyprlang-0.6.4/tests/parse/main.cpp     2025-07-25 21:26:39.000000000 
+0200
@@ -48,7 +48,7 @@
 }
 
 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 @@
 
         // 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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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;

Reply via email to