Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
LegacyManifest.cpp
Go to the documentation of this file.
1#include "LegacyManifest.hpp"
2
3#include <algorithm>
4#include <charconv>
5#include <fstream>
6#include <limits>
7#include <optional>
8#include <sstream>
9#include <unordered_set>
10#include <utility>
11
12namespace monopoly::data
13{
14 namespace
15 {
16 [[nodiscard]] ManifestError makeError(
18 const std::filesystem::path& path,
19 std::size_t line,
20 std::string detail)
21 {
22 return { code, path, line, std::move(detail) };
23 }
24
25
26 [[nodiscard]] std::string_view trim(std::string_view text) noexcept
27 {
28 constexpr std::string_view Whitespace = " \t\r\n";
29 const auto first = text.find_first_not_of(Whitespace);
30
31 if (first == std::string_view::npos)
32 {
33 return {};
34 }
35
36 return text.substr(
37 first,
38 text.find_last_not_of(Whitespace) - first + 1);
39 }
40
41
42 [[nodiscard]] std::optional<std::uint32_t> parseUnsigned(
43 std::string_view text) noexcept
44 {
45 text = trim(text);
46 int base = 10;
47
48 if (text.size() > 2 && text[0] == '0' &&
49 (text[1] == 'x' || text[1] == 'X'))
50 {
51 text.remove_prefix(2);
52 base = 16;
53 }
54
55 if (text.empty())
56 {
57 return std::nullopt;
58 }
59
60 std::uint32_t value{};
61 const auto result = std::from_chars(
62 text.data(), text.data() + text.size(), value, base);
63
64 if (result.ec != std::errc{} ||
65 result.ptr != text.data() + text.size())
66 {
67 return std::nullopt;
68 }
69
70 return value;
71 }
72
73
74 [[nodiscard]] std::optional<LegacyDataType> typeFromSymbol(
75 std::string_view symbol) noexcept
76 {
77 if (symbol.starts_with("BMP_"))
78 {
80 }
81
82 if (symbol.starts_with("CNK_"))
83 {
85 }
86
87 if (symbol.starts_with("HMD_"))
88 {
90 }
91
92 if (symbol.starts_with("TAB_"))
93 {
94 // Tableaux generiques du builder : mapping DataUAP prouve
95 // dans la table de prefixes de L_Data.cpp.
97 }
98
99 if (symbol.starts_with("WAV_"))
100 {
102 }
103
104 return std::nullopt;
105 }
106
107
108 [[nodiscard]] bool isSymbolCharacter(char value) noexcept
109 {
110 return
111 (value >= 'a' && value <= 'z') ||
112 (value >= 'A' && value <= 'Z') ||
113 (value >= '0' && value <= '9') ||
114 value == '_';
115 }
116 }
117
118
119 std::string_view manifestErrorCodeName(
120 ManifestErrorCode code) noexcept
121 {
122 switch (code)
123 {
124 case ManifestErrorCode::None: return "None";
125 case ManifestErrorCode::FileOpenFailed: return "FileOpenFailed";
127 return "MissingGeneratorSignature";
129 return "MissingDeclaredCount";
131 return "MalformedDefinition";
132 case ManifestErrorCode::UnknownItemPrefix: return "UnknownItemPrefix";
133 case ManifestErrorCode::TagOutOfRange: return "TagOutOfRange";
134 case ManifestErrorCode::DuplicateSymbol: return "DuplicateSymbol";
135 case ManifestErrorCode::DuplicateTag: return "DuplicateTag";
136 case ManifestErrorCode::NonContiguousTag: return "NonContiguousTag";
137 case ManifestErrorCode::CountMismatch: return "CountMismatch";
138 case ManifestErrorCode::OutputOpenFailed: return "OutputOpenFailed";
139 case ManifestErrorCode::OutputWriteFailed: return "OutputWriteFailed";
140 }
141
142 return "InvalidManifestErrorCode";
143 }
144
145
146 std::expected<LegacyBankManifest, ManifestError> parseDmakeManifest(
147 std::istream& input,
148 std::string bankName,
149 std::filesystem::path diagnosticPath)
150 {
152 manifest.bankName = std::move(bankName);
153 bool hasSignature = false;
154 std::optional<std::uint32_t> declaredCount;
155 std::unordered_set<std::string> symbols;
156 std::unordered_set<DataTag> tags;
157 std::string line;
158 std::size_t lineNumber = 0;
159
160 while (std::getline(input, line))
161 {
162 ++lineNumber;
163
164 if (line.find("DMAKE99: Data file builder ver 2.03") !=
165 std::string::npos)
166 {
167 hasSignature = true;
168 }
169
170 constexpr std::string_view CountPrefix = "There were ";
171 constexpr std::string_view CountSuffix =
172 " unique items in the DF file.";
173 const auto countStart = line.find(CountPrefix);
174
175 if (countStart != std::string::npos)
176 {
177 const auto digitsStart = countStart + CountPrefix.size();
178 const auto countEnd = line.find(CountSuffix, digitsStart);
179
180 if (countEnd != std::string::npos)
181 {
182 declaredCount = parseUnsigned(std::string_view(line).substr(
184 }
185 }
186
187 auto view = trim(line);
188
189 if (!view.starts_with("#define"))
190 {
191 continue;
192 }
193
194 view.remove_prefix(std::string_view("#define").size());
195 view = trim(view);
196 const auto symbolEnd = std::find_if(
197 view.begin(), view.end(),
198 [](char value) { return !isSymbolCharacter(value); });
199 const auto symbolLength = static_cast<std::size_t>(
200 std::distance(view.begin(), symbolEnd));
201
202 if (symbolLength == 0)
203 {
204 return std::unexpected(makeError(
208 "#define has no symbol"));
209 }
210
211 const std::string symbol(view.substr(0, symbolLength));
212 const auto rawValue = trim(view.substr(symbolLength));
213
214 if (symbol == "DEF_blank")
215 {
216 continue;
217 }
218
219 const auto type = typeFromSymbol(symbol);
220
221 if (!type)
222 {
223 return std::unexpected(makeError(
227 "unsupported DMake item prefix in " + symbol));
228 }
229
230 const auto rawTag = parseUnsigned(rawValue);
231
232 if (!rawTag)
233 {
234 return std::unexpected(makeError(
238 "item tag is not an unsigned decimal or hexadecimal value"));
239 }
240
241 if (*rawTag > std::numeric_limits<DataTag>::max())
242 {
243 return std::unexpected(makeError(
247 "DMake item tag exceeds the 16-bit DataTag space"));
248 }
249
250 const auto tag = static_cast<DataTag>(*rawTag);
251
252 if (!symbols.emplace(symbol).second)
253 {
254 return std::unexpected(makeError(
258 "duplicate DMake symbol " + symbol));
259 }
260
261 if (!tags.emplace(tag).second)
262 {
263 return std::unexpected(makeError(
267 "duplicate DMake numeric tag"));
268 }
269
270 const auto expectedTag = manifest.entries.size();
271
272 if (tag != expectedTag)
273 {
274 return std::unexpected(makeError(
278 "DMake tags must follow their source order from zero"));
279 }
280
281 manifest.entries.push_back({ tag, *type, symbol });
282 }
283
284 if (!hasSignature)
285 {
286 return std::unexpected(makeError(
289 0,
290 "header is not identified as DMAKE99 version 2.03 output"));
291 }
292
293 if (!declaredCount)
294 {
295 return std::unexpected(makeError(
298 0,
299 "DMake declared item count was not found"));
300 }
301
302 if (*declaredCount != manifest.entries.size())
303 {
304 return std::unexpected(makeError(
307 0,
308 "DMake declared item count differs from parsed definitions"));
309 }
310
311 manifest.declaredItemCount = *declaredCount;
312 return manifest;
313 }
314
315
316 std::expected<LegacyBankManifest, ManifestError> readDmakeManifest(
317 const std::filesystem::path& path)
318 {
319 std::ifstream input(path);
320
321 if (!input)
322 {
323 return std::unexpected(makeError(
325 path,
326 0,
327 "unable to open generated DMake header"));
328 }
329
330 return parseDmakeManifest(input, path.stem().string(), path);
331 }
332
333
334 std::expected<void, ManifestError> writeManifestTsv(
335 const std::filesystem::path& outputPath,
336 std::span<const LegacyBankManifest> manifests)
337 {
338 std::ofstream output(outputPath, std::ios::trunc);
339
340 if (!output)
341 {
342 return std::unexpected(makeError(
345 0,
346 "unable to create manifest output"));
347 }
348
349 output << "bank\ttag\ttype\tsymbol\n";
350
351 for (const auto& manifest : manifests)
352 {
353 for (const auto& entry : manifest.entries)
354 {
355 output
356 << manifest.bankName << '\t'
357 << entry.tag << '\t'
358 << legacyDataTypeName(entry.type) << '\t'
359 << entry.symbol << '\n';
360 }
361 }
362
363 if (!output)
364 {
365 return std::unexpected(makeError(
368 0,
369 "unable to finish manifest output"));
370 }
371
372 return {};
373 }
374}
std::string_view text
unsigned index
data::DataTag first
std::string path
Bytes input
std::deque< Bytes > output
std::uint16_t value
Definition Timers.cpp:16
std::string_view legacyDataTypeName(LegacyDataType type) noexcept
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::expected< LegacyBankManifest, ManifestError > readDmakeManifest(const std::filesystem::path &path)
std::expected< void, ManifestError > writeManifestTsv(const std::filesystem::path &outputPath, std::span< const LegacyBankManifest > manifests)
std::string_view manifestErrorCodeName(ManifestErrorCode code) noexcept
std::expected< LegacyBankManifest, ManifestError > parseDmakeManifest(std::istream &input, std::string bankName, std::filesystem::path diagnosticPath)