Torch
Loading...
Searching...
No Matches
BKEmitText.h
1#pragma once
2
3#include <cctype>
4#include <cstdint>
5#include <cstdio>
6#include <initializer_list>
7#include <iomanip>
8#include <stdexcept>
9#include <string>
10#include <type_traits>
11#include <yaml-cpp/yaml.h>
12
13// Shared yaml/text emission formatters
14#define YAML_HEX(num) YAML::Hex << (num) << YAML::Dec
15#define FORMAT_HEX(x, w) \
16 std::hex << std::uppercase << std::setfill('0') << std::setw(w) << x << std::nouppercase << std::dec
17
18namespace BK64 {
19
20// Emit a text string for modding yaml.
21inline void EmitText(YAML::Emitter& out, const std::string& str) {
22 const char* s = str.c_str();
23 bool plain = true;
24 for (const char* p = s; *p != '\0'; p++) {
25 if (static_cast<uint8_t>(*p) < 0x20 || static_cast<uint8_t>(*p) > 0x7E || *p == '\\') {
26 plain = false;
27 break;
28 }
29 }
30 if (plain) {
31 out << YAML::DoubleQuoted << s;
32 return;
33 }
34 std::string escaped;
35 for (const char* p = s; *p != '\0'; p++) {
36 const auto c = static_cast<uint8_t>(*p);
37 if (c == '\\') {
38 escaped += "\\\\";
39 } else if (c < 0x20 || c > 0x7E) {
40 char buf[5];
41 std::snprintf(buf, sizeof(buf), "\\x%02X", c);
42 escaped += buf;
43 } else {
44 escaped += static_cast<char>(c);
45 }
46 }
47 out << YAML::SingleQuoted << escaped;
48}
49
50// Reverse EmitText's escapes on a string read back from modding yaml.
51inline std::string DecodeText(const std::string& str) {
52 std::string out;
53 out.reserve(str.size());
54 for (size_t i = 0; i < str.size(); i++) {
55 if (str[i] == '\\' && i + 1 < str.size()) {
56 if (str[i + 1] == '\\') {
57 out += '\\';
58 i++;
59 continue;
60 }
61 if ((str[i + 1] == 'x' || str[i + 1] == 'X') && i + 3 < str.size() &&
62 std::isxdigit(static_cast<unsigned char>(str[i + 2])) &&
63 std::isxdigit(static_cast<unsigned char>(str[i + 3]))) {
64 out += static_cast<char>(std::stoi(str.substr(i + 2, 2), nullptr, 16));
65 i += 3;
66 continue;
67 }
68 }
69 out += str[i];
70 }
71 return out;
72}
73
74// Modding yaml readers. They throw, naming where the bad value is.
75[[noreturn]] inline void YamlFail(const std::string& where, const std::string& problem) {
76 throw std::runtime_error(where + ": " + problem);
77}
78
79inline uint32_t YamlNumber(const YAML::Node& node, const std::string& where, uint32_t max) {
80 const std::string text = node.as<std::string>();
81 size_t used = 0;
82 unsigned long value = 0;
83 try {
84 value = std::stoul(text, &used, 0);
85 } catch (const std::exception&) { used = 0; }
86 if (used == 0 || used != text.size()) {
87 YamlFail(where, "\"" + text + "\" isn't a number");
88 }
89 if (value > max) {
90 YamlFail(where, text + " is over " + std::to_string(max));
91 }
92 return static_cast<uint32_t>(value);
93}
94
95inline int16_t YamlShort(const YAML::Node& node, const std::string& where) {
96 const std::string text = node.as<std::string>();
97 size_t used = 0;
98 long value = 0;
99 try {
100 value = std::stol(text, &used, 0);
101 } catch (const std::exception&) { used = 0; }
102 if (used == 0 || used != text.size()) {
103 YamlFail(where, "\"" + text + "\" isn't a number");
104 }
105 if (value < INT16_MIN || value > INT16_MAX) {
106 YamlFail(where, text + " is out of range");
107 }
108 return static_cast<int16_t>(value);
109}
110
111// The same for a key the entry has to have
112inline uint32_t YamlNumber(const YAML::Node& entry, const char* key, const std::string& where, uint32_t max) {
113 if (!entry[key]) {
114 YamlFail(where, std::string("needs ") + key);
115 }
116 return YamlNumber(entry[key], where + " " + key, max);
117}
118
119inline int16_t YamlShort(const YAML::Node& entry, const char* key, const std::string& where) {
120 if (!entry[key]) {
121 YamlFail(where, std::string("needs ") + key);
122 }
123 return YamlShort(entry[key], where + " " + key);
124}
125
126inline float YamlFloat(const YAML::Node& entry, const char* key, const std::string& where) {
127 if (!entry[key]) {
128 YamlFail(where, std::string("needs ") + key);
129 }
130 try {
131 return entry[key].as<float>();
132 } catch (const YAML::Exception&) {
133 YamlFail(where + " " + key, "\"" + entry[key].as<std::string>() + "\" isn't a number");
134 }
135}
136
137// A modding file's one entry, keyed by the asset's name.
138inline YAML::Node YamlEntry(const std::vector<uint8_t>& buffer, const std::string& where) {
139 YAML::Node root;
140 try {
141 root = YAML::Load(std::string(buffer.begin(), buffer.end()));
142 } catch (const YAML::Exception& e) { YamlFail(where, e.what()); }
143 if (!root.IsMap() || root.size() != 1) {
144 YamlFail(where, "needs one top-level key, the asset's name");
145 }
146 return root.begin()->second;
147}
148
149// Every key the map holds has to be one of keys.
150inline void YamlKeys(const YAML::Node& node, std::initializer_list<const char*> keys, const std::string& where) {
151 if (!node.IsMap()) {
152 YamlFail(where, "needs to be a map");
153 }
154 for (const auto& item : node) {
155 const std::string key = item.first.as<std::string>();
156 bool known = false;
157 for (const char* name : keys) {
158 known |= key == name;
159 }
160 if (!known) {
161 YamlFail(where, "doesn't take " + key);
162 }
163 }
164}
165
166template <typename T> void ReadList(const YAML::Node& list, T* values, int count, const std::string& at) {
167 if (!list.IsSequence() || (int)list.size() != count) {
168 YamlFail(at, "needs " + std::to_string(count) + " numbers");
169 }
170 for (int i = 0; i < count; i++) {
171 if constexpr (std::is_same_v<T, int16_t>) {
172 values[i] = YamlShort(list[i], at);
173 } else {
174 try {
175 values[i] = list[i].as<T>();
176 } catch (const YAML::Exception&) { YamlFail(at, "\"" + list[i].as<std::string>() + "\" isn't a number"); }
177 }
178 }
179}
180
181template <typename T>
182void ReadList(const YAML::Node& entry, const char* key, T* values, int count, const std::string& where) {
183 const YAML::Node list = entry[key];
184 if (!list) {
185 YamlFail(where, std::string("needs a ") + key);
186 }
187 ReadList(list, values, count, where + " " + key);
188}
189
190template <typename T> void EmitList(YAML::Emitter& out, const char* key, const T* values, int count) {
191 out << YAML::Key << key << YAML::Value << YAML::Flow << YAML::BeginSeq;
192 for (int i = 0; i < count; i++) {
193 out << values[i];
194 }
195 out << YAML::EndSeq;
196}
197
198inline void EmitList(YAML::Emitter& out, const char* key, std::initializer_list<int> values) {
199 EmitList(out, key, values.begin(), (int)values.size());
200}
201
202} // namespace BK64