SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
Text.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include <BufferStream.h>
6
7using namespace sourcepp;
8
10 static const EscapeSequenceMap defaultEscapeSequences{
11 {'\'', '\''},
12 {'\"', '\"'},
13 {'?', '?'},
14 {'\\', '\\'},
15 {'a', '\a'},
16 {'b', '\b'},
17 {'f', '\f'},
18 {'n', '\n'},
19 {'r', '\r'},
20 {'t', '\t'},
21 {'v', '\v'},
22 };
23 return defaultEscapeSequences;
24}
25
27 static const EscapeSequenceMap noEscapeSequences{};
28 return useEscapes ? getDefaultEscapeSequences() : noEscapeSequences;
29}
30
31const std::vector<std::string_view>& parser::text::Tokenizer::Result::getTokens() const {
32 return this->tokens;
33}
34
36 return this->tokens.size();
37}
38
40 return this->head;
41}
42
44 this->head = std::clamp<uint64_t>(head_, 0, this->tokens.size());
45}
46
48 if (this->head >= this->tokens.size()) {
49 throw std::overflow_error{""};
50 }
51 return this->tokens[this->head++];
52}
53
54std::string_view parser::text::Tokenizer::Result::peek() const {
55 if (this->head >= this->tokens.size()) {
56 throw std::overflow_error{""};
57 }
58 return this->tokens[this->head];
59}
60
64
66 this->singleLineCommentStart = singleLineCommentStart_;
67 return *this;
68}
69
71 this->multiLineCommentStart = multiLineCommentStart_;
72 return *this;
73}
74
76 this->multiLineCommentEnd = multiLineCommentEnd_;
77 return *this;
78}
79
80//parser::text::Tokenizer& parser::text::Tokenizer::setQuotes(std::initializer_list<std::pair<char, char>> quotes_) {
81// this->quotes = quotes_;
82// return *this;
83//}
84
86 this->escapeSequences = escapeSequences_;
87 return *this;
88}
89
90parser::text::Tokenizer::Result parser::text::Tokenizer::parse(std::string_view text, std::string& backingString) const {
91 BufferStreamReadOnly stream{text};
92
93 backingString.resize(text.length());
94 BufferStream backing{backingString};
95
96 Result result;
97 try {
98 while (stream.tell() < stream.size()) {
99 if (this->singleLineCommentStart.empty()) {
100 if (this->multiLineCommentStart.empty()) {
101 eatWhitespace(stream);
102 } else {
104 }
105 } else {
106 if (this->multiLineCommentStart.empty()) {
108 } else {
110 }
111 }
112 result.tokens.push_back(readStringToBuffer(stream, backing, /*this->quotes*/ {DEFAULT_QUOTES}, this->escapeSequences));
113 }
114 } catch (const std::overflow_error&) {}
115 return result;
116}
117
119 // Reverse escape sequences map (assume that it's bidirectional)
120 EscapeSequenceMap specialSequences;
121 for (const auto& [normal, special] : escapeSequences) {
122 specialSequences[special] = normal;
123 }
124
125 std::string out;
126 for (int i = 0; i < str.length(); i++) {
127 if (specialSequences.contains(str[i])) {
128 out += '\\';
129 out += specialSequences[str[i]];
130 } else {
131 out += str[i];
132 }
133 }
134 return out;
135}
136
138 std::string out;
139 for (int i = 0; i < str.length(); i++) {
140 if (!escapeSequences.empty() && str[i] == '\\' && i < str.length() - 1) {
141 if (auto n = str[i + 1]; escapeSequences.contains(n)) {
142 out += escapeSequences.at(n);
143 } else {
144 out += str[i];
145 out += str[i + 1];
146 }
147 } else {
148 out += str[i];
149 }
150 }
151 return out;
152}
153
154void parser::text::eatWhitespace(BufferStream& stream) {
155 while (stream.tell() < stream.size() && string::isWhitespace(stream.read<char>())) {}
156 if (stream.tell() == stream.size() && string::isWhitespace(stream.seek(-1, std::ios::cur).read<char>())) return;
157 stream.seek(-1, std::ios::cur);
158}
159
160void parser::text::eatSingleLineComment(BufferStream& stream) {
161 while (stream.tell() < stream.size() && !string::isNewLine(stream.read<char>())) {}
162}
163
164void parser::text::eatMultiLineComment(BufferStream& stream, std::string_view multiLineCommentEnd) {
165 while (!std::ranges::equal(stream.read_span<char>(multiLineCommentEnd.length()), multiLineCommentEnd)) {
166 stream.seek(-static_cast<int64_t>(multiLineCommentEnd.length() - 1), std::ios::cur);
167 }
168}
169
172}
173
174void parser::text::eatWhitespaceAndMultiLineComments(BufferStream& stream, std::string_view multiLineCommentStart) {
176}
177
178// NOLINTNEXTLINE(*-no-recursion)
179void parser::text::eatWhitespaceAndComments(BufferStream& stream, std::string_view singleLineCommentStart, std::string_view multiLineCommentStart) {
180 eatWhitespace(stream);
181
182 if (!singleLineCommentStart.empty()) {
183 if (std::ranges::equal(stream.read_span<char>(singleLineCommentStart.length()), singleLineCommentStart)) {
184 eatSingleLineComment(stream);
186 return;
187 }
188 stream.seek(-static_cast<int64_t>(singleLineCommentStart.length()), std::ios::cur);
189 }
190
191 if (!multiLineCommentStart.empty()) {
192 if (std::ranges::equal(stream.read_span<char>(multiLineCommentStart.length()), multiLineCommentStart)) {
193 eatMultiLineComment(stream);
195 return;
196 }
197 stream.seek(-static_cast<int64_t>(multiLineCommentStart.length()), std::ios::cur);
198 }
199}
200
201bool parser::text::tryToEatChar(BufferStream& stream, char c) {
202 if (stream.tell() == stream.size() || stream.peek<char>() != c) {
203 return false;
204 }
205 stream.skip();
206 return true;
207}
208
209std::string parser::text::readString(BufferStream& stream, std::initializer_list<std::pair<char, char>> quotes, const EscapeSequenceMap& escapeSequences) {
210 std::string out;
211
212 bool stopAtWhitespace = true;
213 std::pair<char, char> quoteUsed;
214 auto c = stream.read<char>();
215 for (const auto& quote : quotes) {
216 if (c == quote.first) {
217 stopAtWhitespace = false;
218 quoteUsed = quote;
219 break;
220 }
221 }
222 if (stopAtWhitespace) {
223 out += c;
224 }
225
226 for (c = stream.read<char>(); (stopAtWhitespace && !string::isWhitespace(c)) || (!stopAtWhitespace && quoteUsed.second != c); stream >> c) {
227 if (!escapeSequences.empty() && c == '\\') {
228 auto n = stream.read<char>();
229 if (stopAtWhitespace && string::isWhitespace(n)) {
230 break;
231 }
232 if (escapeSequences.contains(n)) {
233 out += escapeSequences.at(n);
234 } else if (!stopAtWhitespace && quoteUsed.second == c) {
235 break;
236 } else {
237 out += c;
238 out += n;
239 }
240 } else {
241 out += c;
242 }
243 }
244
245 return out;
246}
247
248std::string_view parser::text::readStringToBuffer(BufferStream& stream, BufferStream& backing, std::initializer_list<std::pair<char, char>> quotes, const EscapeSequenceMap& escapeSequences) {
249 const auto startSpan = backing.tell();
250
251 bool stopAtWhitespace = true;
252 std::pair<char, char> quoteUsed;
253 auto c = stream.read<char>();
254 for (const auto& quote : quotes) {
255 if (c == quote.first) {
256 stopAtWhitespace = false;
257 quoteUsed = quote;
258 break;
259 }
260 }
261 if (stopAtWhitespace) {
262 backing << c;
263 }
264
265 for (c = stream.read<char>(); (stopAtWhitespace && !string::isWhitespace(c)) || (!stopAtWhitespace && quoteUsed.second != c); stream >> c) {
266 if (!escapeSequences.empty() && c == '\\') {
267 auto n = stream.read<char>();
268 if (stopAtWhitespace && string::isWhitespace(n)) {
269 break;
270 }
271 if (escapeSequences.contains(n)) {
272 backing << escapeSequences.at(n);
273 } else if (!stopAtWhitespace && quoteUsed.second == c) {
274 break;
275 } else {
276 backing << c << n;
277 }
278 } else {
279 backing << c;
280 }
281 }
282
283 backing << '\0';
284 // ReSharper disable once CppRedundantCastExpression
285 return {reinterpret_cast<const char*>(backing.data()) + startSpan, static_cast<std::string_view::size_type>(backing.tell() - 1 - startSpan)};
286}
287
288std::string parser::text::readUnquotedString(BufferStream& stream, const EscapeSequenceMap& escapeSequences) {
289 return readString(stream, {}, escapeSequences);
290}
291
292std::string_view parser::text::readUnquotedStringToBuffer(BufferStream& stream, BufferStream& backing, const EscapeSequenceMap& escapeSequences) {
293 return readStringToBuffer(stream, backing, {}, escapeSequences);
294}
295
296std::string parser::text::readUnquotedString(BufferStream& stream, std::string_view end, const EscapeSequenceMap& escapeSequences) {
297 std::string out;
298
299 for (char c = stream.read<char>(); !string::isWhitespace(c) && end.find(c) == std::string_view::npos; c = stream.read<char>()) {
300 if (!escapeSequences.empty() && c == '\\') {
301 if (auto n = stream.read<char>(); escapeSequences.contains(n)) {
302 out += escapeSequences.at(n);
303 } else if (string::isWhitespace(n) || end.find(n) != std::string_view::npos) {
304 break;
305 } else {
306 out += c;
307 out += n;
308 }
309 } else {
310 out += c;
311 }
312 }
313
314 return out;
315}
316
317std::string_view parser::text::readUnquotedStringToBuffer(BufferStream& stream, BufferStream& backing, std::string_view end, const EscapeSequenceMap& escapeSequences) {
318 const auto startSpan = backing.tell();
319
320 for (char c = stream.read<char>(); !string::isWhitespace(c) && end.find(c) == std::string_view::npos; c = stream.read<char>()) {
321 if (!escapeSequences.empty() && c == '\\') {
322 if (auto n = stream.read<char>(); escapeSequences.contains(n)) {
323 backing << escapeSequences.at(n);
324 } else if (string::isWhitespace(n) || end.find(n) != std::string_view::npos) {
325 break;
326 } else {
327 backing << c << n;
328 }
329 } else {
330 backing << c;
331 }
332 }
333
334 backing << '\0';
335 // ReSharper disable once CppRedundantCastExpression
336 return {reinterpret_cast<const char*>(backing.data()) + startSpan, static_cast<std::string_view::size_type>(backing.tell() - 1 - startSpan)};
337}
std::vector< std::string_view > tokens
Definition Text.h:76
const std::vector< std::string_view > & getTokens() const
Definition Text.cpp:31
std::string_view peek() const
Definition Text.cpp:54
Tokenizer & setEscapeSequences(const EscapeSequenceMap &escapeSequences_)
Definition Text.cpp:85
Tokenizer & setMultiLineCommentStart(std::string_view multiLineCommentStart_)
Definition Text.cpp:70
std::string_view singleLineCommentStart
Definition Text.h:95
Result parse(std::string_view text, std::string &backingString) const
Definition Text.cpp:90
EscapeSequenceMap escapeSequences
Definition Text.h:100
Tokenizer & setSingleLineCommentStart(std::string_view singleLineCommentStart_)
Definition Text.cpp:65
Tokenizer & setMultiLineCommentEnd(std::string_view multiLineCommentEnd_)
Definition Text.cpp:75
std::string_view multiLineCommentStart
Definition Text.h:96
std::string_view multiLineCommentEnd
Definition Text.h:97
std::string convertSpecialCharsToEscapes(std::string_view str, const EscapeSequenceMap &escapeSequences)
Convert special characters like \n to escaped special characters like \\n.
Definition Text.cpp:118
std::string convertEscapesToSpecialChars(std::string_view str, const EscapeSequenceMap &escapeSequences)
Convert escaped special characters like \\n to special characters like \n.
Definition Text.cpp:137
void eatMultiLineComment(BufferStream &stream, std::string_view multiLineCommentEnd=DEFAULT_MULTI_LINE_COMMENT_END)
If a multi line comment is detected, eat its contents.
Definition Text.cpp:164
std::string readString(BufferStream &stream, std::initializer_list< std::pair< char, char > > quotes={DEFAULT_QUOTES}, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
Definition Text.cpp:209
std::unordered_map< char, char > EscapeSequenceMap
Definition Text.h:19
void eatSingleLineComment(BufferStream &stream)
If a single line comment is detected, eat its contents.
Definition Text.cpp:160
void eatWhitespaceAndSingleLineComments(BufferStream &stream, std::string_view singleLineCommentStart=DEFAULT_SINGLE_LINE_COMMENT_START)
Eat all whitespace and single line comments after the current stream position.
Definition Text.cpp:170
void eatWhitespace(BufferStream &stream)
Eat all whitespace after the current stream position.
Definition Text.cpp:154
bool tryToEatChar(BufferStream &stream, char c)
If the given char exists at the current position, skip over it.
Definition Text.cpp:201
constexpr std::pair DEFAULT_QUOTES
Definition Text.h:17
const EscapeSequenceMap & getDefaultEscapeSequencesOrNone(bool useEscapes)
Definition Text.cpp:26
std::string_view readUnquotedStringToBuffer(BufferStream &stream, BufferStream &backing, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
Definition Text.cpp:292
std::string readUnquotedString(BufferStream &stream, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
Definition Text.cpp:288
void eatWhitespaceAndComments(BufferStream &stream, std::string_view singleLineCommentStart=DEFAULT_SINGLE_LINE_COMMENT_START, std::string_view multiLineCommentStart=DEFAULT_MULTI_LINE_COMMENT_START)
Eat all whitespace and comments after the current stream position.
Definition Text.cpp:179
std::string_view readStringToBuffer(BufferStream &stream, BufferStream &backing, std::initializer_list< std::pair< char, char > > quotes={DEFAULT_QUOTES}, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
Definition Text.cpp:248
const EscapeSequenceMap & getDefaultEscapeSequences()
Definition Text.cpp:9
void eatWhitespaceAndMultiLineComments(BufferStream &stream, std::string_view multiLineCommentStart=DEFAULT_MULTI_LINE_COMMENT_START)
Eat all whitespace and multi line comments after the current stream position.
Definition Text.cpp:174
bool isWhitespace(char c)
If a char is a whitespace character.
Definition String.cpp:31
bool isNewLine(char c)
If a char is a newline character.
Definition String.cpp:23