forked from Mirrors/openclonk
1044 lines
27 KiB
C++
1044 lines
27 KiB
C++
/*
|
|
* 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 <class T>
|
|
void StdCompilerBinWrite::WriteValue(const T &rValue)
|
|
{
|
|
// Copy data
|
|
if (fSecondPass)
|
|
*getMBufPtr<T>(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<char>(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<char>(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<char>(Buf, iPos++))
|
|
if (iPos >= Buf.getSize())
|
|
{
|
|
excEOF(); return;
|
|
}
|
|
// Copy data
|
|
str.assign(getBufPtr<char>(Buf, iStart), getBufPtr<char>(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<unsigned long>(iPos));
|
|
}
|
|
|
|
template <class T>
|
|
inline void StdCompilerBinRead::ReadValue(T &rValue)
|
|
{
|
|
// Pufferüberhang prüfen
|
|
if (iPos + sizeof(T) > Buf.getSize())
|
|
{ excEOF(); return; }
|
|
// Kopieren
|
|
rValue = *getBufPtr<T>(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<char *>(pData), reinterpret_cast<char *>(pData) + iSize);
|
|
break;
|
|
case RCT_All:
|
|
case RCT_Idtf:
|
|
case RCT_IdtfAllowEmpty:
|
|
case RCT_ID:
|
|
Buf.Append(reinterpret_cast<char *>(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<const unsigned char *>(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<char>(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<char *>(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<char>(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<char **>(&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<char **>(&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<char>(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<unsigned char>(*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<const char *>(pData);
|
|
if (!szPosition || !*szPosition)
|
|
DebugLogF("WARNING: %s (in %s)", szError, szName);
|
|
else
|
|
DebugLogF("WARNING: %s (in %s, %s)", szError, szPosition, szName);
|
|
}
|