1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017, The OpenClonk Team and contributors
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18#include "StdCompiler.h"
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <ctype.h>
23
24#include <algorithm>
25#include <cinttypes>
26#include <cstring>
27#include <format>
28#include <utility>
29
30StdCompiler::NameGuard::NameGuard(NameGuard &&other) noexcept
31 : compiler{std::exchange(obj&: other.compiler, new_val: nullptr)}, foundName{other.foundName} {}
32
33StdCompiler::NameGuard &StdCompiler::NameGuard::operator=(NameGuard &&other) noexcept
34{
35 compiler = std::exchange(obj&: other.compiler, new_val: nullptr);
36 foundName = other.foundName;
37 return *this;
38}
39
40StdCompiler::NameGuard::~NameGuard()
41{
42 End();
43}
44
45void StdCompiler::NameGuard::End()
46{
47 if (compiler)
48 {
49 compiler->NameEnd();
50 compiler = nullptr;
51 }
52}
53
54void StdCompiler::NameGuard::Abort()
55{
56 if (compiler)
57 {
58 compiler->NameEnd(fBreak: true);
59 compiler = nullptr;
60 }
61}
62
63void StdCompiler::NameGuard::Disarm() noexcept
64{
65 compiler = nullptr;
66}
67
68// *** StdCompiler
69
70
71char StdCompiler::SeparatorToChar(Sep eSep)
72{
73 switch (eSep)
74 {
75 case SEP_SEP: return ',';
76 case SEP_SEP2: return ';';
77 case SEP_SET: return '=';
78 case SEP_PART: return '.';
79 case SEP_PART2: return ':';
80 case SEP_PLUS: return '+';
81 case SEP_START: return '(';
82 case SEP_END: return ')';
83 case SEP_START2: return '[';
84 case SEP_END2: return ']';
85 case SEP_VLINE: return '|';
86 case SEP_DOLLAR: return '$';
87 }
88 assert(false);
89 return ' ';
90}
91
92bool StdCompiler::IsIdentifierChar(char c) noexcept
93{
94 return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '-';
95}
96
97bool StdCompiler::IsIdentifier(std::string_view str)
98{
99 return std::all_of(first: begin(cont&: str), last: end(cont&: str), pred: StdCompiler::IsIdentifierChar);
100}
101
102// *** StdCompilerBinWrite
103
104void StdCompilerBinWrite::QWord (int64_t &rInt) { WriteValue(rValue: rInt); }
105void StdCompilerBinWrite::QWord (uint64_t &rInt) { WriteValue(rValue: rInt); }
106void StdCompilerBinWrite::DWord (int32_t &rInt) { WriteValue(rValue: rInt); }
107void StdCompilerBinWrite::DWord (uint32_t &rInt) { WriteValue(rValue: rInt); }
108void StdCompilerBinWrite::Word (int16_t &rShort) { WriteValue(rValue: rShort); }
109void StdCompilerBinWrite::Word (uint16_t &rShort) { WriteValue(rValue: rShort); }
110void StdCompilerBinWrite::Byte (int8_t &rByte) { WriteValue(rValue: rByte); }
111void StdCompilerBinWrite::Byte (uint8_t &rByte) { WriteValue(rValue: rByte); }
112void StdCompilerBinWrite::Boolean (bool &rBool) { WriteValue(rValue: rBool); }
113void StdCompilerBinWrite::Character(char &rChar) { WriteValue(rValue: rChar); }
114
115void StdCompilerBinWrite::String(char *szString, size_t iMaxLength, RawCompileType eType)
116{
117 WriteData(pData: szString, iSize: strlen(s: szString) + 1);
118}
119
120void StdCompilerBinWrite::String(std::string &str, RawCompileType type)
121{
122 WriteData(pData: str.c_str(), iSize: str.size() + 1);
123}
124
125template <class T>
126void StdCompilerBinWrite::WriteValue(const T &rValue)
127{
128 // Copy data
129 if (fSecondPass)
130 *Buf.getMPtr<T>(iPos) = rValue;
131 iPos += sizeof(rValue);
132}
133
134void StdCompilerBinWrite::WriteData(const void *pData, size_t iSize)
135{
136 // Copy data
137 if (fSecondPass)
138 Buf.Write(pnData: pData, inSize: iSize, iAt: iPos);
139 iPos += iSize;
140}
141
142void StdCompilerBinWrite::Raw(void *pData, size_t iSize, RawCompileType eType)
143{
144 // Copy data
145 if (fSecondPass)
146 Buf.Write(pnData: pData, inSize: iSize, iAt: iPos);
147 iPos += iSize;
148}
149
150void StdCompilerBinWrite::Begin()
151{
152 fSecondPass = false; iPos = 0;
153}
154
155void StdCompilerBinWrite::BeginSecond()
156{
157 Buf.New(inSize: iPos);
158 fSecondPass = true; iPos = 0;
159}
160
161// *** StdCompilerBinRead
162
163void StdCompilerBinRead::QWord(int64_t &rInt) { ReadValue(rValue&: rInt); }
164void StdCompilerBinRead::QWord(uint64_t &rInt) { ReadValue(rValue&: rInt); }
165void StdCompilerBinRead::DWord(int32_t &rInt) { ReadValue(rValue&: rInt); }
166void StdCompilerBinRead::DWord(uint32_t &rInt) { ReadValue(rValue&: rInt); }
167void StdCompilerBinRead::Word(int16_t &rShort) { ReadValue(rValue&: rShort); }
168void StdCompilerBinRead::Word(uint16_t &rShort) { ReadValue(rValue&: rShort); }
169void StdCompilerBinRead::Byte(int8_t &rByte) { ReadValue(rValue&: rByte); }
170void StdCompilerBinRead::Byte(uint8_t &rByte) { ReadValue(rValue&: rByte); }
171void StdCompilerBinRead::Boolean(bool &rBool) { ReadValue(rValue&: rBool); }
172void StdCompilerBinRead::Character(char &rChar) { ReadValue(rValue&: rChar); }
173
174void StdCompilerBinRead::String(char *szString, size_t iMaxLength, RawCompileType eType)
175{
176 // At least one byte data needed
177 if (iPos >= Buf.getSize())
178 {
179 excEOF(); return;
180 }
181 // Copy until no data left
182 char *pPos = szString;
183 while (*pPos++ = *Buf.getPtr<char>(pos: iPos++))
184 if (iPos >= Buf.getSize())
185 {
186 excEOF(); return;
187 }
188 else if (pPos > szString + iMaxLength)
189 {
190 excCorrupt(message: "string too long"); return;
191 }
192}
193
194void StdCompilerBinRead::String(std::string &str, RawCompileType type)
195{
196 // At least one byte data needed
197 if (iPos >= Buf.getSize())
198 {
199 excEOF(); return;
200 }
201 const auto iStart = iPos;
202 // Search string end
203 while (*Buf.getPtr<char>(pos: iPos++))
204 if (iPos >= Buf.getSize())
205 {
206 excEOF(); return;
207 }
208 // Copy data
209 str.assign(first: Buf.getPtr<char>(pos: iStart), last: Buf.getPtr<char>(pos: iPos - 1));
210}
211
212void StdCompilerBinRead::Raw(void *pData, size_t iSize, RawCompileType eType)
213{
214 if (iPos + iSize > Buf.getSize())
215 {
216 excEOF(); return;
217 }
218 // Copy data
219 memcpy(dest: pData, src: Buf.getPtr(i: iPos), n: iSize);
220 iPos += iSize;
221}
222
223std::string StdCompilerBinRead::getPosition() const
224{
225 return std::format(fmt: "byte {}", args: iPos);
226}
227
228template <class T>
229inline void StdCompilerBinRead::ReadValue(T &rValue)
230{
231 // Don't read beyond end of buffer
232 if (iPos + sizeof(T) > Buf.getSize())
233 {
234 excEOF(); return;
235 }
236 // Copy
237 rValue = *Buf.getPtr<T>(iPos);
238 iPos += sizeof(T);
239}
240
241void StdCompilerBinRead::Begin()
242{
243 iPos = 0;
244}
245
246// *** StdCompilerINIWrite
247
248StdCompiler::NameGuard StdCompilerINIWrite::Name(const char *szName)
249{
250 // Sub-Namesections exist, so it's a section. Write name if not already done so.
251 if (fPutName) PutName(fSection: true);
252 // Push struct
253 Naming *pnNaming = new Naming;
254 pnNaming->Name.Copy(pnData: szName);
255 pnNaming->Parent = pNaming;
256 pNaming = pnNaming;
257 iDepth++;
258 // Done
259 fPutName = true; fInSection = false;
260 return {this, true};
261}
262
263void StdCompilerINIWrite::NameEnd(bool fBreak)
264{
265 // Append newline
266 if (!fPutName && !fInSection)
267 buf += "\r\n";
268 fPutName = false;
269 // Note this makes it impossible to distinguish an empty name section from
270 // a non-existing name section.
271
272 // Pop
273 assert(iDepth);
274 Naming *poNaming = pNaming;
275 pNaming = poNaming->Parent;
276 delete poNaming;
277 iDepth--;
278 // We're inside a section now
279 fInSection = true;
280}
281
282bool StdCompilerINIWrite::Separator(Sep eSep)
283{
284 if (fInSection)
285 {
286 // Re-put section name
287 PutName(fSection: true);
288 }
289 else
290 {
291 PrepareForValue();
292 buf += SeparatorToChar(eSep);
293 }
294 return true;
295}
296
297void StdCompilerINIWrite::QWord(int64_t &rInt)
298{
299 PrepareForValue();
300 buf += std::format(fmt: "{}", args&: rInt);
301}
302
303void StdCompilerINIWrite::QWord(uint64_t &rInt)
304{
305 PrepareForValue();
306 buf += std::format(fmt: "{}", args&: rInt);
307}
308
309void StdCompilerINIWrite::DWord(int32_t &rInt)
310{
311 PrepareForValue();
312 buf += std::format(fmt: "{}", args&: rInt);
313}
314
315void StdCompilerINIWrite::DWord(uint32_t &rInt)
316{
317 PrepareForValue();
318 buf += std::format(fmt: "{}", args&: rInt);
319}
320
321void StdCompilerINIWrite::Word(int16_t &rInt)
322{
323 PrepareForValue();
324 buf += std::format(fmt: "{}", args&: rInt);
325}
326
327void StdCompilerINIWrite::Word(uint16_t &rInt)
328{
329 PrepareForValue();
330 buf += std::format(fmt: "{}", args&: rInt);
331}
332
333void StdCompilerINIWrite::Byte(int8_t &rByte)
334{
335 PrepareForValue();
336 buf += std::format(fmt: "{:d}", args&: rByte);
337}
338
339void StdCompilerINIWrite::Byte(uint8_t &rInt)
340{
341 PrepareForValue();
342 buf += std::format(fmt: "{:d}", args&: rInt);
343}
344
345void StdCompilerINIWrite::Boolean(bool &rBool)
346{
347 PrepareForValue();
348 buf += rBool ? "true" : "false";
349}
350
351void StdCompilerINIWrite::Character(char &rChar)
352{
353 PrepareForValue();
354 buf += rChar;
355}
356
357void StdCompilerINIWrite::String(char *szString, size_t iMaxLength, RawCompileType eType)
358{
359 StringN(szString, iMaxLength, eType);
360}
361
362void StdCompilerINIWrite::StringN(const char *szString, size_t iMaxLength, RawCompileType eType)
363{
364 PrepareForValue();
365 switch (eType)
366 {
367 case RCT_Escaped:
368 WriteEscaped(szString, pEnd: szString + strlen(s: szString));
369 break;
370 case RCT_All:
371 case RCT_Idtf:
372 case RCT_IdtfAllowEmpty:
373 case RCT_ID:
374 buf += szString;
375 }
376}
377
378void StdCompilerINIWrite::String(std::string &str, RawCompileType type)
379{
380 StringN(szString: str.c_str(), iMaxLength: str.size(), eType: type);
381}
382
383void StdCompilerINIWrite::Raw(void *pData, size_t iSize, RawCompileType eType)
384{
385 switch (eType)
386 {
387 case RCT_Escaped:
388 WriteEscaped(szString: reinterpret_cast<char *>(pData), pEnd: reinterpret_cast<char *>(pData) + iSize);
389 break;
390 case RCT_All:
391 case RCT_Idtf:
392 case RCT_IdtfAllowEmpty:
393 case RCT_ID:
394 buf.append(s: reinterpret_cast<char *>(pData), n: iSize);
395 }
396}
397
398void StdCompilerINIWrite::Begin()
399{
400 pNaming = nullptr;
401 fPutName = false;
402 iDepth = 0;
403 fInSection = false;
404 buf.clear();
405}
406
407void StdCompilerINIWrite::End()
408{
409 // Ensure all namings were closed properly
410 assert(!iDepth);
411}
412
413void StdCompilerINIWrite::PrepareForValue()
414{
415 // Put name (value-type), if not already done so
416 if (fPutName) PutName(fSection: false);
417 // No data allowed inside of sections
418 assert(!fInSection);
419 // No values allowed on top-level - must be contained in at least one section
420 assert(iDepth > 1);
421}
422
423void StdCompilerINIWrite::WriteEscaped(const char *szString, const char *pEnd)
424{
425 buf += '"';
426 // Try to write chunks as huge as possible of "normal" chars.
427 // Note this excludes '\0', so the standard Append() can be used.
428 const char *pStart, *pPos; pStart = pPos = szString;
429 bool fLastNumEscape = false; // catch "\1""1", which must become "\1\61"
430 for (; pPos < pEnd; pPos++)
431 if (!isprint(static_cast<unsigned char>(*pPos)) || *pPos == '\\' || *pPos == '"' || (fLastNumEscape && isdigit(static_cast<unsigned char>(*pPos))))
432 {
433 // Write everything up to this point
434 if (pPos - pStart) buf.append(s: pStart, n: pPos - pStart);
435 // Escape
436 fLastNumEscape = false;
437 switch (*pPos)
438 {
439 case '\a': buf += "\\a"; break;
440 case '\b': buf += "\\b"; break;
441 case '\f': buf += "\\f"; break;
442 case '\n': buf += "\\n"; break;
443 case '\r': buf += "\\r"; break;
444 case '\t': buf += "\\t"; break;
445 case '\v': buf += "\\v"; break;
446 case '\"': buf += "\\\""; break;
447 case '\\': buf += "\\\\"; break;
448 default:
449 buf += std::format(fmt: "\\{:o}", args: *reinterpret_cast<const unsigned char *>(pPos));
450 fLastNumEscape = true;
451 }
452 // Set pointer
453 pStart = pPos + 1;
454 }
455 else
456 fLastNumEscape = false;
457 // Write the rest
458 if (pEnd - pStart) buf.append(s: pStart, n: pEnd - pStart);
459 buf += '"';
460}
461
462void StdCompilerINIWrite::WriteIndent(bool fSection)
463{
464 // Do not indent level 1 (level 0 values aren't allowed - see above)
465 int iIndent = iDepth - 1;
466 // Sections are indented more, even though they belong to this level
467 if (!fSection) iIndent--;
468 // Do indention
469 if (iIndent <= 0) return;
470 buf.append(n: iIndent * 2, c: ' ');
471}
472
473void StdCompilerINIWrite::PutName(bool fSection)
474{
475 if (fSection && !buf.empty())
476 buf += "\r\n";
477 WriteIndent(fSection);
478 // Put name
479 if (fSection)
480 buf += std::format(fmt: "[{}]\r\n", args: pNaming->Name.getData());
481 else
482 buf += std::format(fmt: "{}=", args: pNaming->Name.getData());
483 // Set flag
484 fPutName = false;
485}
486
487// *** StdCompilerINIRead
488
489StdCompilerINIRead::StdCompilerINIRead()
490 : pNameRoot(nullptr), iDepth(0), iRealDepth(0) {}
491
492StdCompilerINIRead::~StdCompilerINIRead()
493{
494 FreeNameTree();
495}
496
497// Naming
498StdCompiler::NameGuard StdCompilerINIRead::Name(const char *szName)
499{
500 // Increase depth
501 iDepth++;
502 // Parent category virtual?
503 if (iDepth - 1 > iRealDepth)
504 return {this, false};
505 // Name must be alphanumerical and non-empty (force it)
506 if (!isalpha(static_cast<unsigned char>(*szName)))
507 {
508 assert(false); return {this, false};
509 }
510 for (const char *p = szName + 1; *p; p++)
511 // C4Update needs Name**...
512 if (!isalnum(static_cast<unsigned char>(*p)) && *p != ' ' && *p != '_' && *p != '*')
513 {
514 assert(false); return {this, false};
515 }
516 // Search name
517 NameNode *pNode;
518 for (pNode = pName->FirstChild; pNode; pNode = pNode->NextChild)
519 if (pNode->Pos && pNode->Name == szName)
520 break;
521 // Not found?
522 if (!pNode)
523 {
524 NotFoundName = szName;
525 return {this, false};
526 }
527 // Save tree position, indicate success
528 pName = pNode;
529 pPos = pName->Pos;
530 pReenter = nullptr;
531 iRealDepth++;
532 return {this, true};
533}
534
535void StdCompilerINIRead::NameEnd(bool fBreak)
536{
537 assert(iDepth > 0);
538 if (iRealDepth == iDepth)
539 {
540 // Remove childs
541 for (NameNode *pNode = pName->FirstChild, *pNext; pNode; pNode = pNext)
542 {
543 // Report unused entries
544 if (pNode->Pos && !fBreak)
545 Warn(fmt: "Unexpected {} \"{}\"!", args: pNode->Section ? "section" : "value", args: pNode->Name.getData());
546 // delete node
547 pNext = pNode->NextChild;
548 delete pNode;
549 }
550 // Remove name so it won't be found again
551 NameNode *pParent = pName->Parent;
552 (pName->PrevChild ? pName->PrevChild->NextChild : pParent->FirstChild) = pName->NextChild;
553 (pName->NextChild ? pName->NextChild->PrevChild : pParent->LastChild) = pName->PrevChild;
554 delete pName;
555 // Go up
556 pName = pParent;
557 iRealDepth--;
558 }
559 // Decrease depth
560 iDepth--;
561 // This is the middle of nowhere
562 pPos = nullptr; pReenter = nullptr;
563}
564
565bool StdCompilerINIRead::FollowName(const char *szName)
566{
567 // Current naming virtual?
568 if (iDepth > iRealDepth)
569 return false;
570 // Next section must be the one
571 if (!pName->NextChild || pName->NextChild->Name != szName)
572 {
573 // End current naming
574 NameEnd();
575 // Go into virtual naming
576 iDepth++;
577 return false;
578 }
579 // End current naming
580 NameEnd();
581 // Start new one
582 Name(szName).Disarm();
583 // Done
584 return true;
585}
586
587// Separators
588bool StdCompilerINIRead::Separator(Sep eSep)
589{
590 if (iDepth > iRealDepth) return false;
591 // In section?
592 if (pName->Section)
593 {
594 // Store current name, search another section with the same name
595 StdStrBuf CurrName;
596 CurrName.Take(Buf2&: pName->Name);
597 NameEnd();
598 auto guard = Name(szName: CurrName.getData());
599 guard.Disarm();
600 return static_cast<bool>(guard);
601 }
602 // Position saved back from separator mismatch?
603 if (pReenter) { pPos = pReenter; pReenter = nullptr; }
604 // Nothing to read?
605 if (!pPos) return false;
606 // Read (while skipping over whitespace)
607 SkipWhitespace();
608 // Separator mismatch? Let all read attempts fail until the correct separator is found or the naming ends.
609 if (*pPos != SeparatorToChar(eSep)) { pReenter = pPos; pPos = nullptr; return false; }
610 // Go over separator, success
611 pPos++;
612 return true;
613}
614
615void StdCompilerINIRead::NoSeparator()
616{
617 // Position saved back from separator mismatch?
618 if (pReenter) { pPos = pReenter; pReenter = nullptr; }
619}
620
621int StdCompilerINIRead::NameCount(const char *szName)
622{
623 // not in virtual naming
624 if (iDepth > iRealDepth || !pName) return 0;
625 // count within current name
626 int iCount = 0;
627 NameNode *pNode;
628 for (pNode = pName->FirstChild; pNode; pNode = pNode->NextChild)
629 // if no name is given, all valid subsections are counted
630 if (pNode->Pos && (!szName || pNode->Name == szName))
631 ++iCount;
632 return iCount;
633}
634
635// Various data readers
636void StdCompilerINIRead::QWord(int64_t &rInt)
637{
638 rInt = ReadNum(function: strtoll);
639}
640
641void StdCompilerINIRead::QWord(uint64_t &rInt)
642{
643 rInt = ReadNum(function: strtoull);
644}
645
646void StdCompilerINIRead::DWord(int32_t &rInt)
647{
648 rInt = ReadNum(function: strtol);
649}
650
651void StdCompilerINIRead::DWord(uint32_t &rInt)
652{
653 rInt = ReadNum(function: strtoul);
654}
655
656void StdCompilerINIRead::Word(int16_t &rShort)
657{
658 const int MIN = -(1 << 15), MAX = (1 << 15) - 1;
659 int iNum = ReadNum(function: strtol);
660 if (iNum < MIN || iNum > MAX)
661 Warn(fmt: "number out of range ({} to {}): {} ", args: MIN, args: MAX, args&: iNum);
662 rShort = BoundBy(bval: iNum, lbound: MIN, rbound: MAX);
663}
664
665void StdCompilerINIRead::Word(uint16_t &rShort)
666{
667 const unsigned int MIN = 0, MAX = (1 << 16) - 1;
668 unsigned int iNum = ReadNum(function: strtoul);
669 if (iNum > MAX)
670 Warn(fmt: "number out of range ({} to {}): {} ", args: MIN, args: MAX, args&: iNum);
671 rShort = BoundBy(bval: iNum, lbound: MIN, rbound: MAX);
672}
673
674void StdCompilerINIRead::Byte(int8_t &rByte)
675{
676 const int MIN = -(1 << 7), MAX = (1 << 7) - 1;
677 int iNum = ReadNum(function: strtol);
678 if (iNum < MIN || iNum > MAX)
679 Warn(fmt: "number out of range ({} to {}): {} ", args: MIN, args: MAX, args&: iNum);
680 rByte = BoundBy(bval: iNum, lbound: MIN, rbound: MAX);
681}
682
683void StdCompilerINIRead::Byte(uint8_t &rByte)
684{
685 const unsigned int MIN = 0, MAX = (1 << 8) - 1;
686 unsigned int iNum = ReadNum(function: strtoul);
687 if (iNum > MAX)
688 Warn(fmt: "number out of range ({} to {}): {} ", args: MIN, args: MAX, args&: iNum);
689 rByte = BoundBy(bval: iNum, lbound: MIN, rbound: MAX);
690}
691
692void StdCompilerINIRead::Boolean(bool &rBool)
693{
694 if (!pPos) { notFound(szWhat: "Boolean"); return; }
695 if (*pPos == '1' && !isdigit(static_cast<unsigned char>(*(pPos + 1))))
696 {
697 rBool = true; pPos++;
698 }
699 else if (*pPos == '0' && !isdigit(static_cast<unsigned char>(*(pPos + 1))))
700 {
701 rBool = false; pPos++;
702 }
703 else if (SEqual2(szStr1: pPos, szStr2: "true"))
704 {
705 rBool = true; pPos += 4;
706 }
707 else if (SEqual2(szStr1: pPos, szStr2: "false"))
708 {
709 rBool = false; pPos += 5;
710 }
711 else
712 {
713 notFound(szWhat: "Boolean"); return;
714 }
715}
716
717void StdCompilerINIRead::Character(char &rChar)
718{
719 if (!pPos || !isalpha(static_cast<unsigned char>(*pPos)))
720 {
721 notFound(szWhat: "Character"); return;
722 }
723 rChar = *pPos++;
724}
725
726void StdCompilerINIRead::String(char *szString, size_t iMaxLength, RawCompileType eType)
727{
728 // Read data
729 StdBuf Buf = ReadString(iLength: iMaxLength, eTyped: eType, fAppendNull: true);
730 // Copy
731 SCopy(szSource: Buf.getPtr<char>(), sTarget: szString, iMaxL: iMaxLength);
732}
733
734void StdCompilerINIRead::String(std::string &str, RawCompileType type)
735{
736 // For Backwards compatibility: Escaped strings default to normal strings if no escaped string is given
737 if (type == RCT_Escaped && pPos && *pPos != '"') type = RCT_All;
738 // Get length
739 size_t iLength = GetStringLength(eTyped: type);
740 // Read data
741 StdBuf Buf = ReadString(iLength, eTyped: type, fAppendNull: true);
742 str = Buf.getPtr<char>();
743}
744
745void StdCompilerINIRead::Raw(void *pData, size_t iSize, RawCompileType eType)
746{
747 // Read data
748 StdBuf Buf = ReadString(iLength: iSize, eTyped: eType, fAppendNull: false);
749 // Correct size?
750 if (Buf.getSize() != iSize)
751 Warn(fmt: "got {} bytes raw data, but {} bytes expected!", args: Buf.getSize(), args&: iSize);
752 // Copy
753 std::memmove(dest: pData, src: Buf.getData(), n: iSize);
754}
755
756std::string StdCompilerINIRead::getPosition() const
757{
758 if (pPos)
759 return std::format(fmt: "line {}", args: SGetLine(szText: Buf.getData(), cpPosition: pPos));
760 else if (iDepth == iRealDepth)
761 {
762 if (pName->Section)
763 {
764 return std::format(fmt: "section \"{}\", after line {}", args: pName->Name.getData(), args: SGetLine(szText: Buf.getData(), cpPosition: pName->Pos));
765 }
766 else
767 {
768 return std::format(fmt: "value \"{}\", line {}", args: pName->Name.getData(), args: SGetLine(szText: Buf.getData(), cpPosition: pName->Pos));
769 }
770 }
771 else if (iRealDepth)
772 return std::format(fmt: "missing value/section \"{}\" inside section \"{}\" (line {})", args: NotFoundName.getData(), args: pName->Name.getData(), args: SGetLine(szText: Buf.getData(), cpPosition: pName->Pos));
773 else
774 return std::format(fmt: "missing value/section \"{}\"", args: NotFoundName.getData());
775}
776
777void StdCompilerINIRead::Begin()
778{
779 // Already running? This may happen if someone confuses Compile with Value.
780 assert(!iDepth && !iRealDepth && !pNameRoot);
781 // Create tree
782 CreateNameTree();
783 // Start must be inside a section
784 iDepth = iRealDepth = 0;
785 pPos = nullptr; pReenter = nullptr;
786}
787
788void StdCompilerINIRead::End()
789{
790 assert(!iDepth && !iRealDepth);
791 FreeNameTree();
792}
793
794void StdCompilerINIRead::CreateNameTree()
795{
796 FreeNameTree();
797 // Create root node
798 pName = pNameRoot = new NameNode();
799 // No input? Stop
800 if (!Buf) return;
801 // Start scanning
802 pPos = Buf.getPtr(i: 0);
803 while (*pPos)
804 {
805 // Go over whitespace
806 int iIndent = 0;
807 while (*pPos == ' ' || *pPos == '\t')
808 {
809 pPos++; iIndent++;
810 }
811 // Name/Section?
812 bool fSection = *pPos == '[' && isalpha(static_cast<unsigned char>(*(pPos + 1)));
813 if (fSection || isalpha(static_cast<unsigned char>(*pPos)))
814 {
815 // Treat values as if they had more indention
816 // (so they become children of sections on the same level)
817 if (!fSection) iIndent++; else pPos++;
818 // Go up in tree structure if there is less indention
819 while (pName->Parent && pName->Indent >= iIndent)
820 pName = pName->Parent;
821 // Copy name
822 StdStrBuf Name;
823 while (isalnum(static_cast<unsigned char>(*pPos)) || *pPos == ' ' || *pPos == '_')
824 Name.AppendChar(cChar: *pPos++);
825 while (*pPos == ' ' || *pPos == '\t') pPos++;
826 if (*pPos != (fSection ? ']' : '='))
827 {
828 // Warn, ignore
829 if (isprint(static_cast<unsigned char>(*pPos)))
830 {
831 Warn(fmt: "Unexpected character ('{}'): %s ignored", args: unsigned(*pPos), args: fSection ? "section" : "value");
832 }
833 else
834 {
835 Warn(fmt: "Unexpected character ('{:#02x}'): %s ignored", args: unsigned(*pPos), args: fSection ? "section" : "value");
836 }
837 }
838 else
839 {
840 pPos++;
841 // Create new node
842 NameNode *pPrev = pName->LastChild;
843 pName =
844 pName->LastChild =
845 (pName->LastChild ? pName->LastChild->NextChild : pName->FirstChild) =
846 new NameNode(pName);
847 pName->PrevChild = pPrev;
848 pName->Name.Take(Buf2&: Name);
849 pName->Pos = pPos;
850 pName->Indent = iIndent;
851 pName->Section = fSection;
852 // Values don't have children (even if the indention looks like it)
853 if (!fSection)
854 pName = pName->Parent;
855 }
856 }
857 // Skip line
858 while (*pPos && (*pPos != '\n' && *pPos != '\r'))
859 pPos++;
860 while (*pPos == '\n' || *pPos == '\r')
861 pPos++;
862 }
863 // Set pointer back
864 pName = pNameRoot;
865}
866
867void StdCompilerINIRead::FreeNameTree()
868{
869 // free all nodes
870 FreeNameNode(pNode: pNameRoot);
871 pName = pNameRoot = nullptr;
872}
873
874void StdCompilerINIRead::FreeNameNode(NameNode *pDelNode)
875{
876 NameNode *pNode = pDelNode;
877 while (pNode)
878 {
879 if (pNode->FirstChild)
880 pNode = pNode->FirstChild;
881 else
882 {
883 NameNode *pDelete = pNode;
884 if (pDelete == pDelNode) { delete pDelete; break; }
885 if (pNode->NextChild)
886 pNode = pNode->NextChild;
887 else
888 {
889 pNode = pNode->Parent;
890 if (pNode) pNode->FirstChild = nullptr;
891 }
892 delete pDelete;
893 }
894 }
895}
896
897void StdCompilerINIRead::SkipWhitespace()
898{
899 while (*pPos == ' ' || *pPos == '\t')
900 pPos++;
901}
902
903size_t StdCompilerINIRead::GetStringLength(RawCompileType eRawType)
904{
905 // Excpect valid position
906 if (!pPos)
907 {
908 notFound(szWhat: "String"); return 0;
909 }
910 // Skip whitespace
911 SkipWhitespace();
912 // Save position
913 const char *pStart = pPos;
914 // Escaped? Go over '"'
915 if (eRawType == RCT_Escaped && *pPos++ != '"')
916 {
917 notFound(szWhat: "Escaped string"); return 0;
918 }
919 // Search end of string
920 size_t iLength = 0;
921 while (!TestStringEnd(eType: eRawType))
922 {
923 // Read a character (we're just counting atm)
924 if (eRawType == RCT_Escaped)
925 ReadEscapedChar();
926 else
927 pPos++;
928 // Count it
929 iLength++;
930 }
931 // Reset position, return the length
932 pPos = pStart;
933 return iLength;
934}
935
936StdBuf StdCompilerINIRead::ReadString(size_t iLength, RawCompileType eRawType, bool fAppendNull)
937{
938 // Excpect valid position
939 if (!pPos)
940 {
941 notFound(szWhat: "String"); return StdBuf();
942 }
943 // Skip whitespace
944 SkipWhitespace();
945 // Escaped? Go over '"'
946 if (eRawType == RCT_Escaped && *pPos++ != '"')
947 {
948 notFound(szWhat: "Escaped string"); return StdBuf();
949 }
950 // Create buffer
951 StdBuf OutBuf; OutBuf.New(inSize: iLength + (fAppendNull ? sizeof('\0') : 0));
952 // Read
953 char *pOut = OutBuf.getMPtr<char>();
954 while (iLength && !TestStringEnd(eType: eRawType))
955 {
956 // Read a character
957 if (eRawType == RCT_Escaped)
958 *pOut++ = ReadEscapedChar();
959 else
960 *pOut++ = *pPos++;
961 // Count it
962 iLength--;
963 }
964 // Escaped: Go over '"'
965 if (eRawType == RCT_Escaped)
966 {
967 while (*pPos != '"')
968 {
969 if (!*pPos || *pPos == '\n' || *pPos == '\r')
970 {
971 Warn(fmt: "string not terminated!");
972 pPos--;
973 break;
974 }
975 pPos++;
976 }
977 pPos++;
978 }
979 // Nothing read? Identifiers need to be non-empty
980 if (pOut == OutBuf.getData() && (eRawType == RCT_Idtf || eRawType == RCT_ID))
981 {
982 notFound(szWhat: "String"); return StdBuf();
983 }
984 // Append null
985 if (fAppendNull)
986 *pOut = '\0';
987 // Shrink, if less characters were read
988 OutBuf.Shrink(iShrink: iLength);
989 // Done
990 return OutBuf;
991}
992
993bool StdCompilerINIRead::TestStringEnd(RawCompileType eType)
994{
995 switch (eType)
996 {
997 case RCT_Escaped: return *pPos == '"' || !*pPos || *pPos == '\n' || *pPos == '\r';
998 case RCT_All: return !*pPos || *pPos == '\n' || *pPos == '\r';
999 // '-' is needed for Layers in Scenario.txt (C4NameList) and other Material-Texture combinations
1000 case RCT_Idtf: case RCT_IdtfAllowEmpty: case RCT_ID: return !IsIdentifierChar(c: *pPos);
1001 }
1002 // unreachable
1003 return true;
1004}
1005
1006char StdCompilerINIRead::ReadEscapedChar()
1007{
1008 // Catch some no-noes like \0, \n etc.
1009 if (*pPos >= 0 && iscntrl(static_cast<unsigned char>(*pPos)))
1010 {
1011 Warn(fmt: "Nonprintable character found in string: {:02x}", args: static_cast<unsigned char>(*pPos));
1012 return *pPos++;
1013 }
1014 // Not escaped? Just return it
1015 if (*pPos != '\\') return *pPos++;
1016 // What type of escape?
1017 switch (*++pPos)
1018 {
1019 case 'a': pPos++; return '\a';
1020 case 'b': pPos++; return '\b';
1021 case 'f': pPos++; return '\f';
1022 case 'n': pPos++; return '\n';
1023 case 'r': pPos++; return '\r';
1024 case 't': pPos++; return '\t';
1025 case 'v': pPos++; return '\v';
1026 case '\'': pPos++; return '\'';
1027 case '"': pPos++; return '"';
1028 case '\\': pPos++; return '\\';
1029 case '?': pPos++; return '?';
1030 case 'x':
1031 // Treat '\x' as 'x' - damn special cases
1032 if (!isxdigit(static_cast<unsigned char>(*++pPos)))
1033 return 'x';
1034 else
1035 {
1036 // Read everything that looks like it might be hexadecimal - MSVC does it this way, so do not sue me.
1037 int iCode = 0;
1038 do
1039 {
1040 iCode = iCode * 16 + (isdigit(static_cast<unsigned char>(*pPos)) ? *pPos - '0' : *pPos - 'a' + 10); pPos++;
1041 } while (isxdigit(static_cast<unsigned char>(*pPos)));
1042 // Done. Don't bother to check the range (we aren't doing anything mission-critical here, are we?)
1043 return char(iCode);
1044 }
1045 default:
1046 // Not octal? Let it pass through.
1047 if (!isdigit(static_cast<unsigned char>(*pPos)) || *pPos >= '8')
1048 return *pPos++;
1049 else
1050 {
1051 // Read it the octal way.
1052 int iCode = 0;
1053 do
1054 {
1055 iCode = iCode * 8 + (*pPos - '0'); pPos++;
1056 } while (isdigit(static_cast<unsigned char>(*pPos)) && *pPos < '8');
1057 // Done. See above.
1058 return char(iCode);
1059 }
1060 }
1061 // unreachable
1062 assert(false);
1063}
1064
1065void StdCompilerINIRead::notFound(const char *szWhat)
1066{
1067 excNotFound(message: "{} expected", args: szWhat);
1068}
1069