#include #include "kblib/convert.h" #include "map.h" using namespace std::string_literals; #include "gen/cmap.inc" const std::string_view cmaps_default{cmap_inc, sizeof(cmap_inc)}; cmap_t current_cmap = scan_cmap(cmaps_default, "__default_maps"); auto lookupMap(std::string_view query) -> unsigned { for (unsigned m = 0; m != current_cmap.size(); ++m) { if (current_cmap[m].first == query) { return m; } } return kblib::fromStr(query, "palette ID"); } auto scan_cmap(std::string_view maps, std::string_view filename) -> cmap_t { int line{1}; /* Format of data file: * * e := {empty} * delim := : * char := [^:] * string := char* {Empty, or a char plus a string} * digit := [0-9] * number := digit+ * hexdigit := [0-9A-Fa-f] * byte := hexdigit . hexdigit * color := byte . byte . byte * space := [ \n\t,;#] * lsep := ([:punct:] | [:space:])* * clist := color . space* . clist {colors may have arbitrary spacing after} * cmap := string . delim . number . space+ . clist * * file := space* . delim . cmap . (delim . cmap)* * */ const std::string_view space{" \r\n\t,;#"}, eofspace{" \r\n\t"}; constexpr char delim{':'}, escape{'\\'}; constexpr auto npos = std::string_view::npos; cmap_t out; std::pair working; std::string number, color; int expected{}, remaining{}; // A clist must have as many colors as the preceding number // state machine: enum class states : uint8_t { Preamble, // Start of file PreEscape, //:: is an escape for : String, // A cmap-name StringEscape, //\c in a cmap-name is replaced with c Number, // A decimal number of colors to expect CList, // Between colors HexColor, // Reads 6 hex characters, ignores spacing chars } state = states::Preamble; for (auto c : maps) { if (c == '\n') { ++line; } switch (state) { case states::Preamble: if (c == delim) { state = states::PreEscape; } break; case states::PreEscape: if (c == delim) { state = states::Preamble; break; } else { state = states::String; } [[fallthrough]]; case states::String: if (c == escape) { state = states::StringEscape; } else if (c == delim) { if (not working.first.length()) { throw malformed_file_error{ kblib::concat(filename, ':', line, ": For cmap #", out.size(), ": cmap names cannot be empty")}; } state = states::Number; } else { working.first += c; } break; case states::StringEscape: working.first += c; state = states::String; break; case states::Number: if (isdigit(c)) { number += c; } else if (space.find(c) != npos) { if (number.empty()) { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "cmap length cannot be blank")}; } state = states::CList; expected = remaining = kblib::parse_integer(number, 10); number.clear(); if (not expected) { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "cmap length cannot be 0")}; } } else { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "encountered unexpected ", c, " while reading number")}; } break; case states::CList: if (isxdigit(c)) { color += c; state = states::HexColor; } else if (c == delim) { if (remaining) { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "expected ", expected, " colors, got ", expected - remaining)}; } else { out.push_back(working); working.first.clear(); working.second.clear(); state = states::String; } } break; case states::HexColor: if (isxdigit(c)) { color += c; } else if (c == delim) { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "encountered delimiter while reading color: ", color)}; } else if (space.find(c) == npos) { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "illegal character in color: ", '"', color, c, "\" ", +c)}; } if (color.length() == 6) { --remaining; working.second.push_back(pnm::color_from_hex(color)); color.clear(); state = states::CList; } break; } } switch (state) { case states::Preamble: throw malformed_file_error{ kblib::concat(filename, ':', line, ": no cmaps in file")}; case states::PreEscape: [[fallthrough]]; case states::StringEscape: throw malformed_file_error{kblib::concat( filename, ':', line, ": encountered end of file in escape")}; case states::Number: throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "expected number, got end of file")}; case states::HexColor: throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "encountered end of file while reading color: ", color)}; case states::String: if (working.first.find_first_not_of(eofspace) != npos) { throw malformed_file_error{kblib::concat( filename, ':', line, ": For cmap #", out.size(), ": ", "encountered end of file while reading cmap-name: ", working.first)}; } break; case states::CList: if (remaining) { throw malformed_file_error{ kblib::concat(filename, ':', line, ": For cmap #", out.size(), ": ", working.first, ": ", "expected ", expected, " colors, got ", expected - remaining)}; } else { out.push_back(working); } break; } return out; }