30 return std::regex_replace(source, std::regex(R
"(/\*[\s\S]*?\*/|([^\\:]|^)//.*$)", std::regex_constants::multiline), "$1");
35 std::string output = source;
36 output = std::regex_replace(output, std::regex(R
"((\n\n){3,})"), "\n\n");
40 static std::string
run(
const std::string& source,
41 const std::unordered_map<std::string, std::string>& includes,
42 const bool stripDefines)
46 return run(source, includes, options);
49 static std::string
run(
const std::string& source,
50 const std::unordered_map<std::string, std::string>& includes = {},
53 std::unordered_map<std::string, std::string> defines;
54 std::unordered_map<std::string, std::string> injectDefines;
57 output = trimEndPerLine(output);
58 output = preprocess(output, defines, injectDefines, includes, options.
stripDefines);
62 output = processArraySize(output, defines);
63 output = injectDefinesIntoSource(output, injectDefines);
68 struct ConditionalFrame
70 bool parentKeep =
true;
71 bool branchTaken =
false;
75 static std::string trim(
const std::string& s)
78 while (start < s.size() && std::isspace(
static_cast<unsigned char>(s[start]))) {
81 size_t end = s.size();
82 while (end > start && std::isspace(
static_cast<unsigned char>(s[end - 1]))) {
85 return s.substr(start, end - start);
88 static std::string trimEndPerLine(
const std::string& source)
90 std::stringstream in(source);
94 while (std::getline(in, line)) {
95 while (!line.empty() && (line.back() ==
' ' || line.back() ==
'\t' || line.back() ==
'\r')) {
107 static bool active(
const std::vector<ConditionalFrame>& stack)
109 return stack.empty() ? true : stack.back().keep;
112 static bool parseDefinedExpr(
const std::string& expr,
const std::unordered_map<std::string, std::string>& defines)
114 const std::string e = trim(expr);
116 static const std::regex definedPattern(R
"(^(!)?\s*defined\(([^)]+)\)\s*$)");
117 if (std::regex_match(e, match, definedPattern)) {
118 const bool negated = match[1].matched;
119 const std::string
id = trim(match[2].str());
120 const bool value = defines.contains(
id);
121 return negated ? !value : value;
126 static std::optional<bool> parseComparisonExpr(
const std::string& expr,
const std::unordered_map<std::string, std::string>& defines)
129 static const std::regex comparison(R
"(^\s*([A-Za-z_]\w*)\s*(==|!=|<=|>=|<|>)\s*([\w"']+)\s*$)");
130 if (!std::regex_match(expr, match, comparison)) {
134 const std::string lhsId = match[1].str();
135 const std::string op = match[2].str();
136 std::string rhs = match[3].str();
138 auto it = defines.find(lhsId);
139 const std::string lhsRaw = (it != defines.end()) ? it->second :
"0";
141 auto stripQuotes = [](std::string s) {
142 if (s.size() >= 2 && ((s.front() ==
'\'' && s.back() ==
'\'') || (s.front() ==
'"' && s.back() ==
'"'))) {
143 return s.substr(1, s.size() - 2);
148 const std::string lhs = stripQuotes(lhsRaw);
149 rhs = stripQuotes(rhs);
151 auto toDouble = [](
const std::string& s) -> std::optional<double> {
153 const double v = std::strtod(s.c_str(), &end);
154 if (end && *end ==
'\0') {
160 if (
const auto ln = toDouble(lhs); ln.has_value()) {
161 if (
const auto rn = toDouble(rhs); rn.has_value()) {
162 if (op ==
"==")
return *ln == *rn;
163 if (op ==
"!=")
return *ln != *rn;
164 if (op ==
"<")
return *ln < *rn;
165 if (op ==
"<=")
return *ln <= *rn;
166 if (op ==
">")
return *ln > *rn;
167 if (op ==
">=")
return *ln >= *rn;
171 if (op ==
"==")
return lhs == rhs;
172 if (op ==
"!=")
return lhs != rhs;
173 if (op ==
"<")
return lhs < rhs;
174 if (op ==
"<=")
return lhs <= rhs;
175 if (op ==
">")
return lhs > rhs;
176 if (op ==
">=")
return lhs >= rhs;
180 static bool evalExpr(
const std::string& expr,
const std::unordered_map<std::string, std::string>& defines)
182 const std::string e = trim(expr);
187 const size_t orPos = e.find(
"||");
188 if (orPos != std::string::npos) {
189 return evalExpr(e.substr(0, orPos), defines) || evalExpr(e.substr(orPos + 2), defines);
192 const size_t andPos = e.find(
"&&");
193 if (andPos != std::string::npos) {
194 return evalExpr(e.substr(0, andPos), defines) && evalExpr(e.substr(andPos + 2), defines);
197 if (parseDefinedExpr(e, defines)) {
201 static const std::regex negDefinedPattern(R
"(^\s*!\s*defined\(([^)]+)\)\s*$)");
202 if (std::regex_match(e, negDefinedPattern)) {
203 return parseDefinedExpr(e, defines);
206 if (
const auto comparison = parseComparisonExpr(e, defines); comparison.has_value()) {
210 if (defines.contains(e)) {
211 const auto& value = defines.at(e);
212 return !(value ==
"0" || value ==
"false" || value.empty());
215 if (e ==
"true")
return true;
216 if (e ==
"false")
return false;
220 static std::string preprocess(
const std::string& source,
221 std::unordered_map<std::string, std::string>& defines,
222 std::unordered_map<std::string, std::string>& injectDefines,
223 const std::unordered_map<std::string, std::string>& includes,
224 const bool stripDefines)
226 std::stringstream in(source);
228 std::vector<ConditionalFrame> stack;
229 std::vector<std::string> output;
231 static const std::regex includePattern(R
"(^\s*#include\s+"([\w-]+)(?:\s*,\s*([\w-]+))?"\s*$)");
232 static const std::regex definePattern(R
"(^\s*#define\s+([^\s]+)\s*(.*)$)");
233 static const std::regex undefPattern(R
"(^\s*#undef\s+([^\s]+)\s*$)");
234 static const std::regex extensionPattern(R
"(^\s*#extension\s+([\w-]+)\s*:\s*(enable|require)\s*$)");
235 static const std::regex ifdefPattern(R
"(^\s*#ifdef\s+(.+)$)");
236 static const std::regex ifndefPattern(R
"(^\s*#ifndef\s+(.+)$)");
237 static const std::regex ifPattern(R
"(^\s*#if\s+(.+)$)");
238 static const std::regex elifPattern(R
"(^\s*#elif\s+(.+)$)");
239 static const std::regex elsePattern(R
"(^\s*#else\s*$)");
240 static const std::regex endifPattern(R
"(^\s*#endif\s*$)");
242 while (std::getline(in, line)) {
244 const bool keep = active(stack);
246 if (std::regex_match(line, match, includePattern)) {
248 const auto includeIt = includes.find(match[1].str());
249 if (includeIt != includes.end()) {
250 output.push_back(includeIt->second);
252 if (match[2].matched) {
253 const auto includeIt2 = includes.find(match[2].str());
254 if (includeIt2 != includes.end()) {
255 output.push_back(includeIt2->second);
262 if (std::regex_match(line, match, definePattern)) {
264 const std::string
id = trim(match[1].str());
265 std::string value = trim(match[2].str());
270 if (
id.size() > 2 &&
id.front() ==
'{' &&
id.back() ==
'}') {
271 injectDefines[id] = value;
277 if (!stripDefines && keep) {
278 output.push_back(line);
283 if (std::regex_match(line, match, undefPattern)) {
285 defines.erase(trim(match[1].str()));
287 if (!stripDefines && keep) {
288 output.push_back(line);
293 if (std::regex_match(line, match, extensionPattern)) {
295 defines[trim(match[1].str())] =
"true";
297 if (!stripDefines && keep) {
298 output.push_back(line);
303 if (std::regex_match(line, match, ifdefPattern)) {
304 const bool parentKeep = keep;
305 const bool cond = parentKeep && defines.contains(trim(match[1].str()));
306 stack.push_back({parentKeep, cond, parentKeep && cond});
310 if (std::regex_match(line, match, ifndefPattern)) {
311 const bool parentKeep = keep;
312 const bool cond = parentKeep && !defines.contains(trim(match[1].str()));
313 stack.push_back({parentKeep, cond, parentKeep && cond});
317 if (std::regex_match(line, match, ifPattern)) {
318 const bool parentKeep = keep;
319 const bool cond = parentKeep && evalExpr(match[1].str(), defines);
320 stack.push_back({parentKeep, cond, parentKeep && cond});
324 if (std::regex_match(line, match, elifPattern)) {
325 if (!stack.empty()) {
326 auto& top = stack.back();
327 const bool cond = top.parentKeep && !top.branchTaken && evalExpr(match[1].str(), defines);
330 top.branchTaken =
true;
336 if (std::regex_match(line, elsePattern)) {
337 if (!stack.empty()) {
338 auto& top = stack.back();
339 top.keep = top.parentKeep && !top.branchTaken;
340 top.branchTaken =
true;
345 if (std::regex_match(line, endifPattern)) {
346 if (!stack.empty()) {
353 output.push_back(line);
358 for (
size_t i = 0; i < output.size(); ++i) {
360 if (i + 1 < output.size()) {
367 static std::string processArraySize(std::string source,
368 const std::unordered_map<std::string, std::string>& defines)
370 for (
const auto& [key, value] : defines) {
372 std::strtol(value.c_str(), &end, 10);
373 if (!end || *end !=
'\0') {
377 source = std::regex_replace(source, std::regex(
"\\\\[" + key +
"\\\\]"),
"[" + value +
"]");
382 static std::string injectDefinesIntoSource(
const std::string& source,
383 const std::unordered_map<std::string, std::string>& injectDefines)
385 if (injectDefines.empty()) {
389 std::stringstream in(source);
394 while (std::getline(in, line)) {
395 if (line.find(
'#') == std::string::npos) {
396 for (
const auto& [key, value] : injectDefines) {
397 line = std::regex_replace(line, std::regex(regexEscape(key)), value);
411 static std::string regexEscape(
const std::string& input)
413 static const std::regex specials(R
"([.^$|()\\[\]{}*+?])");
414 return std::regex_replace(input, specials, R
"(\$&)");
417 static std::string stripUnusedColorAttachments(
const std::string& source,
const bool enabled)
423 static const std::regex fragColorPattern(R
"((pcFragColor[1-8])\b)");
425 std::unordered_map<int, int> counts;
426 for (
auto it = std::sregex_iterator(source.begin(), source.end(), fragColorPattern);
427 it != std::sregex_iterator(); ++it) {
428 const auto& match = *it;
429 const std::string token = match[1].str();
430 const int index = token.back() -
'0';
434 bool anySingleUse =
false;
435 for (
const auto& [_, c] : counts) {
446 std::stringstream in(source);
448 std::vector<std::string> keep;
450 while (std::getline(in, line)) {
452 if (std::regex_search(line, m, fragColorPattern)) {
453 const int index = m[1].str().back() -
'0';
454 if (index > 0 && counts[index] == 1) {
458 keep.push_back(line);
462 for (
size_t i = 0; i < keep.size(); ++i) {
464 if (i + 1 < keep.size()) {