Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
LegacyTextFormat.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <limits>
5
7{
8 namespace
9 {
10 [[nodiscard]] char16_t upperAscii(char16_t value) noexcept
11 {
12 if (value >= u'a' && value <= u'z')
13 return static_cast<char16_t>(value - (u'a' - u'A'));
14 return value;
15 }
16
17 void appendBounded(
18 std::u16string& output,
19 std::u16string_view value,
20 std::size_t limit)
21 {
22 if (output.size() >= limit || value.empty()) return;
23 const auto available = limit - output.size();
24 output.append(value.substr(0, available));
25 }
26
27 void appendNumber(
28 std::u16string& output,
29 std::int64_t value,
30 std::size_t limit)
31 {
32 const auto ascii = std::to_string(value);
33 if (output.size() >= limit) return;
34 const auto count = std::min(ascii.size(), limit - output.size());
35 for (std::size_t index = 0; index < count; ++index)
36 output.push_back(static_cast<char16_t>(
37 static_cast<unsigned char>(ascii[index])));
38 }
39
40 [[nodiscard]] std::expected<std::u16string, std::string>
43 std::int64_t value)
44 {
45 if (value < 0 ||
46 value > std::numeric_limits<rules::PlayerNumber>::max())
47 return std::unexpected(
48 "legacy message player number is outside the portable range");
49 if (!resolvers.playerName)
50 return std::unexpected(
51 "legacy message requires a player-name resolver");
52 return resolvers.playerName(
53 static_cast<rules::PlayerNumber>(value));
54 }
55 }
56
57 std::expected<std::u16string, std::string>
58 wideToUtf16(std::wstring_view text)
59 {
60 std::u16string result;
61 result.reserve(text.size());
62
63 if constexpr (sizeof(wchar_t) == 2)
64 {
65 for (std::size_t index = 0; index < text.size(); ++index)
66 {
67 const auto value =
68 static_cast<std::uint32_t>(text[index]);
69 if (value >= 0xD800U && value <= 0xDBFFU)
70 {
71 if (index + 1 >= text.size())
72 return std::unexpected(
73 "truncated UTF-16 surrogate pair");
74 const auto low =
75 static_cast<std::uint32_t>(text[index + 1]);
76 if (low < 0xDC00U || low > 0xDFFFU)
77 return std::unexpected(
78 "invalid UTF-16 surrogate pair");
79 result.push_back(static_cast<char16_t>(value));
80 result.push_back(static_cast<char16_t>(low));
81 ++index;
82 continue;
83 }
84 if (value >= 0xDC00U && value <= 0xDFFFU)
85 return std::unexpected(
86 "isolated UTF-16 low surrogate");
87 result.push_back(static_cast<char16_t>(value));
88 }
89 }
90 else
91 {
92 for (const auto value : text)
93 {
94 const auto codePoint =
95 static_cast<std::uint32_t>(value);
96 if (codePoint > 0x10FFFFU ||
97 (codePoint >= 0xD800U && codePoint <= 0xDFFFU))
98 return std::unexpected(
99 "invalid wide-character Unicode code point");
100 if (codePoint <= 0xFFFFU)
101 {
102 result.push_back(static_cast<char16_t>(codePoint));
103 continue;
104 }
105
106 const auto adjusted = codePoint - 0x10000U;
107 result.push_back(static_cast<char16_t>(
108 0xD800U + (adjusted >> 10U)));
109 result.push_back(static_cast<char16_t>(
110 0xDC00U + (adjusted & 0x3FFU)));
111 }
112 }
113 return result;
114 }
115
116 std::expected<std::u16string, std::string>
118 std::u16string_view pattern,
121 std::size_t limit)
122 {
123 std::u16string output;
124 output.reserve(std::min(pattern.size(), limit));
125
126 for (std::size_t index = 0;
127 index < pattern.size() && output.size() < limit;)
128 {
129 if (pattern[index] != u'^')
130 {
131 output.push_back(pattern[index++]);
132 continue;
133 }
134
135 if (index + 1 >= pattern.size())
136 break;
137
138 const auto code = upperAscii(pattern[index + 1]);
139 index += 2;
140
141 switch (code)
142 {
143 case u'1':
145 break;
146 case u'2':
148 break;
149 case u'3':
151 break;
152 case u'4':
154 break;
155 case u'A':
157 break;
158 case u'P':
159 case u'Q':
160 {
161 const auto player = resolvedPlayer(
162 resolvers,
163 code == u'P' ? arguments.numberC : arguments.numberB);
164 if (!player) return std::unexpected(player.error());
165 appendBounded(output, *player, limit);
166 break;
167 }
168 case u'S':
169 if (arguments.numberD >= 0 &&
170 arguments.numberD <
171 static_cast<std::int64_t>(rules::SquareCount))
172 {
173 if (!resolvers.squareName)
174 return std::unexpected(
175 "legacy message requires a square-name resolver");
176 const auto square =
177 resolvers.squareName(arguments.numberD);
178 if (!square) return std::unexpected(square.error());
179 appendBounded(output, *square, limit);
180 }
181 else
182 {
183 appendBounded(output, u"?", limit);
184 }
185 break;
186 case u'T':
187 {
188 if (arguments.numberE < 0 ||
189 static_cast<std::uint64_t>(arguments.numberE) >
190 std::numeric_limits<std::uint32_t>::max())
191 return std::unexpected(
192 "legacy property set is outside the 32-bit range");
193 if (!resolvers.propertySetNames)
194 return std::unexpected(
195 "legacy message requires a property-set resolver");
196 const auto properties = resolvers.propertySetNames(
197 static_cast<std::uint32_t>(arguments.numberE));
198 if (!properties)
199 return std::unexpected(properties.error());
200 appendBounded(output, *properties, limit);
201 break;
202 }
203 default:
204 // Userifce.cpp writes InputStringPntr (the '^') rather than
205 // the unknown code into TempWString[1]. Preserve that quirk.
206 appendBounded(output, u"^^", limit);
207 break;
208 }
209 }
210
211 // The retail bound counts UTF-16 units. Modern names may contain
212 // supplementary characters, so never publish half of their last pair.
213 if (!output.empty() && output.back() >= 0xD800 && output.back() <= 0xDBFF)
214 output.pop_back();
215 return output;
216 }
217}
std::size_t count
std::string_view text
unsigned index
std::deque< Bytes > output
std::uint16_t value
Definition Timers.cpp:16
std::expected< std::u16string, std::string > wideToUtf16(std::wstring_view text)
std::expected< std::u16string, std::string > formatLegacyMessage(std::u16string_view pattern, const LegacyFormatArguments &arguments, const LegacyFormatResolvers &resolvers, std::size_t limit)
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr std::size_t SquareCount
Definition RuleTypes.hpp:24