Skip to content

Instantly share code, notes, and snippets.

@tritao
Created April 26, 2025 09:53
Show Gist options
  • Save tritao/d1e4c71a45642d13f4ce14182e319584 to your computer and use it in GitHub Desktop.
Save tritao/d1e4c71a45642d13f4ce14182e319584 to your computer and use it in GitHub Desktop.
#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