/*
 * 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("\r\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("\r\n");
	WriteIndent(fSection);
	// Put name
	if (fSection)
		Buf.AppendFormat("[%s]\r\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);
}
