#include "idlib/text/lexer.h" #include "idlib/filesystem/file.h" #include "idlib/filesystem/filesystem.h" #include "idlib/lib_print.h" #include #include #include #include #include #include #include #include namespace { #define LEX_PUNCTUATION(text, value) { const_cast(text), value } const punctuation_t defaultPunctuations[] = { LEX_PUNCTUATION(">>=", P_RSHIFT_ASSIGN), LEX_PUNCTUATION("<<=", P_LSHIFT_ASSIGN), LEX_PUNCTUATION("...", P_PARMS), LEX_PUNCTUATION("##", P_PRECOMPMERGE), LEX_PUNCTUATION("&&", P_LOGIC_AND), LEX_PUNCTUATION("||", P_LOGIC_OR), LEX_PUNCTUATION(">=", P_LOGIC_GEQ), LEX_PUNCTUATION("<=", P_LOGIC_LEQ), LEX_PUNCTUATION("==", P_LOGIC_EQ), LEX_PUNCTUATION("!=", P_LOGIC_UNEQ), LEX_PUNCTUATION("*=", P_MUL_ASSIGN), LEX_PUNCTUATION("/=", P_DIV_ASSIGN), LEX_PUNCTUATION("%=", P_MOD_ASSIGN), LEX_PUNCTUATION("+=", P_ADD_ASSIGN), LEX_PUNCTUATION("-=", P_SUB_ASSIGN), LEX_PUNCTUATION("++", P_INC), LEX_PUNCTUATION("--", P_DEC), LEX_PUNCTUATION("&=", P_BIN_AND_ASSIGN), LEX_PUNCTUATION("|=", P_BIN_OR_ASSIGN), LEX_PUNCTUATION("^=", P_BIN_XOR_ASSIGN), LEX_PUNCTUATION(">>", P_RSHIFT), LEX_PUNCTUATION("<<", P_LSHIFT), LEX_PUNCTUATION("::", P_SCOPE_RESOLUTION), LEX_PUNCTUATION(".", P_MEMBER_SELECTION_OBJECT), LEX_PUNCTUATION("->", P_MEMBER_SELECTION_POINTER), LEX_PUNCTUATION(".*", P_POINTER_TO_MEMBER_OBJECT), LEX_PUNCTUATION("->*", P_POINTER_TO_MEMBER_POINTER), LEX_PUNCTUATION("*", P_MUL), LEX_PUNCTUATION("/", P_DIV), LEX_PUNCTUATION("%", P_MOD), LEX_PUNCTUATION("+", P_ADD), LEX_PUNCTUATION("-", P_SUB), LEX_PUNCTUATION("=", P_ASSIGN), LEX_PUNCTUATION("&", P_BIN_AND), LEX_PUNCTUATION("|", P_BIN_OR), LEX_PUNCTUATION("^", P_BIN_XOR), LEX_PUNCTUATION("~", P_BIN_NOT), LEX_PUNCTUATION("!", P_LOGIC_NOT), LEX_PUNCTUATION(">", P_LOGIC_GREATER), LEX_PUNCTUATION("<", P_LOGIC_LESS), LEX_PUNCTUATION(",", P_COMMA), LEX_PUNCTUATION(";", P_SEMICOLON), LEX_PUNCTUATION(":", P_COLON), LEX_PUNCTUATION("?", P_QUESTIONMARK), LEX_PUNCTUATION("(", P_PARENTHESESOPEN), LEX_PUNCTUATION(")", P_PARENTHESESCLOSE), LEX_PUNCTUATION("{", P_BRACEOPEN), LEX_PUNCTUATION("}", P_BRACECLOSE), LEX_PUNCTUATION("[", P_SQBRACKETOPEN), LEX_PUNCTUATION("]", P_SQBRACKETCLOSE), LEX_PUNCTUATION("\\", P_BACKSLASH), LEX_PUNCTUATION("#", P_PRECOMP), LEX_PUNCTUATION("$", P_DOLLAR), LEX_PUNCTUATION("'", P_APOSTROPHE), LEX_PUNCTUATION("\"", P_QUOTE), LEX_PUNCTUATION("@", P_AT), { nullptr, 0 } }; #undef LEX_PUNCTUATION bool IsNameStart(const int character) { return std::isalpha(static_cast(character)) != 0 || character == '_'; } bool IsNameCharacter(const int character) { return std::isalnum(static_cast(character)) != 0 || character == '_'; } void AppendRange(idStr& string, const char* first, const char* last) { while (first < last) string.Append(*first++); } bool TokenMatchesType(const idToken& token, const int type, const int subtype) { if (token.type != type) return false; if (type == TT_NUMBER) return (token.subtype & subtype) == subtype; if (type == TT_PUNCTUATION) return token.subtype == subtype; return true; } const char* TokenTypeName(const int type) { switch (type) { case TT_STRING: return "string"; case TT_LITERAL: return "literal"; case TT_NUMBER: return "number"; case TT_NAME: return "name"; case TT_PUNCTUATION: return "punctuation"; default: return "unknown type"; } } } // namespace idLexer::idLexer(const int flags_) : loaded(false), filename(""), allocated(0), buffer(nullptr), script_p(nullptr), end_p(nullptr), lastScript_p(nullptr), whiteSpaceStart_p(nullptr), whiteSpaceEnd_p(nullptr), fileTime(0), length(0), line(0), lastline(0), flags(flags_), punctuations(nullptr), punctuationtable(nullptr), nextpunctuation(nullptr), next(nullptr), errorMsg(), hadError(false), hadWarning(false) { SetPunctuations(nullptr); } idLexer::~idLexer() { FreeSource(); } void idLexer::CreatePunctuationTable(const punctuation_t* const list) { if (punctuationtable != nullptr) std::free(punctuationtable); if (nextpunctuation != nullptr) std::free(nextpunctuation); punctuationtable = static_cast(std::malloc(256 * sizeof(int))); int count = 0; while (list != nullptr && list[count].p != nullptr) ++count; nextpunctuation = count > 0 ? static_cast(std::malloc(count * sizeof(int))) : nullptr; if (punctuationtable == nullptr) return; std::fill(punctuationtable, punctuationtable + 256, -1); if (nextpunctuation != nullptr) { std::fill(nextpunctuation, nextpunctuation + count, -1); } // The recovered table chains longest spellings first for each byte. for (int index = 0; index < count; ++index) { const unsigned char first = static_cast(list[index].p[0]); int* link = &punctuationtable[first]; while (*link >= 0 && std::strlen(list[*link].p) >= std::strlen(list[index].p)) { link = &nextpunctuation[*link]; } nextpunctuation[index] = *link; *link = index; } } void idLexer::SetPunctuations(const punctuation_t* punctuation) { if (punctuation == nullptr) punctuation = defaultPunctuations; CreatePunctuationTable(punctuation); punctuations = punctuation; } const char* idLexer::GetPunctuationFromId(const int id) const { if (punctuations == nullptr) return "unknown punctuation"; for (int index = 0; punctuations[index].p != nullptr; ++index) { if (punctuations[index].n == id) return punctuations[index].p; } return "unknown punctuation"; } int idLexer::GetPunctuationId(const char* const punctuation) const { if (punctuations == nullptr || punctuation == nullptr) return 0; for (int index = 0; punctuations[index].p != nullptr; ++index) { if (idStr::Cmp(punctuations[index].p, punctuation) == 0) { return punctuations[index].n; } } return 0; } bool idLexer::CheckString(const char* const string) const { if (script_p == nullptr || string == nullptr) return false; const std::size_t count = std::strlen(string); return script_p + count <= end_p && std::memcmp(script_p, string, count) == 0; } bool idLexer::LoadFile(const char* const fileName, const bool OSPath) { return LoadFilePartial(fileName, OSPath, (std::numeric_limits::max)()); } bool idLexer::LoadFilePartial(const char* const fileName, const bool OSPath, const int maximumLength) { if (loaded) { idLibPrint::Error("LoadFile: another script already loaded"); } if (fileSystem == nullptr || fileName == nullptr) return false; idFileLocal file(fileSystem->OpenFileRead(fileName, true, false)); if (file.file == nullptr) return false; const std::int64_t fileLength = file->Length(); length = static_cast((std::max)(0, (std::min)(fileLength, maximumLength))); char* memory = static_cast(std::malloc(length + 1u)); if (memory == nullptr) return false; if (file->Read(memory, length) != length) { std::free(memory); length = 0; return false; } memory[length] = '\0'; buffer = memory; allocated = 1; fileTime = file->Timestamp(); filename = OSPath ? file->GetFullPath() : file->GetName(); loaded = true; script_p = buffer; end_p = buffer + length; lastScript_p = buffer; line = lastline = 1; return true; } bool idLexer::LoadMemory(const char* const pointer, const unsigned int memoryLength, const char* const name) { if (loaded) { idLibPrint::Error("LoadMemory: another script already loaded"); } if (pointer == nullptr) return false; filename = name != nullptr ? name : "memory"; buffer = pointer; length = memoryLength; fileTime = 0; script_p = pointer; end_p = pointer + memoryLength; lastScript_p = pointer; line = lastline = 1; allocated = 0; loaded = true; return true; } void idLexer::FreeSource() { if (punctuationtable != nullptr) { std::free(punctuationtable); punctuationtable = nullptr; } if (nextpunctuation != nullptr) { std::free(nextpunctuation); nextpunctuation = nullptr; } if (allocated != 0) std::free(const_cast(buffer)); allocated = 0; buffer = nullptr; script_p = nullptr; end_p = nullptr; lastScript_p = nullptr; whiteSpaceStart_p = nullptr; whiteSpaceEnd_p = nullptr; length = 0; filename.Clear(); loaded = false; } void idLexer::Reset() { whiteSpaceStart_p = nullptr; whiteSpaceEnd_p = nullptr; line = lastline = 1; script_p = lastScript_p = buffer; } bool idLexer::SetScriptP(const char* const pointer) { if (buffer == nullptr || end_p == nullptr || pointer < buffer || pointer > end_p) return false; script_p = pointer; return true; } void idLexer::UnreadToken() { script_p = lastScript_p; line = lastline; } bool idLexer::EndOfFile() const { return script_p == nullptr || end_p == nullptr || script_p >= end_p; } bool idLexer::HadError() const { return hadError; } bool idLexer::HadWarning() const { return hadWarning; } void idLexer::Error(const char* const format, ...) { hadError = true; if ((flags & LEXFL_NOERRORS) != 0) return; char detail[1024] = {}; va_list arguments; va_start(arguments, format); _vsnprintf_s(detail, sizeof(detail), _TRUNCATE, format != nullptr ? format : "", arguments); va_end(arguments); char message[1400] = {}; if ((flags & LEXFL_VCSTYLEREPORTS) != 0) { _snprintf_s(message, sizeof(message), _TRUNCATE, "%s(%d) : error : %s", filename.c_str(), line, detail); } else { _snprintf_s(message, sizeof(message), _TRUNCATE, "file %s, line %d: %s", filename.c_str(), line, detail); } if (errorMsg.Length() == 0) errorMsg = message; if ((flags & LEXFL_NOFATALERRORS) != 0) { idLibPrint::Warning("%s", message); return; } idLibPrint::Error("%s", message); } void idLexer::Warning(const char* const format, ...) { hadWarning = true; if ((flags & LEXFL_NOWARNINGS) != 0) return; char detail[1024] = {}; va_list arguments; va_start(arguments, format); _vsnprintf_s(detail, sizeof(detail), _TRUNCATE, format != nullptr ? format : "", arguments); va_end(arguments); idLibPrint::Warning("file %s, line %d: %s", filename.c_str(), line, detail); } bool idLexer::SkipWhiteSpace(const bool currentLine) { if (script_p == nullptr || script_p >= end_p) return false; for (;;) { while (script_p < end_p && static_cast(*script_p) <= 32u) { if (*script_p++ == '\n') { ++line; if (currentLine) return true; } } if (script_p >= end_p || *script_p == '\0') return false; if (script_p + 1 < end_p && script_p[0] == '/' && script_p[1] == '/') { script_p += 2; while (script_p < end_p && *script_p != '\0' && *script_p != '\n') ++script_p; if (script_p < end_p && *script_p == '\n') { ++script_p; ++line; if (currentLine) return true; continue; } return false; } if (script_p + 1 < end_p && script_p[0] == '/' && script_p[1] == '*') { script_p += 2; while (script_p < end_p) { if (script_p + 1 < end_p && script_p[0] == '*' && script_p[1] == '/') { script_p += 2; break; } if (*script_p == '\n') ++line; if (script_p + 1 < end_p && script_p[0] == '/' && script_p[1] == '*') Warning("nested comment"); ++script_p; } if (script_p >= end_p) return false; continue; } return true; } } bool idLexer::ReadEscapeCharacter(char* const character) { if (script_p == nullptr || script_p >= end_p || *script_p != '\\') { return false; } ++script_p; if (script_p >= end_p) return false; if ((flags & LEXFL_NOEMITSTRINGESCAPECHARS) != 0) { *character = *script_p++; return true; } const char escape = *script_p++; switch (escape) { case '\\': *character = '\\'; return true; case '\'': *character = '\''; return true; case '"': *character = '"'; return true; case '?': *character = '?'; return true; case 'a': *character = '\a'; return true; case 'b': *character = '\b'; return true; case 'f': *character = '\f'; return true; case 'n': *character = '\n'; return true; case 'r': *character = '\r'; return true; case 't': *character = '\t'; return true; case 'v': *character = '\v'; return true; case 'x': { unsigned int value = 0; int digits = 0; while (script_p < end_p && std::isxdigit(static_cast(*script_p))) { const char digit = *script_p++; value = value * 16u + static_cast( digit >= '0' && digit <= '9' ? digit - '0' : (digit | 32) - 'a' + 10); ++digits; } if (digits == 0) Error("unknown escape char"); if (value > 255u) Warning("too large value in escape character"); *character = static_cast(value); return true; } default: if (escape >= '0' && escape <= '9') { unsigned int value = static_cast(escape - '0'); while (script_p < end_p && *script_p >= '0' && *script_p <= '9') { value = value * 10u + static_cast(*script_p++ - '0'); } if (value > 255u) Warning("too large value in escape character"); *character = static_cast(value); return true; } Error("unknown escape char"); *character = escape; return false; } } bool idLexer::ReadString(idToken& token, const int quote) { token.type = quote == '"' ? TT_STRING : TT_LITERAL; ++script_p; for (;;) { while (script_p < end_p && *script_p != quote) { if (*script_p == '\0' || *script_p == '\n') { Error("missing trailing quote"); return false; } if (*script_p == '\\' && (flags & LEXFL_NOSTRINGESCAPECHARS) == 0) { if ((flags & LEXFL_NOEMITSTRINGESCAPECHARS) != 0) { token.Append('\\'); } char escaped = 0; if (!ReadEscapeCharacter(&escaped)) return false; token.Append(escaped); } else { token.Append(*script_p++); } } if (script_p >= end_p) { Error("missing trailing quote"); return false; } ++script_p; if (quote != '"' || (flags & LEXFL_NOSTRINGCONCAT) != 0) break; const char* const afterQuote = script_p; const int quoteLine = line; if (!SkipWhiteSpace(false) || script_p >= end_p || *script_p != quote) { script_p = afterQuote; line = quoteLine; break; } ++script_p; } if (token.type == TT_LITERAL) { if ((flags & LEXFL_ALLOWMULTICHARLITERALS) == 0 && token.Length() != 1) { Warning("literal is not one character long"); } token.subtype = token.Length() > 0 ? static_cast(token.c_str()[0]) : 0; } else { token.subtype = token.Length(); } return true; } bool idLexer::ReadRawStringBlock(idToken& token) { if (!CheckString("<%")) return false; script_p += 2; token.type = TT_STRING; while (script_p < end_p && !CheckString("%>")) { if (*script_p == '\n') ++line; token.Append(*script_p++); } if (script_p >= end_p) { Error("missing %%> for raw string block"); return false; } script_p += 2; token.subtype = token.Length(); return true; } bool idLexer::ReadName(idToken& token) { token.type = TT_NAME; while (script_p < end_p) { const char character = *script_p; const bool accepted = IsNameCharacter(character) || ((flags & LEXFL_ONLYSTRINGS) != 0 && character == '-') || ((flags & LEXFL_ALLOWPATHNAMES) != 0 && (character == '/' || character == '\\' || character == ':' || character == '.' || character == '@')) || ((flags & LEXFL_ALLOWWILDCARD) != 0 && character == '*'); if (!accepted) break; token.Append(character); ++script_p; } token.subtype = token.Length(); return token.Length() != 0; } bool idLexer::ReadNumber(idToken& token) { token.type = TT_NUMBER; token.subtype = 0; const char* const start = script_p; bool isFloat = false; bool isHex = false; bool isBinary = false; bool isOctal = false; int dots = 0; if (script_p + 1 < end_p && script_p[0] == '0' && (script_p[1] == 'x' || script_p[1] == 'X')) { isHex = true; script_p += 2; while (script_p < end_p && std::isxdigit(static_cast(*script_p))) { ++script_p; } } else if (script_p + 1 < end_p && script_p[0] == '0' && (script_p[1] == 'b' || script_p[1] == 'B')) { isBinary = true; script_p += 2; while (script_p < end_p && (*script_p == '0' || *script_p == '1')) { ++script_p; } } else { if (*script_p == '0' && script_p + 1 < end_p && script_p[1] != '.') isOctal = true; while (script_p < end_p) { const char character = *script_p; if (character >= '0' && character <= '9') { ++script_p; continue; } if (character == '.') { ++dots; ++script_p; continue; } break; } if (dots == 1) isFloat = true; if (script_p < end_p && (*script_p == 'e' || *script_p == 'E')) { isFloat = true; ++script_p; if (script_p < end_p && (*script_p == '+' || *script_p == '-')) { ++script_p; } while (script_p < end_p && std::isdigit( static_cast(*script_p))) ++script_p; } if (dots > 1 && (flags & LEXFL_ALLOWIPADDRESSES) != 0) { token.subtype |= TT_IPADDRESS; if (script_p < end_p && *script_p == ':') { token.subtype |= TT_IPPORT; ++script_p; while (script_p < end_p && std::isdigit( static_cast(*script_p))) ++script_p; } } } bool unsignedSuffix = false; bool longSuffix = false; while (script_p < end_p) { const char suffix = *script_p; if (suffix == 'u' || suffix == 'U') { unsignedSuffix = true; ++script_p; } else if (suffix == 'l' || suffix == 'L') { longSuffix = true; ++script_p; } else if (suffix == 'f' || suffix == 'F') { isFloat = true; ++script_p; } else { break; } } AppendRange(token, start, script_p); token.subtype |= isFloat ? TT_FLOAT : TT_INTEGER; token.subtype |= isHex ? TT_HEX : isBinary ? TT_BINARY : isOctal && !isFloat ? TT_OCTAL : TT_DECIMAL; if (unsignedSuffix) token.subtype |= TT_UNSIGNED; if (longSuffix) token.subtype |= TT_LONG; errno = 0; if (isBinary) { unsigned int value = 0; for (const char* cursor = start + 2; cursor < script_p && (*cursor == '0' || *cursor == '1'); ++cursor) { value = value * 2u + static_cast(*cursor - '0'); } token.intvalue = value; token.floatvalue = static_cast(value); } else if (isFloat) { token.floatvalue = std::strtof(token.c_str(), nullptr); token.intvalue = static_cast(token.floatvalue); } else { const int base = isHex ? 16 : isOctal ? 8 : 10; token.intvalue = static_cast( std::strtoul(token.c_str(), nullptr, base)); token.floatvalue = static_cast(token.intvalue); } token.subtype |= TT_VALUESVALID; return script_p > start; } bool idLexer::ReadPunctuation(idToken& token) { if (punctuations == nullptr || script_p >= end_p) return false; int best = -1; std::size_t bestLength = 0; for (int index = 0; punctuations[index].p != nullptr; ++index) { const std::size_t count = std::strlen(punctuations[index].p); if (count <= bestLength || script_p + count > end_p) continue; if (std::memcmp(script_p, punctuations[index].p, count) == 0) { best = index; bestLength = count; } } if (best < 0) return false; static_cast(token) = punctuations[best].p; token.type = TT_PUNCTUATION; token.subtype = punctuations[best].n; script_p += bestLength; return true; } bool idLexer::ReadToken(idToken& token) { if (!loaded) idLibPrint::Error("ReadToken: no file loaded"); if (script_p == nullptr || script_p >= end_p) return false; lastScript_p = script_p; lastline = line; token.Clear(); token.type = 0; token.subtype = 0; token.intvalue = 0; token.floatvalue = 0.0f; token.flags = 0; whiteSpaceStart_p = script_p; token.whiteSpaceStart_p = script_p; if (!SkipWhiteSpace(false)) return false; if (script_p >= end_p) return false; whiteSpaceEnd_p = script_p; token.whiteSpaceEnd_p = script_p; token.line = line; token.linesCrossed = line - lastline; const char character = *script_p; if ((flags & LEXFL_ONLYSTRINGS) != 0) { if (character == '"' || character == '\'') { return ReadString(token, character); } return ReadName(token); } if (std::isdigit(static_cast(character)) || (character == '.' && script_p + 1 < end_p && std::isdigit(static_cast(script_p[1])))) { if (!ReadNumber(token)) return false; if ((flags & LEXFL_ALLOWNUMBERNAMES) != 0 && script_p < end_p && IsNameStart(*script_p)) { token.type = TT_NAME; while (script_p < end_p && IsNameCharacter(*script_p)) { token.Append(*script_p++); } token.subtype = token.Length(); } return true; } if ((flags & LEXFL_ALLOWWILDCARD) != 0 && character == '*') { return ReadName(token); } if ((flags & LEXFL_NOSTRINGS) == 0 && (character == '"' || character == '\'')) { return ReadString(token, character); } if (IsNameStart(character) || ((flags & LEXFL_ALLOWPATHNAMES) != 0 && character == '.')) { return ReadName(token); } if ((flags & LEXFL_ALLOWRAWSTRINGBLOCKS) != 0 && CheckString("<%")) return ReadRawStringBlock(token); if (ReadPunctuation(token)) return true; Error("unknown punctuation %c", character); return false; } bool idLexer::ReadTokenOnLine(idToken& token) { const char* const previous = script_p; const int previousLine = line; if (!ReadToken(token)) return false; if (token.linesCrossed == 0) return true; script_p = previous; line = previousLine; return false; } bool idLexer::ExpectTokenString(const char* const string) { idToken token; if (!ReadToken(token)) { Error("couldn't find expected '%s'", string != nullptr ? string : ""); return false; } if (idStr::Cmp(token.c_str(), string) != 0) { Error("expected '%s' but found '%s'", string, token.c_str()); return false; } return true; } bool idLexer::ExpectTokenType(const int type, const int subtype, idToken& token) { if (!ReadToken(token)) { Error("couldn't read expected token"); return false; } if (token.type != type) { Error("expected a %s but found '%s'", TokenTypeName(type), token.c_str()); return false; } if (!TokenMatchesType(token, type, subtype)) { if (type == TT_PUNCTUATION) { Error("expected '%s' but found '%s'", GetPunctuationFromId(subtype), token.c_str()); } else { Error("token '%s' has the wrong subtype", token.c_str()); } return false; } return true; } bool idLexer::ExpectAnyToken(idToken& token) { if (ReadToken(token)) return true; Error("couldn't read expected token"); return false; } bool idLexer::CheckTokenString(const char* const string) { idToken token; if (!ReadToken(token)) return false; if (idStr::Cmp(token.c_str(), string) == 0) return true; UnreadToken(); return false; } bool idLexer::CheckTokenType(const int type, const int subtype, idToken& token) { idToken read; if (!ReadToken(read)) return false; if (!TokenMatchesType(read, type, subtype)) { UnreadToken(); return false; } token = read; return true; } bool idLexer::PeekTokenString(const char* const string) { idToken token; if (!ReadToken(token)) return false; const bool result = idStr::Cmp(token.c_str(), string) == 0; UnreadToken(); return result; } bool idLexer::PeekTokenType(const int type, const int subtype, idToken& token) { idToken read; if (!ReadToken(read)) return false; const bool result = TokenMatchesType(read, type, subtype); UnreadToken(); if (result) token = read; return result; } bool idLexer::SkipUntilString(const char* const string) { idToken token; while (ReadToken(token)) { if (idStr::Cmp(token.c_str(), string) == 0) return true; } return false; } bool idLexer::SkipRestOfLine() { idToken token; while (ReadToken(token)) { if (token.linesCrossed != 0) { UnreadToken(); return true; } } return false; } bool idLexer::SkipBracedSection(const bool parseFirstBrace) { int depth = parseFirstBrace ? 0 : 1; idToken token; while (ReadToken(token)) { if (token.type != TT_PUNCTUATION) continue; if (idStr::Cmp(token.c_str(), "{") == 0) ++depth; else if (idStr::Cmp(token.c_str(), "}") == 0 && --depth <= 0) { return true; } } return false; } bool idLexer::ParseBracedSectionExact(idStr& out, const bool parseFirstBrace, const char openBrace, const char closeBrace) { out.Clear(); int depth = 1; if (parseFirstBrace) { char brace[2] = { openBrace, '\0' }; if (!ExpectTokenString(brace)) return false; out.Append(openBrace); } idToken token; while (ReadToken(token)) { idStr whiteSpace; GetWhiteSpaceBeforeToken(token, whiteSpace); out.Append(whiteSpace); if (token.type == TT_STRING) { out.Append('"'); out.Append(token); out.Append('"'); } else if (token.type == TT_LITERAL) { out.Append('\''); out.Append(token); out.Append('\''); } else { out.Append(token); } if (token.type != TT_STRING) { if (token.Length() > 0 && token.c_str()[0] == openBrace) ++depth; else if (token.Length() > 0 && token.c_str()[0] == closeBrace && --depth <= 0) { return true; } } } return false; } const char* idLexer::ParseRestOfLine(idStr& out) { out.Clear(); if (script_p == nullptr) return out.c_str(); const char* first = script_p; while (script_p < end_p && *script_p != '\0' && *script_p != '\n') { ++script_p; } const char* last = script_p; while (first < last && static_cast(*first) <= 32u) ++first; while (last > first && static_cast(last[-1]) <= 32u) --last; AppendRange(out, first, last); if (script_p < end_p && *script_p == '\n') { ++script_p; ++line; } return out.c_str(); } const char* idLexer::ParseCompleteLine(idStr& out) { out.Clear(); if (script_p == nullptr) return out.c_str(); const char* const first = script_p; while (script_p < end_p && *script_p != '\0') { const char character = *script_p++; if (character == '\n') { ++line; break; } } AppendRange(out, first, script_p); return out.c_str(); } int idLexer::ParseInt() { idToken token; if (!ReadToken(token)) { Error("couldn't read expected integer"); return 0; } bool negative = false; if (token.type == TT_PUNCTUATION && idStr::Cmp(token.c_str(), "-") == 0) { negative = true; if (!ExpectTokenType(TT_NUMBER, TT_INTEGER, token)) return 0; } else if (token.type != TT_NUMBER || (token.subtype & TT_INTEGER) == 0) { Error("expected integer value, found '%s'", token.c_str()); return 0; } const int value = static_cast(token.intvalue); return negative ? -value : value; } unsigned int idLexer::ParseUnsignedInt() { idToken token; if (!ExpectTokenType(TT_NUMBER, TT_INTEGER, token)) return 0; return token.intvalue; } bool idLexer::ParseBool() { idToken token; if (!ReadToken(token)) { Error("couldn't read expected boolean"); return false; } if (token.type == TT_NUMBER) return token.intvalue != 0; if (idStr::Cmp(token.c_str(), "true") == 0) return true; if (idStr::Cmp(token.c_str(), "false") == 0) return false; Error("expected boolean value, found '%s'", token.c_str()); return false; } float idLexer::ParseFloat(bool* const errorFlag) { if (errorFlag != nullptr) *errorFlag = false; idToken token; if (!ReadToken(token)) { if (errorFlag != nullptr) { Warning("couldn't read expected floating point number"); *errorFlag = true; } else Error("couldn't read expected floating point number"); return 0.0f; } bool negative = false; if (token.type == TT_PUNCTUATION && idStr::Cmp(token.c_str(), "-") == 0) { negative = true; if (!ExpectTokenType(TT_NUMBER, 0, token)) { if (errorFlag != nullptr) *errorFlag = true; return 0.0f; } } else if (token.type != TT_NUMBER) { if (errorFlag != nullptr) { Warning("expected float value, found '%s'", token.c_str()); *errorFlag = true; } else Error("expected float value, found '%s'", token.c_str()); return 0.0f; } return negative ? -token.floatvalue : token.floatvalue; } bool idLexer::Parse1DMatrix(const int x, float* const matrix, const bool expectCommas) { if (!ExpectTokenString("(")) return false; for (int index = 0; index < x; ++index) { matrix[index] = ParseFloat(); if (expectCommas && index != x - 1 && !ExpectTokenString(",")) return false; } return ExpectTokenString(")"); } bool idLexer::Parse2DMatrix(const int y, const int x, float* const matrix) { if (!ExpectTokenString("(")) return false; for (int index = 0; index < y; ++index) { if (!Parse1DMatrix(x, matrix + index * x, false)) return false; } return ExpectTokenString(")"); } int idLexer::GetLastWhiteSpace(idStr& whiteSpace) const { whiteSpace.Clear(); if (whiteSpaceStart_p == nullptr || whiteSpaceEnd_p == nullptr) return 0; AppendRange(whiteSpace, whiteSpaceStart_p, whiteSpaceEnd_p); return whiteSpace.Length(); } int idLexer::GetNextWhiteSpace(idStr& whiteSpace, const bool currentLine) { whiteSpaceStart_p = script_p; SkipWhiteSpace(currentLine); whiteSpaceEnd_p = script_p; return GetLastWhiteSpace(whiteSpace); } bool idLexer::GetWhiteSpaceBeforeToken(const idToken& token, idStr& whiteSpace) const { whiteSpace.Clear(); if (buffer == nullptr || token.whiteSpaceStart_p == nullptr || token.whiteSpaceEnd_p == nullptr || token.whiteSpaceStart_p < buffer || token.whiteSpaceEnd_p < token.whiteSpaceStart_p || token.whiteSpaceEnd_p > end_p) return false; AppendRange(whiteSpace, token.whiteSpaceStart_p, token.whiteSpaceEnd_p); return true; }