parser: add support for basic arithmetic

Adds support for  expressions that take left and right hand side and an operation (+-*/) -> e.g.

fixes #67
This commit is contained in:
Vaxry 2025-05-07 17:50:22 +01:00
parent 397600c42b
commit 6726cfd54b
Signed by: vaxry
GPG key ID: 665806380871D640
5 changed files with 89 additions and 6 deletions

View file

@ -37,7 +37,7 @@ add_compile_options(
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
find_package(PkgConfig REQUIRED) 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") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/hyprlang.hpp")

View file

@ -12,6 +12,7 @@
#include <cstring> #include <cstring>
#include <hyprutils/string/VarList.hpp> #include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
#include <hyprutils/string/ConstVarList.hpp>
using namespace Hyprlang; using namespace Hyprlang;
using namespace Hyprutils::String; using namespace Hyprutils::String;
@ -510,6 +511,56 @@ void CConfigImpl::parseComment(const std::string& comment) {
currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set"; currentFlags.noError = args[2] == "true" || args[2] == "yes" || args[2] == "enable" || args[2] == "enabled" || args[2] == "set";
} }
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) { CParseResult CConfig::parseLine(std::string line, bool dynamic) {
CParseResult result; CParseResult result;
@ -571,6 +622,8 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
// limit unwrapping iterations to 100. if exceeds, raise error // limit unwrapping iterations to 100. if exceeds, raise error
for (size_t i = 0; i < 100; ++i) { for (size_t i = 0; i < 100; ++i) {
bool anyMatch = false; bool anyMatch = false;
// parse variables
for (auto& var : impl->variables) { for (auto& var : impl->variables) {
// don't parse LHS variables if this is a variable... // don't parse LHS variables if this is a variable...
const auto LHSIT = ISVARIABLE ? std::string::npos : LHS.find("$" + var.name); const auto LHSIT = ISVARIABLE ? std::string::npos : LHS.find("$" + var.name);
@ -589,6 +642,24 @@ CParseResult CConfig::parseLine(std::string line, bool dynamic) {
anyMatch = true; anyMatch = true;
} }
// parse expressions $(somevar + 2)
// We only support single expressions for now
while (RHS.contains("$(")) {
const auto BEGIN_EXPR = RHS.find("$(");
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 + 1);
} else
break;
}
if (!anyMatch) if (!anyMatch)
break; break;
@ -728,8 +799,7 @@ CParseResult CConfig::parseRawStream(const std::string& stream) {
if (!line) { if (!line) {
switch (line.error()) { switch (line.error()) {
case GETNEXTLINEFAILURE_EOF: case GETNEXTLINEFAILURE_EOF: break;
break;
case GETNEXTLINEFAILURE_BACKSLASH: case GETNEXTLINEFAILURE_BACKSLASH:
if (!impl->parseError.empty()) if (!impl->parseError.empty())
impl->parseError += "\n"; impl->parseError += "\n";
@ -781,8 +851,7 @@ CParseResult CConfig::parseFile(const char* file) {
if (!line) { if (!line) {
switch (line.error()) { switch (line.error()) {
case GETNEXTLINEFAILURE_EOF: case GETNEXTLINEFAILURE_EOF: break;
break;
case GETNEXTLINEFAILURE_BACKSLASH: case GETNEXTLINEFAILURE_BACKSLASH:
if (!impl->parseError.empty()) if (!impl->parseError.empty())
impl->parseError += "\n"; impl->parseError += "\n";

View file

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <expected>
struct SHandler { struct SHandler {
std::string name = ""; std::string name = "";
@ -95,6 +96,7 @@ class CConfigImpl {
Hyprlang::SConfigOptions configOptions; Hyprlang::SConfigOptions configOptions;
void parseComment(const std::string& comment); void parseComment(const std::string& comment);
std::expected<float, std::string> parseExpression(const std::string& s);
struct { struct {
bool noError = false; bool noError = false;

View file

@ -14,6 +14,9 @@ $MY_VAR = 1337
$MY_VAR_2 = $MY_VAR $MY_VAR_2 = $MY_VAR
testVar = $MY_VAR$MY_VAR_2 testVar = $MY_VAR$MY_VAR_2
$EXPR_VAR = $(MY_VAR + 2)
testExpr = $(EXPR_VAR - 4)
testEnv = $SHELL testEnv = $SHELL
source = ./colors.conf source = ./colors.conf

View file

@ -107,6 +107,7 @@ int main(int argc, char** argv, char** envp) {
// setup config // setup config
config.addConfigValue("testInt", (Hyprlang::INT)0); config.addConfigValue("testInt", (Hyprlang::INT)0);
config.addConfigValue("testExpr", (Hyprlang::INT)0);
config.addConfigValue("testFloat", 0.F); config.addConfigValue("testFloat", 0.F);
config.addConfigValue("testVec", Hyprlang::SVector2D{.x = 69, .y = 420}); config.addConfigValue("testVec", Hyprlang::SVector2D{.x = 69, .y = 420});
config.addConfigValue("testString", ""); config.addConfigValue("testString", "");
@ -197,6 +198,10 @@ int main(int argc, char** argv, char** envp) {
EXPECT(*T3, EXP); EXPECT(*T3, EXP);
EXPECT(*T4, "Hello World! # This is not a comment!"); 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 static values // test static values
std::cout << " → Testing static values\n"; std::cout << " → Testing static values\n";
static auto* const PTESTINT = config.getConfigValuePtr("testInt")->getDataStaticPtr(); static auto* const PTESTINT = config.getConfigValuePtr("testInt")->getDataStaticPtr();
@ -243,6 +248,10 @@ int main(int argc, char** argv, char** envp) {
EXPECT(config.parseDynamic("$RECURSIVE1 = d").error, false); EXPECT(config.parseDynamic("$RECURSIVE1 = d").error, false);
EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringRecursive")), std::string{"dbc"}); EXPECT(std::any_cast<const char*>(config.getConfigValue("testStringRecursive")), std::string{"dbc"});
// 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 // test env variables
std::cout << " → Testing env variables\n"; std::cout << " → Testing env variables\n";
EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv")), std::string{getenv("SHELL")}); EXPECT(std::any_cast<const char*>(config.getConfigValue("testEnv")), std::string{getenv("SHELL")});