/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ #include "C4Include.h" #include "lib/StdCompiler.h" // *** StdCompiler void StdCompiler::Warn(const char *szWarning, ...) { // Got warning callback? if (!pWarnCB) return; // Format message va_list args; va_start(args, szWarning); StdStrBuf Msg; Msg.FormatV(szWarning, args); // do callback (*pWarnCB)(pWarnData, getPosition().getData(), Msg.getData()); } char StdCompiler::SeparatorToChar(Sep eSep) { switch (eSep) { case SEP_SEP: return ','; case SEP_SEP2: return ';'; case SEP_SET: return '='; case SEP_PART: return '.'; case SEP_PART2: return ':'; case SEP_PLUS: return '+'; case SEP_START: return '('; case SEP_END: return ')'; case SEP_START2: return '['; case SEP_END2: return ']'; case SEP_VLINE: return '|'; case SEP_DOLLAR: return '$'; default: assert(!"Unhandled Separator value"); } return ' '; } bool StdCompiler::IsStringEnd(char c, RawCompileType eType) { switch (eType) { case RCT_Escaped: return c == '"' || !c || c == '\n' || c == '\r'; case RCT_All: return !c || c == '\n' || c == '\r'; // '-' is needed for Layers in Scenario.txt (C4NameList) and other Material-Texture combinations case RCT_Idtf: case RCT_IdtfAllowEmpty: case RCT_ID: return !isalnum((unsigned char)c) && c != '_' && c != '-'; } // unreachable return true; } // *** StdCompilerBinWrite void StdCompilerBinWrite::DWord(int32_t &rInt) { WriteValue(rInt); } void StdCompilerBinWrite::DWord(uint32_t &rInt) { WriteValue(rInt); } void StdCompilerBinWrite::Word(int16_t &rShort) { WriteValue(rShort); } void StdCompilerBinWrite::Word(uint16_t &rShort) { WriteValue(rShort); } void StdCompilerBinWrite::Byte(int8_t &rByte) { WriteValue(rByte); } void StdCompilerBinWrite::Byte(uint8_t &rByte) { WriteValue(rByte); } void StdCompilerBinWrite::Boolean(bool &rBool) { WriteValue(rBool); } void StdCompilerBinWrite::Character(char &rChar) { WriteValue(rChar); } void StdCompilerBinWrite::String(char *szString, size_t iMaxLength, RawCompileType eType) { WriteData(szString, strlen(szString) + 1); } void StdCompilerBinWrite::String(char **pszString, RawCompileType eType) { if (*pszString) WriteData(*pszString, strlen(*pszString) + 1); else WriteValue('\0'); } void StdCompilerBinWrite::String(std::string &str, RawCompileType type) { WriteData(str.c_str(), str.size() + 1); } template void StdCompilerBinWrite::WriteValue(const T &rValue) { // Copy data if (fSecondPass) *getMBufPtr(Buf, iPos) = rValue; iPos += sizeof(rValue); } void StdCompilerBinWrite::WriteData(const void *pData, size_t iSize) { // Copy data if (fSecondPass) Buf.Write(pData, iSize, iPos); iPos += iSize; } void StdCompilerBinWrite::Raw(void *pData, size_t iSize, RawCompileType eType) { // Copy data if (fSecondPass) Buf.Write(pData, iSize, iPos); iPos += iSize; } void StdCompilerBinWrite::Begin() { fSecondPass = false; iPos = 0; } void StdCompilerBinWrite::BeginSecond() { Buf.New(iPos); fSecondPass = true; iPos = 0; } // *** StdCompilerBinRead void StdCompilerBinRead::DWord(int32_t &rInt) { ReadValue(rInt); } void StdCompilerBinRead::DWord(uint32_t &rInt) { ReadValue(rInt); } void StdCompilerBinRead::Word(int16_t &rShort) { ReadValue(rShort); } void StdCompilerBinRead::Word(uint16_t &rShort) { ReadValue(rShort); } void StdCompilerBinRead::Byte(int8_t &rByte) { ReadValue(rByte); } void StdCompilerBinRead::Byte(uint8_t &rByte) { ReadValue(rByte); } void StdCompilerBinRead::Boolean(bool &rBool) { ReadValue(rBool); } void StdCompilerBinRead::Character(char &rChar) { ReadValue(rChar); } void StdCompilerBinRead::String(char *szString, size_t iMaxLength, RawCompileType eType) { // At least one byte data needed if (iPos >= Buf.getSize()) { excEOF(); return; } // Copy until no data left char *pPos = szString; while ((*pPos++ = *getBufPtr(Buf, iPos++))) if (iPos >= Buf.getSize()) { excEOF(); return; } else if (pPos > szString + iMaxLength) { excCorrupt("string too long"); return; } } void StdCompilerBinRead::String(char **pszString, RawCompileType eType) { // At least one byte data needed if (iPos >= Buf.getSize()) { excEOF(); return; } int iStart = iPos; // Search string end while (*getBufPtr(Buf, iPos++)) if (iPos >= Buf.getSize()) { excEOF(); return; } // Allocate and copy data *pszString = (char *) malloc(iPos - iStart); memcpy(*pszString, Buf.getPtr(iStart), iPos - iStart); } void StdCompilerBinRead::String(std::string &str, RawCompileType type) { // At least one byte data needed if (iPos >= Buf.getSize()) { excEOF(); return; } int iStart = iPos; // Search string end while (*getBufPtr(Buf, iPos++)) if (iPos >= Buf.getSize()) { excEOF(); return; } // Copy data str.assign(getBufPtr(Buf, iStart), getBufPtr(Buf, iPos)); } void StdCompilerBinRead::Raw(void *pData, size_t iSize, RawCompileType eType) { if (iPos + iSize > Buf.getSize()) { excEOF(); return; } // Copy data memcpy(pData, Buf.getPtr(iPos), iSize); iPos += iSize; } StdStrBuf StdCompilerBinRead::getPosition() const { return FormatString("byte %ld", static_cast(iPos)); } template inline void StdCompilerBinRead::ReadValue(T &rValue) { // Pufferüberhang prüfen if (iPos + sizeof(T) > Buf.getSize()) { excEOF(); return; } // Kopieren rValue = *getBufPtr(Buf, iPos); iPos += sizeof(T); } void StdCompilerBinRead::Begin() { iPos = 0; } // *** StdCompilerINIWrite bool StdCompilerINIWrite::Name(const char *szName) { // Sub-Namesections exist, so it's a section. Write name if not already done so. if (fPutName) PutName(true); // Push struct Naming *pnNaming = new Naming; pnNaming->Name.Copy(szName); pnNaming->Parent = pNaming; pNaming = pnNaming; iDepth++; // Done fPutName = true; fInSection = false; return true; } void StdCompilerINIWrite::NameEnd(bool fBreak) { // Append newline if (!fPutName && !fInSection) Buf.Append("\n"); fPutName = false; // Note this makes it impossible to distinguish an empty name section from // a non-existing name section. // Pop assert(iDepth); Naming *poNaming = pNaming; pNaming = poNaming->Parent; delete poNaming; iDepth--; // We're inside a section now fInSection = true; } bool StdCompilerINIWrite::Separator(Sep eSep) { if (fInSection) { // Re-put section name PutName(true); } else { PrepareForValue(); Buf.AppendChar(SeparatorToChar(eSep)); } return true; } void StdCompilerINIWrite::DWord(int32_t &rInt) { PrepareForValue(); Buf.AppendFormat("%d", rInt); } void StdCompilerINIWrite::DWord(uint32_t &rInt) { PrepareForValue(); Buf.AppendFormat("%u", rInt); } void StdCompilerINIWrite::Word(int16_t &rInt) { PrepareForValue(); Buf.AppendFormat("%d", rInt); } void StdCompilerINIWrite::Word(uint16_t &rInt) { PrepareForValue(); Buf.AppendFormat("%u", rInt); } void StdCompilerINIWrite::Byte(int8_t &rByte) { PrepareForValue(); Buf.AppendFormat("%d", rByte); } void StdCompilerINIWrite::Byte(uint8_t &rInt) { PrepareForValue(); Buf.AppendFormat("%u", rInt); } void StdCompilerINIWrite::Boolean(bool &rBool) { PrepareForValue(); Buf.Append(rBool ? "true" : "false"); } void StdCompilerINIWrite::Character(char &rChar) { PrepareForValue(); Buf.AppendFormat("%c", rChar); } void StdCompilerINIWrite::String(char *szString, size_t iMaxLength, RawCompileType eType) { StringN(szString, strnlen(szString, iMaxLength), eType); } void StdCompilerINIWrite::StringN(const char *szString, size_t iLength, RawCompileType eType) { PrepareForValue(); switch (eType) { case RCT_Escaped: WriteEscaped(szString, szString + iLength); break; case RCT_All: case RCT_Idtf: case RCT_IdtfAllowEmpty: case RCT_ID: Buf.Append(szString); } } void StdCompilerINIWrite::String(char **pszString, RawCompileType eType) { assert(pszString); char cNull = '\0'; char * szString = *pszString ? *pszString : &cNull; String(szString, strlen(szString), eType); } void StdCompilerINIWrite::Raw(void *pData, size_t iSize, RawCompileType eType) { switch (eType) { case RCT_Escaped: WriteEscaped(reinterpret_cast(pData), reinterpret_cast(pData) + iSize); break; case RCT_All: case RCT_Idtf: case RCT_IdtfAllowEmpty: case RCT_ID: Buf.Append(reinterpret_cast(pData), iSize); } } void StdCompilerINIWrite::String(std::string &str, RawCompileType type) { StringN(str.c_str(), str.size(), type); } void StdCompilerINIWrite::Begin() { pNaming = nullptr; fPutName = false; iDepth = 0; fInSection = false; Buf.Clear(); } void StdCompilerINIWrite::End() { // Ensure all namings were closed properly assert(!iDepth); } void StdCompilerINIWrite::PrepareForValue() { // Put name (value-type), if not already done so if (fPutName) PutName(false); // No data allowed inside of sections assert(!fInSection); // No values allowed on top-level - must be contained in at least one section assert(iDepth > 1); } void StdCompilerINIWrite::WriteEscaped(const char *szString, const char *pEnd) { Buf.AppendChar('"'); // Try to write chunks as huge as possible of "normal" chars. // Note this excludes '\0', so the standard Append() can be used. const char *pStart, *pPos; pStart = pPos = szString; bool fLastNumEscape = false; // catch "\1""1", which must become "\1\61" for (; pPos < pEnd; pPos++) if (!isprint((unsigned char)(unsigned char) *pPos) || *pPos == '\\' || *pPos == '"' || (fLastNumEscape && isdigit((unsigned char)*pPos))) { // Write everything up to this point if (pPos - pStart) Buf.Append(pStart, pPos - pStart); // Escape fLastNumEscape = false; switch (*pPos) { case '\a': Buf.Append(R"(\a)"); break; case '\b': Buf.Append(R"(\b)"); break; case '\f': Buf.Append(R"(\f)"); break; case '\n': Buf.Append(R"(\n)"); break; case '\r': Buf.Append(R"(\r)"); break; case '\t': Buf.Append(R"(\t)"); break; case '\v': Buf.Append(R"(\v)"); break; case '\"': Buf.Append(R"(\")"); break; case '\\': Buf.Append(R"(\\)"); break; default: Buf.AppendFormat(R"(\%o)", *reinterpret_cast(pPos)); fLastNumEscape = true; } // Set pointer pStart = pPos + 1; } else fLastNumEscape = false; // Write the rest if (pEnd - pStart) Buf.Append(pStart, pEnd - pStart); Buf.AppendChar('"'); } void StdCompilerINIWrite::WriteIndent(bool fSection) { // Do not indent level 1 (level 0 values aren't allowed - see above) int iIndent = iDepth - 1; // Sections are indented more, even though they belong to this level if (!fSection) iIndent--; // Do indention if (iIndent <= 0) return; Buf.AppendChars(' ', iIndent * 2); } void StdCompilerINIWrite::PutName(bool fSection) { if (fSection && Buf.getLength()) Buf.Append("\n"); WriteIndent(fSection); // Put name if (fSection) Buf.AppendFormat("[%s]\n", pNaming->Name.getData()); else Buf.AppendFormat("%s=", pNaming->Name.getData()); // Set flag fPutName = false; } // *** StdCompilerINIRead StdCompilerINIRead::StdCompilerINIRead() = default; StdCompilerINIRead::~StdCompilerINIRead() { FreeNameTree(); } // Naming bool StdCompilerINIRead::Name(const char *szName) { // Increase depth iDepth++; // Parent category virtual? if (iDepth - 1 > iRealDepth) return false; // Name must be alphanumerical and non-empty (force it) if (!isalpha((unsigned char)*szName)) { assert(false); return false; } for (const char *p = szName + 1; *p; p++) // C4Update needs Name**... if (!isalnum((unsigned char)*p) && *p != ' ' && *p != '_' && *p != '*') { assert(false); return false; } // Search name NameNode *pNode; for (pNode = pName->FirstChild; pNode; pNode = pNode->NextChild) if (pNode->Pos && pNode->Name == szName) break; // Not found? if (!pNode) { NotFoundName = szName; return false; } // Save tree position, indicate success pName = pNode; pPos = pName->Pos; pReenter = nullptr; iRealDepth++; return true; } void StdCompilerINIRead::NameEnd(bool fBreak) { assert(iDepth > 0); if (iRealDepth == iDepth) { // Remove childs for (NameNode *pNode = pName->FirstChild, *pNext; pNode; pNode = pNext) { // Report unused entries if (pNode->Pos && !fBreak) Warn(R"(Unexpected %s "%s"!)", pNode->Section ? "section" : "value", pNode->Name.getData()); // delete node pNext = pNode->NextChild; delete pNode; } // Remove name so it won't be found again NameNode *pParent = pName->Parent; (pName->PrevChild ? pName->PrevChild->NextChild : pParent->FirstChild) = pName->NextChild; (pName->NextChild ? pName->NextChild->PrevChild : pParent->LastChild) = pName->PrevChild; delete pName; // Go up pName = pParent; iRealDepth--; } // Decrease depth iDepth--; // This is the middle of nowhere pPos = nullptr; pReenter = nullptr; } bool StdCompilerINIRead::FollowName(const char *szName) { // Current naming virtual? if (iDepth > iRealDepth) return false; // Next section must be the one if (!pName->NextChild || pName->NextChild->Name != szName) { // End current naming NameEnd(); // Go into virtual naming iDepth++; return false; } // End current naming NameEnd(); // Start new one Name(szName); // Done return true; } // Separators bool StdCompilerINIRead::Separator(Sep eSep) { if (iDepth > iRealDepth) return false; // In section? if (pName->Section) { // Store current name, search another section with the same name StdStrBuf CurrName = pName->Name; NameEnd(); return Name(CurrName.getData()); } // Position saved back from separator mismatch? if (pReenter) { pPos = pReenter; pReenter = nullptr; } // Nothing to read? if (!pPos) return false; // Read (while skipping over whitespace) SkipWhitespace(); // Separator mismatch? Let all read attempts fail until the correct separator is found or the naming ends. if (*pPos != SeparatorToChar(eSep)) { pReenter = pPos; pPos = nullptr; return false; } // Go over separator, success pPos++; return true; } void StdCompilerINIRead::NoSeparator() { // Position saved back from separator mismatch? if (pReenter) { pPos = pReenter; pReenter = nullptr; } } int StdCompilerINIRead::NameCount(const char *szName) { // not in virtual naming if (iDepth > iRealDepth || !pName) return 0; // count within current name int iCount = 0; NameNode *pNode; for (pNode = pName->FirstChild; pNode; pNode = pNode->NextChild) // if no name is given, all valid subsections are counted if (pNode->Pos && (!szName || pNode->Name == szName)) ++iCount; return iCount; } const char *StdCompilerINIRead::GetNameByIndex(size_t idx) const { // not in virtual naming if (iDepth > iRealDepth || !pName) return nullptr; // count within current name NameNode *pNode; for (pNode = pName->FirstChild; pNode; pNode = pNode->NextChild) // all valid subsections are counted if (pNode->Pos) if (!idx--) return pNode->Name.getData(); // index out of range return nullptr; } // Various data readers void StdCompilerINIRead::DWord(int32_t &rInt) { rInt = ReadNum(); } void StdCompilerINIRead::DWord(uint32_t &rInt) { rInt = ReadUNum(); } void StdCompilerINIRead::Word(int16_t &rShort) { const int MIN = -(1 << 15), MAX = (1 << 15) - 1; int iNum = ReadNum(); if (iNum < MIN || iNum > MAX) Warn("number out of range (%d to %d): %d ", MIN, MAX, iNum); rShort = Clamp(iNum, MIN, MAX); } void StdCompilerINIRead::Word(uint16_t &rShort) { const unsigned int MIN = 0, MAX = (1 << 16) - 1; unsigned int iNum = ReadUNum(); if (iNum > MAX) Warn("number out of range (%u to %u): %u ", MIN, MAX, iNum); rShort = Clamp(iNum, MIN, MAX); } void StdCompilerINIRead::Byte(int8_t &rByte) { const int MIN = -(1 << 7), MAX = (1 << 7) - 1; int iNum = ReadNum(); if (iNum < MIN || iNum > MAX) Warn("number out of range (%d to %d): %d ", MIN, MAX, iNum); rByte = Clamp(iNum, MIN, MAX); } void StdCompilerINIRead::Byte(uint8_t &rByte) { const unsigned int MIN = 0, MAX = (1 << 8) - 1; unsigned int iNum = ReadUNum(); if (iNum > MAX) Warn("number out of range (%u to %u): %u ", MIN, MAX, iNum); rByte = Clamp(iNum, MIN, MAX); } void StdCompilerINIRead::Boolean(bool &rBool) { if (!pPos) { notFound("Boolean"); return; } if (*pPos == '1' && !isdigit((unsigned char)*(pPos+1))) { rBool = true; pPos ++; } else if (*pPos == '0' && !isdigit((unsigned char)*(pPos+1))) { rBool = false; pPos ++; } else if (SEqual2(pPos, "true")) { rBool = true; pPos += 4; } else if (SEqual2(pPos, "false")) { rBool = false; pPos += 5; } else { notFound("Boolean"); return; } } void StdCompilerINIRead::Character(char &rChar) { if (!pPos || !isalpha((unsigned char)*pPos)) { notFound("Character"); return; } rChar = *pPos++; } void StdCompilerINIRead::String(char *szString, size_t iMaxLength, RawCompileType eType) { // Read data StdBuf Buf = ReadString(iMaxLength, eType, true); // Copy SCopy(getBufPtr(Buf), szString, iMaxLength); } void StdCompilerINIRead::String(char **pszString, RawCompileType eType) { // Get length size_t iLength = GetStringLength(eType); // Read data StdBuf Buf = ReadString(iLength, eType, true); // Set *pszString = reinterpret_cast(Buf.GrabPointer()); } void StdCompilerINIRead::String(std::string &str, RawCompileType type) { // Get length size_t iLength = GetStringLength(type); // Read data StdBuf Buf = ReadString(iLength, type, true); str = getBufPtr(Buf); } void StdCompilerINIRead::Raw(void *pData, size_t iSize, RawCompileType eType) { // Read data StdBuf Buf = ReadString(iSize, eType, false); // Correct size? if (Buf.getSize() != iSize) Warn("got %u bytes raw data, but %u bytes expected!", Buf.getSize(), iSize); // Copy MemCopy(Buf.getData(), pData, iSize); } uint32_t StdCompilerINIRead::getLineNumberOfPos(const char *pos) const { // Figure out quickly whether we already know which line this is auto entry = std::lower_bound(lineBreaks.begin(), lineBreaks.end(), pos); if (entry != lineBreaks.end()) { return std::distance(lineBreaks.begin(), entry) + 1; } // Otherwise search through the buffer until we find out, filling the // cache in the process const char *cursor = Buf.getData(); if (!lineBreaks.empty()) cursor = *(lineBreaks.end() - 1) + 1; for (;;) { if (*cursor == '\0' || *cursor == '\n') { lineBreaks.push_back(cursor); // If we're at the end of the file or have found the line break // past the requested position, we're done for now if (*cursor == '\0' || pos < cursor) { break; } } ++cursor; } return std::distance(lineBreaks.begin(), std::lower_bound(lineBreaks.begin(), lineBreaks.end(), pos)) + 1; } StdStrBuf StdCompilerINIRead::getPosition() const { if (pPos) return FormatString("line %d", getLineNumberOfPos(pPos)); else if (iDepth == iRealDepth) return FormatString(pName->Section ? R"(section "%s", after line %d)" : R"(value "%s", line %d)", pName->Name.getData(), getLineNumberOfPos(pName->Pos)); else if (iRealDepth) return FormatString(R"(missing value/section "%s" inside section "%s" (line %d))", NotFoundName.getData(), pName->Name.getData(), getLineNumberOfPos(pName->Pos)); else return FormatString(R"(missing value/section "%s")", NotFoundName.getData()); } void StdCompilerINIRead::Begin() { // Already running? This may happen if someone confuses Compile with Value. assert(!iDepth && !iRealDepth && !pNameRoot); // Create tree CreateNameTree(); // Start must be inside a section iDepth = iRealDepth = 0; pPos = nullptr; pReenter = nullptr; } void StdCompilerINIRead::End() { assert(!iDepth && !iRealDepth); FreeNameTree(); } void StdCompilerINIRead::CreateNameTree() { FreeNameTree(); // Create root node pName = pNameRoot = new NameNode(); // No input? Stop if (!Buf) return; // Start scanning pPos = Buf.getPtr(0); while (*pPos) { // Go over whitespace int iIndent = 0; while (*pPos == ' ' || *pPos == '\t') { pPos++; iIndent++; } // Name/Section? bool fSection = *pPos == '[' && isalpha((unsigned char)*(pPos+1)); if (fSection || isalpha((unsigned char)*pPos)) { // Treat values as if they had more indention // (so they become children of sections on the same level) if (!fSection) iIndent++; else pPos++; // Go up in tree structure if there is less indention while (pName->Parent && pName->Indent >= iIndent) pName = pName->Parent; // Copy name StdStrBuf Name; while (isalnum((unsigned char)*pPos) || *pPos == ' ' || *pPos == '_') Name.AppendChar(*pPos++); while (*pPos == ' ' || *pPos == '\t') pPos++; if ( *pPos != (fSection ? ']' : '=') ) // Warn, ignore Warn(isprint((unsigned char)*pPos) ? "Unexpected character ('%c'): %s ignored" : "Unexpected character ('0x%02x'): %s ignored", unsigned(*pPos), fSection ? "section" : "value"); else { pPos++; // Create new node NameNode *pPrev = pName->LastChild; pName = pName->LastChild = (pName->LastChild ? pName->LastChild->NextChild : pName->FirstChild) = new NameNode(pName); pName->PrevChild = pPrev; pName->Name.Take(std::move(Name)); pName->Pos = pPos; pName->Indent = iIndent; pName->Section = fSection; // Values don't have children (even if the indention looks like it) if (!fSection) pName = pName->Parent; } } // Skip line while (*pPos && (*pPos != '\n' && *pPos != '\r')) pPos++; while (*pPos == '\n' || *pPos == '\r') pPos++; } // Set pointer back pName = pNameRoot; } void StdCompilerINIRead::FreeNameTree() { // free all nodes FreeNameNode(pNameRoot); pName = pNameRoot = nullptr; } void StdCompilerINIRead::FreeNameNode(NameNode *pDelNode) { NameNode *pNode = pDelNode; while (pNode) { if (pNode->FirstChild) pNode = pNode->FirstChild; else { NameNode *pDelete = pNode; if (pDelete == pDelNode) { delete pDelete; break; } if (pNode->NextChild) pNode = pNode->NextChild; else { pNode = pNode->Parent; if (pNode) pNode->FirstChild = nullptr; } delete pDelete; } } } void StdCompilerINIRead::SkipWhitespace() { while (*pPos == ' ' || *pPos == '\t') pPos++; } void StdCompilerINIRead::SkipNum() { while (*pPos == '+' || *pPos == '-' || isdigit((unsigned char)*pPos)) pPos++; } long StdCompilerINIRead::ReadNum() { if (!pPos) { notFound("Number"); return 0; } // Skip whitespace SkipWhitespace(); // Read number. If this breaks, Günther is to blame! const char *pnPos = pPos; long iNum = strtol(pPos, const_cast(&pnPos), 10); // Could not read? if (!iNum && pnPos == pPos) { notFound("Number"); return 0; } // Get over it pPos = pnPos; return iNum; } unsigned long StdCompilerINIRead::ReadUNum() { if (!pPos) { notFound("Number"); return 0; } // Skip whitespace SkipWhitespace(); // Read number. If this breaks, Günther is to blame! const char *pnPos = pPos; unsigned long iNum = strtoul(pPos, const_cast(&pnPos), 10); // Could not read? if (!iNum && pnPos == pPos) { notFound("Number"); return 0; } // Get over it pPos = pnPos; return iNum; } size_t StdCompilerINIRead::GetStringLength(RawCompileType eRawType) { // Excpect valid position if (!pPos) { notFound("String"); return 0; } // Skip whitespace SkipWhitespace(); // Save position const char *pStart = pPos; // Escaped? Go over '"' if (eRawType == RCT_Escaped && *pPos++ != '"') { notFound("Escaped string"); return 0; } // Search end of string size_t iLength = 0; while (!TestStringEnd(eRawType)) { // Read a character (we're just counting atm) if (eRawType == RCT_Escaped) ReadEscapedChar(); else pPos++; // Count it iLength++; } // Reset position, return the length pPos = pStart; return iLength; } StdBuf StdCompilerINIRead::ReadString(size_t iLength, RawCompileType eRawType, bool fAppendNull) { // Excpect valid position if (!pPos) { notFound("String"); return StdBuf(); } // Skip whitespace SkipWhitespace(); // Escaped? Go over '"' if (eRawType == RCT_Escaped && *pPos++ != '"') { notFound("Escaped string"); return StdBuf(); } // Create buffer StdBuf OutBuf; OutBuf.New(iLength + (fAppendNull ? sizeof('\0') : 0)); // Read char *pOut = getMBufPtr(OutBuf); while (iLength && !TestStringEnd(eRawType)) { // Read a character if (eRawType == RCT_Escaped) *pOut++ = ReadEscapedChar(); else *pOut++ = *pPos++; // Count it iLength--; } // Escaped: Go over '"' if (eRawType == RCT_Escaped) { while (*pPos != '"') { if (!*pPos || *pPos == '\n' || *pPos == '\r') { Warn("string not terminated!"); pPos--; break; } pPos++; } pPos++; } // Nothing read? Identifiers need to be non-empty if (pOut == OutBuf.getData() && (eRawType == RCT_Idtf || eRawType == RCT_ID)) { notFound("String"); return StdBuf(); } // Append null if (fAppendNull) *pOut = '\0'; // Shrink, if less characters were read OutBuf.Shrink(iLength); // Done return OutBuf; } char StdCompilerINIRead::ReadEscapedChar() { // Catch some no-noes like \0, \n etc. if (*pPos >= 0 && iscntrl((unsigned char)*pPos)) { Warn("Nonprintable character found in string: %02x", static_cast(*pPos)); return *pPos; } // Not escaped? Just return it if (*pPos != '\\') return *pPos++; // What type of escape? switch (*++pPos) { case 'a': pPos++; return '\a'; case 'b': pPos++; return '\b'; case 'f': pPos++; return '\f'; case 'n': pPos++; return '\n'; case 'r': pPos++; return '\r'; case 't': pPos++; return '\t'; case 'v': pPos++; return '\v'; case '\'': pPos++; return '\''; case '"': pPos++; return '"'; case '\\': pPos++; return '\\'; case '?': pPos++; return '?'; case 'x': // Treat '\x' as 'x' - damn special cases if (!isxdigit((unsigned char)*++pPos)) return 'x'; else { // Read everything that looks like it might be hexadecimal - MSVC does it this way, so do not sue me. int iCode = 0; do { iCode = iCode * 16 + (isdigit((unsigned char)*pPos) ? *pPos - '0' : *pPos - 'a' + 10); pPos++; } while (isxdigit((unsigned char)*pPos)); // Done. Don't bother to check the range (we aren't doing anything mission-critical here, are we?) return char(iCode); } default: // Not octal? Let it pass through. if (!isdigit((unsigned char)*pPos) || *pPos >= '8') return *pPos++; else { // Read it the octal way. int iCode = 0; do { iCode = iCode * 8 + (*pPos - '0'); pPos++;} while (isdigit((unsigned char)*pPos) && *pPos < '8'); // Done. See above. return char(iCode); } } // unreachable assert (false); } void StdCompilerINIRead::notFound(const char *szWhat) { excNotFound("%s expected", szWhat); } void StdCompilerWarnCallback(void *pData, const char *szPosition, const char *szError) { const char *szName = reinterpret_cast(pData); if (!szPosition || !*szPosition) DebugLogF("WARNING: %s (in %s)", szError, szName); else DebugLogF("WARNING: %s (in %s, %s)", szError, szPosition, szName); }