shithub: rgbds

Download patch

ref: 6b0cab32a67247be33fcfa8d509603fb52020920
parent: cc27169ecdd4e29944e207acc4a39f25e78b0a8f
author: ISSOtm <[email protected]>
date: Sat Apr 9 09:47:38 EDT 2022

Implement inline palette spec parsing

--- a/Makefile
+++ b/Makefile
@@ -108,6 +108,7 @@
 	src/gfx/main.o \
 	src/gfx/pal_packing.o \
 	src/gfx/pal_sorting.o \
+	src/gfx/pal_spec.o \
 	src/gfx/process.o \
 	src/gfx/proto_palette.o \
 	src/gfx/reverse.o \
--- /dev/null
+++ b/include/gfx/pal_spec.hpp
@@ -1,0 +1,16 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 2022, Eldred Habert and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef RGBDS_GFX_PAL_SPEC_HPP
+#define RGBDS_GFX_PAL_SPEC_HPP
+
+#include <string_view>
+
+void parseInlinePalSpec(char const * const arg);
+
+#endif /* RGBDS_GFX_PAL_SPEC_HPP */
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -73,6 +73,7 @@
     "gfx/main.cpp"
     "gfx/pal_packing.cpp"
     "gfx/pal_sorting.cpp"
+    "gfx/pal_spec.cpp"
     "gfx/process.cpp"
     "gfx/proto_palette.cpp"
     "gfx/reverse.cpp"
--- a/src/gfx/main.cpp
+++ b/src/gfx/main.cpp
@@ -26,6 +26,7 @@
 #include "platform.h"
 #include "version.h"
 
+#include "gfx/pal_spec.hpp"
 #include "gfx/process.hpp"
 #include "gfx/reverse.hpp"
 
@@ -223,9 +224,8 @@
 
 static void parsePaletteSpec(char const *arg) {
 	if (arg[0] == '#') {
-		// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
 		options.palSpecType = Options::EXPLICIT;
-		// TODO
+		parseInlinePalSpec(arg);
 	} else if (strcasecmp(arg, "embedded") == 0) {
 		// Use PLTE, error out if missing
 		options.palSpecType = Options::EMBEDDED;
@@ -668,6 +668,14 @@
 			}
 			return "???";
 		}());
+		if (options.palSpecType == Options::EXPLICIT) {
+			fputs("\t[\n", stderr);
+			for (std::array<Rgba, 4> const &pal : options.palSpec) {
+				fprintf(stderr, "\t\t#%06x, #%06x, #%06x, #%06x,\n", pal[0].toCSS() >> 8,
+				        pal[1].toCSS() >> 8, pal[2].toCSS() >> 8, pal[3].toCSS() >> 8);
+			}
+			fputs("\t]\n", stderr);
+		}
 		fprintf(stderr, "\tDedup unit: %" PRIu16 "x%" PRIu16 " tiles\n", options.unitSize[0],
 		        options.unitSize[1]);
 		fprintf(stderr,
--- /dev/null
+++ b/src/gfx/pal_spec.cpp
@@ -1,0 +1,151 @@
+/*
+ * This file is part of RGBDS.
+ *
+ * Copyright (c) 2022, Eldred Habert and RGBDS contributors.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "gfx/pal_spec.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdio>
+
+#include "gfx/main.hpp"
+
+using namespace std::string_view_literals;
+
+constexpr uint8_t nibble(char c) {
+	if (c >= 'a') {
+		assert(c <= 'f');
+		return c - 'a' + 10;
+	} else if (c >= 'A') {
+		assert(c <= 'F');
+		return c - 'A' + 10;
+	} else {
+		assert(c >= '0' && c <= '9');
+		return c - '0';
+	}
+}
+
+constexpr uint8_t toHex(char c1, char c2) {
+	return nibble(c1) * 16 + nibble(c2);
+}
+
+constexpr uint8_t singleToHex(char c) {
+	return toHex(c, c);
+}
+
+void parseInlinePalSpec(char const * const rawArg) {
+	// List of #rrggbb/#rgb colors, comma-separated, palettes are separated by colons
+
+	std::string_view arg(rawArg);
+	using size_type = decltype(arg)::size_type;
+
+	auto parseError = [&rawArg, &arg](size_type ofs, size_type len, char const *fmt,
+	                                  auto &&...args) {
+		(void)arg; // With NDEBUG, `arg` is otherwise not used
+		assert(ofs <= arg.length());
+		assert(len <= arg.length());
+
+		error(fmt, args...);
+		fprintf(stderr,
+		        "In inline palette spec: %s\n"
+		        "                        ",
+		        rawArg);
+		for (auto i = ofs; i; --i) {
+			putc(' ', stderr);
+		}
+		for (auto i = len; i; --i) {
+			putc('^', stderr);
+		}
+		putc('\n', stderr);
+	};
+
+	auto skipWhitespace = [&arg](size_type &pos) {
+		pos = std::min(arg.find_first_not_of(" \t", pos), arg.length());
+	};
+
+	options.palSpec.clear();
+	options.palSpec
+	    .emplace_back(); // Not default-initialized, but value-initialized, so we get zeros
+
+	size_type n = 0; // Index into the argument
+	// TODO: store max `nbColors` ever reached, and compare against palette size later
+	size_t nbColors = 0; // Number of colors in the current palette
+	for (;;) {
+		++n; // Ignore the '#' (checked either by caller or previous loop iteration)
+
+		Rgba &color = options.palSpec.back()[nbColors];
+		auto pos = std::min(arg.find_first_not_of("0123456789ABCDEFabcdef"sv, n), arg.length());
+		switch (pos - n) {
+		case 3:
+			color = Rgba(singleToHex(arg[n + 0]), singleToHex(arg[n + 1]), singleToHex(arg[n + 2]),
+			             0xFF);
+			break;
+		case 6:
+			color = Rgba(toHex(arg[n + 0], arg[n + 1]), toHex(arg[n + 2], arg[n + 3]),
+			             toHex(arg[n + 4], arg[n + 5]), 0xFF);
+			break;
+		case 0:
+			parseError(n - 1, 1, "Missing color after '#'");
+			return;
+		default:
+			parseError(n, pos - n, "Unknown color specification");
+			return;
+		}
+		n = pos;
+
+		// Skip whitespace, if any
+		skipWhitespace(n);
+
+		// Skip comma/colon, or end
+		if (n == arg.length()) {
+			break;
+		}
+		switch (arg[n]) {
+		case ',':
+			++n; // Skip it
+
+			++nbColors;
+
+			// A trailing comma may be followed by a colon
+			skipWhitespace(n);
+			if (n == arg.length()) {
+				break;
+			} else if (arg[n] != ':') {
+				if (nbColors == 4) {
+					parseError(n, 1, "Each palette can only contain up to 4 colors");
+					return;
+				}
+				break;
+			}
+			[[fallthrough]];
+
+		case ':':
+			++n;
+			skipWhitespace(n);
+
+			nbColors = 0; // Start a new palette
+			// Avoid creating a spurious empty palette
+			if (n != arg.length()) {
+				options.palSpec.emplace_back();
+			}
+			break;
+
+		default:
+			parseError(n, 1, "Unexpected character, expected ',', ':', or end of argument");
+			return;
+		}
+
+		// Check again to allow trailing a comma/colon
+		if (n == arg.length()) {
+			break;
+		}
+		if (arg[n] != '#') {
+			parseError(n, 1, "Unexpected character, expected '#'");
+			return;
+		}
+	}
+}