Created
April 26, 2025 09:53
-
-
Save tritao/d1e4c71a45642d13f4ce14182e319584 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string> | |
#include <cctype> | |
#include <stdexcept> | |
#include <unordered_map> | |
#include <sstream> | |
#include <iomanip> | |
#include <iostream> | |
#include <algorithm> | |
// Simple RGB color | |
struct Color { | |
int r, g, b; | |
}; | |
// Linearly interpolate each channel toward white (lighten) or black (darken) | |
Color lighten(const Color& c, int pct) { | |
auto blend = [&](int channel){ | |
return channel + (255 - channel) * pct / 100; | |
}; | |
return { blend(c.r), blend(c.g), blend(c.b) }; | |
} | |
Color darken(const Color& c, int pct) { | |
auto blend = [&](int channel){ | |
return channel * (100 - pct) / 100; | |
}; | |
return { blend(c.r), blend(c.g), blend(c.b) }; | |
} | |
class Parser { | |
public: | |
// vars: map from names (without '@') to colors | |
Parser(const std::string& txt, | |
const std::unordered_map<std::string,Color>& vars) | |
: input(txt), pos(0), variables(vars) | |
{} | |
Color parseExpression() { | |
skipWs(); | |
if (peek() == '@') { | |
return parseVariable(); | |
} | |
else if (peek() == '"' || peek() == '#') { | |
return parseString(); | |
} | |
else { | |
return parseFunctionCall(); | |
} | |
} | |
private: | |
const std::string input; | |
size_t pos; | |
const std::unordered_map<std::string,Color>& variables; | |
// Helpers | |
char peek() const { | |
return pos < input.size() ? input[pos] : '\0'; | |
} | |
char get() { | |
return pos < input.size() ? input[pos++] : '\0'; | |
} | |
void skipWs() { | |
while (std::isspace(peek())) pos++; | |
} | |
void expect(char c) { | |
skipWs(); | |
if (get() != c) | |
throw std::runtime_error(std::string("Expected '") + c + "'"); | |
} | |
// @name | |
Color parseVariable() { | |
expect('@'); | |
std::string name; | |
while (std::isalnum(peek()) || peek()=='_' ) { | |
name += get(); | |
} | |
auto it = variables.find(name); | |
if (it == variables.end()) | |
throw std::runtime_error("Unknown variable: @" + name); | |
return it->second; | |
} | |
// "#rrggbb" or double-quoted | |
Color parseString() { | |
skipWs(); | |
std::string s; | |
if (peek() == '"') { | |
get(); // consume " | |
while (peek() && peek() != '"') s += get(); | |
expect('"'); | |
} else { | |
// allow bare #rrggbb | |
while (std::isxdigit(peek()) || peek()=='#') s += get(); | |
} | |
if (s.size()!=7 || s[0]!='#') | |
throw std::runtime_error("Invalid color literal: " + s); | |
int r = std::stoi(s.substr(1,2), nullptr, 16); | |
int g = std::stoi(s.substr(3,2), nullptr, 16); | |
int b = std::stoi(s.substr(5,2), nullptr, 16); | |
return {r,g,b}; | |
} | |
// lighten( expr , pct% ) or darken(...) | |
Color parseFunctionCall() { | |
skipWs(); | |
// read function name | |
std::string fn; | |
while (std::isalpha(peek())) fn += get(); | |
if (fn != "lighten" && fn != "darken") | |
throw std::runtime_error("Expected 'lighten' or 'darken', got: " + fn); | |
expect('('); | |
Color base = parseExpression(); | |
expect(','); | |
int pct = parsePercentage(); | |
expect(')'); | |
return fn=="lighten" | |
? lighten(base, pct) | |
: darken(base, pct); | |
} | |
// 0 to 100, ending with '%' | |
int parsePercentage() { | |
skipWs(); | |
std::string num; | |
while (std::isdigit(peek())) num += get(); | |
if (num.empty()) throw std::runtime_error("Expected percentage"); | |
if (peek()=='%') get(); else throw std::runtime_error("Expected '%'"); | |
int v = std::stoi(num); | |
if (v < 0 || v > 100) throw std::runtime_error("Percentage out of range"); | |
return v; | |
} | |
}; | |
// Example usage | |
int main() { | |
// Set up your variables | |
std::unordered_map<std::string,Color> vars { | |
{"primary", { 0x10, 0x80, 0xFF }}, | |
{"accent", { 0xFF, 0x40, 0x00 }} | |
}; | |
// Some test strings | |
for (auto expr : { | |
"darken(@primary, 20%)", | |
"lighten(#123456, 50%)", | |
"darken(lighten(@accent,10%), 30%)" | |
}) { | |
try { | |
Parser p(expr, vars); | |
Color c = p.parseExpression(); | |
std::ostringstream out; | |
out << "#" << std::hex << std::setw(2) << std::setfill('0') << c.r | |
<< std::setw(2) << std::setfill('0') << c.g | |
<< std::setw(2) << std::setfill('0') << c.b; | |
std::cout << expr << " → " << out.str() << "\n"; | |
} | |
catch (std::exception& e) { | |
std::cerr << "Error parsing `" << expr << "`: " << e.what() << "\n"; | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment